Repository: qwerasd205/PixelCode Branch: main Commit: f66db587f476 Files: 52 Total size: 67.9 KB Directory structure: gitextract_78x4u5yl/ ├── .gitignore ├── LICENSE ├── README.md ├── activate.sh ├── dist/ │ ├── css/ │ │ ├── PixelCode-Black.css │ │ ├── PixelCode-Bold.css │ │ ├── PixelCode-DemiBold.css │ │ ├── PixelCode-ExtraBlack.css │ │ ├── PixelCode-ExtraBold.css │ │ ├── PixelCode-ExtraLight.css │ │ ├── PixelCode-Light.css │ │ ├── PixelCode-Medium.css │ │ ├── PixelCode-Regular.css │ │ ├── PixelCode-Thin.css │ │ └── all.css │ └── otf/ │ ├── PixelCode-Black-Italic.otf │ ├── PixelCode-Black.otf │ ├── PixelCode-Bold-Italic.otf │ ├── PixelCode-Bold.otf │ ├── PixelCode-DemiBold-Italic.otf │ ├── PixelCode-DemiBold.otf │ ├── PixelCode-ExtraBlack-Italic.otf │ ├── PixelCode-ExtraBlack.otf │ ├── PixelCode-ExtraBold-Italic.otf │ ├── PixelCode-ExtraBold.otf │ ├── PixelCode-ExtraLight-Italic.otf │ ├── PixelCode-ExtraLight.otf │ ├── PixelCode-Italic.otf │ ├── PixelCode-Light-Italic.otf │ ├── PixelCode-Light.otf │ ├── PixelCode-Medium-Italic.otf │ ├── PixelCode-Medium.otf │ ├── PixelCode-Thin-Italic.otf │ ├── PixelCode-Thin.otf │ └── PixelCode.otf ├── examples/ │ ├── oembed.json │ ├── quick_brown_fox.js │ └── specimen.html ├── index.html ├── pyrightconfig.json ├── requirements.txt └── src/ ├── build.sh ├── build_from_images.py ├── gen_braille.py ├── gen_charlist.py ├── gen_template.py ├── glyphs/ │ ├── +calt/ │ │ └── calt.fea │ ├── +onum/ │ │ └── onum.fea │ ├── +shifted/ │ │ └── shifted.fea │ ├── +ss01/ │ │ └── ss01.fea │ └── ligatures/ │ └── ignore.fea └── watch.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .DS_Store build/ archive/ .venv ================================================ FILE: LICENSE ================================================ Copyright (c) 2022, Qwerasd This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: https://scripts.sil.org/OFL ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ----------------------------------------------------------- PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. ================================================ FILE: README.md ================================================ ![Pixel Code](https://qwerasd205.github.io/PixelCode/examples/banner.png) ![Example image](https://qwerasd205.github.io/PixelCode/examples/quick_brown_fox.js.png) Pixel Code is a pixel font designed to actually be good for programming. You can read more about the font [here](https://qwerasd205.github.io/PixelCode). ================================================ FILE: activate.sh ================================================ #!/bin/sh -e cd "$(readlink -f $(dirname "$0"))" echo "Creating virtual environment..." python3 -m venv .venv echo "Activating..." source .venv/bin/activate echo "" echo "================" echo "" echo "Installing requirements..." pip3 install -r ./requirements.txt echo "" echo "================" echo "" echo "Done setting up virtual environment." echo "Use the venv in your shell by calling \`source .venv/bin/activate\`, or \`. .venv/bin/activate.fish\` for fish shell." echo "Deactivate with \`deactivate\`." ================================================ FILE: dist/css/PixelCode-Black.css ================================================ @font-face { font-family: "Pixel Code"; src: url("../woff2/PixelCode-Black.woff2") format("woff2"); font-weight: 900; font-style: normal; } @font-face { font-family: "Pixel Code"; src: url("../woff2/PixelCode-Black-Italic.woff2") format("woff2"); font-weight: 900; font-style: oblique; } ================================================ FILE: dist/css/PixelCode-Bold.css ================================================ @font-face { font-family: "Pixel Code"; src: url("../woff2/PixelCode-Bold.woff2") format("woff2"); font-weight: 700; font-style: normal; } @font-face { font-family: "Pixel Code"; src: url("../woff2/PixelCode-Bold-Italic.woff2") format("woff2"); font-weight: 700; font-style: oblique; } ================================================ FILE: dist/css/PixelCode-DemiBold.css ================================================ @font-face { font-family: "Pixel Code"; src: url("../woff2/PixelCode-DemiBold.woff2") format("woff2"); font-weight: 600; font-style: normal; } @font-face { font-family: "Pixel Code"; src: url("../woff2/PixelCode-DemiBold-Italic.woff2") format("woff2"); font-weight: 600; font-style: oblique; } ================================================ FILE: dist/css/PixelCode-ExtraBlack.css ================================================ @font-face { font-family: "Pixel Code"; src: url("../woff2/PixelCode-ExtraBlack.woff2") format("woff2"); font-weight: 950; font-style: normal; } @font-face { font-family: "Pixel Code"; src: url("../woff2/PixelCode-ExtraBlack-Italic.woff2") format("woff2"); font-weight: 950; font-style: oblique; } ================================================ FILE: dist/css/PixelCode-ExtraBold.css ================================================ @font-face { font-family: "Pixel Code"; src: url("../woff2/PixelCode-ExtraBold.woff2") format("woff2"); font-weight: 800; font-style: normal; } @font-face { font-family: "Pixel Code"; src: url("../woff2/PixelCode-ExtraBold-Italic.woff2") format("woff2"); font-weight: 800; font-style: oblique; } ================================================ FILE: dist/css/PixelCode-ExtraLight.css ================================================ @font-face { font-family: "Pixel Code"; src: url("../woff2/PixelCode-ExtraLight.woff2") format("woff2"); font-weight: 200; font-style: normal; } @font-face { font-family: "Pixel Code"; src: url("../woff2/PixelCode-ExtraLight-Italic.woff2") format("woff2"); font-weight: 200; font-style: oblique; } ================================================ FILE: dist/css/PixelCode-Light.css ================================================ @font-face { font-family: "Pixel Code"; src: url("../woff2/PixelCode-Light.woff2") format("woff2"); font-weight: 300; font-style: normal; } @font-face { font-family: "Pixel Code"; src: url("../woff2/PixelCode-Light-Italic.woff2") format("woff2"); font-weight: 300; font-style: oblique; } ================================================ FILE: dist/css/PixelCode-Medium.css ================================================ @font-face { font-family: "Pixel Code"; src: url("../woff2/PixelCode-Medium.woff2") format("woff2"); font-weight: 500; font-style: normal; } @font-face { font-family: "Pixel Code"; src: url("../woff2/PixelCode-Medium-Italic.woff2") format("woff2"); font-weight: 500; font-style: oblique; } ================================================ FILE: dist/css/PixelCode-Regular.css ================================================ @font-face { font-family: "Pixel Code"; src: url("../woff2/PixelCode.woff2") format("woff2"); font-weight: 400; font-style: normal; } @font-face { font-family: "Pixel Code"; src: url("../woff2/PixelCode-Italic.woff2") format("woff2"); font-weight: 400; font-style: oblique; } ================================================ FILE: dist/css/PixelCode-Thin.css ================================================ @font-face { font-family: "Pixel Code"; src: url("../woff2/PixelCode-Thin.woff2") format("woff2"); font-weight: 100; font-style: normal; } @font-face { font-family: "Pixel Code"; src: url("../woff2/PixelCode-Thin-Italic.woff2") format("woff2"); font-weight: 100; font-style: oblique; } ================================================ FILE: dist/css/all.css ================================================ @import './PixelCode-Thin.css'; @import './PixelCode-ExtraLight.css'; @import './PixelCode-Light.css'; @import './PixelCode-Regular.css'; @import './PixelCode-Medium.css'; @import './PixelCode-DemiBold.css'; @import './PixelCode-Bold.css'; @import './PixelCode-ExtraBold.css'; @import './PixelCode-Black.css'; @import './PixelCode-ExtraBlack.css'; ================================================ FILE: examples/oembed.json ================================================ {"title":"Pixel Code","author_name":"Qwerasd"} ================================================ FILE: examples/quick_brown_fox.js ================================================ class Animal { constructor (options) { this.type = options?.type ?? 'dog'; this.adjectives = options?.adjectives; } get description () { return `${this.adjectives.join(' ')} ${this.type}`; } describe_action (verb, object) { return `${this.description} ${verb} ${object.description}.`; } } const adjective_prepender = adjective => animal => new Animal({ type: animal.type, adjectives: [adjective].concat(animal.adjectives) }); const The = adjective_prepender('The'); const the = adjective_prepender('the'); const quick_brown_fox = new Animal({ type: 'fox', adjectives: ['quick', 'brown'] }); const lazy_dog = new Animal({ adjectives: ['lazy'] }); console.log(The(quick_brown_fox).describe_action('jumps over', the(lazy_dog))); // "The quick brown fox jumps over the lazy dog." ================================================ FILE: examples/specimen.html ================================================ Moved This page has been moved. Click here if you are not redirected automatically. ================================================ FILE: index.html ================================================ Pixel Code

Pixel Code

A pixel font that's actually good for programming.

Pixel Code is a monospace pixel art style programming font which is designed to maximize readability and code-friendliness, while sticking to a pixel grid.

Each glyph was carefully designed to be as legible as possible, with special consideration given to their roles in programming. Furthermore, the OpenType Contextual Alternates feature has been leveraged to provide pseudo-kerning, eliminating uneven gaps caused by thin letters like 'i' and 'l', while avoiding the need to reduce their legibility by making them wider.

Pixel Code also includes a full set of programming ligatures inspired by those in fonts like Fira Code. Ligatures reduce visual noise, consolidating the meanings of multi-glyph structures automatically, so that the programmer's brain can spend more of its resources on actually programming.

I hope you enjoy the font, I put a lot of work in to it!

Download

Quick Start

How do I use this?

Local Installation (for code editors, IDEs, etc.)

Download the latest release from GitHub and install your desired weights and styles on your computer. If you don't know which format to install, I recommend using the TTF versions.

Once the font is installed you should be able to configure your editor to use it using the name "Pixel Code".


Weights and Styles

Comes in a complete range of weights and regular/italic styles.

These alternate weights are automatically generated from the default (400).

100 Thin + Italic | * The Five Boxing Wizards Jump Quickly * 200 ExtraLight + Italic | * The Five Boxing Wizards Jump Quickly * 300 Light + Italic | * The Five Boxing Wizards Jump Quickly * 400 Regular + Italic | * The Five Boxing Wizards Jump Quickly * 500 Medium + Italic | * The Five Boxing Wizards Jump Quickly * 600 DemiBold + Italic | * The Five Boxing Wizards Jump Quickly * 700 Bold + Italic | * The Five Boxing Wizards Jump Quickly * 800 ExtraBold + Italic | * The Five Boxing Wizards Jump Quickly * 900 Black + Italic | * The Five Boxing Wizards Jump Quickly * 950 ExtraBlack + Italic | * The Five Boxing Wizards Jump Quickly *

Unicode Coverage

Language support & various useful glyphs.

Pixel Code supports Latin, Greek, Cyrillic, and Hebrew alphabets.

Í stórum hring vegarins, margir fólk leitar að ljósi sem leiðbeinir þeim. Some find it in the gentle breeze of the Caribbean sea, while others discover it in the vastness of the Siberian tundra. La vie est une aventure, une quête perpétuelle de bonheur et de sens. In dem großen Weg des Lebens suchen viele Menschen nach dem Licht, das sie führen wird. En el corazón de la selva amazónica, hay una calma inquebrantable que te invita a reflexionar. Em coração de Lisboa, há um fado que ecoa pelas vielas estreitas, contando histórias de amor e saudade. La vita è un'avventura, una ricerca perpetua di felicità e significato. A l'ànima del Barri Gòtic de Barcelona, hi ha un silenci serè que t'invita a reflexionar. В большом мире много красоты и удивительных моментов, которые ожидают нас впереди. У дворишту старог дворца, под широким небом, стоји стара храстова капија, која сведочи о прошлим временима. В големия свят има толкова много красота и чудесни моменти, които ни очакват напред. У великому світі багато краси та дивовижних моментів, які чекають нас вперед. Во големиот свет има толку многу убавина и чудесни моменти, кои не чекаат напред. Στον μεγάλο δρόμο της ζωής, πολλοί άνθρωποι ψάχνουν για το φως που θα τους καθοδηγήσει. במסע הגדול של החיים, הרבים מחפשים את האור שינחיל אותם.
Sample text courtesy of ChatGPT

And has box drawing characters, geometric shapes, dingbats, and various technical symbols.

╭─ Box Drawing ──────────╮ │ ┌─┬┐ ╔═╦╗ ╓─╥╖ ╒═╤╕ │ │ │ ││ ║ ║║ ║ ║║ │ ││ │ │ ├─┼┤ ╠═╬╣ ╟─╫╢ ╞═╪╡ │ │ └─┴┘ ╚═╩╝ ╙─╨╜ ╘═╧╛ │ │ ╭───────────────────╮ │ │ │ ╔═══╗ ╭───────╮ │▒ │ │ │ ╚═╦═╝ ╰───────╯ │▒ │ │ ╞═╤══╩══╤═══════════╡▒ │ │ │ ├──┬──┤ │▒ │ │ │ └──┴──┘ │▒ │ │ ╰───────────────────╯▒ │ │ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ │ ╰────────────────────────╯ ╭─ Geometric Shapes ──╮ │ ■ □ ▢ ▣ ▤ ▥ ▦ ▧ ▨ ▩ │ │ ▪ ▫ ▬ ▭ ▮ ▯ ▲ △ │ │ ▴ ▵ ▶ ▷ ▸ ▹ ► ▻ ▼ ▽ │ │ ▾ ▿ ◀ ◁ ◂ ◃ ◄ ◅ ◆ ◇ │ │ ◈ ◉ ◊ ○ ◌ ◍ ◎ ● ◐ ◑ │ │ ◒ ◓ ◔ ◕ ◖ ◗ ◘ ◙ ◚ ◛ │ │ ◜ ◝ ◞ ◟ ◠ ◡ ◢ ◣ ◤ ◥ │ │ ◦ ◧ ◨ ◩ ◪ ◫ ◭ ◮ ◯ │ │ ◰ ◱ ◲ ◳ ◴ ◵ ◶ ◷ ◸ ◹ │ │ ◺ ◻ ◼ ◿ │ ╰─────────────────────╯ ╭─ Dingbats ───────╮ │ ✅ ✉✊✋✌ │ │ ✓✔✕✖✗✘✙✚✛✜ │ │ ✡✢✣✤✥✦✧✨ │ │ ✳✴ │ │ ✿❀ ❇❈ │ │ ❌ ❎❏❐❑❒❓❔❕ │ │ ❗❘❙❚❛❜❝❞ ❡❢❣❤❥ │ │ ❦❧❨❩❪❫❬❭❮❯❰❱❲❳❴❵ │ │ ❶❷❸❹❺❻❼❽❾❿➀➁➂➃➄➅ │ │ ➆➇➈➉➊➋➌➍➎➏➐➑➒➓➔ │ │ ➕➖➗➘➙➚➛➜➝➞➟➠➡ │ │ ➢➣➤➥➦➧➨➩➪➫➬➭➮➯➰ │ │ ➱➲➳➴➵➶➷➸➹➺➻➼➽➾➿ │ ╰──────────────────╯ ──── Misc. ──── # Powerline                    # Fira Progress Bar              ✓

The full set of glyphs in Pixel Code can be viewed below.

  ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ 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 [ \ ] ^ _ ` 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 { | } ~ ¡ ¢ £ ¤ ¥ ¦ § ¨ ª « ¬ ¯ ° ± ² ³ ´ µ ¶ · ¸ ¹ º » ¼ ½ ¾ ¿ À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï Ð Ñ Ò Ó Ô Õ Ö × Ø Ù Ú Û Ü Ý Þ ß à á â ã ä å æ ç è é ê ë ì í î ï ð ñ ò ó ô õ ö ÷ ø ù ú û ü ý þ ÿ Ā ā Ă ă Ą ą Ć ć Ĉ ĉ Ċ ċ Č č Ď ď Đ đ Ē ē Ĕ ĕ Ė ė Ę ę Ě ě Ĝ ĝ Ğ ğ Ġ ġ Ģ ģ Ĥ ĥ Ħ ħ Ĩ ĩ Ī ī Ĭ ĭ Į į İ ı IJ ij Ĵ ĵ Ķ ķ ĸ Ĺ ĺ Ļ ļ Ľ ľ Ŀ ŀ Ł ł Ń ń Ņ ņ Ň ň ʼn Ŋ ŋ Ō ō Ŏ ŏ Ő ő Œ œ Ŕ ŕ Ŗ ŗ Ř ř Ś ś Ŝ ŝ Ş ş Š š Ţ ţ Ť ť Ŧ ŧ Ũ ũ Ū ū Ŭ ŭ Ů ů Ű ű Ų ų Ŵ ŵ Ŷ ŷ Ÿ Ź ź Ż ż Ž ž ʰ ʱ ʲ ʳ ʴ ʶ ʷ ʸ ˀ ˁ ˂ ˃ ˄ ˅ ˆ ˇ ˈ ˉ ˊ ˋ ˌ ˍ ˎ ˏ ː ˑ ˔ ˕ ˖ ˗ ˘ ˙ ˚ ˜ ˝ ˟ ˡ ˢ ˣ ˤ ˥ ˦ ˧ ˨ ˩ ˪ ˫ ˬ ˭ ˯ ˰ ˱ ˲ ˳ ˴ ˵ ˶ ˷ ˸ ˹ ˺ ˻ ˼ ˽ ˾ Ͱ ͱ Ͳ ͳ ʹ ͵ Ͷ ͷ ͺ ͻ ͼ ͽ ; Ϳ ΄ ΅ Ά · Έ Ή Ί Ό Ύ Ώ ΐ Α Β Γ Δ Ε Ζ Η Θ Ι Κ Λ Μ Ν Ξ Ο Π Ρ Σ Τ Υ Φ Χ Ψ Ω Ϊ Ϋ ά έ ή ί ΰ α β γ δ ε ζ η θ ι κ λ μ ν ξ ο π ρ ς σ τ υ φ χ ψ ω ϊ ϋ ό ύ ώ Ϗ ϐ ϑ ϒ ϓ ϔ ϕ ϖ ϗ Ϙ ϙ Ϛ ϛ Ϝ ϝ Ϟ ϟ Ϡ ϡ ϱ ϲ ϳ ϴ ϵ ϶ Ϸ ϸ Ϲ Ϻ ϻ ϼ Ͻ Ͼ Ͽ Ѐ Ё Ђ Ѓ Є Ѕ І Ї Ј Љ Њ Ћ Ќ Ѝ Ў Џ А Б В Г Д Е Ж З И Й К Л М Н О П Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь Э Ю Я а б в г д е ж з и й к л м н о п р с т у ф х ц ч ш щ ъ ы ь э ю я ѐ ё ђ ѓ є ѕ і ї ј љ њ ћ ќ ѝ ў џ א ב ג ד ה ו ז ח ט י ך כ ל ם מ ן נ ס ע ף פ ץ צ ק ר ש ת ׳ ״ ᴬ ᴭ ᴮ ᴰ ᴱ ᴲ ᴳ ᴴ ᴵ ᴶ ᴷ ᴸ ᴹ ᴺ ᴻ ᴼ ᴽ ᴾ ᴿ ᵀ ᵁ ᵂ ᵃ ᵅ ᵇ ᵈ ᵉ ᵍ ᵎ ᵏ ᵐ ᵑ ᵒ ᵖ ᵗ ᵘ ᵛ ᵝ ᵢ ᵣ ᵤ ᵥ ᵦ ‐ ‑ ‒ – — ― ‖ ‘ ’ ‚ ‛ “ ” „ ‟ † ‡ • ‣ ․ ‥ … ‧ ′ ″ ‴ ‵ ‶ ‷ ‸ ‹ › ‼ ‽ ‾ ‿ ⁀ ⁁ ⁂ ⁃ ⁅ ⁆ ⁇ ⁈ ⁉ ⁋ ⁌ ⁍ ⁎ ⁐ ⁑ ⁒ ⁓ ⁔ ⁕ ⁖ ⁘ ⁙ ⁚ ⁛ ⁜ ⁝ ⁞ ⁰ ⁱ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ⁺ ⁻ ⁼ ⁽ ⁾ ⁿ ₀ ₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉ ₊ ₋ ₌ ₍ ₎ ₐ ₑ ₒ ₓ ₕ ₖ ₗ ₘ ₙ ₚ ₛ ₜ ← ↑ → ↓ ↕ ↖ ↗ ↘ ↙ ↩ ↪ ↰ ↱ ↲ ↳ ↴ ↵ ↶ ↷ ↸ ↺ ↻ ↼ ↽ ↾ ↿ ⇀ ⇁ ⇂ ⇃ ⇠ ⇡ ⇢ ⇣ ⇦ ⇧ ⇨ ⇩ ⇪ ─ ━ │ ┃ ┄ ┅ ┆ ┇ ┊ ┋ ┌ ┍ ┎ ┏ ┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟ ┠ ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯ ┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿ ╀ ╁ ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏ ═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟ ╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯ ╰ ╱ ╲ ╳ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿ ▀ ▁ ▂ ▃ ▄ ▅ ▆ ▇ ▉ ▊ ▋ ▌ ▍ ▎ ▏ ▐ ░ ▒ ▓ ▔ ▕ ▖ ▗ ▘ ▙ ▚ ▛ ▜ ▝ ▞ ▟ ■ □ ▢ ▣ ▤ ▥ ▦ ▧ ▨ ▩ ▪ ▫ ▬ ▭ ▮ ▯ ▲ △ ▴ ▵ ▶ ▷ ▸ ▹ ► ▻ ▼ ▽ ▾ ▿ ◀ ◁ ◂ ◃ ◄ ◅ ◆ ◇ ◈ ◉ ◊ ○ ◌ ◍ ◎ ● ◐ ◑ ◒ ◓ ◔ ◕ ◖ ◗ ◘ ◙ ◚ ◛ ◜ ◝ ◞ ◟ ◠ ◡ ◢ ◣ ◤ ◥ ◦ ◧ ◨ ◩ ◪ ◫ ◭ ◮ ◯ ◰ ◱ ◲ ◳ ◴ ◵ ◶ ◷ ◸ ◹ ◺ ◻ ◼ ◿ ✅✉ ✊✋✌ ✓ ✔ ✕ ✖ ✗ ✘ ✙ ✚ ✛ ✜ ✡ ✢ ✣ ✤ ✥ ✦ ✧ ✨✳ ✴ ✿ ❀ ❇ ❈ ❌❎❏ ❐ ❑ ❒ ❓❔❕❗❘ ❙ ❚ ❛ ❜ ❝ ❞ ❡ ❢ ❣ ❤ ❥ ❦ ❧ ❨ ❩ ❪ ❫ ❬ ❭ ❮ ❯ ❰ ❱ ❲ ❳ ❴ ❵ ❶ ❷ ❸ ❹ ❺ ❻ ❼ ❽ ❾ ❿ ➀ ➁ ➂ ➃ ➄ ➅ ➆ ➇ ➈ ➉ ➊ ➋ ➌ ➍ ➎ ➏ ➐ ➑ ➒ ➓ ➔ ➕➖➗➘ ➙ ➚ ➛ ➜ ➝ ➞ ➟ ➠ ➡ ➢ ➣ ➤ ➥ ➦ ➧ ➨ ➩ ➪ ➫ ➬ ➭ ➮ ➯ ➰➱ ➲ ➳ ➴ ➵ ➶ ➷ ➸ ➹ ➺ ➻ ➼ ➽ ➾ ➿⟲ ⟳ ⟵ ⟶ ⠁ ⠂ ⠃ ⠄ ⠅ ⠆ ⠇ ⠈ ⠉ ⠊ ⠋ ⠌ ⠍ ⠎ ⠏ ⠐ ⠑ ⠒ ⠓ ⠔ ⠕ ⠖ ⠗ ⠘ ⠙ ⠚ ⠛ ⠜ ⠝ ⠞ ⠟ ⠠ ⠡ ⠢ ⠣ ⠤ ⠥ ⠦ ⠧ ⠨ ⠩ ⠪ ⠫ ⠬ ⠭ ⠮ ⠯ ⠰ ⠱ ⠲ ⠳ ⠴ ⠵ ⠶ ⠷ ⠸ ⠹ ⠺ ⠻ ⠼ ⠽ ⠾ ⠿ ⡀ ⡁ ⡂ ⡃ ⡄ ⡅ ⡆ ⡇ ⡈ ⡉ ⡊ ⡋ ⡌ ⡍ ⡎ ⡏ ⡐ ⡑ ⡒ ⡓ ⡔ ⡕ ⡖ ⡗ ⡘ ⡙ ⡚ ⡛ ⡜ ⡝ ⡞ ⡟ ⡠ ⡡ ⡢ ⡣ ⡤ ⡥ ⡦ ⡧ ⡨ ⡩ ⡪ ⡫ ⡬ ⡭ ⡮ ⡯ ⡰ ⡱ ⡲ ⡳ ⡴ ⡵ ⡶ ⡷ ⡸ ⡹ ⡺ ⡻ ⡼ ⡽ ⡾ ⡿ ⢀ ⢁ ⢂ ⢃ ⢄ ⢅ ⢆ ⢇ ⢈ ⢉ ⢊ ⢋ ⢌ ⢍ ⢎ ⢏ ⢐ ⢑ ⢒ ⢓ ⢔ ⢕ ⢖ ⢗ ⢘ ⢙ ⢚ ⢛ ⢜ ⢝ ⢞ ⢟ ⢠ ⢡ ⢢ ⢣ ⢤ ⢥ ⢦ ⢧ ⢨ ⢩ ⢪ ⢫ ⢬ ⢭ ⢮ ⢯ ⢰ ⢱ ⢲ ⢳ ⢴ ⢵ ⢶ ⢷ ⢸ ⢹ ⢺ ⢻ ⢼ ⢽ ⢾ ⢿ ⣀ ⣁ ⣂ ⣃ ⣄ ⣅ ⣆ ⣇ ⣈ ⣉ ⣊ ⣋ ⣌ ⣍ ⣎ ⣏ ⣐ ⣑ ⣒ ⣓ ⣔ ⣕ ⣖ ⣗ ⣘ ⣙ ⣚ ⣛ ⣜ ⣝ ⣞ ⣟ ⣠ ⣡ ⣢ ⣣ ⣤ ⣥ ⣦ ⣧ ⣨ ⣩ ⣪ ⣫ ⣬ ⣭ ⣮ ⣯ ⣰ ⣱ ⣲ ⣳ ⣴ ⣵ ⣶ ⣷ ⣸ ⣹ ⣺ ⣻ ⣼ ⣽ ⣾ ⣿ ⤌ ⤍ ⤎ ⤏ ⤙ ⤚ ⤛ ⤜ ⤡ ⤢ ⤣ ⤤ ⤥ ⤦ ⤴ ⤵ ⤶ ⤷ ⤸ ⤹ ⤺ ⤻ ⤾ ⤿ ⥢ ⥣ ⥤ ⥥ ⥦ ⥧ ⥨ ⥩ ⥪ ⥫ ⥬ ⥭ ⥮ ⥯ ⥰ ⬀ ⬁ ⬂ ⬃ ⬅ ⬆ ⬇ ⬈ ⬉ ⬊ ⬋ ⬎ ⬏ ⬐ ⬑ ⬒ ⬓ ⬔ ⬕ ⬖ ⬗ ⬘ ⬙ ⬚ ⬛⬜⬝ ⬞ ⬥ ⬦ ⬧ ⬨ ⭕⭠ ⭡ ⭢ ⭣ ⭥ ⭦ ⭧ ⭨ ⭩ ⭮ ⭯ ⮌ ⮍ ⮎ ⮏ ⮐ ⮑ ⮒ ⮓ ⮕ ⮬ ⮭ ⮮ ⮯ ⮺ ⮻ ⮼ ⯀ ⯁ ⯅ ⯆ ⯇ ⯈ ⯊ ⯋ ⯑ ⯒ ⯾ ⯿ ⱼ ⱽ ꟲ ꟳ ꟴ ꟹ                                � 🬀 🬁 🬂 🬃 🬄 🬅 🬆 🬇 🬈 🬉 🬊 🬋 🬌 🬍 🬎 🬏 🬐 🬑 🬒 🬓 🬔 🬕 🬖 🬗 🬘 🬙 🬚 🬛 🬜 🬝 🬞 🬟 🬠 🬡 🬢 🬣 🬤 🬥 🬦 🬧 🬨 🬩 🬪 🬫 🬬 🬭 🬮 🬯 🬰 🬱 🬲 🬳 🬴 🬵 🬶 🬷 🬸 🬹 🬺 🬻 🬼 🬽 🬾 🬿 🭀 🭁 🭂 🭃 🭄 🭅 🭆 🭇 🭈 🭉 🭊 🭋 🭌 🭍 🭎 🭏 🭐 🭑 🭒 🭓 🭔 🭕 🭖 🭗 🭘 🭙 🭚 🭛 🭜 🭝 🭞 🭟 🭠 🭡 🭢 🭣 🭤 🭥 🭦 🭧 🭨 🭩 🭪 🭫 🭬 🭭 🭮 🭯 🮌 🮍 🮎 🮏 🮐 🮑 🮒 🮔 🮕 🮖 🮗 🮘 🮙 🮚 🮛 🮜 🮝 🮞 🮟 🮠 🮡 🮢 🮣 🮤 🮥 🮦 🮧 🮨 🮩 🮪 🮫 🮬 🮭 🮮 🮯 🮰 🮱 🮲 🮳 🮴 🮵 🮶 🮷 🮸 🮹 🮺 🮻 🮼 🮽 🮾 🮿 🯀 🯁 🯂 🯃 🯄 🯅 🯆 🯇 🯈 🯉 🯊 🯰 🯱 🯲 🯳 🯴 🯵 🯶 🯷 🯸 🯹

You can explore the togglable OpenType features of this font below.


Programming Ligatures

(to reduce visual noise)

Can be toggled with the "Ligatures" OpenType feature.

(liga)

Ligatures

Comments: // Single-line /* Multi-line */ /// Triple slash <!-- HTML-style --> Comparisons: x >= y x <= y x != y x == y x !== y x === y Arrows: => -> <- Assignments: a := b a *= b a /= b a |= b a >>= b a <<= b a +|= b a -|= b a *|= b a <<|= b Bitwise: p >> q p << q p >>> q p <<< q Miscellaneous: j || k j ** k std::namespace optional?.chaining https://example.com


Context Aware Punctuation

for better visual flow.

Can be toggled with the "Contextual Alternates" OpenType feature.

(calt)

0xFF 1920x1080 | Simplified x for hexadecimal and dimensions. *EQ *eq | Asterisks, colons, hyphens and plus signs A:E a:e | will move up or down depending on if they C-O c-o | follow (or precede) an uppercase or lower X+Z x+z | case letter.


Pseudo-Kerning

to avoid unseemly gaps.

Most monospace fonts simply use wide serifs on the i and l characters in order to make them fit nicely in with other glyphs without creating unnatural gaps on either side. I was not satisfied with this, as doing so reduces their legibility by making the letterforms less identifiable. So instead, I implemented "pseudo-kerning" that uses contextual subsitution of the l character to push it to the left by a pixel when it follows a letter with a wide right bearing like i and u.

I recommend enabling this feature for static text, but not for editable text, where it can be jarring to have the characters jump around as you type.

Can be enabled with the "Stylistic Set 20" OpenType feature.

(ss20)

will ......|. Notice, that even though hull ......| toggling the calt feature frill .....|.. moves these 'l's to the tailing ...|.. left, the characters to module ....|. the right remain unmoved. castle ....|.......................... soliloquy .|... Monospace is preserved.


Stylistic Alternates

(for those who prefer)

There are some alternate styles available for certain sets of glyphs.

(onum)

Oldstyle Figures (onum)

10, 9, 8, 7, 6, 5, 4, 3, 2, 1 -- Lift-off! What was it? 525_600 minutes in a year? const PI = 3.14159265358979323846264338; // TODO: Increase precision. let [x1, y1] = [0.12569, 0.45780];

(ss01)

Straight Comma (ss01)

{a:b.c,d:e}; | Originally, when Pixel Code was first released, it had a :;:;:;:;:;:; | less distinct style for the comma and semicolon; the old ,.,.,.,.,.,. | style has been made available as a togglable feature, so .:;,.:;,.:;, | that it can still be used if you preferred it.


================================================ FILE: pyrightconfig.json ================================================ { "venv": ".venv", "venvPath": "." } ================================================ FILE: requirements.txt ================================================ fontmake fonttools ufolib2 pillow watchdog ================================================ FILE: src/build.sh ================================================ #!/bin/sh -e cd "$(readlink -f $(dirname "$0"))" # Pixel Code build script # Activate python virtual environment. ../activate.sh source ../.venv/bin/activate # Build UFOs from images and fea files. python3 build_from_images.py # Variable font (experimental) # fontmake ../build/PixelCode.designspace -o variable --output-dir ../dist/variable # woff2_compress ../dist/variable/*.ttf # Call fontmake to build the UFOs in to OTF and TTF fonts. fontmake -u ../build/*.ufo -o otf --output-dir ../dist/otf fontmake -u ../build/*.ufo -o ttf --output-dir ../dist/ttf # Create woff2 fonts from the TTFs. ls ../dist/ttf/*.ttf | xargs -I {} woff2_compress {} mv ../dist/ttf/*.woff2 ../dist/woff2 ================================================ FILE: src/build_from_images.py ================================================ import os import sys import math from math import sqrt # from fontTools.designspaceLib import DesignSpaceDocument from fontTools.pens.areaPen import AreaPen from fontTools.agl import UV2AGL, AGL2UV from fontTools.unicodedata import east_asian_width from ufoLib2.objects import Contour, Glyph, Info, Features, Point from ufoLib2 import Font from PIL import Image from typing import Dict, List, Any from ufoLib2.objects.info import GaspBehavior, GaspRangeRecord WEIGHT_NAMES = { 100: 'Thin', 200: 'ExtraLight', 300: 'Light', 400: 'Regular', 500: 'Medium', 600: 'DemiBold', 700: 'Bold', 800: 'ExtraBold', 900: 'Black', 950: 'ExtraBlack', } TOTAL_HEIGHT = 12 ADVANCE = 6 BASELINE = 10 ASCENT = BASELINE DESCENT = 2 X_HEIGHT = 5 CAP_HEIGHT = 7 LINE_GAP = (TOTAL_HEIGHT - ASCENT - DESCENT) SCALE = 112 scaleFactor = 1 def adjustCoords(x, y): x *= scaleFactor y *= scaleFactor return (x * SCALE, (BASELINE - y) * SCALE) def makeGlyph(name: str, img: Image.Image): # glyphPen = TTGlyphPen(None) glyph = Glyph(name) pen = glyph.getPen() #TransformPen(glyphPen, (1.1, 0, 0, 1.0, 0, 0)) vectors: List[List[Any]] = [[list([0, 0]) for _ in range(img.height + 1)] for _ in range(img.width + 1)] for y in range(0, img.height): for x in range(0, img.width): if not img.getpixel((x, y)) == 0: vectors[x][y][0] -= 1 vectors[x][y][1] += 1 vectors[x][y+1][0] += 1 vectors[x+1][y][1] -= 1 def getMag(x, y, vert): if x >= len(vectors): return None if y >= len(vectors[x]): return None return vectors[x][y][vert] def next(x, y, vert): if vert: offset = vectors[x][y][1] n = 0 if offset == -1 else 1 s = 0 if offset == 1 else 1 if getMag(x, y+offset, 1) == offset: return (x, y+offset, True) if getMag(x-n, y+n, 0) == -offset: return (x-n, y+n, False) if getMag(x-s, y+n, 0) == offset: return (x-s, y+n, False) else: offset = vectors[x][y][0] n = 0 if offset == -1 else 1 s = 0 if offset == 1 else 1 if getMag(x+offset, y, 0) == offset: return (x+offset, y, False) if getMag(x+n, y-s, 1) == offset: return (x+n, y-s, True) if getMag(x+n, y-n, 1) == -offset: return (x+n, y-n, True) return None def follow(x, y, vert): nextPosition = next(x, y, vert) if vert: vectors[x][y][1] = 0 else: vectors[x][y][0] = 0 if nextPosition is not None: (nx, ny, nv) = nextPosition px = x py = y if nv: if (px != nx): px = nx pen.lineTo(adjustCoords(px, py)) if (py != ny): py = ny pen.lineTo(adjustCoords(px, py)) else: if (py != ny): py = ny pen.lineTo(adjustCoords(px, py)) if (px != nx): px = nx pen.lineTo(adjustCoords(px, py)) follow(nx, ny, nv) for y in range(0, img.height): for x in range(0, img.width): if vectors[x][y][0] != 0: pen.moveTo(adjustCoords(x, y)) follow(x, y, False) pen.closePath() continue if vectors[x][y][1] != 0: pen.moveTo(adjustCoords(x, y)) follow(x, y, True) pen.closePath() continue return glyph # return glyphPen.glyph( # dropImpliedOnCurves = True, # ) def isClockwise(g: Contour): area = AreaPen() g.draw(area) return area.value < 0 def dot(ax, ay, bx, by): return ax*bx + ay*by def sign(n): return n / abs(n) def thicken(g: Glyph, amount: int): factor = amount / 2500 xfactor = 1 yfactor = 0.5 if amount > 0 else 1 for contour in g.contours: clockwise = isClockwise(contour) s = factor * (0.5 if amount > 0 and clockwise else 1) ln = len(contour.points) px = contour.points[ln - 1].x py = contour.points[ln - 1].y sx = contour.points[0].x sy = contour.points[0].y for i in range(ln): point = contour.points[i] next = contour.points[i + 1] if i < ln - 1 else Point(sx, sy) pnx = next.x - point.x pny = next.y - point.y ppx = px - point.x ppy = py - point.y lnp = sqrt(ppx*ppx + ppy*ppy) lnn = sqrt(pnx*pnx + pny*pny) d = dot(ppx/lnp, ppy/lnp, pnx/lnn, pny/lnn) xo = s * (next.y - py) * (2 if d == 0 else 1) * xfactor yo = s * (px - next.x) * (2 if d == 0 else 1) * yfactor px = point.x py = point.y # Keep ligature parts touching the edge correctly: if amount > 0 or (g.name is not None and "_" not in g.name) or point.x != 0 and point.x != ADVANCE * SCALE: point.x += xo point.y += yo # Re-align the so the glyph to have a pixel-perfect # left side bearing and baseline placement. g.move((2 * SCALE * factor * xfactor, 2 * SCALE * factor * yfactor)) return g def thin(g: Glyph, amount: int): thicken(g, -amount) return g def italicize(g: Glyph, slope: float): for contour in g.contours: # We skew the x coordinate of points # from the baseline to italicize. for point in contour.points: point.x += point.y * slope return g def glyphName(c): return UV2AGL[c] if c in UV2AGL else f"uni{c:02X}" def charWidth(c): ch = chr(c) east_asian_class = east_asian_width(ch) # For Full Width and Wide characters, return 2, otherwise return 1. if east_asian_class == "F" or east_asian_class == "W": return 2 return 1 STRIDE_X = 6 STRIDE_Y = 12 WHITESPACE_GLYPHS = [ord(' ')] glyphs: Dict[str, Glyph] = {} ligaGlyphOrder = [] characterMap = {} altCharacterMap = {} advanceWidths = {} features = {} ligatures = [] def getOrd(c): if len(c) == 1: return ord(c) return AGL2UV[c] def addLigature(parts, path, suffix, lazy = False): names = [glyphName(getOrd(c)) for c in parts] ligaName = "_".join(names) if suffix: ligaName = f"{ligaName}{suffix}" partNames = [f"{ligaName}.{i}" for i in range(len(parts))] with Image.open(path) as atlas: print(f"\"{"".join([chr(getOrd(c)) for c in parts])}\": {atlas.width}x{atlas.height}") atlas = atlas.convert("1") x_stride = int(STRIDE_X / scaleFactor) y_stride = int(STRIDE_Y / scaleFactor) i = 0 for x in range(0, atlas.width, x_stride): glyph = atlas.crop((x, 0, x + x_stride, y_stride)) name = partNames[i] glyphs[name] = makeGlyph(name, glyph) ligaGlyphOrder.append(name) advanceWidths[name] = ADVANCE * SCALE i += 1 glyphs[ligaName] = makeGlyph(ligaName, atlas.crop((0, 0, 0, 0))) ligaGlyphOrder.append(ligaName) advanceWidths[ligaName] = ADVANCE * SCALE ligatures.append({ "name": ligaName, "from": names, "to": partNames, "lazy": lazy, }) def addGlyphsFromDir(dir, suffix = ""): global scaleFactor for file in os.listdir(dir): fullPath = f"{dir}/{file}" if os.path.isdir(fullPath): if file.startswith("+"): addGlyphsFromDir(fullPath, suffix = f"{suffix}.{file[1:]}") else: addGlyphsFromDir(fullPath, suffix = suffix) continue if file.endswith(".fea"): with open(fullPath) as file: features[fullPath] = "\n".join(file.readlines()) continue if file.endswith(".png"): name: str = file.rsplit(".", 1)[0] if "@" in name: # e.g. "name@2X.png" name, scale = name.split("@") scaleFactor = 1 / int(scale[:-1]) else: scaleFactor = 1 if name.startswith('"'): if name.endswith("?"): addLigature(name[1:-2], fullPath, suffix, True) else: addLigature(name[1:-1], fullPath, suffix) continue if "_" in name and not name.startswith("_"): if name.endswith("?"): addLigature(name[:-1].split("_"), fullPath, suffix, True) else: addLigature(name.split("_"), fullPath, suffix) continue if "-" in name: (start, end) = name.split("-") start = ord(start[1]) if start.startswith("'") else int(start, 16) end = ord(end[1]) if end.startswith("'") else int(end, 16) with Image.open(fullPath) as atlas: print(f"{file}: {atlas.width}x{atlas.height}, {start}-{end}") atlas = atlas.convert("1") x_stride = int(STRIDE_X / scaleFactor) y_stride = int(STRIDE_Y / scaleFactor) c = start for y in range(0, atlas.height, y_stride): x = 0 while x < atlas.width: width = charWidth(c) # Safety checks for wide characters if width > 1: # Wrap to next row early if wide char would be split by edge of atlas. if x + x_stride * width > atlas.width: break # If the left half is completely empty, but the right half isn't, the # author probably didn't realize there was a wide glyph. Warn them. left_half = atlas.crop((x, y, x + x_stride, y + y_stride)) right_half = atlas.crop((x + x_stride, y, x + x_stride * 2, y + y_stride)) if len(left_half.getcolors() or ()) == 1 and len(right_half.getcolors() or ()) > 1: print(f"WARNING! Potential misalignment in atlas at codepoint {c:02X} due to wide character!") print("WARNING! To avoid such misalignments, use gen_template.py for a reference template!") glyph = atlas.crop((x, y, x + x_stride * width, y + y_stride)) if c in WHITESPACE_GLYPHS or len(glyph.getcolors() or ()) > 1: name = glyphName(c) if suffix: name = f"{name}{suffix}" glyphs[name] = makeGlyph(name, glyph) advanceWidths[name] = ADVANCE * SCALE * width if not suffix: characterMap[c] = name else: altCharacterMap[c] = name c += 1 x += x_stride * width if c > end: break if c > end: break continue continue with Image.open("./glyphs/_.notdef.png") as notdef: print(f"_.notdef.png: {notdef.width}x{notdef.height}") glyphs[".notdef"] = makeGlyph(".notdef", notdef) advanceWidths[".notdef"] = ADVANCE * SCALE characterMap[0] = ".notdef" addGlyphsFromDir("./glyphs") # Duplicate .notdef for U+FFFD (Replacement Character) glyphs["uniFFFD"] = glyphs[".notdef"] advanceWidths["uniFFFD"] = ADVANCE * SCALE characterMap[0xFFFD] = "uniFFFD" def getFamilyName(): return "Pixel Code" def getStyleName(weight: int = 400, italicAngle: float = 0.0): return f"{WEIGHT_NAMES[weight]}{" Italic" if italicAngle > 0.0 else ""}" def getStyleNameShort(weight: int = 400, italicAngle: float = 0.0): return f"{"Italic" if italicAngle > 0.0 else ""}" if weight == 400 else getStyleName(weight, italicAngle) def getFullNameShort(weight: int = 400, italicAngle: float = 0.0): return f"{getFamilyName().replace(" ", "")} {getStyleNameShort(weight, italicAngle)}".rstrip().replace(" ", "-") def writeUFO(weight: int = 400, italicAngle: float = 0.0): ufo = Font() ufo.glyphOrder = ( [name for _, name in characterMap.items()] + [name for _, name in altCharacterMap.items()] + ligaGlyphOrder ) for name, glyph in glyphs.items(): g = Glyph(name) g.copyDataFromGlyph(glyph) if weight > 400: thicken(g, weight - 400) elif weight < 400: thin(g, 400 - weight) if italicAngle > 0: italicize(g, math.tan((math.pi / 180) * italicAngle)) g.width = advanceWidths[name] ufo.addGlyph(g) for c, name in characterMap.items(): ufo[name].unicodes = [c] isItalic = italicAngle > 0 familyName = getFamilyName() styleName = getStyleName(weight, italicAngle) styleNameShort = getStyleNameShort(weight, italicAngle) fullName = f"{familyName} {styleNameShort}".strip() majorVersion = 2 minorVersion = 2 styleMapFamilyName = f"{familyName} {WEIGHT_NAMES[weight]}" styleMapStyleName = "regular" if weight > 400: styleMapStyleName = "bold" if italicAngle > 0: styleMapStyleName += " italic" elif italicAngle > 0: styleMapStyleName = "italic" ufo.info = Info( versionMajor = majorVersion, versionMinor = minorVersion, # Names familyName = familyName, styleName = styleName, postscriptFullName = fullName, # Stylemap styleMapFamilyName = styleMapFamilyName, styleMapStyleName = styleMapStyleName, # Copyright copyright = "Copyright (c) Qwerasd 2024", # Name Table (name) openTypeNamePreferredFamilyName = familyName, openTypeNamePreferredSubfamilyName = styleNameShort, openTypeNameCompatibleFullName = fullName, openTypeNameDescription = "A pixel font that's actually good for programming.", openTypeNameVersion = f"Version {majorVersion}.{minorVersion}", openTypeNameDesigner = "Qwerasd", # Metrics unitsPerEm = 1008, descender = DESCENT * SCALE, ascender = ASCENT * SCALE, xHeight = X_HEIGHT * SCALE, capHeight = CAP_HEIGHT * SCALE, # Italic angle in counter-clockwise degrees from the vertical. # Zero for upright text, negative for text that leans to the right (forward). italicAngle = -italicAngle, # Horizontal Header (hhea) openTypeHheaAscender = BASELINE * SCALE - 1, openTypeHheaDescender = -DESCENT * SCALE + 1, openTypeHheaLineGap = LINE_GAP * SCALE, # OS/2 openTypeOS2Selection = [7], # bit 7: USE_TYPO_METRICS openTypeOS2TypoAscender = ASCENT * SCALE - 1, openTypeOS2TypoDescender = -DESCENT * SCALE + 1, openTypeOS2TypoLineGap = LINE_GAP * SCALE, openTypeOS2WinAscent = BASELINE * SCALE, openTypeOS2WinDescent = (DESCENT + LINE_GAP) * SCALE, openTypeOS2Panose = [ # https://monotype.github.io/panose/pan2.htm # Family Kind # (2 = Latin Text) 2, # Serif Style Classification # (0 = Any) 0, # Weight # (0 = Any) 0, # Proportion # (9 = Monospaced) 9, # Contrast # (0 = Any) 0, # Stroke Variation # (0 = Any) 0, # Arm Style # (0 = Any) 0, # Letterform # (5 = Oblique/Square, 8 = Normal/Square) 5 if isItalic else 8, # Midline # (0 = Any) 0, # X-height # (3 = Constant/Standard) 3 ], openTypeOS2WeightClass = weight, openTypeOS2VendorID = "qwer", # Postscript (post) postscriptUnderlinePosition = int(-2 * SCALE), postscriptUnderlineThickness = int(SCALE), postscriptIsFixedPitch = True, ) # Grid-fitting and Scan-conversion Procedure (gasp) ufo.info.openTypeGaspRangeRecords = [ GaspRangeRecord( 0xFFFF, [GaspBehavior.GRIDFIT] ) ] combinedFea = "" afterLiga = "" for file, fea in features.items(): if fea.startswith("# AFTER_LIGATURES"): afterLiga += f"# {file}\n" afterLiga += f"{fea}\n" continue combinedFea += f"# {file}\n" combinedFea += f"{fea}\n" ligaFea = "# GENERATED/LIGATURES\n" ligaFea += "feature liga {\n" ligatures.sort(key = lambda liga: len(liga["from"]), reverse = True) for ligature in ligatures: if ligature["lazy"]: ligaFea += f"sub space {" ".join([f"{c}'" for c in ligature["from"]])} space by {ligature["name"]};\n" else: ligaFea += f"sub {" ".join(ligature["from"])} by {ligature["name"]};\n" for ligature in ligatures: ligaFea += f"sub {ligature["name"]} by {" ".join(ligature["to"])};\n" ligaFea += "} liga;\n" combinedFea += ligaFea combinedFea += afterLiga ufo.features = Features(combinedFea) ufoPath = os.path.abspath(f"../build/{getFullNameShort(weight, italicAngle)}.ufo") ufo.save(ufoPath, overwrite = True) return ufoPath # Ensure the build directory exists. if not os.path.exists("../build"): os.mkdir("../build") if not os.path.exists("../build/instances"): os.mkdir("../build/instances") # doc = DesignSpaceDocument() # doc.addAxisDescriptor( # maximum = 950, # minimum = 100, # default = 400, # name = "weight", # tag = "wght", # ) # doc.addAxisDescriptor( # maximum = 14, # minimum = 0, # default = 0, # name = "slant", # tag = "slnt", # ) MASTER_WEIGHTS = [100, 400, 950] genWeights = WEIGHT_NAMES.keys() if len(sys.argv) < 2 else map(int, sys.argv[1:]) for weight in genWeights: regularPath = writeUFO(weight) italicPath = writeUFO(weight, 14) # # if weight in MASTER_WEIGHTS: # doc.addSourceDescriptor( # path = regularPath, # name = f"master.PixelCode.{getStyleName(weight)}", # familyName = getFamilyName(), # styleName = getStyleName(weight), # location = dict(weight = weight, slant = 0), # copyLib = True, # copyInfo = True, # copyGroups = True, # copyFeatures = True, # ) # doc.addSourceDescriptor( # path = italicPath, # name = f"master.PixelCode.{getStyleName(weight, 14)}", # familyName = getFamilyName(), # styleName = getStyleName(weight, 14), # location = dict(weight = weight, slant = 14), # copyLib = True, # copyInfo = True, # copyGroups = True, # copyFeatures = True, # ) # # doc.addInstanceDescriptor( # name = f"instance_{getStyleName(weight)}", # familyName = getFamilyName(), # styleName = getStyleName(weight), # path = os.path.abspath(f"../build/instances/{getFullNameShort(weight)}.ufo"), # location = dict(weight = weight, slant = 0), # kerning = False, # info = True, # postScriptFontName = getFullNameShort(weight), # styleMapFamilyName = getFamilyName(), # styleMapStyleName = getStyleName(weight), # ) # doc.addInstanceDescriptor( # name = f"instance_{getStyleName(weight, 14)}", # familyName = getFamilyName(), # styleName = getStyleName(weight, 14), # path = os.path.abspath(f"../build/instances/{getFullNameShort(weight, 14)}.ufo"), # location = dict(weight = weight, slant = 14), # kerning = False, # info = True, # postScriptFontName = getFullNameShort(weight, 14), # styleMapFamilyName = getFamilyName(), # styleMapStyleName = getStyleName(weight, 14), # ) # # doc.addInstanceDescriptor( # name = "instance_Italic", # familyName = getFamilyName(), # styleName = "Italic", # path = os.path.abspath(f"../build/instances/{getFamilyName().replace(" ", "")}-Italic.ufo"), # location = dict(weight = 400, slant = 14), # kerning = False, # info = True, # postScriptFontName = f"{getFamilyName().replace(" ", "")}-Italic", # styleMapFamilyName = getFamilyName(), # styleMapStyleName = "Italic", # ) # # doc.write("../build/PixelCode.designspace") ================================================ FILE: src/gen_braille.py ================================================ from PIL import Image, ImageDraw CELL_WIDTH = 6 CELL_HEIGHT = 12 IMAGE_WIDTH = 16 * CELL_WIDTH IMAGE_HEIGHT = 16 * CELL_HEIGHT canvas = Image.new('RGB', (IMAGE_WIDTH, IMAGE_HEIGHT), 'black') draw = ImageDraw.Draw(canvas) def drawPattern(i, x, y): if i & 0b00000001: draw.point((x + 1, y + 4), 'white') if i & 0b00000010: draw.point((x + 1, y + 6), 'white') if i & 0b00000100: draw.point((x + 1, y + 8), 'white') if i & 0b00001000: draw.point((x + 3, y + 4), 'white') if i & 0b00010000: draw.point((x + 3, y + 6), 'white') if i & 0b00100000: draw.point((x + 3, y + 8), 'white') if i & 0b01000000: draw.point((x + 1, y + 10), 'white') if i & 0b10000000: draw.point((x + 3, y + 10), 'white') x = 0 y = 0 for i in range(256): drawPattern(i, x, y) x += CELL_WIDTH if x >= IMAGE_WIDTH: x = 0 y += CELL_HEIGHT canvas.save("./glyphs/2800-28FF.png") ================================================ FILE: src/gen_charlist.py ================================================ from fontTools.unicodedata import east_asian_width from fontTools.ttLib import TTFont from html import escape FONT = TTFont("../dist/ttf/PixelCode.ttf") def charWidth(c): ch = chr(c) east_asian_class = east_asian_width(ch) # For Full Width and Wide characters, return 2, otherwise return 1. if east_asian_class == "F" or east_asian_class == "W": return 2 return 1 def printAllChars(font): chars = {0} for cmap in font['cmap'].tables: if cmap.isUnicode(): for c in cmap.cmap: chars.add(c) listed = [c for c in chars] listed.sort() x = 0 for c in listed: w = charWidth(c) print(f"{escape(chr(c))}{"" if w > 1 else " "}", end="") x += 2 if x >= 64: print("") x = 0 printAllChars(FONT) ================================================ FILE: src/gen_template.py ================================================ import sys import os from PIL import Image, ImageFont, ImageDraw from fontTools.unicodedata import east_asian_width from fontTools.ttLib import TTFont from math import ceil def printUsage(): print("gen_template: Creates a (high resolution) reference template image for a codepoint range.") print("Usage: gen_template [columns]") print("Example: gen_template 0000 007F 16") exit(1) if len(sys.argv) < 3: printUsage() start = 0 end = 0 columns = 0 try: start = int(sys.argv[1], 16) end = int(sys.argv[2], 16) columns = int(sys.argv[3]) if len(sys.argv) >= 4 else 16 except Exception: printUsage() def charWidth(c): ch = chr(c) east_asian_class = east_asian_width(ch) # For Full Width and Wide characters, return 2, otherwise return 1. if east_asian_class == "F" or east_asian_class == "W": return 2 return 1 GLYPH_COUNT = 1 + end - start COLS = 0 ROWS = 1 x = 0 for c in range(GLYPH_COUNT): cp = c + start w = charWidth(cp) if w > 1 and x == columns - 1: ROWS += 1 x = 0 x += w if x > columns: x -= columns ROWS += 1 COLS = max(x, COLS) LEFT_BEARING = 0 RIGHT_BEARING = 1 ADVANCE = 6 NON_SPACE_WIDTH = ADVANCE - RIGHT_BEARING - LEFT_BEARING BASELINE = 10 DESCENT = 1 LEADING = 1 TOTAL_HEIGHT = BASELINE + DESCENT + LEADING X_HEIGHT = 5 CAP_HEIGHT = 7 TEMPLATE_CELL_WIDTH = 128 TEMPLATE_CELL_SCALE = (TEMPLATE_CELL_WIDTH / ADVANCE) TEMPLATE_CELL_HEIGHT = ceil(TEMPLATE_CELL_SCALE * TOTAL_HEIGHT) IMAGE_WIDTH = TEMPLATE_CELL_WIDTH * COLS IMAGE_HEIGHT = TEMPLATE_CELL_HEIGHT * ROWS canvas = Image.new('RGB', (IMAGE_WIDTH, IMAGE_HEIGHT), 'black') draw = ImageDraw.Draw(canvas) FONT_DIRS = [] if sys.platform == "linux": FONT_DIRS = ['/usr/share/fonts', '/usr/local/share/fonts', os.path.expanduser('~/.fonts')] elif sys.platform == "darwin": FONT_DIRS = ['/System/Library/Fonts', '/System/Library/Fonts/Supplemental', '/Library/Fonts', os.path.expanduser('~/Library/Fonts')] def findFont(name): for fdir in FONT_DIRS: fpath = os.path.join(fdir, name) if os.path.isfile(fpath): return fpath # Replace this list with one tuned to your system. FONTS = [p for p in [findFont(f) for f in [ "FiraCode-VF.ttf", "FiraCodeNerdFont-Regular.ttf", "Apple Symbols.ttf", "ZapfDingbats.ttf", "NotoEmoji-Regular.ttf", "NotoSans-Regular.ttf", "Times New Roman.ttf", "Arial Unicode.ttf", # Last ditch effort "unifont-15.1.05.otf", "unifont_upper-15.1.05.otf", "unifont_csur-15.1.05.otf" ]] if p is not None] TT_FONTS = dict() for f in FONTS: TT_FONTS[f] = TTFont(f) print("Using fonts:") print(FONTS) def charInFont(unicode_char, font): for cmap in font['cmap'].tables: if cmap.isUnicode(): if ord(unicode_char) in cmap.cmap: return True return False def getFontFor(char): for fontpath in FONTS: font = TT_FONTS[fontpath] if 'cmap' in font and charInFont(char, font): return fontpath # Fallback to default font return FONTS[0] IMAGE_FONTS = dict() for font in FONTS: IMAGE_FONTS[font] = ImageFont.truetype(font, int((CAP_HEIGHT + DESCENT + LEADING) * TEMPLATE_CELL_SCALE)) def glyphFontFor(char): return IMAGE_FONTS[getFontFor(char)] label_font = ImageFont.truetype("Arial.ttf", int(TEMPLATE_CELL_WIDTH / 15)) wide_i = 0 for i in range(GLYPH_COUNT): cp = start + i if charWidth(cp) > 1 and wide_i % columns == columns - 1: wide_i += 1 x = wide_i % columns y = int(wide_i / columns) x *= ADVANCE y *= TOTAL_HEIGHT if charWidth(cp) > 1: draw.rectangle([(x * TEMPLATE_CELL_SCALE, y * TEMPLATE_CELL_SCALE), ((x + ADVANCE * 2) * TEMPLATE_CELL_SCALE, (y + TOTAL_HEIGHT) * TEMPLATE_CELL_SCALE)], '#331212') x += ADVANCE / 2 x += LEFT_BEARING + NON_SPACE_WIDTH / 2 y += BASELINE x *= TEMPLATE_CELL_SCALE y *= TEMPLATE_CELL_SCALE draw.text((x, y), chr(cp), '#9a9a9a', font = glyphFontFor(chr(cp)), anchor = 'ms') wide_i += charWidth(cp) for i in range(COLS): x = i * ADVANCE draw.line([(x * TEMPLATE_CELL_SCALE, 0), (x * TEMPLATE_CELL_SCALE, IMAGE_HEIGHT)], fill = 'white', width = 2) x += LEFT_BEARING # draw.line([(x * TEMPLATE_CELL_SCALE, 0), (x * TEMPLATE_CELL_SCALE, IMAGE_HEIGHT)], fill = 'blue', width = 2) x += NON_SPACE_WIDTH draw.line([(x * TEMPLATE_CELL_SCALE, 0), (x * TEMPLATE_CELL_SCALE, IMAGE_HEIGHT)], fill = 'blue', width = 2) x += RIGHT_BEARING draw.line([(x * TEMPLATE_CELL_SCALE, 0), (x * TEMPLATE_CELL_SCALE, IMAGE_HEIGHT)], fill = 'white', width = 2) for i in range(ROWS): y = i * TOTAL_HEIGHT draw.line([(0, y * TEMPLATE_CELL_SCALE), (IMAGE_WIDTH, y * TEMPLATE_CELL_SCALE)], fill = 'white', width = 2) y += BASELINE - CAP_HEIGHT draw.line([(0, y * TEMPLATE_CELL_SCALE), (IMAGE_WIDTH, y * TEMPLATE_CELL_SCALE)], fill = 'magenta', width = 2) y += CAP_HEIGHT - X_HEIGHT draw.line([(0, y * TEMPLATE_CELL_SCALE), (IMAGE_WIDTH, y * TEMPLATE_CELL_SCALE)], fill = 'darkgray', width = 2) y += X_HEIGHT draw.line([(0, y * TEMPLATE_CELL_SCALE), (IMAGE_WIDTH, y * TEMPLATE_CELL_SCALE)], fill = 'orange', width = 2) y += DESCENT draw.line([(0, y * TEMPLATE_CELL_SCALE), (IMAGE_WIDTH, y * TEMPLATE_CELL_SCALE)], fill = 'darkgray', width = 2) y += LEADING draw.line([(0, y * TEMPLATE_CELL_SCALE), (IMAGE_WIDTH, y * TEMPLATE_CELL_SCALE)], fill = 'white', width = 2) canvas.save(f"./TEMPLATE_{start:04X}-{end:04X}.png") img = Image.new('RGB', (COLS * ADVANCE, ROWS * TOTAL_HEIGHT), 'black') img.save(f"./{start:04X}-{end:04X}.png") ================================================ FILE: src/glyphs/+calt/calt.fea ================================================ # AFTER_LIGATURES @upper = [ zero one two three four five six seven eight nine A - Z Agrave Aacute Acircumflex Atilde Adieresis Aring AE Ccedilla Egrave Eacute Ecircumflex Edieresis Igrave Iacute Icircumflex Idieresis Eth Ntilde Ograve Oacute Ocircumflex Otilde Odieresis Oslash Ugrave Uacute Ucircumflex Udieresis Yacute Thorn germandbls colon.calt hyphen_greater less_hyphen less_exclam_hyphen_hyphen hyphen_hyphen_greater ]; @lower = [ a - z agrave aacute acircumflex atilde adieresis aring ae ccedilla egrave eacute ecircumflex edieresis igrave iacute icircumflex idieresis eth ntilde ograve oacute ocircumflex otilde odieresis oslash ugrave uacute ucircumflex udieresis yacute thorn ydieresis zero.onum one.onum two.onum three.onum four.onum five.onum six.onum seven.onum eight.onum nine.onum plus.calt hyphen.calt asterisk.calt l.shifted ]; @digit = [ zero one two three four five six seven eight nine zero.onum one.onum two.onum three.onum four.onum five.onum six.onum seven.onum eight.onum nine.onum ]; @hex_digit = [ @digit a b c d e f A B C D E F ]; @zero = [ zero zero.onum ]; lookup CPUNCT { sub plus by plus.calt; sub hyphen by hyphen.calt; sub asterisk by asterisk.calt; sub colon by colon.calt; sub x by x.calt; } CPUNCT; feature calt { # *EQ *eq ignore sub @upper asterisk' space @lower; ignore sub @upper space asterisk' space @lower; ignore sub asterisk asterisk' asterisk' @lower; ignore sub asterisk asterisk' asterisk' space @lower; sub asterisk' lookup CPUNCT @lower; sub asterisk' lookup CPUNCT space @lower; sub asterisk' lookup CPUNCT asterisk @lower; sub asterisk' lookup CPUNCT asterisk space @lower; # a:e A:E sub @upper colon' lookup CPUNCT @upper; sub @upper space colon' lookup CPUNCT @upper; sub @upper colon' lookup CPUNCT space @upper; sub @upper space colon' lookup CPUNCT space @upper; # C-O c-o ignore sub hyphen hyphen' @lower; sub hyphen' lookup CPUNCT @lower; # X+Z x+z ignore sub plus plus' @lower; sub plus' lookup CPUNCT @lower; # 0xFF 1920x1080 sub @zero x' lookup CPUNCT @hex_digit; sub @digit x' lookup CPUNCT @digit; } calt; ================================================ FILE: src/glyphs/+onum/onum.fea ================================================ @digits = [zero one two three four five six seven eight nine]; @digits.onum = [zero.onum one.onum two.onum three.onum four.onum five.onum six.onum seven.onum eight.onum nine.onum]; feature onum { sub @digits by @digits.onum; } onum; ================================================ FILE: src/glyphs/+shifted/shifted.fea ================================================ @open_right = [i igrave iacute icircumflex idieresis l.shifted u ugrave uacute ucircumflex udieresis q t]; @shiftable = [l]; lookup SHIFT_LETTER { sub l by l.shifted; } SHIFT_LETTER; feature ss20 { featureNames { name "Pseudo-kerning"; }; sub @open_right @shiftable' lookup SHIFT_LETTER; } ss20; ================================================ FILE: src/glyphs/+ss01/ss01.fea ================================================ feature ss01 { featureNames { name "Straight Comma"; }; sub comma by comma.ss01; sub semicolon by semicolon.ss01; } ss01; ================================================ FILE: src/glyphs/ligatures/ignore.fea ================================================ # AFTER_LIGATURES @equalish = [equal equal_equal.0 equal_equal_equal.0]; @equalend = [equal equal_equal.1 equal_equal_equal.2]; @slashish = [slash slash_slash.0 slash_slash_slash.0]; @slashend = [slash slash_slash.1 slash_slash_slash.2]; @asterish = [asterisk asterisk.calt asterisk_asterisk.0]; @asterend = [asterisk asterisk.calt asterisk_asterisk.1]; @barish = [bar bar_bar.0]; @barend = [bar bar_bar.1]; feature liga { # == and === sub @equalend' @equalish by equal; sub equal_equal_equal.1' equal_equal_equal.2 @equalish by equal; sub equal_equal_equal.0' equal_equal_equal.1 equal_equal_equal.2 @equalish by equal; sub @equalend @equalish' by equal; sub @equalend equal_equal_equal.1' by equal; sub @equalend equal_equal_equal.2' by equal; sub @equalend @equalish @equalend' by equal; # // and /// sub @slashend' @slashish by slash; sub slash_slash_slash.1' slash_slash_slash.2 @slashish by slash; sub slash_slash_slash.0' slash_slash_slash.1 slash_slash_slash.2 @slashish by slash; sub @slashend @slashish' by slash; sub @slashend slash_slash_slash.1' by slash; sub @slashend slash_slash_slash.2' by slash; sub @slashend @slashish @slashend' by slash; # ** and || sub @asterend' @asterish by asterisk; sub @asterend @asterish' by asterisk; sub @asterish' @asterend @asterish by asterisk; sub @asterend @asterish @asterend' by asterisk; sub @barend' @barish by bar; sub @barend @barish' by bar; sub @barish' @barend @barish by bar; sub @barend @barish @barend' by bar; } liga; ================================================ FILE: src/watch.py ================================================ import os import time from watchdog.observers import Observer from watchdog.events import FileSystemEvent, FileSystemEventHandler class ReloadingHandler(FileSystemEventHandler): def on_any_event(self, event: FileSystemEvent): print(event) print("File changed, rebuilding...") os.system("python3 ./build_from_images.py 400") print("Done!") observer = Observer() handler = ReloadingHandler() observer.schedule(handler, "./glyphs/", recursive = True) observer.start() try: while True: time.sleep(1) except KeyboardInterrupt: observer.stop() observer.join()