Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6895837d66 | ||
|
|
536227370c | ||
|
|
78d0a7197a | ||
|
|
19e0f59987 | ||
|
|
cbcb5331c2 | ||
|
|
d5fbed6802 | ||
|
|
8e1331f85b | ||
|
|
09ae18caea | ||
|
|
42b4b79cf9 | ||
|
|
eabb3edc2a | ||
|
|
bd42295cab | ||
|
|
e1c44cf227 | ||
|
|
44034119d7 | ||
|
|
b8908173df | ||
|
|
0ad78e0a3e | ||
|
|
a897bc0083 | ||
|
|
f1a9d2fc74 | ||
|
|
97603fd8ad | ||
|
|
4cca081921 | ||
|
|
e1ebb386d6 | ||
|
|
0ff70751e7 | ||
|
|
0cdc990d4d | ||
|
|
acc94e9b6a | ||
|
|
5dcac8c196 | ||
|
|
2c5b9e8830 | ||
|
|
ba0a5ebd21 | ||
|
|
b4800e2afa | ||
|
|
7ae09a1004 | ||
|
|
88cf2f83d9 | ||
|
|
785c13c1a9 | ||
|
|
55e32cac8a | ||
|
|
e8742d37ad | ||
|
|
2232694429 | ||
|
|
95762ac3ff | ||
|
|
1a6e6abfca | ||
|
|
1dcffa0b43 | ||
|
|
3ffa57f4c8 | ||
|
|
b6d9da205d | ||
|
|
26bfacb78b | ||
|
|
37e82e5648 | ||
|
|
bfc605cbb9 |
No files matched your search
@@ -0,0 +1,12 @@
|
||||
---
|
||||
name: Additional Glyphs Request
|
||||
about: Suggest some characters/glyphs/ligatures to be added to Monocraft
|
||||
title: ''
|
||||
labels: additional-glyphs
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Which characters/ligatures/etc. would you like added to Monocraft?**
|
||||
|
||||
*Include any details about your request here. If you would like certain characters to be added to the font, please list every single character in plain text (not just an image, though images can be included) that you would like added. Don't forget to include upper and lower case if applicable.*
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: potential-bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Operating system**
|
||||
Windows, MacOS, etc.
|
||||
|
||||
**Software**
|
||||
VS Code, IntelliJ, etc.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**What feature would you like added to Monocraft?**
|
||||
*Note that if you would like to request additional characters/glyphs/ligatures to be added, please use the "Additional Glyphs Request" template instead*
|
||||
+1
-1
@@ -2,4 +2,4 @@
|
||||
.DS_Store
|
||||
src/__pycache__
|
||||
dist/
|
||||
src/build-font.sh
|
||||
build/
|
||||
@@ -0,0 +1,15 @@
|
||||
# Contributing to Monocraft
|
||||
|
||||
Contributions to the font are always welcome, though please be sure to adhere to these guidelines if you want your changes to be approved.
|
||||
|
||||
## Guiding Principles of Monocraft
|
||||
|
||||
- Characters must look exactly the same as they do in Minecraft
|
||||
- The only exception is if the character is too wide to fit in the font and still be monospaced. In this case, we would modify the glyph to fit if possible. Such modifications will likely need to be run by [@IdreesInc](https://github.com/IdreesInc) first to ensure that the style is kept consistent with other glyphs
|
||||
- Ligatures must always be helpful and never annoying
|
||||
- This means that ligatures that might work in one context (like <= as an arrow) but fail in another (like when <= should instead be "less than or equal to") should not be added to Monocraft
|
||||
- As a rule, it's usually better to only add ligatures that someone might expect rather than every possible ligature so as to avoid ligatures appearing out of place
|
||||
- The code that generates Monocraft should be clean, consistent, and maintainable
|
||||
- If you feel as if a feature would be hard to add cleanly, contact [@IdreesInc](https://github.com/IdreesInc) instead and I'll see if we can work something out
|
||||
|
||||
So long as you keep in line with the above principles, your PR is much more likely to be merged. Good luck!
|
||||
+10
-8
@@ -9,20 +9,22 @@ 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
|
||||
¡ ¢ £ ¤ ¥ ¦ § ¨ © ª « ¬ ® ¯ ° ± ² ³ ´ µ ¶ · ¸ ¹ º » ¼ ½ ¾ ¿ À Á
|
||||
Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï Ð Ñ Ò Ó Ô Õ Ö × Ø Ù Ú Û Ü Ý Þ ß à á
|
||||
â ã ä å æ ç è é ê ë ì í î ï ð ñ ò ó ô õ ö ÷ ø ù ú û ü ý ÿ Ā ā Ă
|
||||
ă Ć ć Ĉ ĉ Ċ ċ Č č Ď ď Ē ē Ĕ ĕ Ė ė Ě ě Ĝ ĝ Ğ ğ Ġ ġ Ĥ ĥ Ĩ ĩ Ī ī Ĭ
|
||||
ĭ İ Ĵ ĵ Ĺ ĺ Ľ ľ Ń ń Ň ň Ō ō Ŏ ŏ Ő ő Ŕ ŕ Ř ř Ś ś Ŝ ŝ Š š Ť ť Ũ ũ
|
||||
Ū ū Ŭ ŭ Ů ů Ű ű Ŵ ŵ Ŷ ŷ Ÿ Ź ź Ż ż Ž
|
||||
ă Ą ą Ć ć Ĉ ĉ Ċ ċ Č č Ď ď Ē ē Ĕ ĕ Ė ė Ę ę Ě ě Ĝ ĝ Ğ ğ Ġ ġ Ģ ģ Ĥ
|
||||
ĥ Ĩ ĩ Ī ī Ĭ ĭ İ Ĵ ĵ Ķ ķ Ĺ ĺ Ļ ļ Ľ ľ Ł ł Ń ń Ņ ņ Ň ň Ō ō Ŏ ŏ Ő ő
|
||||
Œ œ Ŕ ŕ Ř ř Ś ś Ŝ ŝ Ş ş Š š Ť ť Ũ ũ Ū ū Ŭ ŭ Ů ů Ű ű Ŵ ŵ Ŷ ŷ Ÿ Ź
|
||||
ź Ż ż Ž
|
||||
|
||||
ž Ǎ ǎ Ǐ ǐ Ǒ ǒ Ǔ ǔ Ǣ ǣ Ǧ ǧ Ǩ ǩ ǰ Ǵ ǵ Ǹ ǹ Ǽ ǽ Ȟ ȟ Ȧ ȧ Ȯ ȯ Ȳ ȳ Ѐ Ё
|
||||
Ђ Ѓ Є Ѕ І Ї Ј Љ Њ Ћ Ќ Ѝ Ў Џ А Б В Г Д Е Ж З И Й К Л М Н О П Р С
|
||||
Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь Э Ю Я а б в г д е ж з и й к л м н о п р с
|
||||
т у ф х ц ч ш щ ъ ы ь э ю я ѐ ё ђ ѓ є ѕ і ї ј љ њ ћ ќ ѝ ў џ
|
||||
ž Ǎ ǎ Ǐ ǐ Ǒ ǒ Ǔ ǔ Ǣ ǣ Ǧ ǧ Ǩ ǩ ǰ Ǵ ǵ Ǹ ǹ Ǽ ǽ Ș ș Ț ț Ȟ ȟ Ȧ ȧ Ȯ ȯ
|
||||
Ȳ ȳ Ѐ Ё Ђ Ѓ Є Ѕ І Ї Ј Љ Њ Ћ Ќ Ѝ Ў Џ А Б В Г Д Е Ж З И Й К Л М Н
|
||||
О П Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь Э Ю Я а б в г д е ж з и й к л м н
|
||||
о п р с т у ф х ц ч ш щ ъ ы ь э ю я ѐ ё ђ ѓ є ѕ і ї ј љ њ ћ ќ ѝ
|
||||
ў џ
|
||||
|
||||
Ӂ ӂ Ӑ ӑ Ӓ ӓ Ӗ ӗ Ӝ ӝ Ӟ ӟ Ӣ ӣ Ӥ ӥ Ӧ ӧ Ӭ ӭ Ӯ ӯ Ӱ ӱ Ӳ ӳ Ӵ ӵ Ӹ ӹ Ḃ ḃ
|
||||
Ḋ ḋ Ḟ ḟ Ḡ ḡ Ḣ ḣ Ḧ ḧ Ḱ ḱ Ḿ ḿ Ṁ ṁ Ṅ ṅ Ṕ ṕ Ṗ ṗ Ṙ ṙ Ṡ ṡ Ṫ ṫ Ṽ ṽ Ẁ ẁ
|
||||
Ẃ ẃ Ẅ ẅ Ẇ ẇ Ẋ ẋ Ẍ ẍ Ẏ ẏ Ẑ ẑ ẗ ẘ ẙ Ẽ ẽ Ỳ ỳ Ỹ ỹ
|
||||
|
||||
€ ☺ ☻ ♥ ⨰ ⸚
|
||||
€ ← ↑ → ↓ ⇐ ⇑ ⇒ ⇓ ∀ ∷ ☺ ☻ ♥ ⨰ ⸚
|
||||
|
||||
--- Ligatures ---
|
||||
= = -> ==
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 615 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 158 KiB |
+571
-22
@@ -102,8 +102,8 @@
|
||||
"name": "single_quote",
|
||||
"codepoint": 39,
|
||||
"pixels": [
|
||||
[0, 1, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
@@ -997,7 +997,8 @@
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0]
|
||||
]
|
||||
],
|
||||
"leftMargin": -0.5
|
||||
},
|
||||
{
|
||||
"character": "g",
|
||||
@@ -1041,7 +1042,8 @@
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 1]
|
||||
]
|
||||
],
|
||||
"leftMargin": -0.5
|
||||
},
|
||||
{
|
||||
"character": "j",
|
||||
@@ -1071,7 +1073,8 @@
|
||||
[1, 1, 0, 0, 0],
|
||||
[1, 0, 1, 0, 0],
|
||||
[1, 0, 0, 1, 0]
|
||||
]
|
||||
],
|
||||
"leftMargin": 0.5
|
||||
},
|
||||
{
|
||||
"character": "l",
|
||||
@@ -1085,7 +1088,8 @@
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 1]
|
||||
]
|
||||
],
|
||||
"leftMargin": -0.5
|
||||
},
|
||||
{
|
||||
"character": "m",
|
||||
@@ -1201,7 +1205,8 @@
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 1]
|
||||
]
|
||||
],
|
||||
"leftMargin": -0.5
|
||||
},
|
||||
{
|
||||
"character": "u",
|
||||
@@ -1908,13 +1913,13 @@
|
||||
"name": "latin_small_letter_ae",
|
||||
"codepoint": 230,
|
||||
"pixels": [
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 0, 1, 0],
|
||||
[0, 0, 0, 1, 0, 1],
|
||||
[0, 1, 1, 1, 1, 1],
|
||||
[1, 0, 0, 1, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1]
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 1, 0],
|
||||
[0, 0, 1, 0, 1],
|
||||
[0, 1, 1, 1, 1],
|
||||
[1, 0, 1, 0, 0],
|
||||
[0, 1, 1, 1, 1]
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1947,7 +1952,8 @@
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 1]
|
||||
]
|
||||
],
|
||||
"leftMargin": -0.5
|
||||
},
|
||||
{
|
||||
"character": "í",
|
||||
@@ -1962,7 +1968,8 @@
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 1]
|
||||
]
|
||||
],
|
||||
"leftMargin": -0.5
|
||||
},
|
||||
{
|
||||
"character": "î",
|
||||
@@ -1977,7 +1984,8 @@
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 1]
|
||||
]
|
||||
],
|
||||
"leftMargin": -0.5
|
||||
},
|
||||
{
|
||||
"character": "ï",
|
||||
@@ -1991,7 +1999,8 @@
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 1]
|
||||
]
|
||||
],
|
||||
"leftMargin": -0.5
|
||||
},
|
||||
{
|
||||
"character": "ð",
|
||||
@@ -2035,6 +2044,122 @@
|
||||
[0, 1, 1, 1, 0]
|
||||
]
|
||||
},
|
||||
{
|
||||
"character": "Ą",
|
||||
"name": "latin_capital_letter_a_with_ogonek",
|
||||
"codepoint": 260,
|
||||
"pixels": [
|
||||
[0, 1, 1, 1, 0],
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 1, 1, 1, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[0, 0, 0, 1, 0],
|
||||
[0, 0, 0, 0, 1]
|
||||
],
|
||||
"descent": 2
|
||||
},
|
||||
{
|
||||
"character": "ą",
|
||||
"name": "latin_small_letter_a_with_ogonek",
|
||||
"codepoint": 261,
|
||||
"pixels": [
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 1],
|
||||
[0, 1, 1, 1, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[0, 1, 1, 1, 1],
|
||||
[0, 0, 0, 1, 0],
|
||||
[0, 0, 0, 0, 1]
|
||||
],
|
||||
"descent": 2
|
||||
},
|
||||
{
|
||||
"character": "ď",
|
||||
"name": "latin_small_letter_d_with_caron",
|
||||
"codepoint": 271,
|
||||
"pixels": [
|
||||
[0, 0, 0, 1, 0, 1],
|
||||
[0, 0, 0, 1, 0, 1],
|
||||
[0, 1, 1, 1, 0, 0],
|
||||
[1, 0, 0, 1, 0, 0],
|
||||
[1, 0, 0, 1, 0, 0],
|
||||
[1, 0, 0, 1, 0, 0],
|
||||
[0, 1, 1, 1, 0, 0]
|
||||
]
|
||||
},
|
||||
{
|
||||
"character": "Ę",
|
||||
"name": "latin_capital_letter_e_with_ogonek",
|
||||
"codepoint": 280,
|
||||
"pixels": [
|
||||
[1, 1, 1, 1, 1],
|
||||
[1, 0, 0, 0, 0],
|
||||
[1, 1, 1, 0, 0],
|
||||
[1, 0, 0, 0, 0],
|
||||
[1, 0, 0, 0, 0],
|
||||
[1, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 1, 0],
|
||||
[0, 0, 0, 0, 1]
|
||||
],
|
||||
"descent": 2
|
||||
},
|
||||
{
|
||||
"character": "ę",
|
||||
"name": "latin_small_letter_e_with_ogonek",
|
||||
"codepoint": 281,
|
||||
"pixels": [
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 0],
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 1, 1, 1, 1],
|
||||
[1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1],
|
||||
[0, 0, 0, 1, 0],
|
||||
[0, 0, 0, 0, 1]
|
||||
],
|
||||
"descent": 2
|
||||
},
|
||||
{
|
||||
"character": "Ģ",
|
||||
"name": "latin_capital_letter_g_with_cedilla",
|
||||
"codepoint": 290,
|
||||
"pixels": [
|
||||
[0, 1, 1, 1, 1],
|
||||
[1, 0, 0, 0, 0],
|
||||
[1, 0, 0, 1, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 1, 0, 0, 0]
|
||||
],
|
||||
"descent": 2
|
||||
},
|
||||
{
|
||||
"character": "ģ",
|
||||
"name": "latin_small_letter_g_with_cedilla",
|
||||
"codepoint": 291,
|
||||
"pixels": [
|
||||
[0, 0, 0, 1, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[0, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 1],
|
||||
[1, 1, 1, 1, 0]
|
||||
],
|
||||
"descent": 1
|
||||
},
|
||||
{
|
||||
"character": "ĩ",
|
||||
"name": "latin_small_letter_i_with_tilde",
|
||||
@@ -2048,7 +2173,8 @@
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 1]
|
||||
]
|
||||
],
|
||||
"leftMargin": -0.5
|
||||
},
|
||||
{
|
||||
"character": "ī",
|
||||
@@ -2062,7 +2188,8 @@
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 1]
|
||||
]
|
||||
],
|
||||
"leftMargin": -0.5
|
||||
},
|
||||
{
|
||||
"character": "ĭ",
|
||||
@@ -2077,7 +2204,8 @@
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 1]
|
||||
]
|
||||
],
|
||||
"leftMargin": -0.5
|
||||
},
|
||||
{
|
||||
"character": "ĵ",
|
||||
@@ -2096,6 +2224,217 @@
|
||||
],
|
||||
"descent": 1
|
||||
},
|
||||
{
|
||||
"character": "Ķ",
|
||||
"name": "latin_capital_letter_k_with_cedilla",
|
||||
"codepoint": 310,
|
||||
"pixels": [
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 0, 0, 1, 0],
|
||||
[1, 1, 1, 0, 0],
|
||||
[1, 0, 0, 1, 0],
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 1, 0, 0, 0]
|
||||
],
|
||||
"descent": 2
|
||||
},
|
||||
{
|
||||
"character": "ķ",
|
||||
"name": "latin_small_letter_k_with_cedilla",
|
||||
"codepoint": 311,
|
||||
"pixels": [
|
||||
[1, 0, 0, 0, 0],
|
||||
[1, 0, 0, 0, 0],
|
||||
[1, 0, 0, 1, 0],
|
||||
[1, 0, 1, 0, 0],
|
||||
[1, 1, 0, 0, 0],
|
||||
[1, 0, 1, 0, 0],
|
||||
[1, 0, 0, 1, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 1, 0, 0, 0]
|
||||
],
|
||||
"descent": 2,
|
||||
"leftMargin": 0.5
|
||||
},
|
||||
{
|
||||
"character": "Ļ",
|
||||
"name": "latin_capital_letter_l_with_cedilla",
|
||||
"codepoint": 315,
|
||||
"pixels": [
|
||||
[1, 0, 0, 0, 0],
|
||||
[1, 0, 0, 0, 0],
|
||||
[1, 0, 0, 0, 0],
|
||||
[1, 0, 0, 0, 0],
|
||||
[1, 0, 0, 0, 0],
|
||||
[1, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 1, 0],
|
||||
[0, 0, 1, 0, 0]
|
||||
],
|
||||
"descent": 2
|
||||
},
|
||||
{
|
||||
"character": "ļ",
|
||||
"name": "latin_small_letter_l_with_cedilla",
|
||||
"codepoint": 316,
|
||||
"pixels": [
|
||||
[0, 1, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 1],
|
||||
[0, 0, 0, 1, 0],
|
||||
[0, 0, 1, 0, 0]
|
||||
],
|
||||
"descent": 2,
|
||||
"leftMargin": -0.5
|
||||
},
|
||||
{
|
||||
"character": "Ł",
|
||||
"name": "latin_capital_letter_l_with_stroke",
|
||||
"codepoint": 321,
|
||||
"pixels": [
|
||||
[0, 1, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0],
|
||||
[0, 1, 0, 1, 0],
|
||||
[0, 1, 1, 0, 0],
|
||||
[1, 1, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1]
|
||||
]
|
||||
},
|
||||
{
|
||||
"character": "ł",
|
||||
"name": "latin_small_letter_l_with_stroke",
|
||||
"codepoint": 322,
|
||||
"pixels": [
|
||||
[0, 1, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 1],
|
||||
[0, 0, 1, 1, 0],
|
||||
[0, 1, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 1]
|
||||
],
|
||||
"leftMargin": -0.5
|
||||
},
|
||||
{
|
||||
"character": "Ņ",
|
||||
"name": "latin_capital_letter_n_with_cedilla",
|
||||
"codepoint": 325,
|
||||
"pixels": [
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 1, 0, 0, 1],
|
||||
[1, 0, 1, 0, 1],
|
||||
[1, 0, 0, 1, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 1, 0, 0, 0]
|
||||
],
|
||||
"descent": 2
|
||||
},
|
||||
{
|
||||
"character": "ņ",
|
||||
"name": "latin_small_letter_n_with_cedilla",
|
||||
"codepoint": 326,
|
||||
"pixels": [
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 0],
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 1, 0, 0, 0]
|
||||
],
|
||||
"descent": 2
|
||||
},
|
||||
{
|
||||
"character": "Œ",
|
||||
"name": "latin_capital_ligature_oe",
|
||||
"codepoint": 338,
|
||||
"pixels": [
|
||||
[0, 1, 0, 1, 1],
|
||||
[1, 0, 1, 0, 0],
|
||||
[1, 0, 1, 1, 0],
|
||||
[1, 0, 1, 0, 0],
|
||||
[1, 0, 1, 0, 0],
|
||||
[1, 0, 1, 0, 0],
|
||||
[0, 1, 0, 1, 1]
|
||||
]
|
||||
},
|
||||
{
|
||||
"character": "œ",
|
||||
"name": "latin_small_ligature_oe",
|
||||
"codepoint": 339,
|
||||
"pixels": [
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 1, 0],
|
||||
[1, 0, 1, 0, 1],
|
||||
[1, 0, 1, 1, 1],
|
||||
[1, 0, 1, 0, 0],
|
||||
[0, 1, 0, 1, 1]
|
||||
]
|
||||
},
|
||||
{
|
||||
"character": "Ş",
|
||||
"name": "latin_capital_letter_s_with_cedilla",
|
||||
"codepoint": 350,
|
||||
"pixels": [
|
||||
[0, 1, 1, 1, 1],
|
||||
[1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 1],
|
||||
[0, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 0, 0, 1, 0],
|
||||
[0, 0, 1, 0, 0]
|
||||
],
|
||||
"descent": 2
|
||||
},
|
||||
{
|
||||
"character": "ş",
|
||||
"name": "latin_small_letter_s_with_cedilla",
|
||||
"codepoint": 351,
|
||||
"pixels": [
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1],
|
||||
[1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 1],
|
||||
[1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 1, 0],
|
||||
[0, 0, 1, 0, 0]
|
||||
],
|
||||
"descent": 2
|
||||
},
|
||||
{
|
||||
"character": "ť",
|
||||
"name": "latin_small_letter_t_with_caron",
|
||||
"codepoint": 357,
|
||||
"pixels": [
|
||||
[0, 0, 0, 0, 1],
|
||||
[0, 0, 1, 0, 1],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 1]
|
||||
],
|
||||
"leftMargin": -0.5
|
||||
},
|
||||
{
|
||||
"character": "Ů",
|
||||
"name": "latin_capital_letter_u_with_ring_above",
|
||||
@@ -2117,7 +2456,8 @@
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 1]
|
||||
]
|
||||
],
|
||||
"leftMargin": -0.5
|
||||
},
|
||||
{
|
||||
"character": "ĵ",
|
||||
@@ -2136,6 +2476,75 @@
|
||||
],
|
||||
"descent": 1
|
||||
},
|
||||
{
|
||||
"character": "Ș",
|
||||
"name": "latin_capital_letter_s_with_comma_below",
|
||||
"codepoint": 536,
|
||||
"pixels": [
|
||||
[0, 1, 1, 1, 1],
|
||||
[1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 1],
|
||||
[0, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 1, 0, 0, 0]
|
||||
],
|
||||
"descent": 2
|
||||
},
|
||||
{
|
||||
"character": "ș",
|
||||
"name": "latin_small_letter_s_with_comma_below",
|
||||
"codepoint": 537,
|
||||
"pixels": [
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1],
|
||||
[1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 1],
|
||||
[1, 1, 1, 1, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 1, 0, 0, 0]
|
||||
],
|
||||
"descent": 2
|
||||
},
|
||||
{
|
||||
"character": "Ț",
|
||||
"name": "latin_capital_letter_t_with_comma_below",
|
||||
"codepoint": 538,
|
||||
"pixels": [
|
||||
[1, 1, 1, 1, 1],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 1, 0, 0, 0]
|
||||
],
|
||||
"descent": 2
|
||||
},
|
||||
{
|
||||
"character": "ț",
|
||||
"name": "latin_small_letter_t_with_comma_below",
|
||||
"codepoint": 539,
|
||||
"pixels": [
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 1],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 1, 0, 0, 0]
|
||||
],
|
||||
"descent": 2,
|
||||
"leftMargin": -0.5
|
||||
},
|
||||
{
|
||||
"character": "Ѐ",
|
||||
"name": "cyrillic_capital_letter_ie_with_grave",
|
||||
@@ -3247,6 +3656,146 @@
|
||||
[0, 0, 1, 1, 0]
|
||||
]
|
||||
},
|
||||
{
|
||||
"character": "←",
|
||||
"name": "leftwards_arrow",
|
||||
"codepoint": 8592,
|
||||
"pixels": [
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 1, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1],
|
||||
[0, 1, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0]
|
||||
]
|
||||
},
|
||||
{
|
||||
"character": "↑",
|
||||
"name": "upwards_arrow",
|
||||
"codepoint": 8593,
|
||||
"pixels": [
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 1, 1, 1, 0],
|
||||
[1, 0, 1, 0, 1],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0]
|
||||
]
|
||||
},
|
||||
{
|
||||
"character": "→",
|
||||
"name": "rightwards_arrow",
|
||||
"codepoint": 8594,
|
||||
"pixels": [
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 0],
|
||||
[1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 1, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0]
|
||||
]
|
||||
},
|
||||
{
|
||||
"character": "↓",
|
||||
"name": "downwards_arrow",
|
||||
"codepoint": 8595,
|
||||
"pixels": [
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[1, 0, 1, 0, 1],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0]
|
||||
]
|
||||
},
|
||||
{
|
||||
"character": "⇐",
|
||||
"name": "leftwards_double_arrow",
|
||||
"codepoint": 8656,
|
||||
"pixels": [
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 1, 1, 1, 1],
|
||||
[1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0]
|
||||
]
|
||||
},
|
||||
{
|
||||
"character": "⇑",
|
||||
"name": "upwards_double_arrow",
|
||||
"codepoint": 8657,
|
||||
"pixels": [
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 1, 0, 1, 0],
|
||||
[1, 1, 0, 1, 1],
|
||||
[0, 1, 0, 1, 0],
|
||||
[0, 1, 0, 1, 0],
|
||||
[0, 0, 0, 0, 0]
|
||||
]
|
||||
},
|
||||
{
|
||||
"character": "⇒",
|
||||
"name": "rightwards_double_arrow",
|
||||
"codepoint": 8658,
|
||||
"pixels": [
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 1],
|
||||
[1, 1, 1, 1, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0]
|
||||
]
|
||||
},
|
||||
{
|
||||
"character": "⇓",
|
||||
"name": "downwards_double_arrow",
|
||||
"codepoint": 8659,
|
||||
"pixels": [
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 1, 0],
|
||||
[0, 1, 0, 1, 0],
|
||||
[1, 1, 0, 1, 1],
|
||||
[0, 1, 0, 1, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0]
|
||||
]
|
||||
},
|
||||
{
|
||||
"character": "∀",
|
||||
"name": "for_all",
|
||||
"codepoint": 8704,
|
||||
"pixels": [
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 1, 1, 1, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[0, 1, 0, 1, 0],
|
||||
[0, 1, 0, 1, 0],
|
||||
[0, 0, 1, 0, 0]
|
||||
]
|
||||
},
|
||||
{
|
||||
"character": "∷",
|
||||
"name": "proportion",
|
||||
"codepoint": 8759,
|
||||
"pixels": [
|
||||
[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, 0, 0, 0],
|
||||
[1, 0, 0, 0, 1]
|
||||
]
|
||||
},
|
||||
{
|
||||
"character": "☺",
|
||||
"name":"smiling_face",
|
||||
|
||||
+64
-36
@@ -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-2023 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
|
||||
@@ -13,10 +13,12 @@
|
||||
# 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 os
|
||||
import fontforge
|
||||
import json
|
||||
from generate_diacritics import generateDiacritics
|
||||
from generate_examples import generateExamples
|
||||
from polygonizer import PixelImage, generatePolygons
|
||||
|
||||
PIXEL_SIZE = 120
|
||||
|
||||
@@ -34,8 +36,8 @@ def generateFont():
|
||||
monocraft.fullname = "Monocraft"
|
||||
monocraft.copyright = "Idrees Hassan, https://github.com/IdreesInc/Monocraft"
|
||||
monocraft.encoding = "UnicodeFull"
|
||||
monocraft.version = "2.1"
|
||||
monocraft.weight = "Medium"
|
||||
monocraft.version = "2.4"
|
||||
monocraft.weight = "Regular"
|
||||
monocraft.ascent = PIXEL_SIZE * 8
|
||||
monocraft.descent = PIXEL_SIZE
|
||||
monocraft.em = PIXEL_SIZE * 9
|
||||
@@ -48,50 +50,76 @@ def generateFont():
|
||||
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
|
||||
drawn = character
|
||||
|
||||
drawImage(generateImage(character), pen)
|
||||
monocraft[character["name"]].width = PIXEL_SIZE * 6
|
||||
print(f"Generated {len(characters)} characters")
|
||||
|
||||
outputDir = "../dist/"
|
||||
if not os.path.exists(outputDir):
|
||||
os.makedirs(outputDir)
|
||||
|
||||
monocraft.generate(outputDir + "Monocraft-no-ligatures.ttf")
|
||||
|
||||
for ligature in ligatures:
|
||||
lig = monocraft.createChar(-1, ligature["name"])
|
||||
pen = monocraft[ligature["name"]].glyphPen()
|
||||
drawCharacter(ligature, pen)
|
||||
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"])))
|
||||
|
||||
print(f"Generated {len(ligatures)} ligatures")
|
||||
monocraft.generate("../dist/Monocraft.ttf")
|
||||
monocraft.generate("../dist/Monocraft.otf")
|
||||
|
||||
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)
|
||||
monocraft.generate(outputDir + "Monocraft.ttf")
|
||||
monocraft.generate(outputDir + "Monocraft.otf")
|
||||
|
||||
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()
|
||||
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
|
||||
image = image | imageFromArray(arr, x, y)
|
||||
if "reference" in character:
|
||||
image = image | generateImage(charactersByCodepoint[character["reference"]])
|
||||
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
|
||||
|
||||
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(x for a in reversed(arr) for x in a),
|
||||
)
|
||||
|
||||
def drawImage(image, pen):
|
||||
for polygon in generatePolygons(image):
|
||||
start = True
|
||||
for x, y in polygon:
|
||||
x *= PIXEL_SIZE
|
||||
y *= PIXEL_SIZE
|
||||
if start:
|
||||
pen.moveTo(x, y)
|
||||
start = False
|
||||
else:
|
||||
pen.lineTo(x, y)
|
||||
pen.closePath()
|
||||
|
||||
generateFont()
|
||||
generateExamples(characters, ligatures, charactersByCodepoint)
|
||||
@@ -0,0 +1,606 @@
|
||||
# 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 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 generatePolygons(image):
|
||||
for segment, start_pos in segmentize(image):
|
||||
yield from polygonizeSegment(segment, start_pos)
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def polygonizeSegment(image, start_pos):
|
||||
x, y = start_pos
|
||||
|
||||
# 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
|
||||
|
||||
# Set wall
|
||||
d_ccw, d_cw, d_rev = dir.ccw(), dir.cw(), dir.reverse()
|
||||
image[x, y] |= d_ccw
|
||||
|
||||
x_, y_ = d_ccw.move((x, y))
|
||||
assert not image[x_, y_] & CellFlag.ACTIVE
|
||||
|
||||
# Build boundary wall from this point
|
||||
# For all diagrams, it is assumed movement direction is right
|
||||
|
||||
# ? . #
|
||||
# ? X ?
|
||||
# ? ? ?
|
||||
x_, y_ = (dir | d_ccw).move((x, y))
|
||||
if image[x_, y_] & CellFlag.ACTIVE:
|
||||
# Top-right corner
|
||||
ret = [(dir | d_ccw).corner((x, y))]
|
||||
|
||||
# ? . ^
|
||||
# ? X ?
|
||||
# ? ? ?
|
||||
x, y = x_, y_
|
||||
dir = d_ccw
|
||||
return ret
|
||||
|
||||
# ? . .
|
||||
# ? X #
|
||||
# ? ? ?
|
||||
x_, y_ = dir.move((x, y))
|
||||
if image[x_, y_] & CellFlag.ACTIVE:
|
||||
# ? . .
|
||||
# ? X >
|
||||
# ? ? ?
|
||||
x, y = x_, y_
|
||||
return [] # No corner
|
||||
|
||||
# ? . .
|
||||
# ? X .
|
||||
# ? ? #
|
||||
x_, y_ = (dir | d_cw).move((x, y))
|
||||
if image[x_, y_] & CellFlag.ACTIVE:
|
||||
# Two corners: top-right and bottom-right
|
||||
ret = [
|
||||
(dir | d_ccw).corner((x, y)),
|
||||
(dir | d_cw).corner((x, y)),
|
||||
]
|
||||
|
||||
# ? . .
|
||||
# ? X .
|
||||
# ? ? >
|
||||
image[x, y] |= dir
|
||||
x, y = x_, y_
|
||||
return ret
|
||||
|
||||
# ? . .
|
||||
# ? X .
|
||||
# ? # .
|
||||
x_, y_ = d_cw.move((x, y))
|
||||
if image[x_, y_] & CellFlag.ACTIVE:
|
||||
# Top-right corner
|
||||
ret = [(dir | d_ccw).corner((x, y))]
|
||||
|
||||
# ? . .
|
||||
# ? X .
|
||||
# ? v .
|
||||
image[x, y] |= dir
|
||||
x, y = x_, y_
|
||||
dir = d_cw
|
||||
return ret
|
||||
|
||||
# ? . .
|
||||
# ? X .
|
||||
# # . .
|
||||
x_, y_ = (d_rev | d_cw).move((x, y))
|
||||
if image[x_, y_] & 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)),
|
||||
]
|
||||
|
||||
# ? . .
|
||||
# ? X .
|
||||
# v . .
|
||||
image[x, y] |= dir | d_cw
|
||||
x, y = x_, y_
|
||||
dir = d_cw
|
||||
return ret
|
||||
|
||||
# ? . .
|
||||
# # X .
|
||||
# . . .
|
||||
x_, y_ = d_rev.move((x, y))
|
||||
if image[x_, y_] & CellFlag.ACTIVE:
|
||||
# Two corners: top-right and bottom-right
|
||||
ret = [
|
||||
(dir | d_ccw).corner((x, y)),
|
||||
(dir | d_cw).corner((x, y)),
|
||||
]
|
||||
|
||||
# ? . .
|
||||
# < X .
|
||||
# . . .
|
||||
image[x, y] |= dir | d_cw
|
||||
x, y = x_, y_
|
||||
dir = d_rev
|
||||
return ret
|
||||
|
||||
# # . .
|
||||
# . X .
|
||||
# . . .
|
||||
x_, y_ = (d_rev | d_ccw).move((x, y))
|
||||
if image[x_, y_] & 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)),
|
||||
]
|
||||
|
||||
# < . .
|
||||
# . X .
|
||||
# . . .
|
||||
image[x, y] |= dir | d_cw | d_rev
|
||||
x, y = x_, y_
|
||||
dir = d_rev
|
||||
return ret
|
||||
|
||||
# . . .
|
||||
# . X .
|
||||
# . . .
|
||||
image[x, y] |= 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)),
|
||||
]
|
||||
|
||||
def find(l, x):
|
||||
try:
|
||||
return l.index(x)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
# 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]
|
||||
|
||||
assert checkPoly(outer_poly)
|
||||
|
||||
# Emit outer polygon
|
||||
yield outer_poly
|
||||
|
||||
# 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
|
||||
|
||||
# Generate inner polygon
|
||||
for y in range(y_min, y_max):
|
||||
for x in range(x_min, x_max):
|
||||
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
|
||||
else:
|
||||
continue
|
||||
|
||||
# Store iterator value
|
||||
p = x, y
|
||||
|
||||
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]
|
||||
|
||||
assert checkPoly(inner_poly)
|
||||
|
||||
# Emit inner polygon
|
||||
yield inner_poly
|
||||
|
||||
# Restore iterator value
|
||||
x, y = p
|
||||
|
||||
|
||||
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):
|
||||
print('Polygon:\n ' + '\n '.join(f'{x}, {y}'
|
||||
for x, y in poly) + '\n\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runTest()
|
||||
Reference in new issue
Block a user