├── CODE_OF_CONDUCT.md
├── LICENSE.md
├── README.md
├── blob
├── logo.png
├── pull_all_modules.sh
└── wiki
│ ├── artifacts_dl1.png
│ ├── artifacts_dl2.png
│ └── artifacts_dl3.png
├── doc
├── Layers.md
├── Why.md
└── article.md
└── roadmap
├── .timestamp
├── Makefile
├── graph.dot
└── roadmap.png
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Modular Font Editor K (MFEK) Code of Conduct
2 |
3 | The code of conduct of the MFEK project, based on the Django Project's code of conduct (by way of [the Markdown version in the highly Google-ranked repository mfl_api_docs](https://github.com/MasterFacilityList/mfl_api_docs/edit/master/docs/15_code_of_conduct.rst)), consists of the follwing six pillars:
4 |
5 | * **Be friendly and patient**.
6 | * **Be welcoming**. We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability.
7 | * **Be considerate**. Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we're a world-wide community, so you might not be communicating in someone else's primary language.
8 | * **Be respectful**. Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one. Members of the Django community should be respectful when dealing with other members as well as with people outside the Django community.
9 | * **Be careful in the words that you choose**. We are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren't acceptable. This includes, but is not limited to:
10 |
11 | * Violent threats or language directed against another person.
12 | * Discriminatory jokes and language.
13 | * Posting sexually explicit or violent material.
14 | * Posting (or threatening to post) other people's personally identifying information ("doxing").
15 | * Personal insults, especially those using racist or sexist terms.
16 | * Unwelcome sexual attention.
17 | * Advocating for, or encouraging, any of the above behavior.
18 | * Repeated harassment of others. In general, if someone asks you to stop, then stop.
19 |
20 | * **When we disagree, try to understand why**. Disagreements, both social and technical, happen all the time and Django is no exception. It is important that we resolve disagreements and differing views constructively. Remember that we’re different. The strength of Django comes from its varied community, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to err and blaming each other doesn’t get us anywhere, rather offer to help resolving issues and to help learn from mistakes.
21 |
22 | If you believe the code of conduct has been broken, you may email lead developer Fred Brennan <copypasteⒶkittens.ph> and ask for his intervention. Please put ``[MFEK CoC]`` in the subject line. Please consider asking the other party for an apology, if appropriate to your situation, before doing so—we are all human.
23 |
24 | The license of the MFEK CoC is the Creative Commons Attribution 3.0 license, [following Django's lead](https://www.djangoproject.com/conduct/).
25 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Apache License
2 | ==============
3 |
4 | _Version 2.0, January 2004_
5 | _<>_
6 |
7 | ### Terms and Conditions for use, reproduction, and distribution
8 |
9 | #### 1. Definitions
10 |
11 | “License” shall mean the terms and conditions for use, reproduction, and
12 | distribution as defined by Sections 1 through 9 of this document.
13 |
14 | “Licensor” shall mean the copyright owner or entity authorized by the copyright
15 | owner that is granting the License.
16 |
17 | “Legal Entity” shall mean the union of the acting entity and all other entities
18 | that control, are controlled by, or are under common control with that entity.
19 | For the purposes of this definition, “control” means **(i)** the power, direct or
20 | indirect, to cause the direction or management of such entity, whether by
21 | contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
22 | outstanding shares, or **(iii)** beneficial ownership of such entity.
23 |
24 | “You” (or “Your”) shall mean an individual or Legal Entity exercising
25 | permissions granted by this License.
26 |
27 | “Source” form shall mean the preferred form for making modifications, including
28 | but not limited to software source code, documentation source, and configuration
29 | files.
30 |
31 | “Object” form shall mean any form resulting from mechanical transformation or
32 | translation of a Source form, including but not limited to compiled object code,
33 | generated documentation, and conversions to other media types.
34 |
35 | “Work” shall mean the work of authorship, whether in Source or Object form, made
36 | available under the License, as indicated by a copyright notice that is included
37 | in or attached to the work (an example is provided in the Appendix below).
38 |
39 | “Derivative Works” shall mean any work, whether in Source or Object form, that
40 | is based on (or derived from) the Work and for which the editorial revisions,
41 | annotations, elaborations, or other modifications represent, as a whole, an
42 | original work of authorship. For the purposes of this License, Derivative Works
43 | shall not include works that remain separable from, or merely link (or bind by
44 | name) to the interfaces of, the Work and Derivative Works thereof.
45 |
46 | “Contribution” shall mean any work of authorship, including the original version
47 | of the Work and any modifications or additions to that Work or Derivative Works
48 | thereof, that is intentionally submitted to Licensor for inclusion in the Work
49 | by the copyright owner or by an individual or Legal Entity authorized to submit
50 | on behalf of the copyright owner. For the purposes of this definition,
51 | “submitted” means any form of electronic, verbal, or written communication sent
52 | to the Licensor or its representatives, including but not limited to
53 | communication on electronic mailing lists, source code control systems, and
54 | issue tracking systems that are managed by, or on behalf of, the Licensor for
55 | the purpose of discussing and improving the Work, but excluding communication
56 | that is conspicuously marked or otherwise designated in writing by the copyright
57 | owner as “Not a Contribution.”
58 |
59 | “Contributor” shall mean Licensor and any individual or Legal Entity on behalf
60 | of whom a Contribution has been received by Licensor and subsequently
61 | incorporated within the Work.
62 |
63 | #### 2. Grant of Copyright License
64 |
65 | Subject to the terms and conditions of this License, each Contributor hereby
66 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
67 | irrevocable copyright license to reproduce, prepare Derivative Works of,
68 | publicly display, publicly perform, sublicense, and distribute the Work and such
69 | Derivative Works in Source or Object form.
70 |
71 | #### 3. Grant of Patent License
72 |
73 | Subject to the terms and conditions of this License, each Contributor hereby
74 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
75 | irrevocable (except as stated in this section) patent license to make, have
76 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where
77 | such license applies only to those patent claims licensable by such Contributor
78 | that are necessarily infringed by their Contribution(s) alone or by combination
79 | of their Contribution(s) with the Work to which such Contribution(s) was
80 | submitted. If You institute patent litigation against any entity (including a
81 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a
82 | Contribution incorporated within the Work constitutes direct or contributory
83 | patent infringement, then any patent licenses granted to You under this License
84 | for that Work shall terminate as of the date such litigation is filed.
85 |
86 | #### 4. Redistribution
87 |
88 | You may reproduce and distribute copies of the Work or Derivative Works thereof
89 | in any medium, with or without modifications, and in Source or Object form,
90 | provided that You meet the following conditions:
91 |
92 | * **(a)** You must give any other recipients of the Work or Derivative Works a copy of
93 | this License; and
94 | * **(b)** You must cause any modified files to carry prominent notices stating that You
95 | changed the files; and
96 | * **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
97 | all copyright, patent, trademark, and attribution notices from the Source form
98 | of the Work, excluding those notices that do not pertain to any part of the
99 | Derivative Works; and
100 | * **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
101 | Derivative Works that You distribute must include a readable copy of the
102 | attribution notices contained within such NOTICE file, excluding those notices
103 | that do not pertain to any part of the Derivative Works, in at least one of the
104 | following places: within a NOTICE text file distributed as part of the
105 | Derivative Works; within the Source form or documentation, if provided along
106 | with the Derivative Works; or, within a display generated by the Derivative
107 | Works, if and wherever such third-party notices normally appear. The contents of
108 | the NOTICE file are for informational purposes only and do not modify the
109 | License. You may add Your own attribution notices within Derivative Works that
110 | You distribute, alongside or as an addendum to the NOTICE text from the Work,
111 | provided that such additional attribution notices cannot be construed as
112 | modifying the License.
113 |
114 | You may add Your own copyright statement to Your modifications and may provide
115 | additional or different license terms and conditions for use, reproduction, or
116 | distribution of Your modifications, or for any such Derivative Works as a whole,
117 | provided Your use, reproduction, and distribution of the Work otherwise complies
118 | with the conditions stated in this License.
119 |
120 | #### 5. Submission of Contributions
121 |
122 | Unless You explicitly state otherwise, any Contribution intentionally submitted
123 | for inclusion in the Work by You to the Licensor shall be under the terms and
124 | conditions of this License, without any additional terms or conditions.
125 | Notwithstanding the above, nothing herein shall supersede or modify the terms of
126 | any separate license agreement you may have executed with Licensor regarding
127 | such Contributions.
128 |
129 | #### 6. Trademarks
130 |
131 | This License does not grant permission to use the trade names, trademarks,
132 | service marks, or product names of the Licensor, except as required for
133 | reasonable and customary use in describing the origin of the Work and
134 | reproducing the content of the NOTICE file.
135 |
136 | #### 7. Disclaimer of Warranty
137 |
138 | Unless required by applicable law or agreed to in writing, Licensor provides the
139 | Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
140 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
141 | including, without limitation, any warranties or conditions of TITLE,
142 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
143 | solely responsible for determining the appropriateness of using or
144 | redistributing the Work and assume any risks associated with Your exercise of
145 | permissions under this License.
146 |
147 | #### 8. Limitation of Liability
148 |
149 | In no event and under no legal theory, whether in tort (including negligence),
150 | contract, or otherwise, unless required by applicable law (such as deliberate
151 | and grossly negligent acts) or agreed to in writing, shall any Contributor be
152 | liable to You for damages, including any direct, indirect, special, incidental,
153 | or consequential damages of any character arising as a result of this License or
154 | out of the use or inability to use the Work (including but not limited to
155 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or
156 | any and all other commercial damages or losses), even if such Contributor has
157 | been advised of the possibility of such damages.
158 |
159 | #### 9. Accepting Warranty or Additional Liability
160 |
161 | While redistributing the Work or Derivative Works thereof, You may choose to
162 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or
163 | other liability obligations and/or rights consistent with this License. However,
164 | in accepting such obligations, You may act only on Your own behalf and on Your
165 | sole responsibility, not on behalf of any other Contributor, and only if You
166 | agree to indemnify, defend, and hold each Contributor harmless for any liability
167 | incurred by, or claims asserted against, such Contributor by reason of your
168 | accepting any such warranty or additional liability.
169 |
170 | _END OF TERMS AND CONDITIONS_
171 |
172 | ### APPENDIX: How to apply the Apache License to your work
173 |
174 | To apply the Apache License to your work, attach the following boilerplate
175 | notice, with the fields enclosed by brackets `[]` replaced with your own
176 | identifying information. (Don't include the brackets!) The text should be
177 | enclosed in the appropriate comment syntax for the file format. We also
178 | recommend that a file or class name and description of purpose be included on
179 | the same “printed page” as the copyright notice for easier identification within
180 | third-party archives.
181 |
182 | Copyright [yyyy] [name of copyright owner]
183 |
184 | Licensed under the Apache License, Version 2.0 (the "License");
185 | you may not use this file except in compliance with the License.
186 | You may obtain a copy of the License at
187 |
188 | http://www.apache.org/licenses/LICENSE-2.0
189 |
190 | Unless required by applicable law or agreed to in writing, software
191 | distributed under the License is distributed on an "AS IS" BASIS,
192 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
193 | See the License for the specific language governing permissions and
194 | limitations under the License.
195 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Modular Font Editor K
2 |
3 |
4 |
5 | **Modular Font Editor K** (MFEK) is an open source modular font editor. It attempts to apply the Unix adage that each program should do one thing and do it well to a GUI font editor.
6 |
7 | MFEK is still very alpha, and many parts are missing. You can help!
8 |
9 | **[Why MFEK?](https://github.com/MFEK/docs/blob/master/doc/Why.md)**
10 |
11 | To pull all modules, why not use this script?
12 |
13 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/MFEK/docs/master/blob/pull_all_modules.sh)"
14 |
15 | To see who wrote an MFEK module, check its `AUTHORS` file. The two main authors of MFEK are Fredrick R. Brennan (@ctrlcctrlv) and Matthew Blanchard (@MatthewBlanchard).
16 |
17 | ## Current roadmap as of 2021-11-02
18 | 
19 | ### Roadmap key
20 | * Dotted line around module name — module not started.
21 | * Dashed line around module name — module started, yet far from completion.
22 | * No line around module name — module is still far from being begun, and may indeed be unnecessary and never be begun.
23 | * A bold black arrow represents a program calling a program.
24 | * A red arrow represents a program including a library.
25 | * A black arrow represents a library including a library.
26 | * All libraries and programs are assumed to be in Rust unless noted otherwise.
27 |
28 | ## Modular programs
29 |
30 | * [`MFEKglif`](https://github.com/MFEK/glif) (.glif editor w/planned Spiro support)
31 | * [`MFEKpathops`](https://github.com/MFEK/pathops) (applies different kinds of operations to .glif paths)
32 | * [`MFEKstroke`](https://github.com/MFEK/stroke) (applies different kinds of strokes to .glif files with open contours)
33 | * [`MFEKmetadata`](https://github.com/MFEK/metadata) (UFO metadata querier)
34 | * [`MFEKabout`](https://github.com/MFEK/about) (MFEK's about screen)
35 |
36 | ### Planned
37 |
38 | * `MFEKufo` (a launcher for MFEKglif that displays all glyphs)
39 | * `MFEKdesignspace` (design space XML creator/editor)
40 | * `MFEKmetrics` (load UFO file into HarfBuzz and output typed text, edit horizontal/vertical kerning and bearings, test interpolation)
41 | * `MFEKopentype` (OpenType layout editor based on @simoncozens' ideas)
42 | * `MFEKexport` (frontend to fontmake)
43 |
44 | #### Far off
45 |
46 | * `MFEKpshints` (Add PostScript hints to glyphs and test them)
47 | * `MFEKtruetype` (basically would be an open source version of Visual TrueType (VTT))
48 |
49 | ## Libraries
50 |
51 | * [`glifparser.rlib`](https://github.com/MFEK/glifparser.rlib) (a .glif parser)
52 | * [`integer-or-float.rlib`](https://github.com/MFEK/integer_or_float.rlib) (implements a .glif data type)
53 | * (We need this because Norad has no support for `` in `.glif` files, and due to how they went about implementing Norad, fixing that is trickier than having my own glyph parser. Furthermore, as I plan to support Spiro, B-Splines, etc., through UFO format extensions, I should have one anyway.)
54 | * [`icu-data.rlib`](https://github.com/MFEK/icu-data.rlib) (Unicode ICU data without C libicu, currently only encodings)
55 | * [`ipc.rlib`](https://github.com/MFEK/ipc.rlib) (_very_ basic inter-process communication functions)
56 | * [`math.rlib`](https://github.com/MFEK/math.rlib) (implements algorithms for MFEKstroke: Pattern-Along-Path, Variable/Constant Width Stroke, etc.)
57 | * [`spiro.rlib`](https://github.com/MFEK/spiro.rlib) (a Rust implementation of Raph Levien's [Spiro](https://github.com/raphlinus/spiro) curve type)
58 | * [`feaparser.rlib`](https://github.com/MFEK/feaparser.rlib) (an OpenType Feature File Format [`.fea`] parser)
59 | * [`glifrenderer.rlib`](https://github.com/MFEK/glifrenderer.rlib) (a Skia renderer focused on rendering font glyphs in a pleasing way)
60 |
61 | ### Planned
62 |
63 | * libskef (Port of @skef's «Expand Stroke» feature to a reusable C API. Will likely also require `SplineSet` type from FontForge.)
64 |
65 | ## Flow
66 |
67 | MFEK's inter-process communication (IPC) will be minimal. UFO is the format, and most of the time, MFEK modules are going to be starting with just what's on the disk. We can put the planned MFEK modules onto a linear spectrum between _forms_ and _canvases_. The quintessential form is MFEKdesignspace: it is purely a form. The user inputs the names of their UFO masters, their axes, instances, and rules, and out comes a rigidly hierarchical `.designspace` file for consumption by `fontmake` and MFEKinterpolate. As a form, once it's filled out, it's done. We may need to go back and add more rules or instances or what have you, but it's essentially one run and done. Meanwhile, the quintessential canvas is MFEKglif: the user draws their glyphs and can spend as long as they want doing so, and will likely have multiple MFEKglif instances running in parallel. As long as you're working on the font, MFEKglif will probably be open most of the time.
68 |
69 | MFEKstroke is an interesting example because it's in between. The user needs to fill out a form, yes, the stroke parameters — but she also needs to see what the glyph will look like with the parameters, and tweak them to her heart's content to get the best output. However, once parameters are chosen, it becomes almost pure form. It is both form and canvas, as is MFEKinterpolate. MFEKopentype and MFEKkern, meanwhile, have some form elements, but are more canvas than form.
70 |
71 | Most of our IPC can just be `system` calls (launching new processes). At our most advanced, we watch a file or directory for changes and reconcile accordingly.
72 |
73 | Let's consider we want to make a cursive font. Here is how we would proceed, according to my vision:
74 |
75 | * Run MFEKmetadata. When it gets no argument, or the argument of a non-existent directory, it assumes we want a new font. So, we fill out a form.
76 | * We then run MFEKufo and see empty squares. Clicking `A` launches MFEKglif with the command line `MFEKglif glyphs/A_.glif`.
77 | * MFEKglif calls MFEKmetadata as `MFEKmetadata metrics`. MFEKmetadata returns on stdout the em-size, ascender, descender, and x-height and cap-height if known based on the UFO metadata. MFEKglif draws guidelines.
78 | * We start drawing our `A`. We decide to make it a single stroke. We press Ctrl-Shift-E, and MFEKglif launches MFEKstroke, saves the state of the glyph in the undoes list, and begins monitoring `A_.glif` for changes.
79 | * MFEKstroke, likewise, monitors `A_.glif`. If written out, it changes its display. Perhaps auto-saving of every action can be optionally considered. How daring are we? Will our `.glif` file's `` contain undoes? Perhaps!
80 | * The user settles on a stroke and presses Stroke. MFEKstroke writes and exits. MFEKglif dutifully reads from the disk.
81 | * And so on for the basic Latin. It's come time to add an OpenType table. Launch `MFEKopentype`, which will build the font and use HarfBuzz to display it, and auto-update as the user writes their OpenType Layout code. This could be FEA, but it also could be Simon Cozens' [FEZ](https://github.com/simoncozens/fez), a higher level FEA-like syntax. MFEKopentype must be more conservative and only reload the font upon saving of any glyph, not every small action in MFEKglif.
82 | * Finally, we have something we think servicable. In MFEKufo we press Generate, which calls MFEKexport. We're not writing a TrueType generator here, it's a simple form that calls `fontmake` with appropriate arguments.
83 |
84 | Notice that while MFEK grows in size, we can offload one or more steps to FontForge/`fontmake` scripts. So even with only one or two programs, MFEK is immediately useful—we don't need the entire thing done to start using it in production. In fact, I plan to make fonts while I work on MFEK, and use less and less of FontForge over time.
85 |
86 | But our goal is not to totally abandon FontForge, or AFDKO, or fontmake. No, rather, we want a new GUI. But to build our modular font editor, we'll take the good parts out of everything. FontForge is great at dealing with legacy formats: we can imagine a MFEKconvert based on a C library sourced from FontForge code, which calls either that, fontmake, or AFDKO, based on the type of conversion requested.
87 |
88 | ## Code of Conduct (CoC)
89 |
90 | See [`CODE_OF_CONDUCT.md`](https://github.com/MFEK/docs/blob/master/CODE_OF_CONDUCT.md). The MFEK CoC there, last updated 17th November 2021, is that of the whole organization and all of the repositories and communication channels under its umbrella.
91 |
92 | ## Thanks to…
93 |
94 | * Matthew Blanchard;
95 | * Caleb Maclennan;
96 | * Dave Crossland;
97 | * Simon Cozens;
98 | * Eli Heuer;
99 | * Georg Duffner (for EB Garamond ExtraBold, used in our logo);
100 | * All organization members, module authors and contributors;
101 | * All developers of open source font-related software and fonts!
102 |
--------------------------------------------------------------------------------
/blob/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MFEK/docs/07f862966db42a8a0be27c9c335a799fbb2de21c/blob/logo.png
--------------------------------------------------------------------------------
/blob/pull_all_modules.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Pull all current MFEK modules! Latest version @ https://github.com/MFEK/docs/
3 |
4 | MODULES=("glif" "stroke" "pathops" "metadata" "init" "about")
5 |
6 | for m in ${MODULES[@]}; do
7 | if [ ! -d MFEK"$m" ]; then
8 | git clone https://github.com/MFEK/"$m" MFEK"$m"
9 | else
10 | cd MFEK"$m";
11 | git pull --ff-only
12 | cargo build
13 | cd ..
14 | fi
15 | done
16 |
--------------------------------------------------------------------------------
/blob/wiki/artifacts_dl1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MFEK/docs/07f862966db42a8a0be27c9c335a799fbb2de21c/blob/wiki/artifacts_dl1.png
--------------------------------------------------------------------------------
/blob/wiki/artifacts_dl2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MFEK/docs/07f862966db42a8a0be27c9c335a799fbb2de21c/blob/wiki/artifacts_dl2.png
--------------------------------------------------------------------------------
/blob/wiki/artifacts_dl3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MFEK/docs/07f862966db42a8a0be27c9c335a799fbb2de21c/blob/wiki/artifacts_dl3.png
--------------------------------------------------------------------------------
/doc/Layers.md:
--------------------------------------------------------------------------------
1 | # MFEKglif: towards layers and history
2 |
3 | Layers were particularly difficult to add to MFEKglif because of the way the UFO standard handles layers. However, I think I finally have an implementation model that will work, for both the definition of layers and the definition of history (undoes and redoes).
4 |
5 | ## Layers in the UFO standard
6 |
7 | In the UFO standard, each `.glif` file is a layer unto itself. The `glyphs` directory actually refers to the "Foreground" ("Fore") layer in FontForge, and the UFO standard dictates that _new_ `glyphs` directories ought to be made for each of the layers in the font according to the data in `/layercontents.plist`. For example:
8 |
9 | ```xml
10 |
11 |
13 |
14 |
15 |
16 | public.default
17 | glyphs
18 |
19 |
20 | Sketches
21 | glyphs.S_ketches
22 |
23 |
24 | public.background
25 | glyphs.public.background
26 |
27 |
28 |
29 | ```
30 |
31 | would refer to a UFO font with three glyphs directories in the root: `glyphs`, `glyphs.S_ketches`, and `glyphs.public.background`.
32 |
33 | There are only two defined layer keys in the standard (though all beginning in `public.` are reserved): `public.default`, for `glyphs`, and `public.background`, for the Background layer found in many font editors.
34 |
35 | Layer properties are defined _inside_ the `glyphs` directories in `layerinfo.plist` files. In UFO3, only `color` is reserved. Example:
36 |
37 | ```xml
38 |
39 |
40 |
41 |
42 | color
43 | 0,1,1,0.7
44 |
45 |
46 | ```
47 |
48 | Besides this, a `lib` `` can contain font editor-specific data.
49 |
50 | ## Layers in MFEKglif
51 |
52 | This way of doing things will not work well for MFEKglif. For one thing, `.glif` files can be unattached and not always part of a parent UFO. Therefore, we need our own layers format. Layers are how they are in the UFO standard because UFO is expected to be used to generate OpenType fonts, and in the OpenType format, each output layer is defined as a specific glyph. For example, in an emoji font with two colors, you'd have for example `Yellow`, `Black`, `White` and `Red`. Then, from `glyphs.Y_ellow`, `glyphs.B_lack`, etc., the OpenType font would actually have four glyphs for each defined glyph in the font editor.
53 |
54 | So, I think the best way to handle this is much like we've already done by having `MFEKstroke` convert a variable width stroked (VWS) path into a normal `.glif` `` of ``'s. I've come up with a format which will use the `` for holding the other layers, and then have a program which will make the actually `fontmake`-compilable UFO with `glyphs` directories for each layer. I call making the `fontmake`-compilable UFO “Reconciliaton” (see § Reconciliation).
55 |
56 | MFEKglif already can detect if it is being called with the path to a detached `.glif` file or a `.glif` file inside of a `glyphs` directory in a parent UFO. It should further make sure that it is always either being run on one of these two, and not a glyph layer created by other software, which could cause all sorts of collission issues. If the user tries to run e.g. `MFEKglif A.ufo/glyphs.public.background/A_.glif`, MFEKglif should refuse to do that and exit non-0.
57 |
58 | ## MFEKglif's format
59 |
60 | On startup, the first thing is to immediately figure out which contours are in each layer. All of the contours outside of the `` are part of the mandatory `Foreground` layer, and a glyph file cannot have zero layers.
61 |
62 | The `` section will have a new element called `` which will contain `` elements which may contain any number of ``, ``, and `` elements.
63 |
64 | The order of the `` elements is important, and defines the render Z-order. Earlier layers are rendered above later layers.
65 |
66 | ### `` attributes
67 |
68 | 1. `name` ⇒ **String**: Display name of the layer. Must be unique.
69 | 2. `outputtable` ⇒ **Boolean**: Whether this layer should be output during reconciliation. User should have control of this, default to `false`.
70 | 3. `color` _(optional)_ ⇒ **32-bit unsigned integer**: The color of the contours in the layer. One byte each for R, G, B, and A.
71 | 4. `dirname` ⇒ **fn() -> String**: Hidden from the user. Generates by concatenating the string `glyphs.MFEKglif.` plus the `name` with its capital letters followed by underscores. So, for `Sketches`, `dirname` is `glyphs.MFEKglif.S_ketches`. However, for the layer with `mandatory` set to `true` it is always `public.default`.
72 | 5. `mandatory` ⇒ **Boolean**: Only `true` for a single layer, the default (but renamable) `Foreground`.
73 |
74 | ## History
75 |
76 | The `` section will have a `` section containing a `` for each layer. `` will have a `layer` attribute equal to the `dirname`, one `` per layer. (This is for human readability, the `` elements are in the same order as the ``'s.) `` contains any number of ``'s. The first `` is the most recent revision, working backwards. `` has an attribute `redoes`, which is an unsigned integer representing the number of following `` elements which are actually forward in time, but have been undone by the user. It should usually be `0`, only appearing when the user undoes an action and then saves without doing any other action which would cause redoes to be lost.
77 |
78 | ## Reconciliation
79 |
80 | It is a fact that most of the time this system will work just fine and users will not care if their non-`Foreground` layers get written, because they are for prototyping/storage of images and not for writing into the final font.
81 |
82 | However, with the increasing importance of color fonts, it is important to have a program which can split our `.glif` format into the standard `.glif` format. This program should receive a `.ufo` directory as an argument, and not be called by MFEKglif itself, even via IPC, but in future by MFEKufo (the font overview program I envision which will pop MFEKglif instances when users open glyphs just by calling `system`).
83 |
84 | So, given a UFO directory, the reconciliation process is such:
85 |
86 | 1. Read in `/layercontents.plist`. Determine what layers are already defined for this UFO.
87 | 2. If non-`public.default` layers are defined, iterate through all of their glyphs and make sure that none of them are ones that we would split. (None of them contain `` in ``.) If they do, print a helpful message and return non-0. (The user likely got into this situation because they thought they could get around MFEKglif refusing to edit another software's layer by just moving files around, not understanding the clobbering issue.)
88 | 3. In the `glyphs` (`public.default`) directory, figure out all the unique `outputtable` `dirname`'s requested. For example, one glyph in a color (emoji) font might have a `Blue` layer, while another one might have a `Red` but no `Blue`.
89 | 4. Make sure that there is no mismatch between `outputtable` for a given `dirname`. If there is, tell the user and return non-0. (Example message: “Layer Blue in glyph ‹A› is set as "do not output", but layer Blue in glyph ‹a› is set to "output". Cannot continue, please fix the layer properties in MFEKglif.”)
90 | 5. Create empty directories for all the `dirname`'s.
91 | 6. Using the `` `color` attribute, write out a `layerinfo.plist` for the list of `dirname`'s. If there's no color set, write an empty `layerinfo.plist`.
92 | 7. Copy `contents.plist`.
93 | 8. For each `dirname`, open each glyph in `glyphs` and determine if it has the current `dirname` in its `gliflayers`. If it does, copy the contents of its `` into a new `.glif` file with those contents in the document root as normal. _If there is no `dirname` in `gliflayers`_, write an empty `.glif`.
94 |
95 | ## Wrapping up: a note on images
96 |
97 | The UFO standard [reads](https://unifiedfontobject.org/versions/ufo3/glyphs/glif/#image)…
98 |
99 | > ### image: An image reference.↩
100 | >
101 | > This optional element represents an image element in a glyph. It may occur at most once.
102 |
103 | We're ignoring this. We'll put as many images as we please in a `.glif`. The reason it's like this is because it is actually possible to store images in an OpenType font, and again, UFO is mostly focused on creating OpenType fonts from XML data.
104 |
105 | At the time of reconciliation, we can warn the user that subsequent images will either be ignored or can cause errors in some software. But, there's no reason for this arbitrary limitation for a font editor; indeed it's useful to have multiple images in one layer at times.
--------------------------------------------------------------------------------
/doc/Why.md:
--------------------------------------------------------------------------------
1 | # Why?
2 | ### Why MFEK?
3 |
4 | My [Modular Font Editor K](https://github.com/MFEK/docs) project, despite its roadmap, has mystified
5 | some. Why work on it at all, and if I insist, why not work on the Runebender project?
6 |
7 | I aim to explain myself.
8 |
9 | Many have tried to replace FontForge—all have failed. I might fail, in fact, history says I
10 | probably will. Yet, the current state of affairs is so bad I feel I must try.
11 |
12 | # Updates to the status quo since this was written
13 | (Update May 2021: _This prospectus of the project was written in September 2020. Since then some
14 | things have changed, including the degree to which we collaborate with the Runebender project; while
15 | we don't contribute to Runebender itself, we do contribute to a few of its libraries like `kurbo`
16 | and `norad`._)
17 |
18 | (Update November 2021: _Our collaboration with the Rust ecosystem mentioned earlier in the year has
19 | continued thankfully, and `norad`, `flo_curves` and other crates have even made changes with our
20 | project in mind._ ♥)
21 |
22 | # Why we need a new free software font editor
23 | Progress on FontForge has ground to a halt, and often I felt I was the only one interested in
24 | continuing to make progress. It's not hard to understand why this has happened. Maintaining
25 | FontForge is _hard_. Adding new features to it is harder. It is written in a language fewer and
26 | fewer people know and requires C skills fewer and fewer people have.
27 |
28 | Many of FontForge's problems are technical: the biggest difficulty is UI. It uses a custom toolkit,
29 | which despite earnest attempts, I highly doubt it's ever going anywhere. Getting anything done in it
30 | is very difficult and sometmimes nigh impossible.
31 |
32 | This is not where the custom code ends. It has custom code for reading and writing fonts, parsing
33 | feature file syntax, stroking paths, building OpenType layout, and even _rendering text_. The
34 | MetricsView as we call it does not show the output of HarfBuzz; indeed, by default, it doesn't even
35 | rasterize with FreeType, but uses a custom rasterizer.
36 |
37 | All of the custom undocumented code is so vexing as to lead one to despair. The man who understood
38 | it all is gone, and as reading undocumented code is so much harder than writing it, we are in a
39 | state of deadlock.
40 |
41 | Some of the other problems are social. I admit that at the beginning of my becoming more active on
42 | the FontForge bug tracker I was unaccustomed to being in such a role; as I made more and more
43 | contributions my voice suddenly became not just my opinion but authoritative. We had a contentious
44 | battle on the FontForge tracker over licensing and the meaning of the headers in most files, which
45 | alienated a contributor who had previously done a lot. Sometimes I still think they're mad at me: I
46 | won't mention who but I'm sorry to them and won't be pushing further on a relicensing, though
47 | obviously I'm not in a position to tell the owners of the repository what they ought to do.
48 |
49 | Another big problem that a new project doesn't face is dealing with legacy code. We can't just start
50 | ripping all the custom stuff out of FontForge. We will break people's workflows, build scripts, and
51 | fonts. I can't rip out the MetricsView and replace it with HarfBuzz as much as I'd like to: people
52 | probably rely on its output. So, what, we should maintain two ways of showing the user how their
53 | font will look? That will make maintainership harder, not easier.
54 |
55 | Let me give you an example: COLR/CPAL, an emoji (color) font format. I was so ready to work on this.
56 | But working on it is going to be a nightmare. ~~I'm still willing to do it for pay of course, but~~
57 | The UI elements alone are trying. Then we get into the fact that FontForge uses its own custom
58 | rasterizer to generate glyphs in the FontView, and so that needs either a total rewrite, or a
59 | replacement with FreeType. It of course doesn't support color or layering. So am I to integrate a
60 | library, or for some reason write a color font rasterizer when many exist? And let's not even get
61 | into all the UI niggles in the CharView this will introduce; we don't use Skia but rather just call
62 | random Cairo commands. Oh, and there's also a non-Cairo version of the CharView, which must not be
63 | broken without possible conflict as if we deprecate building _sans_ Cairo this will break someone's
64 | build!
65 |
66 | # Why Runebender is not the new free software font editor we need (in my opinion)
67 |
68 | First of all, I have no problem with authors of Runebender and wish them all the success in the
69 | world. This success however has not come yet, FontForge remains dominant. At the time I founded the
70 | MFEK project, Runebender had no commits to it for two months. I was told that actually commits are
71 | happening ... to the UI layer of Runebender. Incredibly, Runebender has an entirely new UI toolkit,
72 | their version of FontForge's GDraw (hopefully without any of its flaws), they call it Druid. That's
73 | not all, they also have an entirely new path rasterizer, called Piet.
74 |
75 | In fact, Runebender is another hive of custom code everywhere, for reasons I do not find logical.
76 | [As I've written in the past, regarding
77 | Alacritty,](https://gist.github.com/ctrlcctrlv/978b3ee4f55d4b4ec415a985e01cb1c9) which was a highly
78 | controversial blog post, Rust projects overly aim towards perfect code. I reject this philosophy.
79 |
80 | [At the time, I replied to criticism of me on
81 | Reddit](https://www.reddit.com/r/rust/comments/ewgczz/rust_maintainer_perfectionism_or_the_tragedy_of/)
82 | thus:
83 |
84 | > > I don't think it's surprising that people who make hobby projects in a language that has
85 | > > correctness as a core design goal would have an attitude of wanting to assure correctness, even
86 | > > at the cost of development velocity. Not everyone wants to move fast and break things.
87 | >
88 | > Sure, that's certainly one way to look at it. It helps to remember though that correctness isn't
89 | > free, and isn't a feature of the language, (although features of the language help create more
90 | > correct programs,) but is in its higher forms, once the Rust compiler and borrow checker are happy
91 | > with the code, highly subjective.
92 |
93 | I don't think it's a big problem to use C(++) in Rust. I don't really know that I agree with the
94 | philosophy that a Rust application should be _entirely_ Rust. For me one of the benefits of Rust is
95 | that I can quickly and safely wrap popular C APIs. I benefit from the work Google puts into Chromium
96 | by using Skia. I benefit from the huge user community of Dear Imgui, including such illustrious
97 | software as SHADERed.
98 |
99 | There simply are not enough hours in the day for me to write a font editor, _and_ a GUI toolkit,
100 | _and_ a path rasterizer, and keep those things well maintained while working on my font editor. But,
101 | @raphlinus and @cymr are much better programmers than me, so maybe they can do it. Let's see, I
102 | guess.
103 |
104 | That is to say, Rust is the perfect tool for the job, it's the Rust users that are the trouble. ;-)
105 |
106 | But, more than this, I've decided that the entire way we're going about this problem is wrong. We
107 | think there should be one program called "font editor", in this case Runebender, and it should do
108 | everything that Glyphsapp does on Apple, and that's that. And so, until "font editor" is production
109 | ready, it's useless in real world fonts. So, no one uses it in their projects, and there's no
110 | _practical_ reason to continue developing it besides hobby, there's no font that you can't make
111 | today that you can make because "font editor" exists. I know of no popular open source font in which
112 | Runebender played a significant, or any, role in its development.
113 |
114 | So let's look at where we really stand in the world of open source fonts. We stand in a fragmented
115 | landscape, and people keep coming along with _massive_ projects — TruFont, Runebender —
116 | to unify this fragmented landscape, and failing. Meanwhile, as FontForge can do less and less of
117 | what we need in modern fonts (emoji fonts, `rand` feature, OpenType Variations), we get more and
118 | more fragmented, and "font editor" needs to do more and more.
119 |
120 | So, "font editor" is just becoming proprietary, Glyphsapp. _This is intolerable._ Open source fonts
121 | should not need proprietary software to build. Glyphsapp is very powerful which is why it is
122 | dangerous. And can we blame its users? I can't.
123 |
124 | I made a modern font recently, [Noto Sans Tagalog](https://github.com/ctrlcctrlv/Noto-Sans-Tagalog)
125 | v3. (Disclosure: Project financially supported by Google. No one at Google or Google itself
126 | necessarily agrees with my views on font editors, obviously.) And all the flaws of FontForge came
127 | tumbling out. Its custom auto-hinter is basically useless, only supports PostScript when industry
128 | best practice is TTF hints, so that needs to be disabled.
129 |
130 | So what did I do here? I used FontForge just to draw my glyphs. I wrote a designspace XML file. I
131 | wrote a script which called on ttfautohint to do the industry best practice hinting. I used fontmake
132 | to smush it all together. I built my modern font only in free software, a fact of which I am proud,
133 | but also sad at how hard it was.
134 |
135 | But I also see opportunity. We need a solution that uses the benefits of open source software
136 | instead of its weaknesses. So, I propose, we need a modular solution, not a monolith. We don't need
137 | "font editor". What we need are ways to test a UFO font with a nice UI, to write OpenType Layout
138 | code, to draw glyphs. We need font editor**s**, that is to say, modular interoperable programs,
139 | interoperable also with Glyphs and FontForge via the UFO format, each program doing one job, and
140 | some day, hopefully, we will have enough programs that we no longer need FontForge, but only some
141 | parts of it I and others will be splitting off into C libraries.
142 |
143 | We instead of fighting futilely the fragmentation, embrace it. We embrace that font editing is not
144 | one skill: it's many skills and tasks. There's no real reason the program that's doing the
145 | auto-hinting needs to be doing the kerning and generating the OpenType tables. The UFO paradigm is
146 | at the end of the day _correct_, we just need a full throated embrace of it. We need to stop writing
147 | "font editor" and start writing font editors.
148 |
149 | [See the roadmap.](https://github.com/mfeq/mfeq/#planned-modules)
150 |
151 | ----
152 |
153 | Fred Brennan
154 |
155 | 3 September 2020
156 |
157 |
159 |
--------------------------------------------------------------------------------
/doc/article.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | https://www.youtube.com/watch?v=JDKT5HZ0qvs
4 |
5 | Check out Fredrick's youtube for a live demonstration!
6 |
7 | # Variable Width Stroking
8 |
9 | I spent a good deal of time over the last month and change trying to put together a variable width stroker. I had a few objectives in mind:
10 |
11 | * Realtime
12 | * Stable (inputs close to each other should give visually similar results)
13 | * Easy to Use
14 |
15 | The first thing I did was look for existing implementations and documentation on how others have accomplished this. I took a look through Inkscape's Powerstroke implementation,
16 | and a found an implementation in R which ended up coming in handy later, but wasn't hugely helpful as I could not make heads or tail of R. There didn't seem to be a good place that
17 | layed out the process in a language I knew or a blog post, so I'm going to walk you through it here.
18 |
19 | ## Overview
20 |
21 | Here's a really stripped down overview of the overall procedure.
22 |
23 | ```python
24 | contour = [bezier1, bezier2, ...]
25 | left_collection = []
26 | right_collection = []
27 |
28 | for bezier in contour
29 | # we'll discuss offset below
30 | left_bezier = offset(bezier, |t| -> normal_offset, |t| -> tangent_offset)
31 | right_bezier = offset(bezier, |t| -> normal_offset, |t| -> tangent_offset))
32 |
33 | left_collection.push(left_bezier)
34 | right_collection.push(right_bezier)
35 |
36 |
37 | flipped_right = map(flip_bezier, right_collection).reverse()
38 | merged_collection = glyph_builder(left_collection)
39 |
40 | # we'll be talking about cap_to below
41 | merged_collection.cap_to(flipped_right.first().first_point())
42 | merged_collection.append(flipped_right)
43 | merged_collection.cap_to(merged_collection.first().first_point())
44 |
45 | # discussed in Mind the gap!
46 | merged_collection.fix_path()
47 | glyph = merged_collection.build()
48 |
49 | ```
50 |
51 | ## Offsetting the curves
52 |
53 | One of the main problems you have to deal with for a good VWS implementation is offsetting your curves. For my application I wanted to be able to specify an offset at the start and end of the bezier curve.
54 |
55 | In my search I found a library called flo_curves: https://github.com/Logicalshift/flo_curves and it had an offset implementation in the language I was already using!
56 | It also provided high quality primitives for things like Points, Lines and Bezier curves. I'm going to describe the broad strokes of how the offset function we use from flo_curves works here.
57 |
58 | 
59 |
60 | ### Splitting at features
61 |
62 | The first step of this algorithm is to split the curves at loops and cusps. This paper cited in Flo_curves source code explains how to find these features far better than I ever could: https://graphics.pixar.com/people/derose/publications/CubicClassification/paper.pdf
63 |
64 | 
65 |
66 | ### Sampling the curve
67 |
68 | The next part of the algorithm is pretty easy. For each of the curves from the split you're going to to sample it an arbitrary amount of times, and then offset the samples.
69 |
70 | You're going to want to evaluate the bezier at the given time t, and calculate the normalized tangent and perpendicular directions at that point.
71 |
72 | 
73 |
74 | Here our normal is in light blue, and tangent in light green.
75 |
76 | *BEWARE*, a very common pitfall with getting tangents and normals with bezier curves is that curves with colocated handles have strange curvatures as t approaches 0 and 1. This can be solved by simply offsetting your evaluations by f64::epsilon and -f64::epsilon when t == 0 or 1 respectively.
77 |
78 | For each sample point you're going to offset along the normal by how thick you want that side of the stroke to be, and you're going to offset along the tangent to change the 'angle of the stroke'. I'll explain how you calculate the tangent offset later in this document.
79 |
80 | 
81 |
82 | ### Fitting a curve to the samples
83 |
84 | After you've sampled the curve and offset those samples you're going to fit a curve to those sampled points. The algorithm used in flo_curves is Philip J Schnieder's from Graphic Gems 1990.
85 |
86 | This stack overflow thread has implementations in both C and C#: https://stackoverflow.com/questions/5525665/smoothing-a-hand-drawn-curve
87 |
88 | 
89 |
90 | ## Mind the gaps!
91 |
92 | 
93 |
94 | The end of one bezier and the start of the next isn't always going to line up, especially at corners close to 90 degrees. We're gonna end up with a gap. You can identify these by looping through each bezier and checking if it's end point matches the next's start point. IF it's within some small amount (I used 0.001) then you merge the two. If not you generate a join. There's a few types of caps and I'm gonna go over how I solved each.
95 |
96 | ### Bevel
97 |
98 | 
99 |
100 | Create a line from the end of the last bazier to the beginning of the next one. This is the trivial case.
101 |
102 | ### Miter
103 |
104 | 
105 |
106 | For a miter join you're gonna want to shoot rays from the start/end of the lines in the direction of their tangent. You will have to flip one so they're both heading the same direction. The point at which these two rays intersect is the point of your miter. You then just line to that point and then the start of the next bezier. You'll probably want to put a reasonable limit on miter and if the intersection is X units away default to a bevel. You could also leave this limit to the user.
107 |
108 | ### Round
109 |
110 | 
111 |
112 | This one is a bit more complicated. You can find the perfect ellipse segment that runs through both points like Inkscape's implementation, but while searching for a solution for this one I found a method that's almost as simple as a miter join and gives perfectly serviceable results.
113 |
114 | You find the intersection point just like a miter join, and then you employ this piece of psuedo-code magic:
115 |
116 | ```python
117 |
118 | #if there's no intersection radius is equal to 2/3 the disntance from from to to.
119 | radius = distance(from, to) * (2/3)
120 |
121 | #if there is an intersection then radius is equal to the minimum distance from either of our points to the intersection
122 | min(distance(from, intersection), distance(to, intersection))
123 |
124 | angle = acos(dot(from, to))
125 | dist_along_tangent = radius*(4/(3*(1/cos(angle/2) + 1)))
126 | arc = Bezier.from_points(
127 | from,
128 | from + from_tangent * dist_along_tangents,
129 | to + to_tangent * dist_along_tangents,
130 | to
131 | )
132 | ```
133 |
134 | I didn't come up with this, but I genuinely can not find the source of it. The ratio in dist_along_tangents gives really decent ellipse segments for most inputs. The only issue this method has is at extreme angles where the intersection is far into the distance. For these cases I check how far the intersection is from the midpoint of from and to. If it's distance is greater than the distance to from from to (I am so sorry) we discard it.
135 |
136 | ### Inside joins
137 |
138 | When the bezier curves overlap instead of failing to meet you're going to want to fall back to a bezel. We rely on a Skia Simplify call to remove internal geometry, but you could do so by clipping the beziers where they intersect. I think this would be a fair bit slower in some cases though as you would need to backtrack along the path looking for intersections.
139 |
140 | ## Put a cap on it
141 |
142 | 
143 |
144 | We've offset all of our curves and collected them into left and right collections. The next thing we need to do is stitch them up into one closed contour. If we're stroking a closed contour we can skip this step and just output the left and right sides as seperate contours since they are already closed.
145 |
146 | If we've got an open contour closing it is actually really easy. You can literally use the exact procedure used for Round joins for Round caps. Butt and Square are trivial.
147 |
148 | ## Getting angular with the editor
149 |
150 | Finding the normal offset for a given mouse postion from a VWS control handle is easy. It's the dot product of the normal and the mouse position both in coordinates relative to the vws control point.
151 |
152 | Where things get a bit trickier is the tangent offset. You could do the same as you're doing for the normal, but you're going to end up with weird lopsided handles on your VWS ribs!
153 |
154 | 
155 |
156 | You can fix this issue by scaling the shorter side's tangent offset like this:
157 |
158 | 
159 | ```
160 | scaled_tangent_offset = short_handle/max(left_offset, right_offset)
161 | final_tangent = tangent * vws_handle.tangent_offset * scaled_tangent_offset
162 | ```
163 | ## Offset closures
164 |
165 | I very much glossed over the contents of the closures in the outline above. Those closures take time t and return a normal or tangent offset respectively.
166 |
167 | The thing is if you just give ever yvws handle an offset and lerp between those you're gonna end up with pretty heavy discontinuities where they meet.
168 |
169 | I highly suggest you use either cosine or cubic interpolation within those functions. Cosine is easy to implement and pretty much eliminates the problem.
170 |
171 | ## Wrapping up
172 |
173 | There's more I could cover here, but I think I gave a pretty good overview on how this is accomplished. Big thanks to Logicalshift for his awesome library. Thanks to Fredrick for giving me the time to work on such a cool problem. I was bussing tables only a few months ago and he gave me a great opportunity. Thanks to coolfontguy69 for suggesting the tangent offsets.
--------------------------------------------------------------------------------
/roadmap/.timestamp:
--------------------------------------------------------------------------------
1 | Sun, 25 Dec 2022 03:15:28 +0000
2 |
--------------------------------------------------------------------------------
/roadmap/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: all
2 | all:
3 | make timestamp roadmap.png
4 |
5 | ODATE := $(shell eval date -Ru)
6 | POINTSIZE := 48
7 |
8 | .PHONY: timestamp
9 | timestamp:
10 | echo "$(ODATE)" > .timestamp
11 |
12 | roadmap.png: graph.dot timestamp
13 | dot -Gdpi=144 -Tpng < graph.dot > graph.png
14 | convert graph.png -font "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf" -pointsize $(POINTSIZE) -gravity SouthWest -stroke '#3c3c3c' -strokewidth 5 -annotate +5+5 "$(ODATE)" -stroke none -fill "#cfc" -annotate +5+5 "$(ODATE)" graph_date.png
15 | mv graph_date.png roadmap.png
16 | rm graph.png
17 |
18 | roadmap.svg: graph.dot
19 | dot -Tsvg < graph.dot > roadmap.svg
20 |
--------------------------------------------------------------------------------
/roadmap/graph.dot:
--------------------------------------------------------------------------------
1 | digraph G {
2 | graph [fontname = "Nimbus-Roman", fontsize=11];
3 | node [fontname = "Nimbus-Roman", fontsize=11];
4 | edge [fontname = "Nimbus-Roman", style=solid, fontsize=11];
5 | MFEKinit[style=dashed];
6 | ipc[style=dashed];
7 | feaparser[style=dashed];
8 | MFEKmetadata[style=dashed];
9 | MFEKufo[style=dotted];
10 | MFEKdesignspace[style=dotted];
11 | MFEKpathops[style=dashed];
12 | MFEKopentype[style=dotted];
13 | MFEKmetrics[style=dotted];
14 | MFEKexport[style=dotted];
15 | MFEKtthint[penwidth=0];
16 | MFEKpshint[penwidth=0];
17 |
18 | spiroinner[label=<spiro-inner
(c2rust)>]; //rlib
19 | skef[label=<libskef.so
(C dylib)>, style=dashed];
20 | skiacpp[label=<Skia
(C++ dylib)>];
21 | imgui[label=<Dear Imgui
(C dylib)>];
22 | freetype2[label=<libfreetype.so
(C dylib)>];
23 | SDL2[label=<SDL2
(C dylib)>];
24 | HarfBuzz[label=<libharfbuzz.so
(C dylib)>];
25 | rafxvulkan[label=vulkan>]; //rlib
26 | vulkan[label=<libvulkan.so
(C dylib…Vulkan SDK?)>];
27 | MFEKtthint[label=<MFEKtthint
(FOSS VTT?)>];
28 | MFEKpshint[label=<MFEKpshint
(unnecessary?)>];
29 | "math" -> "skia-safe"; //rlib
30 | "skia-safe" -> skiacpp; //rlib
31 | "glifparser" -> "skia-safe";
32 | "math" -> "glifparser"; //rlib
33 | "math" -> "kurbo"; //rlib
34 | "math" -> "flo_curves"; //rlib
35 | "math" -> skef [style=dashed];
36 | "MFEKpathops" -> "math" [color="#cc0000"]; //rlib
37 | "MFEKstroke" -> "math" [color="#cc0000"]; //rlib
38 | "MFEKglif" -> "math" [color="#cc0000"]; //rlib
39 | "MFEKglif" -> "MFEKmetadata" [penwidth=3.5];
40 | "MFEKpathops" -> "glifparser" [color="#cc0000"]; //rlib
41 | "MFEKstroke" -> "glifparser" [color="#cc0000"]; //rlib
42 | "MFEKglif" -> "glifparser" [color="#cc0000"]; //rlib
43 | "MFEKinit" -> "glifparser" [color="#cc0000"]; //rlib
44 | "MFEKufo" -> "MFEKinit" [penwidth=3.5];
45 | "MFEKufo" -> "MFEKglif" [penwidth=3.5];
46 | "MFEKglif" -> "MFEKufo" [penwidth=3.5];
47 | "MFEKglif" -> "ipc" [color="#cc0000"]; //rlib
48 | "MFEKufo" -> "ipc" [color="#cc0000"]; //rlib
49 | "MFEKufo" -> "qd-unic" [color="#cc0000"]; //rlib
50 | "MFEKmetadata" -> "qd-unic" [color="#cc0000"]; //rlib
51 | "MFEKmetadata" -> "glifparser" [color="#cc0000"]; //rlib
52 | "MFEKpathops" -> "flo_curves" [color="#cc0000"]; //rlib
53 | "glifrenderer" -> "glifparser"; //rlib
54 | "glifrenderer" -> "math"; //rlib
55 | "glifrenderer" -> "skia-safe"; //rlib
56 | "MFEKufo" -> "norad" [color="#cc0000"]; //rlib
57 | "MFEKmetadata" -> "norad" [color="#cc0000"]; //rlib
58 | "MFEKinit" -> "norad" [color="#cc0000"]; //rlib
59 | "MFEKstroke" -> "skia-safe" [color="#cc0000"]; //rlib
60 | "MFEKglif" -> "skia-safe" [color="#cc0000"]; //rlib
61 | "MFEKpathops" -> "skia-safe" [color="#cc0000"]; //rlib
62 | "MFEKpathops" -> "kurbo" [color="#cc0000"]; //rlib
63 | "imgui-skia\n-renderer" -> "skia-safe"; //rlib
64 | "MFEKglif" -> "MFEKinit" [penwidth=3.5];
65 | "MFEKufo" -> "MFEKstroke" [penwidth=3.5];
66 | "MFEKufo" -> "MFEKmetadata" [penwidth=3.5];
67 | "MFEKufo" -> "MFEKpathops" [penwidth=3.5];
68 | "MFEKglif" -> "glifrenderer" [color="#cc0000"]; //rlib
69 | "MFEKufo" -> "glifrenderer" [color="#cc0000"]; //rlib
70 | "MFEKglif" -> "skulpin" [color="#cc0000"]; //rlib
71 | "MFEKufo" -> "skulpin" [color="#cc0000"]; //rlib
72 | "imgui-skia\n-renderer" -> "imgui-rs"; //rlib
73 | "skulpin" -> "imgui-skia\n-renderer"; //rlib
74 | "skulpin" -> "skia-safe"; //rlib
75 | "skulpin" -> rafxvulkan -> vulkan; //rlib
76 | "sdl2" -> SDL2; //rlib
77 | "imgui-rs" -> imgui; //rlib
78 | "imgui-rs" -> "imgui-sdl2";
79 | "imgui-sdl2" -> "sdl2";
80 | imgui -> SDL2 -> vulkan;
81 | imgui -> freetype2;
82 | //"MFEKufo" -> "MFEKabout" [penwidth=3.5];
83 | "MFEKabout";// -> "imgui-skia\n-renderer"; //rlib
84 | "glifparser" -> "spiro"; //rlib
85 | spiro;
86 | spiro -> spiroinner; //rlib
87 | "MFEKmetrics" -> "skulpin" [color="#cc0000"]; //rlib
88 | "MFEKmetrics" -> "harfbuzz" [color="#cc0000"]; //rlib
89 | "harfbuzz" -> "harfbuzz-sys" -> HarfBuzz;
90 | "MFEKmetrics" -> "MFEKmetadata" [penwidth=3.5];
91 | "MFEKmetrics" -> "MFEKopentype" [penwidth=3.5];
92 | "MFEKopentype" -> "MFEKmetrics" [penwidth=3.5];
93 | "MFEKmetrics" -> "glifrenderer" [color="#cc0000"]; //rlib
94 | skiacpp -> HarfBuzz;
95 | HarfBuzz -> freetype2; //rlib
96 | "MFEKmetrics" -> "glifparser" [color="#cc0000"]; //rlib
97 | "MFEKmetrics" -> "feaparser" [color="#cc0000"]; //rlib
98 | "MFEKmetrics" -> "ipc" [color="#cc0000"]; //rlib
99 | "MFEKdesignspace" -> "MFEKufo" [penwidth=3.5];
100 | "MFEKdesignspace" -> "MFEKabout" [penwidth=3.5];
101 | "MFEKdesignspace" -> "MFEKinit" [penwidth=3.5];
102 | "MFEKdesignspace" -> "MFEKmetadata" [penwidth=3.5];
103 | "MFEKdesignspace" -> "ipc" [color="#cc0000"]; //rlib
104 | "MFEKdesignspace" -> "MFEKmetrics" [penwidth=3.5];
105 | "MFEKdocs" [style=dashed];
106 | "MFEKabout" -> "skulpin" [color="#cc0000"]; //rlib
107 | "MFEKopentype" -> "skulpin" [color="#cc0000"]; //rlib
108 | "MFEKopentype" -> "norad" [color="#cc0000"]; //rlib
109 | "MFEKopentype" -> "glifparser" [color="#cc0000"]; //rlib
110 | "MFEKufo" -> "MFEKexport" [penwidth=3.5];
111 | "MFEKufo" -> "MFEKmetrics" [penwidth=3.5];
112 | "MFEKmetrics" -> "MFEKglif" [penwidth=3.5];
113 | "MFEKdesignspace" -> "MFEKexport" [penwidth=3.5];
114 | "MFEKdesignspace" -> "MFEKopentype" [penwidth=3.5];
115 | "MFEKexport" -> "glifparser" [color="#cc0000"]; //rlib
116 | "MFEKexport" -> "math" [color="#cc0000"]; //rlib
117 | "MFEKexport" -> "skulpin" [color="#cc0000"]; //rlib
118 |
119 | MFEKtthint -> "skulpin" [color="#cc0000"]; //rlib
120 | MFEKpshint -> "skulpin" [color="#cc0000"]; //rlib
121 | MFEKtthint -> "glifparser" [color="#cc0000"]; //rlib
122 | MFEKpshint -> "glifparser" [color="#cc0000"]; //rlib
123 | MFEKtthint -> "glifrenderer" [color="#cc0000"]; //rlib
124 | MFEKpshint -> "glifrenderer" [color="#cc0000"]; //rlib
125 | "MFEKglif" -> MFEKtthint [penwidth=3.5];
126 | "MFEKglif" -> MFEKpshint [penwidth=3.5];
127 |
128 | labelloc="t"
129 | fontsize=32
130 | label=<“but that’ll take years!”: the MFEK roadmap
©2021-3 Fredrick R. Brennan & Modular Font Editor K Authors>
131 | }
132 |
--------------------------------------------------------------------------------
/roadmap/roadmap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MFEK/docs/07f862966db42a8a0be27c9c335a799fbb2de21c/roadmap/roadmap.png
--------------------------------------------------------------------------------