-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Design a new source format (object representation and/or on-disk representation) #10
Comments
Why have masters at all? I understand how they came to be historically and the case for instances (a set of static locations on axes) but if you're designing a new on-disk and object format, why not drop the charade and just go straight for glyphs? Each glyph would have control points that are not just a pair of coordinates but but vectors with enough values to locate them through each axis. Editors could use the instance location data to draw what people currently think of as "masters", but if you're coming up with a new model for holding the data I don't think dragging the current paradigm with you is necessary. |
Interesting idea! I started to come up with reasons why not, but I'm not sure any of the reasons I have are valid. Going "variable first" has a lot of advantages, too. Hmm. Also I'd like to hear any thoughts you have from your investigations on what makes a good version-control-aware on-disk format. |
I think, the way you set up the masters (containing glyphs) are not useful. They are just some positions in the designspace that have some info attached (coordinates, vertical metrics …). What you have as top level "glyphs" might be better called encoding. (Not sure why that should be in a different place than the outlines.) Glyphs and layers should be its own thing. But have some connection to the masters (to get to the vertical metrics). The idea of one set of control points with different coordinates for each point in the design space is bad. You need to be able to store incompatible outlines per glyphs. Not all font projects are variable fonts. There are color fonts, layer fonts, unfinished fonts. One thing I never understood: AFAIK the default for an axis is where the outlines in the glyf table are? Then you NEED a proper master (meaning "master layer" aka a set of outlines/shapes) at that location. And what if you set the default in the axes and later move or remove that master? So in the font storage, you need to point to a master that is supposed to go into the glyf table and it's coordinates will be the default. |
I concur with @alerque in that masters are no longer needed and the “design space” should move into individual glyphs. Since this puts more information into each glyph, I think a glyph should have its own directory. Personally, I dislike the separation of layer in UFO. After working on a glyph, this is what a version control system shows for UFO:
whereas this feels more intuitive to me:
There could also be space for a reserved
|
I would really like to avoid a directory-based system, in favour of a single file, unless there are very strong arguments to do with version control. (And they have to be real arguments - not just that it looks prettier - because git is very good at merging files.) |
@schriftgestalt What would be the rationale for having layers be their own thing, rather than attached to glyphs (inside a master)? I take your point about incompatible masters, which would make storing deltas impossible. For example, sometimes I have a “skeleton master” with open paths, and then extrude the paths to get the real masters. That wouldn’t work under a delta-only model. |
As a compromise, it could be one file per glyphs (including all its layers and meta data). But as I found out by adding a file format with a similar structure, you need to store the glyph order separately and that is again some data that can get out of sync. One reason I added that format was to be able to only write changed glyphs. This is mostly relevant for very big files like CJK. Other than that, I totally prefer single files. |
I meant having glyphs and layers as one thing, outside of the masters. |
A single-file system makes a lot of stuff more complicated in my experience. Want to hide certain aspects of the project from version control? Ignore the relevant file. (Thank again, @schriftgestalt <3) Also, diffs become more difficult to render. Since CommitGlyphs does not (yet?) support the new .glyphspackage format I was looking into making my own glyphspackage GUI. One of the main problems is the interface for a diff. Because a modified .glyph files might have changes on one layer or on multiple layers it is difficult to tell which layers were added, which were deleted, and which were modified and how to present this to the user. Having layers separated by files offloaded that distinction to the VCS which is already equipped to present delete/add as rename if applicable, or to show adding a layer without marking the entire glyph as modified. |
That is a good point against my argument. |
Designers should not be looking at raw diffs, so diff rendering is irrelevant. We can write tools to make things look nicer. |
I lean towards the more files is better than one approach. As you (Simon) say Git is really good at merging files, but people are not so good at keeping track of what is what, as Florian says it's hard to present that info in a UI, and they are harder to manipulate by hand. Taking the multiple file approach too far though is a disaster. With the concept of masters out of the way and just storing Glyphs, one file per glyph is a sweet spot. Georg is right this introduces the need for some ordering data that can get out of sync, but re-ordering something in a single file format is a nightmare for diff UIs compared to file based layouts. What the data files looks like will have at least as big an effect on VCS systems as the file layout. Internally Git tracks blobs across files anyway, but understanding the difference between an addition, a removal, and a change is harder or easier based on the syntax. Git happens to be much better at linewise operations with relatively small amounts of data on each line, with some surrounding container syntax that makes blocks of related stuff easy to match even if everything in the block changes, but not so much that it becomes cruft. |
Yes and no. True designers shouldn't need to be viewing raw diffs, but how well current diff tooling can sort out the difference between adds/removes/changes and how well a programmer can read a diff is a pretty good proxy for how easy it is going to be to come up with visual tooling and a reliable UX that makes sense of the data. |
Ideally, yes. In practice I still look at a lot of raw diffs, hb-shape ascii-diagrams, etc. |
Yeah - but why? Why shouldn’t glyphs (the layer drawing part, not the common glyph metadata part) but attached to masters? |
Maybe |
And maybe a |
If the layer had a name, then there could be a set of axes defined for each named layer. |
Every layer has a name. “Background”, “2021-01-27T23:10:58”, “Condensed”, “BoldDisplay”, “finalfinalfinalv5”. Or are you referring to a specific/format defined name? |
In your new drawing, where to put vertical metrics? And why not attache the glyphs to the masters? Each master would have a list of glyphs and to get to all relevant layers (to check compatibility or to interpolate) you need to go to each master, ask for its glyph for the glyph you are looking for (note: two times the same word for totally different things) |
I don't understand the axis per named layer. |
We could handle vertical metrics the same way a binary font does: one place of metric metadata, with axis-specific variation store. |
Showerthought: We are treating these questions (on disk representation, object hierarchy) as the same but we should address them separately. We might not want to store masters explicitly but we may still want to address them: font.masters[“Light”].capHeight is still a question we might need to answer - for example when converting to UFO. |
I think we think of slightly different things when we speak about "Master". For me, the place to store axis-specific data is called masters. |
Finding a good structure should work for both the disk representation and the object model. There might be small differences here and there but the general structure should agree. that would be confusing for the user and would need a lot plumbing to read and write stuff. |
Regarding single file vs. directories: we found that git and single file are frequently problematic. Think of the amount of diffs generated by changing the color of a row of glyphs or renaming a component and how close these diffs can be to one another, making git consider them one hunk. Now throw in multiple designers changing stuff in different places and merging back and forth and you have a solid stream of merge conflicts on your hands (think Row hammer attack). With the amount of diffs a day of work can result in, designers usually give up and just merge everything. GUI tools would be nice but don't exist (GlyphMerge or whatsitcalled is buggy and unmaintained the last time I looked) and then someone has to maintain them. Partitioning glyphs into files is a blunt instrument, but it helps. |
The solution to that is not necessarily to use the blunt instrument, but perhaps to think about what aspects of a font are normally changed together, and then arranging the file format to ensure they are in the same place. |
(I'm not absolutely wedded to a single-file format, but I want to make sure we're fixing problems, not just applying band-aids to them.) |
Sometimes (not always, but I think in this case) the blunt instrument also makes a better building block for specialized tooling. If the heavy lifting is done you can work that into a friendly UX easier than if you have to muck around in roots of things.
Yes, sort of. If A and B are changed together frequently, separating them from X and Y is a step in the right direction, but keeping A and B independent is even better. Not having changes to A touch B and making sure they are in different hunks is even better. |
I think we need to distinguish between images and backgroundImages. |
In Glyphs, the distinction between svg/sbix image and background images is done by setting a flag on the layer. |
Ok. But background images and “foreground” images are sufficiently different things that I think it makes sense to separate them. And I’m not sure a flag on the layer is sufficiently general - obviously you can’t do this in Glyphs, but what if someone wanted both? |
It's the "middle" of the variable font, and it creates the two mapping segments (min-default, default-max). See designspace files.
I don't think this is actually a meaningful concept in OpenType fonts. What is gained by saying that the letter A is actually RTL Adlam? The shaper's going to ignore you and look in the UCD. And that makes me wonder about why we need to segment the kerning dictionary by direction, because the direction is determined by the inputs to the shaper, not by the font. Looking at the long threads on the UFO spec discussion, the consensus now seems to be "just store the kerns in visual order". |
My last comment was purely informational.
I know that. My question is why we need to store it with the axes. We need to define a default master (that is used to generate glyf table) anyway (and we dont’t what to imply the default master from the |
I think one answer is that it just makes things easier; particularly compatibility with designspace-based workflows. |
But what happens if you have min/default/max settings in your axis and no masters to fill up the space? You always have to compute the values anyway so why store them in the first place? |
Because it does make things easier and improves compatibility with designspace-based workflows. Glyphs is not the only font editor I want to support here. |
Because there might be alternate glyphs without an unicode and you need to know the direction in the editor. In Glyphs I don’t need those vales in the file but to support custom naming, it is useful to be able to store it in the file.
Because you might want to put them in different lookup and you most certainly need different value records for RTL. And the handling of kerning classes are different. A RTL pair class pair is (visually) left class + (visually) right class. In LTR it is the other way around. It is indeed possible to infer most of it (as I did in Glyphs 2) but that is a mess. And there might be pairs that need different values for RTL than for LTR (punctuation). And if you store the values "visually" you need to switch them back to string order on export. And so you still need to know if a pair is RTL or LTR. So you either need the distinction while designing or on export. |
My question is not about Glyphs. I’m trying to understand how designspace-based workflows are supposed to work. Storing (and specially being able to manually set) min/default/max values per axis seems to me like storing the LSB/RSB with the file and expect it to be the values that are used. |
Sure. I think it makes sense to separate data storage issues (expressing what you want to do) from export issues (working out how you do it). There's obvious a balance when designing file formats about how close to the export format you want to be. In cases like default axis location and vertical metrics, I've gone on the side of being friendly to export. But that's because adding those fields is relatively uncomplicated and doesn't add much space to the file. Storing kerning direction is a different issue, though; it you either do it by adding four parallel structures, or tagging each kern pair with direction, neither of which is particularly pleasant if you don't need to do it, and is wasteful for the vast majority of fonts that don't have mixed direction kerning. And I am not convinced that you do need to do it. What we want to be able to express is "these two glyphs should be brought n units together/apart". The second thread (particularly Bahman's example) shows that visual-left-to-right (and equivalent top-to-bottom) storage allows you to create the GPOS lookups you need on export from a single kerning dictionary. The only edge case is when you want a different number of units for neutral characters based on the script direction. But I don't know if that's a real possibility or just a theoretical one. |
I just reread that thread. That example makes look simple. And probably it is. I see one potential problem: How to determine what pairs will be duplicated in the RTL lookup? Just use all pairs that are only composed out of BIDI neutral glyphs (another reason to store script/direction per glyph)? That is convenient as you don’t need to kern them again for RTL but may add a lot pairs that are not needed. And it is not wasteful or anything. So we need horizontal and vertical kerning for sure. Why not add RTL to make it clear? I don’t see a forth? |
OK, so we either need to store direction per glyph or multiple kerning tables, but not both! |
That direction bit in the glyph is used for different purposes. So I would recommend to keep it. And determining what direction a pair belongs to can be hairy. It need to check all kerning classes and check the direction of all glyphs (again a great source of ambiguity (what if a class contains RTL and LTR and what if you have a LRT plus RTL pair)). Computing min/max values for the axes is a pice of cake in comparison and there your argument was to help the exporter? |
I hear what you're saying. I think what I'm going to do - because (from the UFO threads) this is a difficult issue - is assume for now that a single dictionary (plus a direction bit on the glyph) can do what we want. And then, when we have actual implementation examples, decide whether something else is necessary. We don't need to design everything up front, and in fact it's probably bad to decide everything up front before we have a working implementation. |
Is it about "minimal" data exchange format (similar to UFO) or something that should cover real source data? What I see on the latest diagram is not enough to cover our object model (which we store in VFJ). We can convert to it, but that will require a lot of compromises. |
It would be ideal to support a subset of all real-world data. What's missing to support your model? (Note that all objects have an area to support format-specific information which other tools might not be interested in.) |
There are some things that we have (or want to) make different at the font/master level, but much more important is sub-layer part of the object model. We do something that is more like SVG (or, better COLRv1) structure which is layer > element > contour, with element containing all the "drawing" rules such as fill, stroke, clipping, effects (shadow, etc) and shape-building properties (if element is "open" so it should be embedded into the final contour). Element may be defined at the layer level or outside it, then referenced from other layers (a bit similar to lookup/feature relationship in the OT layout definition). Elements may contain references to other elements (we use it to make "filter" elements and element groups). It can be considered a terminology problem (so proposed "layer" is essentially is similar to our "element"), but then should be a way to specify layers hierarchy (so "Bold" layer/master may contain several layers/layer groups, like "serif"). Of course, our structure may be simplified to create the "final" contour, but that will make the format "export", not "source", at least for us. Another part that needs to be clarified is a conception of the "value" (at many levels) that can be much more than simple int or float. Many "values" can be expressions, or delta arrays (or "master values" which is more or less the same). Specification or layer/master horizontal metrics as "zero point" + advance width is OK for "export" but source format may be more flexible: both Glyphs and FL can specify any of LSB/RSB/AW values (as fixed numbers or complex expressions). That, in turn, opens a question of "measurement line": measurement of the SB values as distance from the bounding rectangle is not that effective (at the "source" level). Then italic comes, opening yet another box :). There are some minor things (like missing "slant" value in the proposed Everything above may be a result of my misunderstanding of the original task: I probably mean something different by the "source" format: is it source as |
Thanks very much for the input! I think the current layer model (including Storing values as expressions is something that @alerque was interested in but we need to find a grammar to do that which would allow all implementations to turn those expressions back into a concrete value when needed. I'm not sure which values "under" a master would require delta arrays. Can you explain that a bit more? To make the purpose clear, I think it is a little of both; my personal need is for an abstraction around "font source" to be used as input to Flux, but also to fontmake and other things which will eventually output font binaries. But if we can do this in a way that allows for the best possible interchange of multiple master fonts in source format between font editors, then that's an added bonus, and we may as well aim for it. |
I guess it should be both. Same is actually true for "delta exceptions" (where applicable). Something like |
Having tried to implement the split fields-versus-dict thing, I have come to agree with this; digging in two different places for metrics is more tricky than I thought. However, we do need standard format-independent names for common metrics. |
@yarmola: What sort of values need to support expressions? e.g. do you want this for X and Y of points on a contour? (shiver) And I'm not sure where delta arrays are necessary when most values are related to a master somehow anyway. (By the way, Glyphs provides a really useful example .glyphs source which demonstrates a lot of the more complex features of the file format. It's an extremely useful test case for implementors working on cross-format support! I wonder if FontLab has anything like this...)
I'm just going to a straightforward six-element affine transform now. |
Eventually we want to make "everything" calculable, but you can imagine how complicated it is )) So for now we have expressions for metrics (LSB/RSB/AW), guides and anchors. We also have glyph-building recipes which may be considered "expressions", but at higher level. We also have tags in glyphs, font guides (including zones and TT stems). Glyph tags do many things, tags in guides, etc allow to define "local" sets which appear only if there is an intersection with glyph tags (so many sets of zones is possible). I'll make VFJ sample, but I need to address few issues first: we have "filters" — a kind of element that has some processing instructions instead of path definitions. PowerBrush, Bucket Fill and Glue are simple examples, but there are more. I need to provide "cache" copy of filter-generated paths in VFJ so you will not have to rebuild it. Same thing as the expressions syntax that I propose: "final" data + instructions.
It is not that simple: if you have 2 rotate values, you can interpolate rotate value which is not the same as interpolating 2 linear matrices. (Imagine interpolating from +90 to -90). Currently we do simple affine transform, and it already is not fully compatible with newer specs such as COLRv1.
This is the reality now. But if you look into future, there may be very good reasons to have delta values, first at font dimensions level (or in feature definitions), then at path level. |
OK, I've started making the Python implementation document itself, including generating the object hierarchy diagrams: |
Recently I have been fighting to get fontmake to handle Glyphs open corners, which is making me worry about something to do with this project: different font editors are extending the standard "list of Bezier curves" glyph model in various non-standard ways (open corners, corner/cap components, etc.) which require specialist knowledge and code to handle when converting between formats (especially to binary). There are two ways to handle this:
Neither is good. |
but the point of a library like this is to process font sources. And those will contain more and more custom stuff. So the question is not if option 1 or 2 but how do we make option 2 work without getting crazy. |
Maybe a composite of (possibly nonlinear) functions yielding matrices (linear). The functions can be cached/memoized, and in an editor application, the matrices can help backpropagate edits/inputs to the functions in a way that shouldn't be too unintuitive (i.e. by quantizing on the parameter space, or by inserting a control point in the function). Computing functions like this from masters is also feasible. |
You're overthinking it. |
@simoncozens what are your latest thoughts on "masterless design" and a good source format for COLRv1? |
I think that Babelfont format would work fine for masterless design, as layers can have a location. For full COLRv1 support, we would want to be storing the paint tree directly, so we need a serialization format for that. @madig suggests that axes should be able to declare that they do not affect any advance widths. |
Ideas for hierarchy so far:
The text was updated successfully, but these errors were encountered: