63 Commits
Author SHA1 Message Date
Idrees Hassan 99b32ab406 Add font files to repository for CDN access 2025-01-02 12:17:30 -05:00
Idrees Hassan 9951196477 Revert "Update socials link"
This reverts commit 0034e018aa.
2024-11-15 21:36:17 -05:00
Idrees Hassan 0034e018aa Update socials link 2024-11-15 21:15:50 -05:00
Idrees Hassan 69c175192a Update window title 2024-11-15 18:27:38 -05:00
Idrees Hassan 270bbfb3ab Merge branch 'main' of https://github.com/IdreesInc/Monocraft 2024-08-16 14:53:58 -04:00
Idrees Hassan 63e481ba29 Update index.md 2024-08-16 14:53:52 -04:00
Idrees 7893cb1659 Create CNAME 2024-08-16 14:28:32 -04:00
Idrees Hassan 4d9c1386d7 Add project site 2024-08-16 14:27:22 -04:00
Idrees 0d88c49c2c Merge pull request #148 from IdreesInc/v4
V4 - New font weights, small fixes
2024-08-10 16:53:41 -04:00
Idrees 785603ba93 Merge pull request #136 from eminforks/main
addition of latin small letter dotless i
2024-08-10 16:51:31 -04:00
Idrees 1f972371d1 Merge pull request #139 from ya0guang/patch-1
Change variable name
2024-08-10 16:49:24 -04:00
Idrees 16ded48b20 Merge pull request #130 from Ciubix8513/html-commment-ligature
Add html comment ligature
2024-08-10 16:46:45 -04:00
Idrees 696ada3de7 Merge pull request #128 from Dheatly23/bold-italic
Adds Bold, Italic, and Thin Variants
2024-08-10 16:44:23 -04:00
Idrees Hassan 5db1c0abde Update README.md 2024-08-10 16:44:09 -04:00
Idrees Hassan ee1ca3a096 Update README.md 2024-08-10 16:01:49 -04:00
Idrees Hassan f4ef05670f Update generated file names and version 2024-08-10 15:58:54 -04:00
Idrees 8555032acc Update README.md 2024-06-28 17:59:06 -04:00
Hongbo dfccf446e5 enhance the variable naming to avoid confusion 2024-03-27 22:43:11 -04:00
Idrees 74d9255a80 Update README.md 2024-02-09 17:04:13 -05:00
Idrees f825d5211d Update README.md 2024-02-09 17:04:01 -05:00
Emin Arıcan 5d00a3c3bd latin small letter dotless i 2024-01-13 13:33:20 +03:00
Dheatly23 0d1c8192b8 Reduce Black Boldness 2024-01-08 10:25:57 +07:00
Dheatly23 ed296701fe Port Features 2023-12-24 17:19:25 +07:00
Luna b4294f637b Add html comment ligature 2023-07-07 18:08:49 +03:00
Dheatly23 1ee5a40d6e Reduce Boldness 2023-06-23 20:52:34 +07:00
Dheatly23 5ec7862f57 Adds Bold Italic and Collect Fonts into .ttc 2023-06-23 20:52:16 +07:00
Dheatly23 5bbe9790da Fix Bold Font 2023-06-19 20:24:45 +07:00
Dheatly23 dc7fc21780 Speedup on Bold Variant 2023-06-19 14:45:26 +07:00
Dheatly23 00037d85de Adds Bold and Italic Variant 2023-06-19 13:30:56 +07:00
Idrees e79ff56105 Update README.md 2023-06-10 14:24:54 -04:00
Idrees ec2b8014ad Merge pull request #125 from IdreesInc/v3
V3
2023-06-10 14:24:08 -04:00
Idrees Hassan 9a9e4c8d83 Update readme with new glyphs 2023-06-10 14:18:56 -04:00
Idrees Hassan d4ea373b62 Remove soft hyphen 2023-06-10 14:12:16 -04:00
Idrees Hassan ac2252b81c Update code style 2023-06-10 13:38:20 -04:00
Idrees Hassan bd29dc05a9 Merge branch 'v3-beta' into v3 2023-06-10 13:17:52 -04:00
Idrees 7da29e1173 Merge pull request #116 from Ciubix8513/progress-bar-ligatures
Added continuous ligatures
2023-06-10 13:15:21 -04:00
Luna e10bf402dc Fix typo 2023-05-28 22:23:58 +03:00
Luna d44916965e Fix type continuos -> continuous 2023-05-28 06:13:10 +03:00
Luna c0cabc34e9 Fix incorect continious ligature length 2023-05-11 16:20:56 +03:00
Luna 3080f38f80 Add plus sign continious ligature 2023-05-11 15:58:01 +03:00
Idrees Hassan 0e9012fe82 Update version number 2023-03-22 19:28:26 -04:00
Idrees eced150111 Merge pull request #105 from dantaeusb-mc/main
Extend characters with European non-latin set
2023-03-22 18:33:57 -04:00
Luna 7ef1429d17 Added wavy underline ligature 2023-03-09 15:21:48 +03:00
Luna 9c26e52eb5 Renamed to continuos ligatures 2023-03-07 22:38:11 +03:00
Luna 267882c42d Added more bars, removed debug 2023-03-07 22:33:16 +03:00
Luna 04245a37e7 Added progress bar generation to monocraft, adjusted ==> 2023-03-07 22:21:42 +03:00
Luna f33b99b2b2 Fixed, to work properly 2023-03-07 21:43:31 +03:00
Luna 7f8d16087e Improved documentation(thank you chatGPT), tried to optimize 2023-03-07 20:29:35 +03:00
Luna d994da27d7 Added generate progress bar function 2023-03-07 18:49:25 +03:00
Idrees ebd345245f Merge pull request #113 from IdreesInc/106-cyrillic-ц-renders-higher-than-it-should
Add descent to ц and bump version
2023-02-20 10:36:18 -05:00
Idrees Hassan cec34615f2 Add descent to ц and bump version 2023-02-20 10:36:06 -05:00
Idrees c3be0076a6 Merge pull request #112 from Dheatly23/test-join-polygon
Try to Join Polygons
2023-02-20 10:12:48 -05:00
Idrees 4fb297a1da Merge pull request #108 from Dheatly23/fix-ligature-windows
Fix Broken Ligature on Windows
2023-02-20 10:00:38 -05:00
Dheatly23 cb1ac86f1e Fix Skipping Holes 2023-02-16 13:40:28 +07:00
Dheatly23 d6ef5a8aa4 Try to Join Polygons 2023-02-16 13:32:11 +07:00
Idrees f2b5d120f4 Merge pull request #109 from Dheatly23/non-integer-margin
Take Into Account Non-integer Offsets
2023-02-12 16:23:10 -05:00
Dheatly23 47696c0c27 Use Keyword Argument
Ensure future-proofing for extra data with kwarg.
2023-02-10 11:35:16 +07:00
Dheatly23 6f343ad936 Take Into Account Non-integer Offsets 2023-02-09 11:24:37 +07:00
Dheatly23 98395d8764 Fix Broken Ligature 2023-02-09 09:14:31 +07:00
Dmitry Burlakov 507ccd46a9 Fix lowercase cyrillic tse 2023-02-06 00:59:56 +03:00
Dmitry Burlakov 1e5e6592ce Fix descending direction 2023-02-05 23:56:39 +03:00
Dmitry Burlakov 919feb1b3e Fix descent 2023-02-05 23:54:06 +03:00
Dmitry Burlakov 6957850277 Extend characters 2023-02-05 23:09:02 +03:00
44 changed files with 15543 additions and 190 deletions

No files matched your search

+3 -2
View File
@@ -1,5 +1,6 @@
.DS_Store
src/__pycache__
dist/
build/
build/
docs/_site/
docs/.jekyll-cache/
+13 -5
View File
@@ -4,10 +4,16 @@
![](https://img.shields.io/github/license/IdreesInc/Monocraft)
[![](https://img.shields.io/github/v/release/IdreesInc/Monocraft)](https://GitHub.com/IdreesInc/Monocraft/releases/)
## [`Download it here!`](https://github.com/IdreesInc/Monocraft/releases)
<br/>
![](images/preview.png)
The monospaced font for developers who like Minecraft a bit _too_ much.
If you'd like to see a vectorized version of this font, try [Miracode](https://github.com/IdreesInc/Miracode)!
*Notice: This project is not affiliated with Minecraft or Mojang in any way and is exclusively a fan project. This font emulates the typeface of the font used in the Minecraft UI, but it does not include any assets or font files from the original game.*
## Features
@@ -15,7 +21,7 @@ The monospaced font for developers who like Minecraft a bit _too_ much.
- Minecraft!
- The characters in this font were based around the [typeface](https://github.com/IdreesInc/Minecraft-Font) used in the Minecraft UI, with each glyph updated for better readability and spacing
- Monospaced!
- Each of the 500+ glyphs included in this font have been carefully redesigned to work in a monospaced font
- Each of the 1500+ glyphs included in this font have been carefully redesigned to work in a monospaced font
- Thin characters like "i" and "l" have been reworked with tasteful tails and serifs to look better in a monospaced environment
- Programming ligatures!
- Add some spice to your programming life with all new ligature characters
@@ -29,7 +35,7 @@ The monospaced font for developers who like Minecraft a bit _too_ much.
### Windows
Download the most recent `Monocraft.ttf` file from the [Releases](https://github.com/IdreesInc/Monocraft/releases) page. Right click on the downloaded font and select **Install**. You might need administrative access to install fonts, depending on your machine.
Download the most recent `Monocraft.ttc` file from the [Releases](https://github.com/IdreesInc/Monocraft/releases) page. Right click on the downloaded font and select **Install**. You might need administrative access to install fonts, depending on your machine.
### Mac
@@ -42,11 +48,11 @@ brew install --cask font-monocraft
#### Manually
Download the most recent `Monocraft.ttf` file from the [Releases](https://github.com/IdreesInc/Monocraft/releases) page. Double click on the downloaded font file and select **Install Font** in the window that appears. More help available [here](https://support.apple.com/en-us/HT201749).
Download the most recent `Monocraft.ttc` file from the [Releases](https://github.com/IdreesInc/Monocraft/releases) page. Double click on the downloaded font file and select **Install Font** in the window that appears. More help available [here](https://support.apple.com/en-us/HT201749).
### Linux
Download the most recent `Monocraft.ttf` file from the [Releases](https://github.com/IdreesInc/Monocraft/releases) page. Move the file to ~/.local/share/fonts (create the folder if it doesn't already exist). In a terminal, run `fc-cache -fv`. Alternatively, log out and log in again.
Download the most recent `Monocraft.ttc` file from the [Releases](https://github.com/IdreesInc/Monocraft/releases) page. Move the file to ~/.local/share/fonts (create the folder if it doesn't already exist). In a terminal, run `fc-cache -fv`. Alternatively, log out and log in again.
## How to use
@@ -60,6 +66,8 @@ So far, the following ligatures have been added to the font:
<img src="images/ligatures.png" width="300">
And with the contributions of [@Ciubix8513](https://github.com/Ciubix8513), Monocraft now includes continuous ligatures that enable you to type arrows and lines that _just keep going_.
If there is another character combination that you feel could be a ligature, feel free to create an issue!
### How are these characters generated?
@@ -72,4 +80,4 @@ I've got you covered, check out my rendition of the Minecraft typeface [here](ht
### What if I want to use a font that's actually good?
Understandable, check out [Fira Code](https://github.com/tonsky/FiraCode) for a font with amazing attention to detail, or [Scientifica](https://github.com/nerdypepper/scientifica) for a bitmap font similar to this.
Understandable, check out [Fira Code](https://github.com/tonsky/FiraCode) for a font with amazing attention to detail, or [Scientifica](https://github.com/nerdypepper/scientifica) for a bitmap font similar to this. Feel free to also check out my font [Miracode](https://github.com/IdreesInc/Miracode) for a font based on Monocraft that's a little more usable!
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
monocraft.idreesinc.com
+3
View File
@@ -0,0 +1,3 @@
gem "jekyll"
gem "webrick"
gem "jekyll-remote-theme"
+78
View File
@@ -0,0 +1,78 @@
GEM
specs:
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
colorator (1.1.0)
concurrent-ruby (1.3.4)
em-websocket (0.5.3)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0)
eventmachine (1.2.7)
ffi (1.17.0-arm64-darwin)
forwardable-extended (2.6.0)
http_parser.rb (0.8.0)
i18n (1.8.11)
concurrent-ruby (~> 1.0)
jekyll (4.3.3)
addressable (~> 2.4)
colorator (~> 1.0)
em-websocket (~> 0.5)
i18n (~> 1.0)
jekyll-sass-converter (>= 2.0, < 4.0)
jekyll-watch (~> 2.0)
kramdown (~> 2.3, >= 2.3.1)
kramdown-parser-gfm (~> 1.0)
liquid (~> 4.0)
mercenary (>= 0.3.6, < 0.5)
pathutil (~> 0.9)
rouge (>= 3.0, < 5.0)
safe_yaml (~> 1.0)
terminal-table (>= 1.8, < 4.0)
webrick (~> 1.7)
jekyll-remote-theme (0.4.3)
addressable (~> 2.0)
jekyll (>= 3.5, < 5.0)
jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0)
rubyzip (>= 1.3.0, < 3.0)
jekyll-sass-converter (2.1.0)
sassc (> 2.0.1, < 3.0)
jekyll-watch (2.2.1)
listen (~> 3.0)
kramdown (2.3.1)
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
liquid (4.0.3)
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
mercenary (0.4.0)
pathutil (0.16.2)
forwardable-extended (~> 2.6)
public_suffix (4.0.7)
rb-fsevent (0.11.2)
rb-inotify (0.11.1)
ffi (~> 1.0)
rexml (3.3.5)
strscan
rouge (3.26.1)
rubyzip (2.3.2)
safe_yaml (1.0.5)
sassc (2.4.0)
ffi (~> 1.9)
strscan (3.1.0)
terminal-table (2.0.0)
unicode-display_width (~> 1.1, >= 1.1.1)
unicode-display_width (1.8.0)
webrick (1.7.0)
PLATFORMS
arm64-darwin
DEPENDENCIES
jekyll
jekyll-remote-theme
webrick
BUNDLED WITH
2.5.17
+3
View File
@@ -0,0 +1,3 @@
remote_theme: IdreesInc/Jekyll-Project-Theme
plugins:
- jekyll-remote-theme
+24
View File
@@ -0,0 +1,24 @@
---
window_title: "Get Monocraft - The Minecraft Font for Devs"
title: "Monocraft"
subtitle: "the programming font for the minecraft obsessed"
annotation: "*plus, it has ligatures!"
source_code: "https://github.com/IdreesInc/Monocraft"
background: "linear-gradient(90deg, #f5e1c4, #daf0d3, #BCE7FF)"
layout: "project"
---
{% include link.html
url="https://github.com/IdreesInc/Monocraft/releases"
text="download it here"
%}
{% include link.html
url="https://arstechnica.com/gaming/2022/09/developer-creates-delightful-programming-font-based-on-minecraft/"
text="in the press"
%}
{% include link.html
url="https://socials.idreesinc.com"
text="follow for updates"
%}
+139
View File
@@ -0,0 +1,139 @@
@import url('https://fonts.googleapis.com/css2?family=Epilogue:wght@400;500&display=swap');
html {
font-family: 'Epilogue', sans-serif;
}
html, body {
margin: 0;
padding: 0;
}
body {
display: flex;
flex-direction: column;
color: black;
}
#foil {
position: absolute;
z-index: -1;
width: 60%;
height: 15%;
left: 15%;
top: 30%;
transform: rotate(-20deg);
background-image: url('foil.jpg');
background-size: cover;
background-position: center;
}
#intro-page {
height: 100vh;
width: 65%;
margin-left: 30%;
display: flex;
flex-direction: column;
justify-content: center;
}
#title {
font-size: 100px;
}
#subtitle {
font-size: 35px;
margin-top: 25px;
}
#links {
font-family: Epilogue;
font-size: 30px;
margin-top: 30px;
text-decoration-line: underline;
}
.link {
margin-bottom: 20px;
}
.link a {
color: black;
text-decoration: none;
}
#github a {
color: black;
}
#github {
margin-top: 10px;
}
#annotation {
font-size: 20px;
position: absolute;
bottom: 40px;
right: 40px;
margin-left: 20px;
}
#me {
font-size: 16px;
position: absolute;
bottom: 12px;
right: 40px;
margin-left: 20px;
}
#me a {
color: black;
}
@media screen and (orientation:portrait) and (max-width: 85vh) {
#foil {
width: 90%;
height: 11%;
left: 5%;
top: 27%;
transform: rotate(-20deg);
}
#intro-page {
width: 80%;
margin-top: -10%;
margin-left: 10%;
}
#title {
font-size: 16vw;
}
#subtitle {
font-size: 6.4vw;
margin-top: 50px;
}
#links {
font-size: 6vw;
margin-top: 110px;
}
.link {
margin-bottom: 50px;
}
#annotation {
font-size: 3vw;
position: absolute;
bottom: 6vw;
right: 25px;
}
#me {
font-size: 3vw;
position: absolute;
bottom: 1.5vw;
right: 25px;
}
}
+241 -17
View File
@@ -6,25 +6,39 @@ a b c d e f g h i j k l m n o p q r s t u v w x y z
0 1 2 3 4 5 6 7 8 9
! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~
¡ ¢ £ ¤ ¥ ¦ § ¨ © ª « ¬ ® ¯ ° ± ² ³ ´ µ ¶ · ¸ ¹ º » ¼ ½ ¾ ¿ À Á
Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï Ð Ñ Ò Ó Ô Õ Ö × Ø Ù Ú Û Ü Ý Þ ß à á
â ã ä å æ ç è é ê ë ì í î ï ð ñ ò ó ô õ ö ÷ ø ù ú û ü ý ÿ Ā ā Ă
ă Ą ą Ć ć Ĉ ĉ Ċ ċ Č č Ď ď Ē ē Ĕ ĕ Ė ė Ę ę Ě ě Ĝ ĝ Ğ ğ Ġ ġ Ģ ģ Ĥ
ĥ Ĩ ĩ Ī ī Ĭ ĭ İ Ĵ ĵ Ķ ķ Ĺ ĺ Ļ ļ Ľ ľ Ł ł Ń ń Ņ ņ Ň ň Ō ō Ŏ ŏ Ő ő
Œ œ Ŕ ŕ Ř ř Ś ś Ŝ ŝ Ş ş Š š Ť ť Ũ ũ Ū ū Ŭ ŭ Ů ů Ű ű Ŵ ŵ Ŷ ŷ Ÿ Ź
ź Ż ż Ž
¡ ¢ £ ¤ ¥ ¦ § ¨ © ª « ¬ ® ¯ ° ± ² ³ ´ µ ¶ · ¸ ¹ º » ¼ ½ ¾ ¿ À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï Ð Ñ
Ò Ó Ô Õ Ö × Ø Ù Ú Û Ü Ý Þ ß à á â ã ä å æ ç è é ê ë ì í î ï ð ñ ò ó ô õ ö ÷ ø ù ú û ü ý þ ÿ Ā ā
Ă ă Ą ą Ć ć Ĉ ĉ Ċ ċ Č č Ď ď Ē ē Ĕ ĕ Ė ė Ę ę Ě ě Ĝ ĝ Ğ ğ Ġ ġ Ģ ģ Ĥ ĥ Ĩ ĩ Ī ī Ĭ ĭ İ ı IJ ij Ĵ ĵ Ķ ķ
ĸ Ĺ ĺ Ļ ļ Ľ ľ Ł ł Ń ń Ņ ņ Ň ň Ō ō Ŏ ŏ Ő ő Œ œ Ŕ ŕ Ř ř Ś ś Ŝ ŝ Ş ş Š š Ť ť Ũ ũ Ū ū Ŭ ŭ Ů ů Ű ű Ŵ
ŵ Ŷ ŷ Ÿ Ź ź Ż ż Ž
ž Ǎ ǎ Ǐ ǐ Ǒ ǒ Ǔ ǔ Ǣ ǣ Ǧ ǧ Ǩ ǩ ǰ Ǵ ǵ Ǹ ǹ Ǽ ǽ Ș ș Ț ț Ȟ ȟ Ȧ ȧ Ȯ ȯ
Ȳ ȳ Ѐ Ё Ђ Ѓ Є Ѕ І Ї Ј Љ Њ Ћ Ќ Ѝ Ў Џ А Б В Г Д Е Ж З И Й К Л М Н
О П Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь Э Ю Я а б в г д е ж з и й к л м н
о п р с т у ф х ц ч ш щ ъ ы ь э ю я ѐ ё ђ ѓ є ѕ і ї ј љ њ ћ ќ ѝ
ў џ
ž ſ Ɓ Ƅ ƅ Ɔ Ɖ Ǝ Ə Ɛ Ƒ ƚ ƛ ƞ Ɵ Ƨ ƨ Ʃ ƪ Ʋ Ʒ Ƹ ƹ ƻ Ƽ ƽ ƾ ƿ ǀ ǁ ǂ ǃ lj Ǎ ǎ Ǐ ǐ Ǒ ǒ Ǔ ǔ ǝ Ǣ ǣ Ǧ ǧ Ǩ ǩ
Ǯ ǯ ǰ Ǵ ǵ Ƿ Ǹ ǹ Ǽ ǽ Ș ș Ț ț Ȝ ȝ Ȟ ȟ Ƞ ȡ Ȥ ȥ Ȧ ȧ Ȯ ȯ Ȳ ȳ ȴ ȵ ȶ Ⱥ Ȼ ȼ Ƚ Ⱦ Ɂ ɂ Ƀ Ʉ Ʌ Ɇ ɇ Ɉ ɉ Ɋ ɋ Ɍ
ɍ Ɏ ɏ ɐ ɑ ɒ ɓ ɔ ɕ ɖ ɗ ɘ ə ɚ ɛ ɜ ɝ ɞ ɟ ɠ ɡ ɢ ɤ ɥ ɦ ɧ ɨ ɪ ɫ ɬ ɭ ɮ ɯ ɰ ɱ ɲ ɳ ɴ ɵ ɷ ɸ ɹ ɺ ɻ ɼ ɾ ɿ ʀ
ʁ ʄ ʅ ʆ ʇ ʈ ʉ ʊ ʋ ʌ ʍ ʎ ʏ ʑ ʒ ʓ ʔ ʕ ʖ ʗ ʘ ʙ ʚ ʛ ʜ ʝ ʞ ʟ ʠ ʡ ʢ ʬ ʻ ˌ ˙ ; · Α Β Γ Δ Ε Ζ Η Θ Ι Κ Λ
Μ Ν Ξ Ο Π Ρ Σ Τ Υ Φ Χ Ψ Ω α β γ δ ε ζ η θ ι κ λ μ ν ξ ο π ρ ς σ τ υ φ χ ψ ω ϛ Ѐ Ё Ђ Ѓ Є Ѕ І Ї Ј
Љ Њ Ћ Ќ Ѝ Ў Џ А Б В Г Д Е Ж З И Й К Л М Н О П Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь Э Ю Я а б в г д е ж з и
й к л м н о п р с т у ф х ц ч ш щ ъ ы ь э ю я ѐ ё ђ ѓ є ѕ і ї ј љ њ ћ ќ ѝ ў џ
Ӂ ӂ Ӑ ӑ Ӓ ӓ Ӗ ӗ Ӝ ӝ Ӟ ӟ Ӣ ӣ Ӥ ӥ Ӧ ӧ Ӭ ӭ Ӯ ӯ Ӱ ӱ Ӳ ӳ Ӵ ӵ Ӹ ӹ Ḃ ḃ
Ḋ ḋ Ḟ ḟ Ḡ ḡ Ḣ ḣ Ḧ ḧ Ḱ ḱ Ḿ ḿ Ṁ ṁ Ṅ ṅ Ṕ ṕ Ṗ ṗ Ṙ ṙ Ṡ ṡ Ṫ ṫ Ṽ ṽ Ẁ ẁ
Ẃ ẃ Ẅ ẅ Ẇ ẇ Ẋ ẋ Ẍ ẍ Ẏ ẏ Ẑ ẑ ẗ ẘ ẙ Ẽ ẽ Ỳ ỳ Ỹ ỹ
Ѣ ѣ Ѳ ѳ Ѵ ѵ Ғ ғ Җ җ Қ қ Ң ң Ү ү Ұ ұ Һ һ Ӏ Ӂ ӂ Ӑ ӑ Ӓ ӓ Ӗ ӗ Ә ә Ӛ ӛ Ӝ ӝ Ӟ ӟ Ӣ ӣ Ӥ ӥ Ӧ ӧ Ө ө Ӫ ӫ Ӭ
ӭ Ӯ ӯ Ӱ ӱ Ӳ ӳ Ӵ ӵ Ӹ ӹ Ԁ Ա Բ Գ Դ Ե Զ Է Ը Թ Ժ Ի Լ Խ Ծ Կ Հ Ձ Ղ Ճ Մ Յ Ն Շ Ո Չ Պ Ջ Ռ Ս Վ Տ Ր Ց Ւ Փ Ք
Օ Ֆ ՙ ՚ ՛ ՜ ՝ ՞ ՟ ՠ ա բ գ դ ե զ է ը թ ժ ի լ խ ծ կ հ ձ ղ ճ մ յ ն շ ո չ պ ջ ռ ս վ տ ր ց ւ փ ք օ ֆ
և ֈ ֊ ֏ ־ ׃ ׆ א ב ג ד ה ו ז ח ט י ך כ ל ם מ ן נ ס ע ף פ ץ צ ק ר ש ת װ ױ ײ ׳ ״ ߈ Ⴀ Ⴁ Ⴂ Ⴃ Ⴄ Ⴅ Ⴆ Ⴇ
Ⴈ Ⴉ Ⴊ Ⴋ Ⴌ Ⴍ Ⴎ Ⴏ Ⴐ Ⴑ Ⴒ Ⴓ Ⴔ Ⴕ Ⴖ Ⴗ Ⴘ Ⴙ Ⴚ Ⴛ Ⴜ Ⴝ Ⴞ Ⴟ Ⴠ Ⴡ Ⴢ Ⴣ Ⴤ Ⴥ Ⴧ Ⴭ ა ბ გ დ ე ვ ზ თ ი კ ლ მ ნ ო პ ჟ
რ ს ტ უ ფ ქ ღ ყ შ ჩ ც ძ წ ჭ ხ ჯ ჰ ჱ ჲ ჳ ჴ ჵ ჶ ჷ ჸ ჹ ჺ ჻ ჼ ჽ ჾ ჿ ᗡ ᗺ ᘔ ᚠ ᚢ ᚣ ᚤ ᚥ ᚦ ᚧ ᚨ ᚩ ᚪ ᚫ ᚬ ᚭ
ᚮ ᚯ ᚰ ᚱ ᚲ ᚳ ᚴ ᚶ ᚷ ᚸ ᚹ ᚺ ᚻ ᚼ ᚽ ᚾ ᚿ ᛀ ᛁ ᛂ ᛃ ᛄ ᛅ ᛆ ᛇ ᛈ ᛉ ᛊ ᛋ ᛌ ᛍ ᛎ ᛏ ᛐ ᛑ ᛒ ᛓ ᛔ ᛕ ᛖ ᛗ ᛘ ᛙ ᛚ ᛛ ᛜ ᛝ ᛞ
ᛟ ᛠ ᛡ ᛢ ᛣ ᛤ ᛥ ᛦ ᛧ ᛨ ᛩ ᛪ ᛫ ᛬ ᛭ ᛮ ᛯ ᛰ ᛱ ᛲ ᛳ ᛴ ᛵ ᛶ ᛷ ᛸ ᴀ ᴄ ᴅ ᴇ ᴉ ᴊ ᴋ ᴍ ᴏ ᴘ ᴚ ᴛ ᴜ ᴠ ᴡ ᴢ ᵷ ᶑ Ḃ ḃ Ḋ ḋ
Ḟ ḟ Ḡ ḡ Ḣ ḣ Ḧ ḧ Ḱ ḱ Ḿ ḿ Ṁ ṁ Ṅ ṅ Ṕ ṕ Ṗ ṗ Ṙ ṙ Ṡ ṡ Ṫ ṫ Ṽ ṽ Ẁ ẁ Ẃ ẃ Ẅ ẅ Ẇ ẇ Ẋ ẋ Ẍ ẍ Ẏ ẏ Ẑ ẑ ẗ ẘ ẙ ẛ
ẜ ẝ ẞ ẟ Ẽ ẽ Ỳ ỳ Ỹ ỹ Ỽ ỽ Ỿ ỿ ᾱ Ᾱ ῑ Ῑ ῡ Ῡ ‑ – — ‘ ’ ‚ “ ” „ † ‡ • … ‰ ‱ ′ ″ ‴ ‵ ‶ ‷ ‹ › ※ ‼ ‽ ⁂ ⁈
⁉ ⁊ ⁋ ⁎ ⁏ ⁑ ⁒ ⁗ ⁰ ⁱ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ⁺ ⁻ ⁼ ⁽ ⁾ ₀ ₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉ ₊ ₋ ₌ ₍ ₎ ₠ ₡ ₢ ₣ ₤ ₥ ₦ ₩ ₪
€ ← ↑ → ↓ ⇐ ⇑ ⇒ ⇓ ∀ ∷ ☺ ☻ ♥ ⨰ ⸚
₫ € ₭ ₮ ₰ ₱ ₲ ₳ ₴ ₵ ₶ ₷ ₸ ₹ ₺ ₻ ₼ ₽ ₾ ₿ ℗ ™ Ⅎ ⅁ ⅄ ⅋ ⅐ ⅑ ⅓ ⅔ ⅕ ⅖ ⅗ ⅙ ⅚ ⅛ ⅜ ⅝ ⅞ ⅟ ↉ ← ↑ → ↓ ↔ ⇄ ⇏
⇐ ⇑ ⇒ ⇓ ⇔ ⇵ ∀ ∁ ∂ ∃ ∄ ∉ ∋ ∌ ∑ − ∓ ∛ ∜ ∝ ∞ ∥ ∧ ∨ ∩ ∫ ∮ ∴ ∵ ∷ ≔ ≠ ≢ ⊂ ⊃ ⊄ ⊅ ⊆ ⊇ ⊢ ⊤ ⊥ ⊨ ⊻ ⊼ ⊽ ⋃ ⋆
⌀ ⌂ ⌘ ⌚ ⌛ ⏏ ⏩ ⏪ ⏭ ⏮ ⏯ ⏳ ⏴ ⏵ ⏶ ⏷ ⏸ ⏹ ⏺ ⏻ ⏼ ⏽ □ ▲ △ ▶ ▷ ▼ ▽ ◀ ◁ ◆ ◇ ○ ◎ ● ◘ ◦ ☀ ☁ ☂ ☃ ☄ ★ ☆ ☈ ☔ ☠
☰ ☱ ☲ ☳ ☴ ☵ ☶ ☷ ☹ ☺ ☻ ☽ ♀ ♂ ♠ ♡ ♢ ♣ ♤ ♥ ♦ ♧ ♩ ♪ ♫ ♬ ♭ ♮ ♯ ⚀ ⚁ ⚂ ⚃ ⚄ ⚅ ⚐ ⚑ ⚓ ⚔ ⚗ ⚡ ⚥ ⛄ ⛈ ⛏ ⛨ ✂ ✉
✎ ✔ ✘ ❄ ❌ ❣ ❤ ⟘ ⥝ ⧈ ⨰ ⩑ ⩒ ⫃ ⫄ ⭐ ⭘ ⯪ ⯫ ⱥ ⱦ Ɑ Ɐ ⱱ ⴀ ⴁ ⴂ ⴃ ⴄ ⴅ ⴆ ⴇ ⴈ ⴉ ⴊ ⴋ ⴌ ⴍ ⴎ ⴏ ⴐ ⴑ ⴒ ⴓ ⴔ ⴕ ⴖ ⴗ
ⴘ ⴙ ⴚ ⴛ ⴜ ⴝ ⴞ ⴟ ⴠ ⴡ ⴢ ⴣ ⴤ ⴥ ⸘ ⸚ ⸮ ⸵ ⸸ ⹁ ⹋ 〒 ㄥ Ꜧ ꜧ Ꜩ ꜩ ꜰ ꜱ ꜹ ꜻ Ꝛ ꝛ Ꝺ Ꞁ ꞁ Ꞩ ꞩ ꞯ Ʞ ff fi fl ffi ſt יִ ײַ שׂ
בּ וּ כּ פּ תּ וֹ בֿ פֿ + �
--- Ligatures ---
 = = -> ==
@@ -36,4 +50,214 @@ a b c d e f g h i j k l m n o p q r s t u v w x y z
 < - -> <-
 - > -> ->
 = > -> =>
 < = > -> <=>
 < = > -> <=>
 < ! - - -> <!--
 = = > -> ==>
 = = = > -> ===>
 = = = = > -> ====>
 = = = = = > -> =====>
 = = = = = = >-> ======>
 = = = = = = = >-> =======>
 = = = = = = = = >-> ========>
 = = = = = = = = = >-> =========>
 = = = = = = = = = = >-> ==========>
 = = = = = = = = = = = >-> ===========>
 = = = = = = = = = = = = >-> ============>
 = = = = = = = = = = = = = >-> =============>
 = = = = = = = = = = = = = = >-> ==============>
 = = = = = = = = = = = = = = = >-> ===============>
 = = = = = = = = = = = = = = = = >-> ================>
 = = = = = = = = = = = = = = = = = >-> =================>
 = = = = = = = = = = = = = = = = = = >-> ==================>
 = = = = = = = = = = = = = = = = = = = >-> ===================>
 = = = = = = = = = = = = = = = = = = = = >-> ====================>
 = = = = = = = = = = = = = = = = = = = = = >-> =====================>
 = = = = = = = = = = = = = = = = = = = = = = >-> ======================>
 = = = = = = = = = = = = = = = = = = = = = = = >-> =======================>
 = = = = = = = = = = = = = = = = = = = = = = = = >-> ========================>
 = = = = = = = = = = = = = = = = = = = = = = = = = >-> =========================>
 = = = = = = = = = = = = = = = = = = = = = = = = = = >-> ==========================>
 = = = = = = = = = = = = = = = = = = = = = = = = = = = >-> ===========================>
 = = = = = = = = = = = = = = = = = = = = = = = = = = = = >-> ============================>
 = = = = = = = = = = = = = = = = = = = = = = = = = = = = = >-> =============================>
 = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = >-> ==============================>
 = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = >-> ===============================>
 < = = -> <==
 < = = = -> <===
 < = = = = -> <====
 < = = = = = -> <=====
 < = = = = = =-> <======
 < = = = = = = =-> <=======
 < = = = = = = = =-> <========
 < = = = = = = = = =-> <=========
 < = = = = = = = = = =-> <==========
 < = = = = = = = = = = =-> <===========
 < = = = = = = = = = = = =-> <============
 < = = = = = = = = = = = = =-> <=============
 < = = = = = = = = = = = = = =-> <==============
 < = = = = = = = = = = = = = = =-> <===============
 < = = = = = = = = = = = = = = = =-> <================
 < = = = = = = = = = = = = = = = = =-> <=================
 < = = = = = = = = = = = = = = = = = =-> <==================
 < = = = = = = = = = = = = = = = = = = =-> <===================
 < = = = = = = = = = = = = = = = = = = = =-> <====================
 < = = = = = = = = = = = = = = = = = = = = =-> <=====================
 < = = = = = = = = = = = = = = = = = = = = = =-> <======================
 < = = = = = = = = = = = = = = = = = = = = = = =-> <=======================
 < = = = = = = = = = = = = = = = = = = = = = = = =-> <========================
 < = = = = = = = = = = = = = = = = = = = = = = = = =-> <=========================
 < = = = = = = = = = = = = = = = = = = = = = = = = = =-> <==========================
 < = = = = = = = = = = = = = = = = = = = = = = = = = = =-> <===========================
 < = = = = = = = = = = = = = = = = = = = = = = = = = = = =-> <============================
 < = = = = = = = = = = = = = = = = = = = = = = = = = = = = =-> <=============================
 < = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =-> <==============================
 < = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =-> <===============================
 - - > -> -->
 - - - > -> --->
 - - - - > -> ---->
 - - - - - > -> ----->
 - - - - - - >-> ------>
 - - - - - - - >-> ------->
 - - - - - - - - >-> -------->
 - - - - - - - - - >-> --------->
 - - - - - - - - - - >-> ---------->
 - - - - - - - - - - - >-> ----------->
 - - - - - - - - - - - - >-> ------------>
 - - - - - - - - - - - - - >-> ------------->
 - - - - - - - - - - - - - - >-> -------------->
 - - - - - - - - - - - - - - - >-> --------------->
 - - - - - - - - - - - - - - - - >-> ---------------->
 - - - - - - - - - - - - - - - - - >-> ----------------->
 - - - - - - - - - - - - - - - - - - >-> ------------------>
 - - - - - - - - - - - - - - - - - - - >-> ------------------->
 - - - - - - - - - - - - - - - - - - - - >-> -------------------->
 - - - - - - - - - - - - - - - - - - - - - >-> --------------------->
 - - - - - - - - - - - - - - - - - - - - - - >-> ---------------------->
 - - - - - - - - - - - - - - - - - - - - - - - >-> ----------------------->
 - - - - - - - - - - - - - - - - - - - - - - - - >-> ------------------------>
 - - - - - - - - - - - - - - - - - - - - - - - - - >-> ------------------------->
 - - - - - - - - - - - - - - - - - - - - - - - - - - >-> -------------------------->
 - - - - - - - - - - - - - - - - - - - - - - - - - - - >-> --------------------------->
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - >-> ---------------------------->
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >-> ----------------------------->
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >-> ------------------------------>
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >-> ------------------------------->
 < - - -> <--
 < - - - -> <---
 < - - - - -> <----
 < - - - - - -> <-----
 < - - - - - --> <------
 < - - - - - - --> <-------
 < - - - - - - - --> <--------
 < - - - - - - - - --> <---------
 < - - - - - - - - - --> <----------
 < - - - - - - - - - - --> <-----------
 < - - - - - - - - - - - --> <------------
 < - - - - - - - - - - - - --> <-------------
 < - - - - - - - - - - - - - --> <--------------
 < - - - - - - - - - - - - - - --> <---------------
 < - - - - - - - - - - - - - - - --> <----------------
 < - - - - - - - - - - - - - - - - --> <-----------------
 < - - - - - - - - - - - - - - - - - --> <------------------
 < - - - - - - - - - - - - - - - - - - --> <-------------------
 < - - - - - - - - - - - - - - - - - - - --> <--------------------
 < - - - - - - - - - - - - - - - - - - - - --> <---------------------
 < - - - - - - - - - - - - - - - - - - - - - --> <----------------------
 < - - - - - - - - - - - - - - - - - - - - - - --> <-----------------------
 < - - - - - - - - - - - - - - - - - - - - - - - --> <------------------------
 < - - - - - - - - - - - - - - - - - - - - - - - - --> <-------------------------
 < - - - - - - - - - - - - - - - - - - - - - - - - - --> <--------------------------
 < - - - - - - - - - - - - - - - - - - - - - - - - - - --> <---------------------------
 < - - - - - - - - - - - - - - - - - - - - - - - - - - - --> <----------------------------
 < - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> <-----------------------------
 < - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> <------------------------------
 < - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> <-------------------------------
 - - - -> ---
 - - - - -> ----
 - - - - - -> -----
 - - - - - - -> ------
 - - - - - - --> -------
 - - - - - - - --> --------
 - - - - - - - - --> ---------
 - - - - - - - - - --> ----------
 - - - - - - - - - - --> -----------
 - - - - - - - - - - - --> ------------
 - - - - - - - - - - - - --> -------------
 - - - - - - - - - - - - - --> --------------
 - - - - - - - - - - - - - - --> ---------------
 - - - - - - - - - - - - - - - --> ----------------
 - - - - - - - - - - - - - - - - --> -----------------
 - - - - - - - - - - - - - - - - - --> ------------------
 - - - - - - - - - - - - - - - - - - --> -------------------
 - - - - - - - - - - - - - - - - - - - --> --------------------
 - - - - - - - - - - - - - - - - - - - - --> ---------------------
 - - - - - - - - - - - - - - - - - - - - - --> ----------------------
 - - - - - - - - - - - - - - - - - - - - - - --> -----------------------
 - - - - - - - - - - - - - - - - - - - - - - - --> ------------------------
 - - - - - - - - - - - - - - - - - - - - - - - - --> -------------------------
 - - - - - - - - - - - - - - - - - - - - - - - - - --> --------------------------
 - - - - - - - - - - - - - - - - - - - - - - - - - - --> ---------------------------
 - - - - - - - - - - - - - - - - - - - - - - - - - - - --> ----------------------------
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> -----------------------------
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> ------------------------------
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> -------------------------------
 ^ ^ -> ^^
 ^ ^ ^ -> ^^^
 ^ ^ ^ ^ -> ^^^^
 ^ ^ ^ ^ ^ -> ^^^^^
 ^ ^ ^ ^ ^ ^ -> ^^^^^^
 ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^^^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^^^^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^^^^^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^^^^^^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^^^^^^^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^^^^^^^^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^^^^^^^^^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^^^^^^^^^^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^^^^^^^^^^^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^^^^^^^^^^^^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^^^^^^^^^^^^^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^^^^^^^^^^^^^^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^-> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 + + -> ++
 + + + -> +++
 + + + + -> ++++
 + + + + + -> +++++
 + + + + + + -> ++++++
 + + + + + + +-> +++++++
 + + + + + + + +-> ++++++++
 + + + + + + + + +-> +++++++++
 + + + + + + + + + +-> ++++++++++
 + + + + + + + + + + +-> +++++++++++
 + + + + + + + + + + + +-> ++++++++++++
 + + + + + + + + + + + + +-> +++++++++++++
 + + + + + + + + + + + + + +-> ++++++++++++++
 + + + + + + + + + + + + + + +-> +++++++++++++++
 + + + + + + + + + + + + + + + +-> ++++++++++++++++
 + + + + + + + + + + + + + + + + +-> +++++++++++++++++
 + + + + + + + + + + + + + + + + + +-> ++++++++++++++++++
 + + + + + + + + + + + + + + + + + + +-> +++++++++++++++++++
 + + + + + + + + + + + + + + + + + + + +-> ++++++++++++++++++++
 + + + + + + + + + + + + + + + + + + + + +-> +++++++++++++++++++++
 + + + + + + + + + + + + + + + + + + + + + +-> ++++++++++++++++++++++
 + + + + + + + + + + + + + + + + + + + + + + +-> +++++++++++++++++++++++
 + + + + + + + + + + + + + + + + + + + + + + + +-> ++++++++++++++++++++++++
 + + + + + + + + + + + + + + + + + + + + + + + + +-> +++++++++++++++++++++++++
 + + + + + + + + + + + + + + + + + + + + + + + + + +-> ++++++++++++++++++++++++++
 + + + + + + + + + + + + + + + + + + + + + + + + + + +-> +++++++++++++++++++++++++++
 + + + + + + + + + + + + + + + + + + + + + + + + + + + +-> ++++++++++++++++++++++++++++
 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +-> +++++++++++++++++++++++++++++
 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +-> ++++++++++++++++++++++++++++++
 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +-> +++++++++++++++++++++++++++++++
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 615 KiB

After

Width:  |  Height:  |  Size: 1019 KiB

+14104 -6
View File
File diff suppressed because it is too large. Load diff
+197
View File
@@ -0,0 +1,197 @@
[
{
"head": "=>",
"body": "=",
"head_name": "right thick arrow",
"body_name": "equals sign",
"direction": "right",
"min_length": 1,
"max_length": 30,
"head_pixels": [
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]
],
"body_pixels": [
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]
]
},
{
"head": "<=",
"body": "=",
"head_name": "left thick arrow",
"body_name": "equals sign",
"direction": "left",
"min_length": 1,
"max_length": 30,
"head_pixels": [
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]
],
"body_pixels": [
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]
]
},
{
"head": "->",
"body": "-",
"head_name": "right arrow",
"body_name": "minus",
"direction": "right",
"min_length": 1,
"max_length": 30,
"head_pixels": [
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]
],
"body_pixels": [
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]
]
},
{
"head": "<-",
"body": "-",
"head_name": "left arrow",
"body_name": "minus",
"direction": "left",
"min_length": 1,
"max_length": 30,
"head_pixels": [
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]
],
"body_pixels": [
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]
]
},
{
"head": "-",
"body": "-",
"head_name": "minus",
"body_name": "minus",
"direction": "left",
"min_length": 2,
"max_length": 30,
"head_pixels": [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
],
"body_pixels": [
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]
]
},
{
"head": "^",
"body": "^",
"head_name": "caret",
"body_name": "caret",
"direction": "right",
"min_length": 1,
"max_length": 30,
"head_pixels": [
[0, 0, 1, 1, 0, 0],
[0, 1, 0, 0, 1, 0],
[1, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]
],
"body_pixels": [
[0, 0, 1, 1, 0, 0],
[0, 1, 0, 0, 1, 0],
[1, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]
]
},
{
"head": "+",
"body": "+",
"head_name": "plus",
"body_name": "plus",
"direction": "right",
"min_length": 1,
"max_length": 30,
"head_pixels": [
[0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0],
[0, 0, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 0],
[0, 0, 1, 0, 0, 0],
[0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0]
],
"body_pixels": [
[0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0],
[0, 0, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 1],
[0, 0, 1, 0, 0, 0],
[0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0]
]
}
]
+75
View File
@@ -0,0 +1,75 @@
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import json
import copy
def generate_continuous_ligatures(filename):
"""
Generates continuous ligature data from a JSON file.
The JSON file should have the following structure:
[
{
"head_name": "string",
"body_name": "string",
"head": "string",
"body": "string",
"direction": "left" or "right",
"min_length": int,
"max_length": int,
"head_pixels": [[int, int, ...], ...],
"body_pixels": [[int, int, ...], ...]
},
...
]
Returns a list of progress bars, where each progress bar is a dictionary with the following keys:
- "name": a string representing the name of the progress bar
- "ligature": a string representing the progress bar
- "sequence": a list of integers representing the Unicode code points of the characters in the progress bar
- "pixels": a list of integers representing the pixels of the progress bar
"""
continuous_ligatures = json.load(open(filename))
out = []
for ligature in continuous_ligatures:
name = f'{ligature["head_name"]} {ligature["body_name"]} '
body_pixels = ligature["body_pixels"]
head_pixels = ligature["head_pixels"]
body = [[] for _ in range(len(body_pixels))]
for i in range( ligature["min_length"],ligature["max_length"] + 1):
glyph = {}
# Generate ligature data
glyph["name"] = name + str(i);
if ligature["direction"] == "right":
glyph["ligature"] = ligature["body"] * i + ligature["head"]
else:
glyph["ligature"] = ligature["head"] + ligature["body"] * i
glyph["sequence"] = [ord(c) for c in glyph["ligature"]]
# Generate pixels
# Expand the body by 1
for k in range(len(body)):
body[k] += body_pixels[k]
pixels = []
if ligature["direction"] == "right":
pixels = copy.deepcopy(body)
for j in range(len(pixels)):
pixels[j] += head_pixels[j]
else:
pixels = copy.deepcopy(head_pixels)
for j in range(len(pixels)):
pixels[j] += body[j]
glyph["pixels"] = pixels
out.append(glyph)
return out
+1 -1
View File
@@ -48,7 +48,7 @@ def generateExamples(characters, ligatures, charactersByCodepoint):
index = 0
characterOutput += "\n"
if i in charactersByCodepoint:
if index % 32 == 0:
if index % 48 == 0:
characterOutput += "\n"
characterOutput += chr(i) + " "
index += 1
+15 -1
View File
@@ -149,5 +149,19 @@
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]
]
},
{
"ligature": "<!--",
"name": "html comment",
"sequence": [60, 33, 45, 45],
"pixels": [
[0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]
}
]
]
+309 -42
View File
@@ -1,5 +1,5 @@
# Monocraft, a monospaced font for developers who like Minecraft a bit too much.
# Copyright (C) 2022-2023 Idrees Hassan
# Copyright (C) 2022-2024 Idrees Hassan
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -16,72 +16,254 @@
import os
import fontforge
import json
import math
import argparse
from generate_diacritics import generateDiacritics
from generate_examples import generateExamples
from polygonizer import PixelImage, generatePolygons
from generate_continuous_ligatures import generate_continuous_ligatures
PIXEL_SIZE = 120
characters = json.load(open("./characters.json"))
diacritics = json.load(open("./diacritics.json"))
ligatures = json.load(open("./ligatures.json"))
ligatures += generate_continuous_ligatures("./continuous_ligatures.json")
characters = generateDiacritics(characters, diacritics)
charactersByCodepoint = {}
def generateFont():
monocraft = fontforge.font()
monocraft.fontname = "Monocraft"
monocraft.familyname = "Monocraft"
monocraft.fullname = "Monocraft"
monocraft.copyright = "Idrees Hassan, https://github.com/IdreesInc/Monocraft"
monocraft.encoding = "UnicodeFull"
monocraft.version = "2.4"
monocraft.weight = "Regular"
monocraft.ascent = PIXEL_SIZE * 8
monocraft.descent = PIXEL_SIZE
monocraft.em = PIXEL_SIZE * 9
monocraft.upos = -PIXEL_SIZE # Underline position
monocraft.addLookup("ligatures", "gsub_ligature", (), (("liga",(("dflt",("dflt")),)),))
monocraft.addLookupSubtable("ligatures", "ligatures-subtable")
def parseArgs():
parser = argparse.ArgumentParser()
parser.add_argument(
"--output-ttc",
action="store_true",
dest="output_ttc",
)
parser.add_argument(
"-a",
"--all",
action="store_true",
dest="all",
)
parser.add_argument(
"-O",
"--black",
action="store_true",
dest="black",
)
parser.add_argument(
"-B",
"--bold",
action="store_true",
dest="bold",
)
parser.add_argument(
"-b",
"--semibold",
action="store_true",
dest="semibold",
)
parser.add_argument(
"-l",
"--light",
action="store_true",
dest="light",
)
parser.add_argument(
"-L",
"--extralight",
action="store_true",
dest="extralight",
)
parser.add_argument(
"-i",
"--italic",
action="store_true",
dest="italic",
)
ret = parser.parse_args()
if ret.all:
ret.black = ret.bold = ret.semibold = ret.light = ret.extralight = ret.italic = True
return ret
def generateFont(*, black=False, bold=False, semibold=False, light=False, extralight=False, italic=False, output_ttc=False, **kw):
fontList = [
fontforge.font(),
fontforge.font() if italic else None,
fontforge.font() if black else None,
fontforge.font() if black and italic else None,
fontforge.font() if bold else None,
fontforge.font() if bold and italic else None,
fontforge.font() if semibold else None,
fontforge.font() if semibold and italic else None,
fontforge.font() if light else None,
fontforge.font() if light and italic else None,
fontforge.font() if extralight else None,
fontforge.font() if extralight and italic else None,
]
for font in fontList:
if font is None:
continue
font.fontname = "Monocraft"
font.familyname = "Monocraft"
font.fullname = "Monocraft"
font.copyright = "Idrees Hassan, https://github.com/IdreesInc/Monocraft"
font.encoding = "UnicodeFull"
font.version = "4.0"
font.weight = "Regular"
font.ascent = PIXEL_SIZE * 8
font.descent = PIXEL_SIZE
font.em = PIXEL_SIZE * 9
font.upos = -PIXEL_SIZE # Underline position
font.addLookup("ligatures", "gsub_ligature", (),
(("liga", (("dflt", ("dflt")), ("latn", ("dflt")))), ))
font.addLookupSubtable("ligatures", "ligatures-subtable")
font = fontList[0]
if font is not None:
font.macstyle = 0
font.os2_stylemap = 0x40
font = fontList[1]
if font is not None:
font.fontname = "Monocraft-Italic"
font.fullname = "Monocraft Italic"
font.macstyle = 2
font.os2_stylemap = 1
font.italicangle = -15
font = fontList[2]
if font is not None:
font.fontname = "Monocraft-Black"
font.fullname = "Monocraft Black"
font.weight = "Black"
font.macstyle = 1
font.os2_stylemap = 0x20
font = fontList[3]
if font is not None:
font.fontname = "Monocraft-Black-Italic"
font.fullname = "Monocraft Black Italic"
font.weight = "Black"
font.macstyle = 3
font.os2_stylemap = 0x21
font.italicangle = -15
font = fontList[4]
if font is not None:
font.fontname = "Monocraft-Bold"
font.fullname = "Monocraft Bold"
font.weight = "Bold"
font.macstyle = 1
font.os2_stylemap = 0x20
font = fontList[5]
if font is not None:
font.fontname = "Monocraft-Bold-Italic"
font.fullname = "Monocraft Bold Italic"
font.weight = "Bold"
font.macstyle = 3
font.os2_stylemap = 0x21
font.italicangle = -15
font = fontList[6]
if font is not None:
font.fontname = "Monocraft-SemiBold"
font.fullname = "Monocraft SemiBold"
font.weight = "Demi"
font.macstyle = 1
font.os2_stylemap = 0x20
font = fontList[7]
if font is not None:
font.fontname = "Monocraft-SemiBold-Italic"
font.fullname = "Monocraft SemiBold Italic"
font.weight = "Demi"
font.macstyle = 3
font.os2_stylemap = 0x21
font.italicangle = -15
font = fontList[8]
if font is not None:
font.fontname = "Monocraft-Light"
font.fullname = "Monocraft Light"
font.weight = "Light"
font.macstyle = 0
font.os2_stylemap = 0
font = fontList[9]
if font is not None:
font.fontname = "Monocraft-Light-Italic"
font.fullname = "Monocraft Light Italic"
font.weight = "Light"
font.macstyle = 2
font.os2_stylemap = 1
font.italicangle = -15
font = fontList[10]
if font is not None:
font.fontname = "Monocraft-ExtraLight"
font.fullname = "Monocraft ExtraLight"
font.weight = "Extra-Light"
font.macstyle = 0
font.os2_stylemap = 0
font = fontList[11]
if font is not None:
font.fontname = "Monocraft-ExtraLight-Italic"
font.fullname = "Monocraft ExtraLight Italic"
font.weight = "Extra-Light"
font.macstyle = 2
font.os2_stylemap = 1
font.italicangle = -15
for character in characters:
charactersByCodepoint[character["codepoint"]] = character
monocraft.createChar(character["codepoint"], character["name"])
pen = monocraft[character["name"]].glyphPen()
top = 0
drawn = character
drawImage(generateImage(character), pen)
monocraft[character["name"]].width = PIXEL_SIZE * 6
image, kw = generateImage(character)
createChar(fontList, character["codepoint"], character["name"], image, **kw)
print(f"Generated {len(characters)} characters")
outputDir = "../dist/"
if not os.path.exists(outputDir):
os.makedirs(outputDir)
monocraft.generate(outputDir + "Monocraft-no-ligatures.ttf")
if output_ttc:
fontList[0].generateTtc(
outputDir + "Monocraft-no-ligatures.ttc",
[i for i in fontList[1:] if i is not None],
ttcflags=("merge", ),
layer=1,
)
for ligature in ligatures:
lig = monocraft.createChar(-1, ligature["name"])
pen = monocraft[ligature["name"]].glyphPen()
drawImage(generateImage(ligature), pen)
monocraft[ligature["name"]].width = PIXEL_SIZE * len(ligature["sequence"]) * 6
lig.addPosSub("ligatures-subtable", tuple(map(lambda codepoint: charactersByCodepoint[codepoint]["name"], ligature["sequence"])))
image, kw = generateImage(ligature)
createChar(fontList, -1, ligature["name"], image, width=PIXEL_SIZE * len(ligature["sequence"]) * 6, **kw)
for font in fontList:
if font is None:
continue
font[ligature["name"]].addPosSub("ligatures-subtable", tuple(map(lambda codepoint: charactersByCodepoint[codepoint]["name"], ligature["sequence"])))
print(f"Generated {len(ligatures)} ligatures")
monocraft.generate(outputDir + "Monocraft.ttf")
monocraft.generate(outputDir + "Monocraft.otf")
for font in fontList:
if font is None:
continue
font.generate(f"{outputDir}{font.fontname}.otf")
font.generate(f"{outputDir}{font.fontname}.ttf")
if output_ttc:
fontList[0].generateTtc(
outputDir + "Monocraft.ttc",
[i for i in fontList[1:] if i is not None],
ttcflags=("merge", ),
layer=1,
)
def generateImage(character):
image = PixelImage()
kw = {}
if "pixels" in character:
arr = character["pixels"]
x = int(character["leftMargin"]) if "leftMargin" in character else 0
y = int(-character["descent"]) if "descent" in character else 0
leftMargin = character["leftMargin"] if "leftMargin" in character else 0
x = math.floor(leftMargin)
kw['dx'] = leftMargin - x
descent = -character["descent"] if "descent" in character else 0
y = math.floor(descent)
kw['dy'] = descent - y
image = image | imageFromArray(arr, x, y)
if "reference" in character:
image = image | generateImage(charactersByCodepoint[character["reference"]])
other = generateImage(charactersByCodepoint[character["reference"]])
kw.update(other[1])
image = image | other[0]
if "diacritic" in character:
diacritic = diacritics[character["diacritic"]]
arr = diacritic["pixels"]
@@ -90,7 +272,7 @@ def generateImage(character):
if "diacriticSpace" in character:
y += int(character["diacriticSpace"])
image = image | imageFromArray(arr, x, y)
return image
return (image, kw)
def findHighestY(image):
for y in range(image.y_end - 1, image.y, -1):
@@ -105,15 +287,15 @@ def imageFromArray(arr, x=0, y=0):
y=y,
width=len(arr[0]),
height=len(arr),
data=bytes(x for a in reversed(arr) for x in a),
data=bytes(v for a in reversed(arr) for v in a),
)
def drawImage(image, pen):
for polygon in generatePolygons(image):
def drawPolygon(poly, pen):
for polygon in poly:
start = True
for x, y in polygon:
x *= PIXEL_SIZE
y *= PIXEL_SIZE
x = int(math.floor(x * PIXEL_SIZE))
y = int(math.floor(y * PIXEL_SIZE))
if start:
pen.moveTo(x, y)
start = False
@@ -121,5 +303,90 @@ def drawImage(image, pen):
pen.lineTo(x, y)
pen.closePath()
generateFont()
generateExamples(characters, ligatures, charactersByCodepoint)
BOLD_THIN_DISTS = {
"Black": 0.3,
"Bold": 0.2,
"Demi": 0.1,
"Light": -0.1,
"Extra-Light": -0.3,
}
ITALIC_RATIO = math.tan(math.radians(15))
def boldify(p, boldness):
l = len(p)
for i in range(l):
x, y = p[i]
dx, dy = 0, 0
px, py = p[i - 1]
if px < x:
dy += boldness
elif px > x:
dy -= boldness
elif py < y:
dx -= boldness
else:
dx += boldness
px, py = p[(i + 1) % l]
if px < x:
dy -= boldness
elif px > x:
dy += boldness
elif py < y:
dx += boldness
else:
dx -= boldness
yield (dx + x, dy + y)
def createChar(
fontList,
code,
name,
image=None,
*,
width=None,
dx=0,
dy=0,
glyphclass=None,
):
if image is not None:
poly = [[(x + dx, y + dy) for x, y in p]
for p in generatePolygons(image)]
poly_b = None
poly_t = None
for font in fontList:
if font is None:
continue
char = font.createChar(code, name)
#char.manualHints = True
if glyphclass is not None:
char.glyphclass = glyphclass
if image is None:
char.width = width if width is not None else PIXEL_SIZE * 6
continue
p = poly
try:
dist = BOLD_THIN_DISTS[font.weight]
except KeyError:
dist = 0
if dist > 0:
if poly_b is None:
poly_b = [[(x + dx, y + dy) for x, y in p] for p in generatePolygons(image, join_polygons=False)]
p = (boldify(p, dist) for p in poly_b)
elif dist < 0:
if poly_t is None:
poly_t = [[(x + dx, y + dy) for x, y in p] for p in generatePolygons(image, join_polygons=False, exclude_corners=True)]
p = (boldify(p, dist) for p in poly_t)
if font.macstyle & 2:
p = (((x + y * ITALIC_RATIO, y) for x, y in p) for p in p)
drawPolygon(p, char.glyphPen())
char.width = width if width is not None else PIXEL_SIZE * 6
args = parseArgs()
generateFont(**vars(args))
generateExamples(characters, ligatures, charactersByCodepoint)
+337 -116
View File
@@ -16,6 +16,7 @@ __all__ = [
'generatePolygons',
]
from collections import defaultdict
from enum import IntFlag
import itertools
@@ -93,7 +94,7 @@ class PixelImage:
def __setitem__(self, key, value):
''' Sets a pixel at (x, y).
Do nothing if out of bounds.
'''
x, y = key
@@ -186,10 +187,36 @@ class PixelImage:
return ret
def crop(self, min_x, max_x, min_y, max_y):
if max_x < min_x or max_y < min_y:
raise ValueError('Invalid bounds')
def generatePolygons(image):
min_x = max(min_x, self._x)
max_x = min(max_x, self.x_end)
min_y = max(min_y, self._y)
max_y = min(max_y, self.y_end)
if max_x <= min_x or max_y <= min_y:
return PixelImage()
data = bytearray((max_x - min_x) * (max_y - min_y))
ix = 0
for j in range(min_y, max_y):
for i in range(min_x, max_x):
data[ix] = self[i, j]
ix += 1
return PixelImage(
x=min_x,
y=min_y,
width=max_x - min_x,
height=max_y - min_y,
data=data,
)
def generatePolygons(image, **kw):
for segment, start_pos in segmentize(image):
yield from polygonizeSegment(segment, start_pos)
yield from polygonizeSegment(segment, start_pos, **kw)
def segmentize(image):
@@ -321,25 +348,20 @@ class CellFlag(IntFlag):
return ret
def polygonizeSegment(image, start_pos):
x, y = start_pos
class Turtle:
__slots__ = ['img', 'x', 'y', 'dir']
# Make sure position is top left
assert image[x, y]
assert not (image[x - 1, y] or image[x - 1, y - 1] or image[x, y - 1]
or image[x + 1, y - 1])
dir = CellFlag.RIGHT
def doMove():
nonlocal x, y, dir
def __init__(self, img, x, y, dir):
self.img, self.x, self.y, self.dir = img, x, y, dir
def _move(self):
# Set wall
d_ccw, d_cw, d_rev = dir.ccw(), dir.cw(), dir.reverse()
image[x, y] |= d_ccw
d_ccw, d_cw, d_rev = self.dir.ccw(), self.dir.cw(), self.dir.reverse()
p = self.x, self.y
self.img[p] |= d_ccw
x_, y_ = d_ccw.move((x, y))
assert not image[x_, y_] & CellFlag.ACTIVE
p_ = d_ccw.move(p)
assert not self.img[p_] & CellFlag.ACTIVE
# Build boundary wall from this point
# For all diagrams, it is assumed movement direction is right
@@ -347,199 +369,398 @@ def polygonizeSegment(image, start_pos):
# ? . #
# ? X ?
# ? ? ?
x_, y_ = (dir | d_ccw).move((x, y))
if image[x_, y_] & CellFlag.ACTIVE:
p_ = (self.dir | d_ccw).move(p)
if self.img[p_] & CellFlag.ACTIVE:
# Top-right corner
ret = [(dir | d_ccw).corner((x, y))]
ret = [(self.dir | d_ccw).corner(p)]
# ? . ^
# ? X ?
# ? ? ?
x, y = x_, y_
dir = d_ccw
(self.x, self.y), self.dir = p_, d_ccw
return ret
# ? . .
# ? X #
# ? ? ?
x_, y_ = dir.move((x, y))
if image[x_, y_] & CellFlag.ACTIVE:
p_ = self.dir.move(p)
if self.img[p_] & CellFlag.ACTIVE:
# ? . .
# ? X >
# ? ? ?
x, y = x_, y_
self.x, self.y = p_
return [] # No corner
# ? . .
# ? X .
# ? ? #
x_, y_ = (dir | d_cw).move((x, y))
if image[x_, y_] & CellFlag.ACTIVE:
p_ = (self.dir | d_cw).move(p)
if self.img[p_] & CellFlag.ACTIVE:
# Two corners: top-right and bottom-right
ret = [
(dir | d_ccw).corner((x, y)),
(dir | d_cw).corner((x, y)),
(self.dir | d_ccw).corner(p),
(self.dir | d_cw).corner(p),
]
# ? . .
# ? X .
# ? ? >
image[x, y] |= dir
x, y = x_, y_
self.img[p] |= self.dir
self.x, self.y = p_
return ret
# ? . .
# ? X .
# ? # .
x_, y_ = d_cw.move((x, y))
if image[x_, y_] & CellFlag.ACTIVE:
p_ = d_cw.move(p)
if self.img[p_] & CellFlag.ACTIVE:
# Top-right corner
ret = [(dir | d_ccw).corner((x, y))]
ret = [(self.dir | d_ccw).corner(p)]
# ? . .
# ? X .
# ? v .
image[x, y] |= dir
x, y = x_, y_
dir = d_cw
self.img[p] |= self.dir
(self.x, self.y), self.dir = p_, d_cw
return ret
# ? . .
# ? X .
# # . .
x_, y_ = (d_rev | d_cw).move((x, y))
if image[x_, y_] & CellFlag.ACTIVE:
p_ = (d_rev | d_cw).move(p)
if self.img[p_] & CellFlag.ACTIVE:
# Three corners: top-right, bottom-right, and bottom-left
ret = [
(dir | d_ccw).corner((x, y)),
(dir | d_cw).corner((x, y)),
(d_cw | d_rev).corner((x, y)),
(self.dir | d_ccw).corner(p),
(self.dir | d_cw).corner(p),
(d_cw | d_rev).corner(p),
]
# ? . .
# ? X .
# v . .
image[x, y] |= dir | d_cw
x, y = x_, y_
dir = d_cw
self.img[p] |= self.dir | d_cw
(self.x, self.y), self.dir = p_, d_cw
return ret
# ? . .
# # X .
# . . .
x_, y_ = d_rev.move((x, y))
if image[x_, y_] & CellFlag.ACTIVE:
p_ = d_rev.move(p)
if self.img[p_] & CellFlag.ACTIVE:
# Two corners: top-right and bottom-right
ret = [
(dir | d_ccw).corner((x, y)),
(dir | d_cw).corner((x, y)),
(self.dir | d_ccw).corner(p),
(self.dir | d_cw).corner(p),
]
# ? . .
# < X .
# . . .
image[x, y] |= dir | d_cw
x, y = x_, y_
dir = d_rev
self.img[p] |= self.dir | d_cw
(self.x, self.y), self.dir = p_, d_rev
return ret
# # . .
# . X .
# . . .
x_, y_ = (d_rev | d_ccw).move((x, y))
if image[x_, y_] & CellFlag.ACTIVE:
p_ = (d_rev | d_ccw).move(p)
if self.img[p_] & CellFlag.ACTIVE:
# Four corners: top-right, bottom-right, bottom-left, and top-left
ret = [
(dir | d_ccw).corner((x, y)),
(dir | d_cw).corner((x, y)),
(d_cw | d_rev).corner((x, y)),
(d_ccw | d_rev).corner((x, y)),
(self.dir | d_ccw).corner(p),
(self.dir | d_cw).corner(p),
(d_cw | d_rev).corner(p),
(d_ccw | d_rev).corner(p),
]
# < . .
# . X .
# . . .
image[x, y] |= dir | d_cw | d_rev
x, y = x_, y_
dir = d_rev
self.img[p] |= self.dir | d_cw | d_rev
(self.x, self.y), self.dir = p_, d_rev
return ret
# . . .
# . X .
# . . .
image[x, y] |= dir | d_cw | d_rev
self.img[p] |= self.dir | d_cw | d_rev
# Four corners: top-right, bottom-right, bottom-left, and top-left
return [
(dir | d_ccw).corner((x, y)),
(dir | d_cw).corner((x, y)),
(d_cw | d_rev).corner((x, y)),
(d_ccw | d_rev).corner((x, y)),
(self.dir | d_ccw).corner(p),
(self.dir | d_cw).corner(p),
(d_cw | d_rev).corner(p),
(d_ccw | d_rev).corner(p),
]
def find(l, x):
try:
return l.index(x)
except ValueError:
return None
def _move_4way(self):
# Set wall
d_ccw, d_cw, d_rev = self.dir.ccw(), self.dir.cw(), self.dir.reverse()
p = self.x, self.y
self.img[p] |= d_ccw
# Generate outer polygon
outer_poly = []
line = doMove()
i = find(line, start_pos)
while i is None:
outer_poly += line
line = doMove()
i = find(line, start_pos)
outer_poly += line[:i + 1]
p_ = d_ccw.move(p)
assert not self.img[p_] & CellFlag.ACTIVE
assert checkPoly(outer_poly)
# Build boundary wall from this point
# For all diagrams, it is assumed movement direction is right
# Emit outer polygon
yield outer_poly
# ? . ?
# ? X #
# ? ? ?
p_ = self.dir.move(p)
if self.img[p_] & CellFlag.ACTIVE:
# Calculate bounding box
x_min, y_min, x_max, y_max = x, y, x, y
for x, y in outer_poly:
if x_min > x:
x_min = x
if x_max < x:
x_max = x
if y_min > y:
y_min = y
if y_max < y:
y_max = y
# ? . #
# ? X #
# ? ? ?
p__ = d_ccw.move(p_)
if self.img[p__] & CellFlag.ACTIVE:
# Top-right corner
ret = [(self.dir | d_ccw).corner(p)]
# Generate inner polygon
for y in range(y_min, y_max):
for x in range(x_min, x_max):
# ? . ^
# ? X #
# ? ? ?
(self.x, self.y), self.dir = p__, d_ccw
return ret
# ? . .
# ? X #
# ? ? ?
else:
# ? . .
# ? X >
# ? ? ?
self.x, self.y = p_
return [] # No corner
# ? . ?
# ? X .
# ? # ?
p_ = d_cw.move(p)
if self.img[p_] & CellFlag.ACTIVE:
self.img[p] |= self.dir
# ? . ?
# ? X .
# ? # #
p__ = self.dir.move(p_)
if self.img[p__] & CellFlag.ACTIVE:
# Two corners: top-right and bottom-right
ret = [
(self.dir | d_ccw).corner(p),
(self.dir | d_cw).corner(p),
]
# ? . ?
# ? X .
# ? # >
self.x, self.y = p__
return ret
# ? . ?
# ? X .
# ? # .
else:
# Top-right corner
ret = [(self.dir | d_ccw).corner(p)]
# ? . ?
# ? X .
# ? v .
(self.x, self.y), self.dir = p_, d_cw
return ret
# ? . ?
# # X .
# ? . ?
p_ = d_rev.move(p)
if self.img[p_] & CellFlag.ACTIVE:
self.img[p] |= self.dir | d_cw
# ? . ?
# # X .
# # . ?
p__ = d_cw.move(p_)
if self.img[p__] & CellFlag.ACTIVE:
# Three corners: top-right, bottom-right, and bottom-left
ret = [
(self.dir | d_ccw).corner(p),
(self.dir | d_cw).corner(p),
(d_cw | d_rev).corner(p),
]
# ? . ?
# # X .
# v . .
(self.x, self.y), self.dir = p__, d_cw
return ret
# ? . ?
# # X .
# . . ?
else:
# Two corners: top-right and bottom-right
ret = [
(self.dir | d_ccw).corner(p),
(self.dir | d_cw).corner(p),
]
# ? . ?
# < X .
# . . ?
(self.x, self.y), self.dir = p_, d_rev
return ret
# ? . ?
# . X .
# ? . ?
self.img[p] |= self.dir | d_cw | d_rev
# Four corners: top-right, bottom-right, bottom-left, and top-left
return [
(self.dir | d_ccw).corner(p),
(self.dir | d_cw).corner(p),
(d_cw | d_rev).corner(p),
(d_ccw | d_rev).corner(p),
]
def trace(self, stop_pos, exclude_corners=False):
poly = []
while True:
j = len(poly)
poly += self._move_4way() if exclude_corners else self._move()
try:
i = poly.index(stop_pos, j)
except ValueError:
continue
return poly[:i + 1]
def polygonize(image, exclude_corners=False):
image = PixelImage(image)
ret = []
turtle = Turtle(image, 0, 0, CellFlag.LEFT)
for y in range(image.y, image.y_end):
for x in range(image.x, image.x_end):
v = image[x, y]
if not v & CellFlag.ACTIVE:
continue
if not (image[x, y + 1] & CellFlag.ACTIVE or v & CellFlag.DOWN):
dir = CellFlag.LEFT
if not (v & CellFlag.UP or image[x, y - 1] & CellFlag.ACTIVE):
turtle.dir, stop_pos = CellFlag.RIGHT, (x, y)
elif not (v & CellFlag.RIGHT or image[x + 1, y] & CellFlag.ACTIVE):
turtle.dir, stop_pos = CellFlag.DOWN, (x + 1, y)
else:
continue
turtle.x, turtle.y = x, y
# Store iterator value
p = x, y
ret.append(turtle.trace(stop_pos, exclude_corners)[::-1])
assert checkPoly(ret[-1])
inner_poly = []
line = doMove()
i = None
while i is None:
inner_poly += line
line = doMove()
i = find(line, inner_poly[0])
inner_poly += line[:i]
return ret
assert checkPoly(inner_poly)
# Emit inner polygon
yield inner_poly
def joinPolygons(polygons):
def first_equals(i, j, *, key=lambda x: x):
i, j = enumerate(map(key, i)), enumerate(map(key, j))
try:
(ai, av), (bi, bv) = next(i), next(j)
while av!= bv:
if av < bv:
ai, av = next(i)
else:
bi, bv = next(j)
return ai, bi
except StopIteration:
pass
# Restore iterator value
x, y = p
def merge(i, j):
r = []
i, j = iter(i), iter(j)
a, b = next(i, None), next(j, None)
while a is not None and b is not None:
if a[1] == b[1]:
r.append((min(a[0], b[0]), a[1]))
a, b = next(i, None), next(j, None)
elif a[1] < b[1]:
r.append(a)
a = next(i, None)
else:
r.append(b)
b = next(j, None)
if a is not None:
r.extend(i)
elif b is not None:
r.extend(j)
return r
kf = lambda v: v[1]
def f(p):
l = []
x = None
for i, v in sorted(enumerate(p), key=kf):
if x != v:
x = v
l.append((i, v))
return p, l
def checkPoints(p, l):
if not checkPoly(p):
return False
for i in range(len(l)):
j, v = l[i]
if j >= len(p) or p[j] != v:
print(f'{p}\n{l} Error at {i} ({j}, {v}) (got {None if j >= len(p) else p[j]})')
return False
if i < 1:
continue
_, t = l[i - 1]
if t == v:
print(f'{p}\n{l} Error at {i} ({j}, {v})')
return False
return True
points = [f(i) for i in polygons]
for i in range(len(points) - 1, 0, -1):
p, l = points[i]
for j in range(i):
p_, l_ = points[j]
t = first_equals(l, l_, key=kf)
if t is None:
continue
a, b = t
a, _ = l[a]
b, _ = l_[b]
p = [*p[:a], *p_[b:], *p_[:b], *p[a:]]
t = len(p_)
s = t - b + a
r = a - b
l = merge(
((i + t if i >= a else i, v) for i, v in l),
((i + r if i >= b else i + s, v) for i, v in l_),
)
assert checkPoints(p, l)
points[j] = p, l
del points[i]
break
return [p[0] for p in points]
def generatePolygons(image, *, join_polygons=True, exclude_corners=False, **kw):
ret = polygonize(image, exclude_corners)
if join_polygons:
ret = joinPolygons(ret)
return ret
def checkPoly(poly):
@@ -597,7 +818,7 @@ def testChar(name, pixels):
print(f'Character: {name}\n{image}\n\n')
for poly in generatePolygons(image):
for poly in generatePolygons(image, join_polygons=False, exclude_corners=True):
print('Polygon:\n ' + '\n '.join(f'{x}, {y}'
for x, y in poly) + '\n\n')