76 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
Idrees 6895837d66 Merge pull request #103 from IdreesInc/88-a-small-break-between-pixels
88 Merge adjacent pixels into combined shapes
2023-02-05 13:46:18 -05:00
Idrees Hassan 536227370c Update version and copyright year 2023-02-05 13:45:23 -05:00
Idrees 78d0a7197a Merge pull request #98 from Dheatly23/main
Combine Pixels Into Polygons
2023-02-05 13:38:58 -05:00
Dheatly23 19e0f59987 Adds License 2023-01-13 23:25:24 +07:00
Dheatly23 cbcb5331c2 Fix some bug with mutating iterator variable 2023-01-13 22:38:02 +07:00
Dheatly23 d5fbed6802 Discard polygon splicing
As it turns out, to add holes, just emit the inner polygon
(RIP colinear remover).
2023-01-13 11:37:45 +07:00
Dheatly23 8e1331f85b Rename some more functions 2023-01-13 11:31:33 +07:00
Dheatly23 09ae18caea Removes adjacent colinear points 2023-01-13 11:26:03 +07:00
Dheatly23 42b4b79cf9 Update generator using polygonizer 2023-01-12 17:05:08 +07:00
Dheatly23 eabb3edc2a Rename identifiers 2023-01-12 16:25:16 +07:00
Dheatly23 bd42295cab First prototype of polygonizer 2023-01-12 15:16:42 +07:00
Idrees e1c44cf227 Merge pull request #94 from IdreesInc/93-improve-spacing-around-thin-letters-like-t 2022-12-31 21:37:08 -05:00
Idrees Hassan 44034119d7 Significantly improve spacing for thin characters 2022-12-31 20:07:10 -05:00
45 changed files with 16109 additions and 110 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: 606 KiB

After

Width:  |  Height:  |  Size: 1019 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 158 KiB

+14142 -26
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]
]
}
]
]
+347 -58
View File
@@ -1,5 +1,5 @@
# Monocraft, a monospaced font for developers who like Minecraft a bit too much.
# Copyright (C) 2022 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,88 +16,377 @@
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.2"
monocraft.weight = "Medium"
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
if "pixels" in character:
top = drawCharacter(character, pen)
elif "reference" in character:
top = drawCharacter(charactersByCodepoint[character["reference"]], pen)
if "diacritic" in character:
diacritic = diacritics[character["diacritic"]]
if "diacriticSpace" in character:
top += PIXEL_SIZE * character["diacriticSpace"]
drawGlyph(diacritic["pixels"], pen, 0, top)
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()
drawCharacter(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")
def drawCharacter(character, pen):
if "reference" in character: return drawCharacter(charactersByCodepoint[character["reference"]],pen)
leftMargin = PIXEL_SIZE * character["leftMargin"] if "leftMargin" in character else 0
floor = -PIXEL_SIZE * character["descent"] if "descent" in character else 0
return drawGlyph(character["pixels"], pen, leftMargin, floor)
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 drawGlyph(pixels, pen, startingX, startingY):
top = 0 # The highest point of the character
for rowIndex in range(len(pixels)):
row = pixels[-(rowIndex + 1)]
for columnIndex in range(len(row)):
if row[columnIndex] == 0:
continue
pen.moveTo((columnIndex * PIXEL_SIZE + startingX, rowIndex * PIXEL_SIZE + startingY)) # Bottom left
pen.lineTo((columnIndex * PIXEL_SIZE + startingX, (rowIndex + 1) * PIXEL_SIZE + startingY))
pen.lineTo(((columnIndex + 1) * PIXEL_SIZE + startingX, (rowIndex + 1) * PIXEL_SIZE + startingY))
pen.lineTo(((columnIndex + 1) * PIXEL_SIZE + startingX, rowIndex * PIXEL_SIZE + startingY))
pen.closePath()
top = (rowIndex + 1) * PIXEL_SIZE + startingY
return top
def generateImage(character):
image = PixelImage()
kw = {}
if "pixels" in character:
arr = character["pixels"]
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:
other = generateImage(charactersByCodepoint[character["reference"]])
kw.update(other[1])
image = image | other[0]
if "diacritic" in character:
diacritic = diacritics[character["diacritic"]]
arr = diacritic["pixels"]
x = image.x
y = findHighestY(image) + 1
if "diacriticSpace" in character:
y += int(character["diacriticSpace"])
image = image | imageFromArray(arr, x, y)
return (image, kw)
generateFont()
generateExamples(characters, ligatures, charactersByCodepoint)
def findHighestY(image):
for y in range(image.y_end - 1, image.y, -1):
for x in range(image.x, image.x_end):
if image[x, y]:
return y
return image.y
def imageFromArray(arr, x=0, y=0):
return PixelImage(
x=x,
y=y,
width=len(arr[0]),
height=len(arr),
data=bytes(v for a in reversed(arr) for v in a),
)
def drawPolygon(poly, pen):
for polygon in poly:
start = True
for x, y in polygon:
x = int(math.floor(x * PIXEL_SIZE))
y = int(math.floor(y * PIXEL_SIZE))
if start:
pen.moveTo(x, y)
start = False
else:
pen.lineTo(x, y)
pen.closePath()
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)
+827
View File
@@ -0,0 +1,827 @@
# 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/>.
__all__ = [
'PixelImage',
'generatePolygons',
]
from collections import defaultdict
from enum import IntFlag
import itertools
class PixelImage:
''' Class for managing a pixel image.
Each pixel is a 8-bit integer.
It also store the position and size of the image, allowing easier operation.
'''
__slots__ = ['__data', '_x', '_y', '_w', '_h']
def __init__(self, src=None, *, x=0, y=0, width=0, height=0, data=None):
if src is not None:
self._x, self._y, self._w, self._h, self.__data = src._x, src._y, src._w, src._h, bytearray(
src.__data)
return
if width < 0:
raise ValueError('Width < 0')
if height < 0:
raise ValueError('Height < 0')
self._x, self._y, self._w, self._h = x, y, width, height
if data is not None:
data = bytearray(data)
if len(data) != width * height:
raise ValueError(
f'Data length mismatch (expected {width * height}, got {len(data)})'
)
self.__data = data
else:
self.__data = bytearray(width * height)
@property
def x(self):
return self._x
@property
def y(self):
return self._y
@property
def x_end(self):
return self._x + self._w
@property
def y_end(self):
return self._y + self._h
@property
def width(self):
return self._w
@property
def height(self):
return self._h
@property
def data(self):
return bytes(self.__data)
def __getitem__(self, key):
''' Gets a pixel at (x, y).
Defaults to 0 if out of bounds.
'''
x, y = key
x -= self._x
y -= self._y
if x < 0 or x >= self._w or y < 0 or y >= self._h:
return 0
return self.__data[x + y * self._w]
def __setitem__(self, key, value):
''' Sets a pixel at (x, y).
Do nothing if out of bounds.
'''
x, y = key
x -= self._x
y -= self._y
if x < 0 or x >= self._w or y < 0 or y >= self._h:
return
self.__data[x + y * self._w] = value
def __len__(self):
return self._w * self._h
def __str__(self):
return '\n'.join(' '.join(
str(self[x, y]) for x in range(self.x, self.x_end))
for y in range(self.y, self.y_end))
def __repr__(self):
return f'PixelImage(\n x={self._x},\n' \
f' y={self._y},\n' \
f' width={self._w},\n' \
f' height={self._h},\n' \
f' data=bytes([\n ' + \
',\n '.join(
', '.join(
f'{self[x, y]:#04x}' for x in range(self.x, self.x_end)
) for y in range(self.y, self.y_end)
) + \
'\n ])\n)'
def __hash__(self):
return hash((self._x, self._y, self._w, self._h, self.__data))
def __eq__(self, other):
if not isinstance(other, PixelImage):
return NotImplemented
return self._x == other._x and \
self._y == other._y and \
self._w == other._w and \
self._h == other._h and \
self.__data == other.__data
def __ne__(self, other):
if not isinstance(other, PixelImage):
return NotImplemented
return self._x != other._x or \
self._y != other._y or \
self._w != other._w or \
self._h != other._h or \
self.__data != other.__data
def __or__(self, other):
if not isinstance(other, PixelImage):
return NotImplemented
if self.width == 0 or self.height == 0:
return other
elif other.width == 0 or other.height == 0:
return self
else:
x = min(self._x, other.x)
y = min(self._y, other.y)
x2 = max(self.x_end, other.x_end)
y2 = max(self.y_end, other.y_end)
ret = self.__class__(
x=x,
y=y,
width=x2 - x,
height=y2 - y,
)
dj = self._y - y
i_min = self._x - x
i_max = i_min + self._w
for j in range(self._h):
j_min = j * self._w
_j_min = (j + dj) * ret._w
row = self.__data[j_min:j_min + self._w]
ret.__data[i_min + _j_min:i_max + _j_min] = row
dj = other._y - y
i_min = other.x - x
i_max = i_min + other.width
for j in range(other.height):
j_min = j * other.width
_j_min = (j + dj) * ret._w
for i, i_ in enumerate(range(i_min + _j_min, i_max + _j_min)):
ret.__data[i_] |= other.__data[j_min + i]
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')
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, **kw)
def segmentize(image):
image = PixelImage(image)
for y in range(image.y, image.y_end):
for x in range(image.x, image.x_end):
if not image[x, y]:
continue
# Move segment into new image
ret = PixelImage(
x=image.x,
y=image.y,
width=image.width,
height=image.height,
)
p = (x, y)
# Flood fill
stack = [(x, y)]
while stack:
x, y = stack.pop()
if not image[x, y]:
continue
ret[x, y] = 1
image[x, y] = 0
if image[x - 1, y - 1]:
stack.append((x - 1, y - 1))
if image[x - 1, y]:
stack.append((x - 1, y))
if image[x - 1, y + 1]:
stack.append((x - 1, y + 1))
if image[x, y - 1]:
stack.append((x, y - 1))
if image[x, y + 1]:
stack.append((x, y + 1))
if image[x + 1, y - 1]:
stack.append((x + 1, y - 1))
if image[x + 1, y]:
stack.append((x + 1, y))
if image[x + 1, y + 1]:
stack.append((x + 1, y + 1))
# Yields segment and staring position
x, y = p
yield (ret, p)
class CellFlag(IntFlag):
ACTIVE = 0b0000_0001
UP = 0b0001_0000
DOWN = 0b0010_0000
LEFT = 0b0100_0000
RIGHT = 0b1000_0000
UP_LEFT = UP | LEFT
UP_RIGHT = UP | RIGHT
DOWN_LEFT = DOWN | LEFT
DOWN_RIGHT = DOWN | RIGHT
def move(self, pos):
''' Move coordinate according to direction '''
x, y = pos
if self & self.__class__.UP:
y -= 1
if self & self.__class__.DOWN:
y += 1
if self & self.__class__.LEFT:
x -= 1
if self & self.__class__.RIGHT:
x += 1
return (x, y)
def corner(self, pos):
''' Get corner of a cell '''
x, y = pos
if self & self.__class__.UP_LEFT == self.__class__.UP_LEFT:
return (x, y)
elif self & self.__class__.UP_RIGHT == self.__class__.UP_RIGHT:
return (x + 1, y)
elif self & self.__class__.DOWN_RIGHT == self.__class__.DOWN_RIGHT:
return (x + 1, y + 1)
elif self & self.__class__.DOWN_LEFT == self.__class__.DOWN_LEFT:
return (x, y + 1)
else:
raise ValueError(f'Invalid corner {self}')
def cw(self):
''' Rotate self clockwise '''
ret = self & self.__class__.ACTIVE
if self & self.__class__.UP:
ret |= self.__class__.RIGHT
if self & self.__class__.DOWN:
ret |= self.__class__.LEFT
if self & self.__class__.LEFT:
ret |= self.__class__.UP
if self & self.__class__.RIGHT:
ret |= self.__class__.DOWN
return ret
def ccw(self):
''' Rotate self counterclockwise '''
ret = self & self.__class__.ACTIVE
if self & self.__class__.UP:
ret |= self.__class__.LEFT
if self & self.__class__.DOWN:
ret |= self.__class__.RIGHT
if self & self.__class__.LEFT:
ret |= self.__class__.DOWN
if self & self.__class__.RIGHT:
ret |= self.__class__.UP
return ret
def reverse(self):
''' Reverse self '''
ret = self & self.__class__.ACTIVE
if self & self.__class__.UP:
ret |= self.__class__.DOWN
if self & self.__class__.DOWN:
ret |= self.__class__.UP
if self & self.__class__.LEFT:
ret |= self.__class__.RIGHT
if self & self.__class__.RIGHT:
ret |= self.__class__.LEFT
return ret
class Turtle:
__slots__ = ['img', '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 = self.dir.ccw(), self.dir.cw(), self.dir.reverse()
p = self.x, self.y
self.img[p] |= d_ccw
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
# ? . #
# ? X ?
# ? ? ?
p_ = (self.dir | d_ccw).move(p)
if self.img[p_] & CellFlag.ACTIVE:
# Top-right corner
ret = [(self.dir | d_ccw).corner(p)]
# ? . ^
# ? X ?
# ? ? ?
(self.x, self.y), self.dir = p_, d_ccw
return ret
# ? . .
# ? X #
# ? ? ?
p_ = self.dir.move(p)
if self.img[p_] & CellFlag.ACTIVE:
# ? . .
# ? X >
# ? ? ?
self.x, self.y = p_
return [] # No corner
# ? . .
# ? X .
# ? ? #
p_ = (self.dir | d_cw).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.img[p] |= self.dir
self.x, self.y = p_
return ret
# ? . .
# ? X .
# ? # .
p_ = d_cw.move(p)
if self.img[p_] & CellFlag.ACTIVE:
# Top-right corner
ret = [(self.dir | d_ccw).corner(p)]
# ? . .
# ? X .
# ? v .
self.img[p] |= self.dir
(self.x, self.y), self.dir = p_, d_cw
return ret
# ? . .
# ? X .
# # . .
p_ = (d_rev | 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.img[p] |= self.dir | d_cw
(self.x, self.y), self.dir = p_, d_cw
return ret
# ? . .
# # X .
# . . .
p_ = d_rev.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.img[p] |= self.dir | d_cw
(self.x, self.y), self.dir = p_, d_rev
return ret
# # . .
# . X .
# . . .
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 = [
(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 .
# . . .
self.img[p] |= self.dir | d_cw | d_rev
(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 _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
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
# ? . ?
# ? X #
# ? ? ?
p_ = self.dir.move(p)
if self.img[p_] & CellFlag.ACTIVE:
# ? . #
# ? X #
# ? ? ?
p__ = d_ccw.move(p_)
if self.img[p__] & CellFlag.ACTIVE:
# Top-right corner
ret = [(self.dir | d_ccw).corner(p)]
# ? . ^
# ? 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 (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
ret.append(turtle.trace(stop_pos, exclude_corners)[::-1])
assert checkPoly(ret[-1])
return ret
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
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):
for i in range(-1, len(poly) - 1):
x, y = poly[i]
x_, y_ = poly[i + 1]
if x != x_ and y != y_:
print(f'{poly} Error at {i}')
return False
for i in range(-2, len(poly) - 2):
x, y = poly[i]
x_, y_ = poly[i + 1]
x__, y__ = poly[i + 2]
if x == x_ == x__ or y == y_ == y__:
print(f'{poly} Error at {i}')
return False
return True
# Testing
def runTest():
import json
characters = json.load(open("./characters.json"))
diacritics = json.load(open("./diacritics.json"))
ligatures = json.load(open("./ligatures.json"))
for v in characters:
if 'pixels' not in v:
continue
testChar(v['character'], v['pixels'])
for k, v in diacritics.items():
if 'pixels' not in v:
continue
testChar(k, v['pixels'])
for v in ligatures:
if 'pixels' not in v:
continue
testChar(v['ligature'], v['pixels'])
filter = {}
def testChar(name, pixels):
if filter and name not in filter:
return
image = PixelImage(
width=len(pixels[0]),
height=len(pixels),
data=bytes(x for a in pixels[::-1] for x in a),
)
print(f'Character: {name}\n{image}\n\n')
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')
if __name__ == '__main__':
runTest()