Open ctrlcctrlv opened 3 years ago
I definitely think it's possible, and some of these items seem like pretty obvious fits. Let me think in point form:
lib
type which norad exposes as an arbitrary plist attached to the lib. I need to improve serde support upstream, but I've been thinking about API that lets you assign any type that impls Serialize
& Deserialize
to the lib (or a key on the lib) which would come pretty close to supporting your use-case? There is the downside that modifying that data will require a serialization roundtrip, but I'm not sure if this is a major concern in practice.Glyph::load
to load a single glif
file? Are there other additional functionality you need?skia
feature that adds From
impls for paths, affines, etc?So yes, I agree that collaboration on most of these points sounds very reasonable, with only a few items I'm not sure about.
(I wrote some code to decompose a composite: https://github.com/linebender/norad/blob/add-example-letterspacer/examples/letterspacer.rs#L474-L521)
I haven't looked at MFEK but maybe capabilities can be merged over?
It doesn't look like that code checks if there's circular references, making my version safer.
About the image data, my glifparser.rlib basically does just that: gives you a Vec<u8>
. I also figure out for you if it's a PNG (because if it's not, that violates the UFO glif spec; if the consumer cares about such things, they can bail right here). But I don't try to do much more than that. However, because the UFO glif spec has language around images being able to be colored, I will eventually be handling that, but only for PNG files. I'll add a PNG crate to glifparser and then, if it's an image with color
set, handle the coloring for you and give you a function like Image::bitmap()
you can call. Right now, Image::data()
just gives you a Vec<u8>
though that you need to figure out what to do with.
It doesn't look like that code checks if there's circular references, making my version safer.
Cool. Yes, neither norad nor my code currently deal with malformed component trees.
Cool, doing raw image data works for me, and then if we wanted to use something like the Image
crate we could put that behind a feature flag.
I think ufoLib also passes back bytes and no Image object or anything 🤔
Well, obviously the degree people implement the spec varies, but the spec says:
Coloring Images
There are two places that the color for an image can be defined: the color attribute of the image element and the color defined in layerinfo.plist. The color defined in the image element’s color attribute always takes precedence. If that is not defined and the color is defined for the layer, the layer’s color should be used. If both of these are undefined, the image’s colors, without modification, should be used unless an authoring tool has a default color for images. If a color is to be applied, the authoring tool should convert the image to grayscale and then apply the color. This modified version of the image must not be saved into the images directory.
So, I have no clue how you can sanely do this without parsing the PNG, reading a bitmap, and clobbering the pixels.
I suppose, technically, you could implement it insanely another way:
IHDR
is Color Type 3 (indexed colors), skip to step 3IHDR
Then you can just return back the modified PNG data which you've mangled in memory. But that seems crazy to me, to do that and not just return a bitmap.
I guess this depends on goals? I assume the whole rationale for this is that some authoring tool (robofont?) wanted to be able to use grayscale images as masks, and that ended up being baked into the spec. I would be personally happy to just return the image data and the color information, and then let the user do whatever they want with that.
Put another way: per the spec, I would not consider norad to be "the authoring tool".
@cmyr It is as you say, a matter of goals.
For example, for me, glifparser.rlib is tightly coupled to MFEK project, mostly MFEKglif, but everything else too. Converting back and forth between Skia paths is therefore absolutely a necessity—I put it behind a skia
feature tag and a trait, but obviously all of my software is going to want that tag and trait. And certainly, for me, it makes no sense to do this coloring in MFEKglif. MFEKufo is going to need it to display properly glyph previews of the whole font. MFEKmetrics is going to need it when it's ready. MFEKinterpolate will probably need it, but might not display images at all. For me to just copy therefore the MFEKGlif
type isn't feasible. Maybe some day it'll go into its own library, but then again, a feature tag seems fine to me. It's MFEK/glifparser.rlib, not ctrlcctrlv/glifparser.rlib :-) While I'm working on making it so people can ignore our features which don't apply to all use cases, like skia
and mfek
, obviously, I'm not going to cut them out of the source tree for no appreciable gain, and added confusion around naming. (MFEK/glifparser-mfek.rlib? Yech.)
glifparser.rlib also has a type I didn't talk about since ① it's so new and ② not related to norad, but which illustrates the point: MFEKGlif
. That's behind mfek
feature tag, and its job is to read the MFEK private library, as well as our items in <lib>
(we have some in both. Typically our items go in our private library, but when we implement a third pseudo-standard key, like contour names, which robofont came up with, we put that in the general <lib>
), which will never have a use outside MFEK probably. Things like open contour stroke (variable-width stroking, pattern along path, etc. definitions), our internal glif layers, (we consider a glif file as itself having layers, but these are private layers that won't appear in output, used for things like tracing a scan from a background image or keeping the source contours for a boolean operation) etc. MFEKGlif
(note "G") type therefore would never find its way into Norad core, parsing our private library is totally useless to general .glif
file API's.
I would be personally happy to just return the image data and the color information, and then let the user do whatever they want with that.
However, since the spec mandates what should be done, it makes sense to me a ufo library will contain a standard compliant implementation that every app will need and benefit from being the same. Of course apps can do more, if they wish
@davelab6 This is now implemented in glifparser
.
There's a glifparser::Image
type. It has two functions, load
and decode
, and a field called data
which is a DataOrBitmap
, a self-explanatory enum.
If you call load
, data
will be just the bytes.
If you call decode
after load, data
will be a struct Bitmap{pixels: Vec<u8>, width, height}
. pixels
are guaranteed to be RGBA8888.
If the glifparser::Image
has a color, decode
will apply the color.
If the application changes the color, it can call load
and decode
again to get a new bitmap.
Changing the color is up to the application, but applying the color is obviously the job of the UFO library, since the spec describes how it should be applied, and applications aren't free to just apply colors however they wish.
I updated the README file of glifparser.rlib
recently to reflect its status and the space it takes up in the ecosystem: https://github.com/MFEK/glifparser.rlib
I still haven't found the time to sift through glifparser etc. to see if there's anything to steal... one day!
In my opinion, most obviously stealable parts are:
Glyph
(glifparser Glif
) being a toplevel type and not a subordinate typeHm yes, points 1 sounds interesting, point 2 I need to review (what does norad not support?), but point 3 I'm not sure about. What specifically do you need to have free-standing Glyph
s?
Point three is interesting; there are various projects (such as RoboCJK) that are starting to use the .glif
format but outside the context of a normal UFO. That said, the current Glyph
in norad works fine as a top-level type, or at least should... I don't use it like this anywhere so I'm not totally sure, but it has (for instance) a public load/save API.
Maybe one possible limitation is that it might not serialize the lib
, since normally that's serialized at the layer-level?
Free standing glyphs are normal in font development which is centered around UFO format. There are so many cases you might want them I couldn't even begin to list them all.
Regarding point two, your Image
type is not like ours, ours is a created type that can happen any time after parse time. You get from us at parse time a GlifImage
very similar to your Image
. Our Image
is guaranteed valid, contains the data of the image, and also applies the color
to the image data…if there's any reason why the GlifImage
can't be converted you get back an error and not an Image
. As long as you can get a bitmap though, the coloring is an infallible operation since it just involves math on the pixel color values.
lib
not being serialized would be quite a big issue for me yeah.
Also, my Image
type supports a lot of codecs, but it warns you if you're doing something out-of-spec:
if codec != ImageCodec::PNG {
warn!("Image not PNG! `fontmake` and other UFO spec conformant progams will refuse to embed the bitmap. MFEKglif may still be able to display it, however, so only use it for proofing, not output.");
}
I support BMP, GIF, and (most importantly) JPEG and WebP besides the UFO spec demand of PNG.
The reason I support all these codecs is that people want to trace from books usually and storing many hicolor 600dpi scans losslessly in a font repo is not logical.
Btw my codec matcher might not be the best but I haven't found any valid files it fails on:
let codec = match self.data.unwrap_data().chunks(12).next() {
Some(&[0x42, 0x4D, _, _, _, _, _, _, _, _, _, _]) => ImageCodec::BMP,
Some(&[0xFF, 0xD8, _, _, _, _, _, _, _, _, _, _]) => ImageCodec::JPEG,
Some(&[0x89, 0x50, 0x4E, 0x47, _, _, _, _, _, _, _, _]) => ImageCodec::PNG,
Some(&[0x47, 0x49, 0x46, 0x38, _, _, _, _, _, _, _, _]) => ImageCodec::GIF,
Some(&[0x52, 0x49, 0x46, 0x46, _, _, _, _, 0x57, 0x45, 0x42, 0x50]) => ImageCodec::WebP,
_ => ImageCodec::Unknown
};
If it gets it wrong, then API will just return back an Err(e)
with the reason when it tries to use .with_guessed_format()
from image
crate.
Hello,
I just got done writing glifparser v1.0.
glifparser was written primarily for MFEKglif but it's also used in MFEKstroke, and in MFEKufo (unreleased) for previewing glyphs.
It's currently in
mfek
branch, awaiting @MatthewBlanchard and I to patch MFEKglif and MFEKstroke to support the changes. The biggest change is that there are no more panic's, originally, I just panicked via.expect(...)
in a lot of cases because I knew MFEKglif was the only consumer and had a custom panic unwinder that would pretty print the message in red and make it clear what went wrong.glifparser can do quite a few things norad cannot:
kurbo::Affine
!)I was thinking, maybe
norad
can support usingglifparser
at user option, via compile-time feature. Right now what I'm doing is, usingnorad
with theUfoDataRequest
I added and just not reading glyphs with it, using it for metadata only.I think closer collaboration is possible. Is it desirable?