├── .gitignore ├── LICENSE ├── README.md ├── converter ├── README.md ├── package-lock.json ├── package.json ├── pcf-parser.js └── pcf2opentype.js ├── images ├── example1.png ├── example2.png ├── example3.png ├── example4.png └── example5.png └── release └── v_0_1 └── dotted_songti_v_0_1.zip /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | out 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and not Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | 108 | # Stores VSCode versions used for testing VSCode extensions 109 | .vscode-test 110 | 111 | # yarn v2 112 | 113 | .yarn/cache 114 | .yarn/unplugged 115 | .yarn/build-state.yml 116 | .pnp.* 117 | 118 | # Emacs 119 | 120 | *~ 121 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dotted Chinese Pixel Fonts 点点像素 | 开源的中文像素字体 2 | 3 | ## About 关于 4 | 5 | Dotted Chinese Pixel is a set of open source Chinese pixel-art fonts 6 | released under GPL 2.0. More than 22,000 Unicode glyphs are supported, 7 | covering all GBK charset entries. 8 | 9 | 点点中文像素字体是一组基于 GPL 2.0 协议开源发布的中文像素艺术字体,每 10 | 种字体均支持超过 22000 个 Unicode 字形,覆盖 GBK 编码中的全部简繁体汉 11 | 字。 12 | 13 | 14 | 15 | Current Dotted Chinese Pixel fonts are derived from a bitmap font 16 | created by the [WenQuanYi](http://wenq.org/) project. They are 17 | transformed and retouched based on a vectorized version of WenQuanYi 18 | Bitmap Song. WenQuanYi Bitmap Song is a free open source font licensed 19 | under GPL 2.0. See [wenq.org](http://wenq.org/) for more info. 20 | 21 | 目前发布的点点像素字体都是基于[文泉驿项目](http://wenq.org/)所创建的一 22 | 种点阵字体衍生而来的。它们是文泉驿点阵宋体经矢量化之后,通过变形、美化 23 | 而得到的。文泉驿点阵宋体是基于 GPL 2.0 协议开源的自由字体。请访问 24 | [wenq.org](http://wenq.org/) 以获取该字体的更多信息。 25 | 26 | See 27 | [converter/](https://github.com/wixette/dotted-chinese-fonts/tree/master/converter) 28 | for the source code that is used to automatically vectorize WenQuanYi 29 | Bitmap Song then transform the glyphs into OpenType fonts in different 30 | styles. 31 | 32 | 参考 33 | [converter/](https://github.com/wixette/dotted-chinese-fonts/tree/master/converter) 34 | 中的源代码。该代码自动将文泉驿点阵宋体中的字型矢量化,然后变形加工为不 35 | 同风格的 OpenType 字体。 36 | 37 | ## Download 字体下载 38 | 39 | - [Version 0.1, 2020.05.08](https://github.com/wixette/dotted-chinese-fonts/releases/tag/v0.1) 40 | 41 | ## Examples 字体样例 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /converter/README.md: -------------------------------------------------------------------------------- 1 | # Utility to convert bitmap fonts to OpenType fonts 2 | 3 | The released Dotted Chinese Pixel Fonts are generated with this 4 | utility, based on WenQuanYi bitmap fonts. 5 | 6 | So far this utility is not a general-purpose font converter. It has a 7 | few hard-coded parameters to fulfill the characteristics of WenQuanYi 8 | or other Chinese bitmap fonts. It might not work well for converting 9 | non-Chinese bitmap fonts, unless the metric parameters are updated 10 | accordingly. 11 | 12 | ## Install the dependencies 13 | 14 | ``` 15 | npm install 16 | ``` 17 | 18 | ## Usage 19 | 20 | ``` 21 | ./pcf2opentype.js --help 22 | ``` 23 | 24 | or, 25 | 26 | ``` 27 | node pcf2opentype.js --help 28 | ``` 29 | 30 | WenQuanYi font usually covers over 20K Unicode glyphs. It requires 31 | 4~5GB system memory to vectorize all those glyphs. To avoid exceeding 32 | V8's memory limit, `node.js` has a useful command line option 33 | `--max-old-space-size`. For example, 34 | 35 | ``` 36 | node --max-old-space-size=8192 pcf2opentype.js ... 37 | ``` 38 | 39 | ## Release the fonts 40 | 41 | The converter uses JavaScript module opentype.js to generate OpenType 42 | font file. The output file could be a bit buggy sometimes. For solving 43 | this, we can use font editor software to load, verify, clean and 44 | regenerate the font file. 45 | 46 | I use FontForge to release the generated fonts. FontForge does well in 47 | verifying and fixing format issues, and making the output file ready 48 | for Windows, macOS and Linux. 49 | -------------------------------------------------------------------------------- /converter/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bitmap-to-pixel-font-converter", 3 | "version": "0.1.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/color-name": { 8 | "version": "1.1.1", 9 | "resolved": "https://registry.npm.taobao.org/@types/color-name/download/@types/color-name-1.1.1.tgz?cache=0&sync_timestamp=1588200011932&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fcolor-name%2Fdownload%2F%40types%2Fcolor-name-1.1.1.tgz", 10 | "integrity": "sha1-HBJhu+qhCoBVu8XYq4S3sq/IRqA=" 11 | }, 12 | "ansi-regex": { 13 | "version": "5.0.0", 14 | "resolved": "https://registry.npm.taobao.org/ansi-regex/download/ansi-regex-5.0.0.tgz", 15 | "integrity": "sha1-OIU59VF5vzkznIGvMKZU1p+Hy3U=" 16 | }, 17 | "ansi-styles": { 18 | "version": "4.2.1", 19 | "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-4.2.1.tgz", 20 | "integrity": "sha1-kK51xCTQCNJiTFvynq0xd+v881k=", 21 | "requires": { 22 | "@types/color-name": "^1.1.1", 23 | "color-convert": "^2.0.1" 24 | } 25 | }, 26 | "binary-parser": { 27 | "version": "1.6.2", 28 | "resolved": "https://registry.npm.taobao.org/binary-parser/download/binary-parser-1.6.2.tgz", 29 | "integrity": "sha1-hBCoL/2UAycewYK9keY6Cc7ojL4=" 30 | }, 31 | "camelcase": { 32 | "version": "5.3.1", 33 | "resolved": "https://registry.npm.taobao.org/camelcase/download/camelcase-5.3.1.tgz", 34 | "integrity": "sha1-48mzFWnhBoEd8kL3FXJaH0xJQyA=" 35 | }, 36 | "cliui": { 37 | "version": "6.0.0", 38 | "resolved": "https://registry.npm.taobao.org/cliui/download/cliui-6.0.0.tgz?cache=0&sync_timestamp=1573943292170&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcliui%2Fdownload%2Fcliui-6.0.0.tgz", 39 | "integrity": "sha1-UR1wLAxOQcoVbX0OlgIfI+EyJbE=", 40 | "requires": { 41 | "string-width": "^4.2.0", 42 | "strip-ansi": "^6.0.0", 43 | "wrap-ansi": "^6.2.0" 44 | } 45 | }, 46 | "color-convert": { 47 | "version": "2.0.1", 48 | "resolved": "https://registry.npm.taobao.org/color-convert/download/color-convert-2.0.1.tgz", 49 | "integrity": "sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM=", 50 | "requires": { 51 | "color-name": "~1.1.4" 52 | } 53 | }, 54 | "color-name": { 55 | "version": "1.1.4", 56 | "resolved": "https://registry.npm.taobao.org/color-name/download/color-name-1.1.4.tgz", 57 | "integrity": "sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=" 58 | }, 59 | "decamelize": { 60 | "version": "1.2.0", 61 | "resolved": "https://registry.npm.taobao.org/decamelize/download/decamelize-1.2.0.tgz", 62 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" 63 | }, 64 | "emoji-regex": { 65 | "version": "8.0.0", 66 | "resolved": "https://registry.npm.taobao.org/emoji-regex/download/emoji-regex-8.0.0.tgz?cache=0&sync_timestamp=1586511397703&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Femoji-regex%2Fdownload%2Femoji-regex-8.0.0.tgz", 67 | "integrity": "sha1-6Bj9ac5cz8tARZT4QpY79TFkzDc=" 68 | }, 69 | "find-up": { 70 | "version": "4.1.0", 71 | "resolved": "https://registry.npm.taobao.org/find-up/download/find-up-4.1.0.tgz", 72 | "integrity": "sha1-l6/n1s3AvFkoWEt8jXsW6KmqXRk=", 73 | "requires": { 74 | "locate-path": "^5.0.0", 75 | "path-exists": "^4.0.0" 76 | } 77 | }, 78 | "get-caller-file": { 79 | "version": "2.0.5", 80 | "resolved": "https://registry.npm.taobao.org/get-caller-file/download/get-caller-file-2.0.5.tgz", 81 | "integrity": "sha1-T5RBKoLbMvNuOwuXQfipf+sDH34=" 82 | }, 83 | "is-fullwidth-code-point": { 84 | "version": "3.0.0", 85 | "resolved": "https://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-3.0.0.tgz", 86 | "integrity": "sha1-8Rb4Bk/pCz94RKOJl8C3UFEmnx0=" 87 | }, 88 | "locate-path": { 89 | "version": "5.0.0", 90 | "resolved": "https://registry.npm.taobao.org/locate-path/download/locate-path-5.0.0.tgz", 91 | "integrity": "sha1-Gvujlq/WdqbUJQTQpno6frn2KqA=", 92 | "requires": { 93 | "p-locate": "^4.1.0" 94 | } 95 | }, 96 | "opentype.js": { 97 | "version": "1.3.3", 98 | "resolved": "https://registry.npm.taobao.org/opentype.js/download/opentype.js-1.3.3.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fopentype.js%2Fdownload%2Fopentype.js-1.3.3.tgz", 99 | "integrity": "sha1-ZbhkWwkKGtREBlt4TUQvoZ0QYfY=", 100 | "requires": { 101 | "string.prototype.codepointat": "^0.2.1", 102 | "tiny-inflate": "^1.0.3" 103 | } 104 | }, 105 | "p-limit": { 106 | "version": "2.3.0", 107 | "resolved": "https://registry.npm.taobao.org/p-limit/download/p-limit-2.3.0.tgz", 108 | "integrity": "sha1-PdM8ZHohT9//2DWTPrCG2g3CHbE=", 109 | "requires": { 110 | "p-try": "^2.0.0" 111 | } 112 | }, 113 | "p-locate": { 114 | "version": "4.1.0", 115 | "resolved": "https://registry.npm.taobao.org/p-locate/download/p-locate-4.1.0.tgz", 116 | "integrity": "sha1-o0KLtwiLOmApL2aRkni3wpetTwc=", 117 | "requires": { 118 | "p-limit": "^2.2.0" 119 | } 120 | }, 121 | "p-try": { 122 | "version": "2.2.0", 123 | "resolved": "https://registry.npm.taobao.org/p-try/download/p-try-2.2.0.tgz", 124 | "integrity": "sha1-yyhoVA4xPWHeWPr741zpAE1VQOY=" 125 | }, 126 | "path-exists": { 127 | "version": "4.0.0", 128 | "resolved": "https://registry.npm.taobao.org/path-exists/download/path-exists-4.0.0.tgz", 129 | "integrity": "sha1-UTvb4tO5XXdi6METfvoZXGxhtbM=" 130 | }, 131 | "require-directory": { 132 | "version": "2.1.1", 133 | "resolved": "https://registry.npm.taobao.org/require-directory/download/require-directory-2.1.1.tgz", 134 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" 135 | }, 136 | "require-main-filename": { 137 | "version": "2.0.0", 138 | "resolved": "https://registry.npm.taobao.org/require-main-filename/download/require-main-filename-2.0.0.tgz", 139 | "integrity": "sha1-0LMp7MfMD2Fkn2IhW+aa9UqomJs=" 140 | }, 141 | "set-blocking": { 142 | "version": "2.0.0", 143 | "resolved": "https://registry.npm.taobao.org/set-blocking/download/set-blocking-2.0.0.tgz", 144 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" 145 | }, 146 | "string-width": { 147 | "version": "4.2.0", 148 | "resolved": "https://registry.npm.taobao.org/string-width/download/string-width-4.2.0.tgz", 149 | "integrity": "sha1-lSGCxGzHssMT0VluYjmSvRY7crU=", 150 | "requires": { 151 | "emoji-regex": "^8.0.0", 152 | "is-fullwidth-code-point": "^3.0.0", 153 | "strip-ansi": "^6.0.0" 154 | } 155 | }, 156 | "string.prototype.codepointat": { 157 | "version": "0.2.1", 158 | "resolved": "https://registry.npm.taobao.org/string.prototype.codepointat/download/string.prototype.codepointat-0.2.1.tgz", 159 | "integrity": "sha1-AErUTIr8cnUnsQjNRitNlxzUabw=" 160 | }, 161 | "strip-ansi": { 162 | "version": "6.0.0", 163 | "resolved": "https://registry.npm.taobao.org/strip-ansi/download/strip-ansi-6.0.0.tgz?cache=0&sync_timestamp=1573280518303&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstrip-ansi%2Fdownload%2Fstrip-ansi-6.0.0.tgz", 164 | "integrity": "sha1-CxVx3XZpzNTz4G4U7x7tJiJa5TI=", 165 | "requires": { 166 | "ansi-regex": "^5.0.0" 167 | } 168 | }, 169 | "tiny-inflate": { 170 | "version": "1.0.3", 171 | "resolved": "https://registry.npm.taobao.org/tiny-inflate/download/tiny-inflate-1.0.3.tgz", 172 | "integrity": "sha1-EicVSUkToYBRZqr3yTRnkz7qJsQ=" 173 | }, 174 | "which-module": { 175 | "version": "2.0.0", 176 | "resolved": "https://registry.npm.taobao.org/which-module/download/which-module-2.0.0.tgz", 177 | "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" 178 | }, 179 | "wrap-ansi": { 180 | "version": "6.2.0", 181 | "resolved": "https://registry.npm.taobao.org/wrap-ansi/download/wrap-ansi-6.2.0.tgz", 182 | "integrity": "sha1-6Tk7oHEC5skaOyIUePAlfNKFblM=", 183 | "requires": { 184 | "ansi-styles": "^4.0.0", 185 | "string-width": "^4.1.0", 186 | "strip-ansi": "^6.0.0" 187 | } 188 | }, 189 | "y18n": { 190 | "version": "4.0.1", 191 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", 192 | "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" 193 | }, 194 | "yargs": { 195 | "version": "15.3.1", 196 | "resolved": "https://registry.npm.taobao.org/yargs/download/yargs-15.3.1.tgz", 197 | "integrity": "sha1-lQW0cnY5Y+VK/mAUitJ6MwgY6Ys=", 198 | "requires": { 199 | "cliui": "^6.0.0", 200 | "decamelize": "^1.2.0", 201 | "find-up": "^4.1.0", 202 | "get-caller-file": "^2.0.1", 203 | "require-directory": "^2.1.1", 204 | "require-main-filename": "^2.0.0", 205 | "set-blocking": "^2.0.0", 206 | "string-width": "^4.2.0", 207 | "which-module": "^2.0.0", 208 | "y18n": "^4.0.0", 209 | "yargs-parser": "^18.1.1" 210 | } 211 | }, 212 | "yargs-parser": { 213 | "version": "18.1.3", 214 | "resolved": "https://registry.npm.taobao.org/yargs-parser/download/yargs-parser-18.1.3.tgz", 215 | "integrity": "sha1-vmjEl1xrKr9GkjawyHA2L6sJp7A=", 216 | "requires": { 217 | "camelcase": "^5.0.0", 218 | "decamelize": "^1.2.0" 219 | } 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /converter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bitmap-to-pixel-font-converter", 3 | "version": "0.1.0", 4 | "description": "Converts PCF bitmap font to OpenType pixel font.", 5 | "license": "GPL-2.0-or-later", 6 | "main": "pcf2opentype.js", 7 | "dependencies": { 8 | "binary-parser": "^1.6.2", 9 | "opentype.js": "^1.3.3", 10 | "yargs": "^15.3.1" 11 | }, 12 | "devDependencies": {} 13 | } 14 | -------------------------------------------------------------------------------- /converter/pcf-parser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview JavaScript class to parse PCF font file. 3 | */ 4 | 5 | 6 | const Parser = require('binary-parser').Parser; 7 | const fs = require('fs'); 8 | 9 | 10 | /** 11 | * Loads and parses PCF font file. 12 | * 13 | * https://www.iro.umontreal.ca/~boyer/typophile/doc/pcf-format.html 14 | * provides a detailed description for the PCF format. 15 | * 16 | * This class is not a general-pupose PCF parser for now. It only 17 | * implements a necessary subset of PCF features. 18 | */ 19 | class PcfParser { 20 | /** 21 | * @param {string} pcfFile The path to the PCF file to be loaded. 22 | */ 23 | constructor(pcfFile) { 24 | /** {string} */ 25 | this.pcfFile = pcfFile; 26 | 27 | /** 28 | * The byte buffer of the PCF file contents. 29 | * @type {Buffer} 30 | */ 31 | this.rawBuffer = null; 32 | 33 | /** 34 | * The pcf object parsed from the file. 35 | * @type {Object} 36 | */ 37 | this.pcf = null; 38 | } 39 | 40 | /** 41 | * Parses the PCF file. 42 | * @return {Object} The parsed object. 43 | */ 44 | parse() { 45 | console.log('Reading ' + this.pcfFile + '...'); 46 | this.rawBuffer = fs.readFileSync(this.pcfFile); 47 | console.log('Read ' + this.rawBuffer.length + ' bytes in total.'); 48 | 49 | const headerTableParser = new Parser() 50 | .endianess('little') 51 | .uint32('type') 52 | .uint32('format') 53 | .uint32('size') 54 | .uint32('offset'); 55 | 56 | const mainParser = new Parser() 57 | .endianess('little') 58 | .string('header', { 59 | length: 4 60 | }) 61 | .uint32('tableCount') 62 | .array('tocEntry', { 63 | type: headerTableParser, 64 | length: function() { 65 | return this.tableCount; 66 | } 67 | }); 68 | 69 | console.log('Parsing PCF format...'); 70 | this.pcf = mainParser.parse(this.rawBuffer); 71 | if (this.pcf.header != '\u0001fcp') { 72 | throw new Error('Not a valid PCF file.'); 73 | } 74 | 75 | for (let table of this.pcf.tocEntry) { 76 | table.typeString = PcfParser.tableTypeToString(table.type); 77 | table.formatString = PcfParser.tableFormatToString(table.format); 78 | 79 | switch (table.typeString) { 80 | case 'PCF_GLYPH_NAMES': 81 | this.pcf.glyphNameTable = this.parseGlyphNameTable( 82 | table.offset, table.format); 83 | break; 84 | case 'PCF_BDF_ENCODINGS': 85 | this.pcf.encodingTable = this.parseEncodingTable( 86 | table.offset, table.format); 87 | break; 88 | case 'PCF_METRICS': 89 | this.pcf.metricsTable = this.parseMetricsTable( 90 | table.offset, table.format); 91 | break; 92 | case 'PCF_BITMAPS': 93 | this.pcf.bitmapTable = this.parseBitmapTable( 94 | table.offset, table.format); 95 | break; 96 | } 97 | } 98 | console.log('Parsed ' + 99 | this.pcf.metricsTable.metricsCount + 100 | ' glyphs in total.'); 101 | return this.pcf; 102 | } 103 | 104 | /** 105 | * Converts PCF table type to string. 106 | * @param {number} type The table type. 107 | * @return {string} 108 | */ 109 | static tableTypeToString(type) { 110 | var ret = []; 111 | if (type & 1 << 0) 112 | ret.push('PCF_PROPERTIES'); 113 | if (type & 1 << 1) 114 | ret.push('PCF_ACCELERATORS'); 115 | if (type & 1 << 2) 116 | ret.push('PCF_METRICS'); 117 | if (type & 1 << 3) 118 | ret.push('PCF_BITMAPS'); 119 | if (type & 1 << 4) 120 | ret.push('PCF_INK_METRICS'); 121 | if (type & 1 << 5) 122 | ret.push('PCF_BDF_ENCODINGS'); 123 | if (type & 1 << 6) 124 | ret.push('PCF_SWIDTHS'); 125 | if (type & 1 << 7) 126 | ret.push('PCF_GLYPH_NAMES'); 127 | if (type & 1 << 8) 128 | ret.push('PCF_BDF_ACCELERATORS'); 129 | return ret.join(' | '); 130 | } 131 | 132 | /** 133 | * Converts PCF table format to string. 134 | * @param {number} format The table format. 135 | * @return {string} 136 | */ 137 | static tableFormatToString(format) { 138 | var ret = []; 139 | const value = format & 0xFFFFFF00; 140 | if (value == 0x200) 141 | ret.push('PCF_INKBOUNDS'); 142 | else if (value == 0x100) 143 | ret.push('PCF_COMPRESSED_METRICS'); 144 | else 145 | ret.push('PCF_DEFAULT_FORMAT'); 146 | 147 | if (format & 3 << 0 == 3) 148 | ret.push('PCF_GLYPH_PAD_MASK'); 149 | if (format & 1 << 2) 150 | ret.push('PCF_BYTE_MASK'); 151 | if (format & 1 << 3) 152 | ret.push('PCF_BIT_MASK'); 153 | if (format & 3 << 4 == 3 << 4) 154 | ret.push('PCF_SCAN_UNIT_MASK'); 155 | return ret.join(' | '); 156 | } 157 | 158 | /** 159 | * If the table format is PCF_COMPRESSED_METRICS. 160 | * @param {number} format The table format. 161 | * @return {boolean} 162 | */ 163 | static isCompressedMetrics(format) { 164 | const value = format & 0xFFFFFF00; 165 | return value == 0x100; 166 | } 167 | 168 | /** 169 | * Returns the byte endianess of the specified table format. 170 | * @param {number} format The table format. 171 | * @return {Array} The endianess string and the endianess 172 | * suffix string. 173 | */ 174 | static getEndianessFromFormat(format) { 175 | var endianess = format & 1 << 2 ? 'big' : 'little'; 176 | var endianessSuffix = format & 1 << 2 ? 'be' : 'le'; 177 | return [endianess, endianessSuffix]; 178 | } 179 | 180 | /** 181 | * Parses the glyph name table. 182 | * @param {number} offset The offset to locate the table in the raw buffer. 183 | * @param {number} format The table format. 184 | * @return {Object} The parsed object. 185 | */ 186 | parseGlyphNameTable(offset, format) { 187 | const [endianess, endianessSuffix] = 188 | PcfParser.getEndianessFromFormat(format); 189 | 190 | const tableParser = new Parser() 191 | .seek(offset) 192 | .endianess('little') // The first value is always 193 | // little endian. 194 | .uint32('format') 195 | .endianess(endianess) // The endianess of the following 196 | // numbers are specified by the 197 | // table format. 198 | .uint32('glyphCount') 199 | .array('offsets', { 200 | type: 'uint32' + endianessSuffix, 201 | length: function() { 202 | return this.glyphCount; 203 | } 204 | }) 205 | .uint32('stringSize'); 206 | 207 | var table = tableParser.parse(this.rawBuffer); 208 | 209 | table.strings = []; 210 | var baseOffset = 4 * 2 + 4 * table.glyphCount + 4; 211 | for (const stringOffset of table.offsets) { 212 | const stringParser = new Parser() 213 | .seek(offset + baseOffset + stringOffset) 214 | .string('string', { 215 | zeroTerminated: true 216 | }); 217 | var stringObj = stringParser.parse(this.rawBuffer); 218 | table.strings.push(stringObj.string); 219 | } 220 | return table; 221 | } 222 | 223 | /** 224 | * Parses the encoding table. 225 | * @param {number} offset The offset to locate the table in the raw buffer. 226 | * @param {number} format The table format. 227 | * @return {Object} The parsed object. 228 | */ 229 | parseEncodingTable(offset, format) { 230 | const [endianess, endianessSuffix] = 231 | PcfParser.getEndianessFromFormat(format); 232 | 233 | const tableParser = new Parser() 234 | .seek(offset) 235 | .endianess('little') 236 | .uint32('format') 237 | .endianess(endianess) 238 | .uint16('minCharOrByte2') 239 | .uint16('maxCharOrByte2') 240 | .uint16('minByte1') 241 | .uint16('maxByte1') 242 | .uint16('defaultChar') 243 | .array('glyphIndeces', { 244 | type: 'uint16' + endianessSuffix, 245 | length: function() { 246 | return (this.maxCharOrByte2 - this.minCharOrByte2 + 1) * 247 | (this.maxByte1 - this.minByte1 + 1); 248 | } 249 | }); 250 | return tableParser.parse(this.rawBuffer); 251 | } 252 | 253 | /** 254 | * Parses the metrics table. 255 | * @param {number} offset The offset to locate the table in the raw buffer. 256 | * @param {number} format The table format. 257 | * @return {Object} The parsed object. 258 | */ 259 | parseMetricsTable(offset, format) { 260 | const [endianess, endianessSuffix] = 261 | PcfParser.getEndianessFromFormat(format); 262 | 263 | var entryParser = new Parser(); 264 | var tableParser = new Parser(); 265 | var table = null; 266 | 267 | if (PcfParser.isCompressedMetrics(format)) { 268 | // Compressed metrics format. 269 | entryParser 270 | .endianess(endianess) 271 | .uint8('leftSidedBearing') 272 | .uint8('rightSideBearing') 273 | .uint8('characterWidth') 274 | .uint8('characterAscent') 275 | .uint8('characterDescent'); 276 | tableParser 277 | .seek(offset) 278 | .endianess('little') 279 | .uint32('format') 280 | .endianess(endianess) 281 | .uint16('metricsCount') 282 | .array('metrics', { 283 | type: entryParser, 284 | length: function() { 285 | return this.metricsCount; 286 | } 287 | }); 288 | table = tableParser.parse(this.rawBuffer); 289 | for (let entry of table.metrics) { 290 | entry.leftSidedBearing -= 0x80; 291 | entry.rightSideBearing -= 0x80; 292 | entry.characterWidth -= 0x80; 293 | entry.characterAscent -= 0x80; 294 | entry.characterDescent -= 0x80; 295 | entry.characterAttributes = 0; 296 | } 297 | } else { 298 | // Uncompressed metrics format. 299 | entryParser 300 | .endianess(endianess) 301 | .uint16('leftSidedBearing') 302 | .uint16('rightSideBearing') 303 | .uint16('characterWidth') 304 | .uint16('characterAscent') 305 | .uint16('characterDescent') 306 | .uint16('characterAttributes'); 307 | tableParser 308 | .seek(offset) 309 | .endianess('little') 310 | .uint32('format') 311 | .endianess(endianess) 312 | .uint32('metricsCount') 313 | .array('metrics', { 314 | type: entryParser, 315 | length: function() { 316 | return this.metricsCount; 317 | } 318 | }); 319 | table = tableParser.parse(this.rawBuffer); 320 | } 321 | return table; 322 | } 323 | 324 | /** 325 | * Parses the bitmap table. 326 | * @param {number} offset The offset to locate the table in the raw buffer. 327 | * @param {number} format The table format. 328 | * @return {Object} The parsed object. 329 | */ 330 | parseBitmapTable(offset, format) { 331 | const [endianess, endianessSuffix] = 332 | PcfParser.getEndianessFromFormat(format); 333 | 334 | const tableParser = new Parser() 335 | .seek(offset) 336 | .endianess('little') 337 | .uint32('format') 338 | .endianess(endianess) 339 | .uint32('glyphCount') 340 | .array('offsets', { 341 | type: 'uint32' + endianessSuffix, 342 | length: function() { 343 | return this.glyphCount; 344 | } 345 | }) 346 | .array('bitmapSizes', { 347 | type: 'uint32' + endianessSuffix, 348 | length: 4 349 | }); 350 | 351 | var table = tableParser.parse(this.rawBuffer); 352 | table.bitmapSize = table.bitmapSizes[table.format & 3]; 353 | table.bitmapsBaseOffset = offset + 4 * 2 + 4 * table.glyphCount + 4 * 4; 354 | return table; 355 | } 356 | 357 | /** 358 | * Given a glyph's Unicode value, returns its glyph name. 359 | * @param {number} glyphCode The Unicode value of the glyph. 360 | * @return {string} 361 | */ 362 | getGlyphName(glyphCode) { 363 | if (this.pcf == null) { 364 | throw new Error('The PCF has not been parsed yet.'); 365 | } 366 | const glyphIndex = this.pcf.encodingTable.glyphIndeces[glyphCode]; 367 | return this.pcf.glyphNameTable.strings[glyphIndex]; 368 | } 369 | 370 | /** 371 | * Given a glyph's Unicode value, returns its metrics. 372 | * @param {number} glyphCode The Unicode value of the glyph. 373 | * @return {Object} 374 | */ 375 | getGlyphMetrics(glyphCode) { 376 | if (this.pcf == null) { 377 | throw new Error('The PCF has not been parsed yet.'); 378 | } 379 | const glyphIndex = this.pcf.encodingTable.glyphIndeces[glyphCode]; 380 | return this.pcf.metricsTable.metrics[glyphIndex]; 381 | } 382 | 383 | /** 384 | * Given a glyph's Unicode value, returns the byte buffer of its 385 | * bitmap. 386 | * @param {number} glyphCode The Unicode value of the glyph. 387 | * @return {Buffer} 388 | */ 389 | getGlyphBitmap(glyphCode) { 390 | if (this.pcf == null) { 391 | throw new Error('The PCF has not been parsed yet.'); 392 | } 393 | const glyphIndex = this.pcf.encodingTable.glyphIndeces[glyphCode]; 394 | var bitmapOffset = this.pcf.bitmapTable.offsets[glyphIndex]; 395 | var bitmapEnd = glyphIndex < this.pcf.bitmapTable.glyphCount - 1 ? 396 | this.pcf.bitmapTable.offsets[glyphIndex + 1] : 397 | this.pcf.bitmapTable.bitmapSize; 398 | bitmapOffset += this.pcf.bitmapTable.bitmapsBaseOffset; 399 | bitmapEnd += this.pcf.bitmapTable.bitmapsBaseOffset; 400 | return this.rawBuffer.slice(bitmapOffset, bitmapEnd); 401 | } 402 | }; 403 | 404 | 405 | module.exports.PcfParser = PcfParser; 406 | -------------------------------------------------------------------------------- /converter/pcf2opentype.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node --max-old-space-size=8192 2 | 3 | 4 | const { PcfParser } = require('./pcf-parser'); 5 | const opentype = require('opentype.js'); 6 | const yargs = require('yargs'); 7 | 8 | 9 | const argv = yargs 10 | .usage('Usage: $0 [options]') 11 | 12 | .alias('i', 'input') 13 | .describe('i', 'The bitmap font file to be converted, in PCF format.') 14 | 15 | .alias('o', 'output') 16 | .describe('o', 'The output OpenType font file, in OTF format.') 17 | 18 | .alias('p', 'glyph_size_in_pixel') 19 | .describe('p', 20 | 'The max glyph size in pixel. E.g., 10pt Chinese bitmap font ' + 21 | 'usually uses 13 as the pixel size, 15 for 11pt, 16 for 12pt. ' + 22 | 'etc. This value could be found in the properties table of ' + 23 | 'the PCF file.') 24 | .default('p', 13) 25 | 26 | .alias('s', 'dot_shape') 27 | .choices('s', ['square', 'circle', 'diamond']) 28 | .describe('s', 'The shape of dots.') 29 | .default('s', 'square') 30 | 31 | .alias('t', 'font_style') 32 | .describe('t', 'The style of the generated font.') 33 | .default('t', 'Regular') 34 | 35 | .alias('f', 'family_name') 36 | .describe('f', 'The family name of the generated font.') 37 | 38 | .alias('r', 'font_version') 39 | .describe('r', 'The version of the generated font.') 40 | .default('r', '0.1') 41 | 42 | .alias('e', 'font_designer') 43 | .describe('e', 'The designer of the generated font.') 44 | .default('e', 'wixette') 45 | 46 | .alias('c', 'font_copyright') 47 | .describe('c', 'The copyright of the generated font.') 48 | .default('c', 'Copyright (C) 2020 wixette') 49 | 50 | .alias('l', 'font_license') 51 | .describe('l', 'The license of the generated font.') 52 | .default('l', 'GPL 2.0') 53 | 54 | .alias('d', 'dry_run') 55 | .describe('d', 56 | 'Only exports a few hundreds of glyphs to the target font, ' + 57 | 'for testing purposes.') 58 | .boolean('d') 59 | .default('d', false) 60 | 61 | .alias('g', 'gb2312_only') 62 | .describe('g', 'If set, only GB2312 Chinese glyphs are output.') 63 | .boolean('g') 64 | .default('g', false) 65 | 66 | .demandOption(['i', 'o', 'f']) 67 | .help('h') 68 | .alias('h', 'help') 69 | .alias('v', 'version') 70 | .argv; 71 | 72 | 73 | // Characters to render the bitmap. 74 | const WHITE_PIXEL = '_'; 75 | const BLACK_PIXEL = '#'; 76 | 77 | 78 | // Basic metrics. 79 | const PIXEL_HEIGHT = argv.glyph_size_in_pixel; 80 | const DEFAULT_ASCENT_IN_PIXELS = PIXEL_HEIGHT - 2; 81 | const UNITS_PER_EM = 1000; 82 | const PIXEL_SIZE = UNITS_PER_EM / PIXEL_HEIGHT; 83 | const PIXEL_PADDING = PIXEL_SIZE / 9; 84 | const BYTES_PER_LINE = 4; 85 | const DESCENDER_HEIGHT = PIXEL_SIZE * 3; 86 | 87 | 88 | /** 89 | * Unicode set that covers ASCII glyphs. 90 | * @type {Set} 91 | */ 92 | const ASCII_SET = (function() { 93 | let ret = new Set(); 94 | for (let code = 0x20; code <= 0xFF; code++) 95 | ret.add(code); 96 | return ret; 97 | })(); 98 | 99 | 100 | /** 101 | * Unicode set that covers GB2312 glyphs. 102 | * @type {Set} 103 | */ 104 | const GB2312_SET = (function() { 105 | let ret = new Set(); 106 | const gbkDecoder = new TextDecoder('GBK'); 107 | for (let byte1 = 0xA1; byte1 <= 0xA9; byte1++) { 108 | for (let byte2 = 0xA1; byte2 <= 0xFE; byte2++) { 109 | const bytes = new Uint8Array([byte1, byte2]); 110 | const char = gbkDecoder.decode(bytes); 111 | ret.add(char.charCodeAt(0)); 112 | } 113 | } 114 | for (let byte1 = 0xB0; byte1 <= 0xF7; byte1++) { 115 | for (let byte2 = 0xA1; byte2 <= 0xFE; byte2++) { 116 | const bytes = new Uint8Array([byte1, byte2]); 117 | const char = gbkDecoder.decode(bytes); 118 | ret.add(char.charCodeAt(0)); 119 | } 120 | } 121 | return ret; 122 | })(); 123 | 124 | 125 | /** 126 | * Converts a byte to a 8-char string. 127 | * @param {number} byte The byte. 128 | * @return {string} Binary zero will be replaced with WHITE_PIXEL and 129 | * one will be replaced with BLACK_PIXEL. 130 | */ 131 | function byteToBinaryString(byte) { 132 | var binaryString = parseInt(byte, 10).toString(2); 133 | var paddedString = ('00000000' + binaryString).substr(-8); 134 | return paddedString.replace(/0/g, WHITE_PIXEL).replace(/1/g, BLACK_PIXEL); 135 | } 136 | 137 | 138 | /** 139 | * Converts a glyph's bitmap to a set of binary string lines. 140 | * @param {Buffer} bitmap The glyph bitmap. 141 | * @param {number} width The pixel width of the bitmap. 142 | * @return {Array} The renderred string lines. 143 | */ 144 | function bitmapToBinaryLines(bitmap, width) { 145 | var lines = []; 146 | for (let i = 0; i < bitmap.length; i += BYTES_PER_LINE) { 147 | let line = ''; 148 | for (let j = 0; j < BYTES_PER_LINE; j++) { 149 | line += byteToBinaryString(bitmap[i + j]); 150 | } 151 | lines.push(line.slice(0, width)); 152 | } 153 | return lines; 154 | } 155 | 156 | 157 | /** 158 | * Returns if the glyph should be output to the target font file. 159 | * @param {number} glyphCode The Unicode value of the glyph. 160 | * @return {boolean} 161 | */ 162 | function isAcceptedGlyph(glyphCode) { 163 | if (argv.dry_run) { 164 | return ASCII_SET.has(glyphCode); 165 | } else if (argv.gb2312_only) { 166 | return ASCII_SET.has(glyphCode) || GB2312_SET.has(glyphCode); 167 | } else { 168 | return true; 169 | } 170 | } 171 | 172 | 173 | /** 174 | * Converts screen coordinate to font coordinate. 175 | * @param {number} x X value in screen coordinate system. 176 | * @param {number} y Y value in screen coordinate system. 177 | * @param {glyphTop} glyphTop The top location of a glyph in font 178 | * coordinate system. 179 | * @return {Object} x and y in font coordinate system. 180 | */ 181 | function ScreenXyToFontXy(x, y, glyphTop) { 182 | return [Math.round(x), Math.round(glyphTop - y)]; 183 | } 184 | 185 | 186 | /** 187 | * Draws a square dot. 188 | * @param {opentype.Path} path The opentyp path. 189 | * @param {number} left The left location of the bound box. 190 | * @param {number} top The top location of the bound box. 191 | * @param {number} right The left location of the bound box. 192 | * @param {number} bottom The left location of the bound box. 193 | */ 194 | function drawSquareDot(path, left, top, right, bottom) { 195 | path.moveTo(left, top); 196 | path.lineTo(left, bottom); 197 | path.lineTo(right, bottom); 198 | path.lineTo(right, top); 199 | path.lineTo(left, top); 200 | } 201 | 202 | /** 203 | * Draws a diamond dot. 204 | * @param {opentype.Path} path The opentyp path. 205 | * @param {number} left The left location of the bound box. 206 | * @param {number} top The top location of the bound box. 207 | * @param {number} right The left location of the bound box. 208 | * @param {number} bottom The left location of the bound box. 209 | */ 210 | function drawDiamondDot(path, left, top, right, bottom) { 211 | const RADIUS = (right - left) / 2; 212 | path.moveTo(left + RADIUS, top); 213 | path.lineTo(left, top - RADIUS); 214 | path.lineTo(left + RADIUS, bottom); 215 | path.lineTo(right, top - RADIUS); 216 | path.lineTo(left + RADIUS, top); 217 | } 218 | 219 | /** 220 | * Draws a circle dot. 221 | * @param {opentype.Path} path The opentyp path. 222 | * @param {number} left The left location of the bound box. 223 | * @param {number} top The top location of the bound box. 224 | * @param {number} right The left location of the bound box. 225 | * @param {number} bottom The left location of the bound box. 226 | */ 227 | function drawCircleDot(path, left, top, right, bottom) { 228 | // Cubic Bézier approximation to a circular arc (1/4 of a full 229 | // circle): P0 = (0,1), P1 = (c,1), P2 = (1,c), P3 = (1,0) 230 | const C = 0.551915; 231 | const RADIUS = (right - left) / 2; 232 | path.moveTo(left + RADIUS, top); 233 | path.curveTo(left + RADIUS - RADIUS * C, top, 234 | left, top - RADIUS + RADIUS * C, 235 | left, top - RADIUS); 236 | path.curveTo(left, top - RADIUS - RADIUS * C, 237 | left + RADIUS - RADIUS * C, bottom, 238 | left + RADIUS, bottom); 239 | path.curveTo(left + RADIUS + RADIUS * C, bottom, 240 | right, top - RADIUS - RADIUS * C, 241 | right, top - RADIUS); 242 | path.curveTo(right, top - RADIUS + RADIUS * C, 243 | left + RADIUS + RADIUS * C, top, 244 | left + RADIUS, top); 245 | } 246 | 247 | 248 | /** 249 | * Vectorizes a bitmap glyph. 250 | * @param {Object} glyphInfo The info of the specified glyph. 251 | * @return {opentype.Glyph} 252 | */ 253 | function vectorizeGlyph(glyphInfo) { 254 | const width = glyphInfo.metrics.characterWidth; 255 | const glyphWidth = width * PIXEL_SIZE; 256 | const glyphTop = PIXEL_HEIGHT * PIXEL_SIZE - DESCENDER_HEIGHT; 257 | const xOffset = glyphInfo.metrics.leftSidedBearing * PIXEL_SIZE; 258 | const yOffset = (DEFAULT_ASCENT_IN_PIXELS - 259 | glyphInfo.metrics.characterAscent) * PIXEL_SIZE; 260 | const binaryLines = bitmapToBinaryLines(glyphInfo.bitmap, width); 261 | 262 | const path = new opentype.Path(); 263 | for (let y = 0; y < binaryLines.length; y++) { 264 | const line = binaryLines[y]; 265 | console.log(line); 266 | 267 | for (let x = 0; x < line.length; x++) { 268 | if (line[x] == BLACK_PIXEL) { 269 | const x1 = xOffset + x * PIXEL_SIZE + PIXEL_PADDING; 270 | const y1 = yOffset + y * PIXEL_SIZE + PIXEL_PADDING; 271 | const [left, top] = ScreenXyToFontXy(x1, y1, glyphTop); 272 | const x2 = xOffset + (x + 1) * PIXEL_SIZE - PIXEL_PADDING; 273 | const y2 = yOffset + (y + 1) * PIXEL_SIZE - PIXEL_PADDING; 274 | const [right, bottom] = ScreenXyToFontXy(x2, y2, glyphTop); 275 | 276 | switch (argv.dot_shape) { 277 | case 'square': 278 | drawSquareDot(path, left, top, right, bottom); 279 | break; 280 | case 'circle': 281 | drawCircleDot(path, left, top, right, bottom); 282 | break; 283 | case 'diamond': 284 | drawDiamondDot(path, left, top, right, bottom); 285 | break; 286 | } 287 | } 288 | } 289 | } 290 | path.close(); 291 | 292 | const fontGlyph = new opentype.Glyph({ 293 | name: glyphInfo.name, 294 | unicode: glyphInfo.code, 295 | advanceWidth: glyphWidth, 296 | path: path 297 | }); 298 | return fontGlyph; 299 | } 300 | 301 | 302 | /** 303 | * Converts the input PCF font to the target OpenType font. 304 | */ 305 | function convert() { 306 | var parser = new PcfParser(argv.input); 307 | parser.parse(); 308 | 309 | var glyphs = []; 310 | for (let code = 0; 311 | code < parser.pcf.encodingTable.glyphIndeces.length; 312 | code++) { 313 | const index = parser.pcf.encodingTable.glyphIndeces[code]; 314 | if (index >= 0 && 315 | index < parser.pcf.metricsTable.metricsCount && 316 | isAcceptedGlyph(code)) { 317 | glyphs.push({ 318 | code: code, 319 | index: index, 320 | name: parser.getGlyphName(code), 321 | metrics: parser.getGlyphMetrics(code), 322 | bitmap: parser.getGlyphBitmap(code) 323 | }); 324 | } 325 | } 326 | console.log('Number of glyphs to be converted: ' + glyphs.length); 327 | 328 | // Create the bézier paths for each of the glyphs. 329 | // Note that the .notdef glyph is required. 330 | const notdefGlyph = new opentype.Glyph({ 331 | name: '.notdef', 332 | unicode: 0, 333 | advanceWidth: 8 * PIXEL_SIZE, 334 | path: new opentype.Path() 335 | }); 336 | var fontGlyphs = [notdefGlyph]; 337 | 338 | for (let i = 0; i < glyphs.length; i++) { 339 | console.log(); 340 | console.log((i / glyphs.length * 100) + '% done.'); 341 | console.log('Creating vector glyph for glyph: ' + glyphs[i].name + 342 | ', U+' + glyphs[i].encoding + 343 | ', W=' + glyphs[i].metrics.characterWidth); 344 | console.log(); 345 | fontGlyphs.push(vectorizeGlyph(glyphs[i])); 346 | console.log(); 347 | } 348 | 349 | console.log('Number of glyphs to be output: ' + glyphs.length); 350 | console.log('Outputing font glyphs to ' + argv.output); 351 | const font = new opentype.Font({ 352 | familyName: argv.family_name, 353 | styleName: argv.font_style, 354 | copyright: argv.font_copyright, 355 | designer: argv.font_designer, 356 | license: argv.font_license, 357 | version: argv.font_version, 358 | unitsPerEm: UNITS_PER_EM, 359 | ascender: PIXEL_HEIGHT * PIXEL_SIZE - DESCENDER_HEIGHT, 360 | descender: -DESCENDER_HEIGHT, 361 | glyphs: fontGlyphs 362 | }); 363 | 364 | font.download(argv.output); 365 | console.log('Done.'); 366 | } 367 | 368 | 369 | convert(); 370 | -------------------------------------------------------------------------------- /images/example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wixette/dotted-chinese-fonts/ae60462b56d670a84f96852ab6aaef4dad7c43d5/images/example1.png -------------------------------------------------------------------------------- /images/example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wixette/dotted-chinese-fonts/ae60462b56d670a84f96852ab6aaef4dad7c43d5/images/example2.png -------------------------------------------------------------------------------- /images/example3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wixette/dotted-chinese-fonts/ae60462b56d670a84f96852ab6aaef4dad7c43d5/images/example3.png -------------------------------------------------------------------------------- /images/example4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wixette/dotted-chinese-fonts/ae60462b56d670a84f96852ab6aaef4dad7c43d5/images/example4.png -------------------------------------------------------------------------------- /images/example5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wixette/dotted-chinese-fonts/ae60462b56d670a84f96852ab6aaef4dad7c43d5/images/example5.png -------------------------------------------------------------------------------- /release/v_0_1/dotted_songti_v_0_1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wixette/dotted-chinese-fonts/ae60462b56d670a84f96852ab6aaef4dad7c43d5/release/v_0_1/dotted_songti_v_0_1.zip --------------------------------------------------------------------------------