41 Commits
Author SHA1 Message Date
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
Idrees b8908173df Merge pull request #91 from IdreesInc/90-aphostrope-on-some-letters-are-wrong
Fix ť and ď
2022-12-29 14:02:04 -05:00
Idrees Hassan 0ad78e0a3e Fix ť and ď 2022-12-29 14:01:52 -05:00
Idrees a897bc0083 Merge pull request #87 from IdreesInc/prepare-for-v2.1
Bump version and update glyphs preview
2022-12-28 23:30:06 -05:00
Idrees Hassan f1a9d2fc74 Bump version and update glyphs preview 2022-12-28 23:29:50 -05:00
Idrees 97603fd8ad Merge pull request #86 from IdreesInc/64-support-for-latvian
Add Latvian glyphs
2022-12-28 23:23:11 -05:00
Idrees Hassan 4cca081921 Add Latvian glyphs 2022-12-28 23:22:49 -05:00
Idrees e1ebb386d6 Merge pull request #85 from IdreesInc/49-include-some-more-unicode-symbols-sometimes-used-in-coding
Add some glyphs used in PureScript
2022-12-28 23:06:49 -05:00
Idrees Hassan 0ff70751e7 Add some glyphs used in PureScript 2022-12-28 23:06:31 -05:00
Idrees 0cdc990d4d Merge pull request #84 from IdreesInc/58-missing-romanian-characters
Add Romanian glyphs
2022-12-28 22:40:28 -05:00
Idrees Hassan acc94e9b6a Add Romanian glyphs 2022-12-28 22:40:15 -05:00
Idrees 5dcac8c196 Merge pull request #83 from IdreesInc/29-support-for-polish
Add Polish glyphs
2022-12-28 22:18:59 -05:00
Idrees Hassan 2c5b9e8830 Add Polish glyphs 2022-12-28 22:18:31 -05:00
Idrees ba0a5ebd21 Merge pull request #82 from IdreesInc/13-provide-nerdfont-version
Update .gitignore
2022-12-28 21:50:34 -05:00
Idrees Hassan b4800e2afa Update .gitignore 2022-12-28 21:49:51 -05:00
Idrees 7ae09a1004 Create CONTRIBUTING.md 2022-12-28 20:57:12 -05:00
Idrees 88cf2f83d9 Update issue templates 2022-12-28 20:35:20 -05:00
Idrees 785c13c1a9 Update issue templates 2022-12-28 20:31:39 -05:00
Idrees 55e32cac8a Merge pull request #81 from IdreesInc/10-non-ligature-version
10 Non ligature version
2022-12-28 19:23:02 -05:00
Idrees Hassan e8742d37ad Clean up ligature-free generation 2022-12-28 19:22:25 -05:00
Idrees 2232694429 Merge pull request #78 from Ciubix8513/main
Automated exporting with and without ligatures
2022-12-28 19:11:45 -05:00
Idrees 95762ac3ff Merge pull request #80 from IdreesInc/15-turkish-letters
Add Ş and ş
2022-12-28 19:03:54 -05:00
Idrees Hassan 1a6e6abfca Add Ş and ş 2022-12-28 19:03:41 -05:00
Idrees 1dcffa0b43 Merge pull request #79 from IdreesInc/5-adding-œ-letter
Add Œ and œ
2022-12-28 18:50:22 -05:00
Idrees Hassan 3ffa57f4c8 Add Œ and œ 2022-12-28 18:50:02 -05:00
Luna b6d9da205d Automated exporting with and without ligatures 2022-12-28 12:01:00 +03:00
Idrees 26bfacb78b Merge pull request #76 from Ciubix8513/main
Fixed font not generating if output directory doesn't exist
2022-12-27 20:04:22 -05:00
Luna 37e82e5648 Made output dir local 2022-12-28 04:03:18 +03:00
Luna bfc605cbb9 Fixed font not generating if output directory doesn't exist 2022-12-28 03:54:30 +03:00
11 changed files with 1316 additions and 67 deletions

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.*
+26
View File
@@ -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.
+11
View File
@@ -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
View File
@@ -2,4 +2,4 @@
.DS_Store
src/__pycache__
dist/
src/build-font.sh
build/
+15
View File
@@ -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
View File
@@ -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 ---
 = = -> ==
BIN
View File
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
View File
@@ -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
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-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)
+606
View File
@@ -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()