-
Notifications
You must be signed in to change notification settings - Fork 223
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
Extend the GlyphRun object with set-serialization functions? #24
Comments
This sounds like an interesting idea. For SVG in particular, however, there are many possible ways in which someone might want to convert a GlyphRun to SVG. For example, someone might want to change the color of every glyph, or do some other customization/transform. I'm not sure we could easily allow that sort of customization without adding a lot of complexity, and if we didn't allow customization, I'm not sure how many people would be able to use it. |
Sure, but this wouldn't replace the way you can serialize at the moment, merely add to the ways in which you can. If you want fine pathing control, you should still be able to use the syntax that's currently advertised in the README.md, but if you just want to get a shaped piece of text serialized to SVG (or some other format) then not having to roll your own code each time is very nice indeed. One thing that might be worth doing, though, is renaming the functions to reflect what they really do: |
I would like to support this request by Pomax. I use fontkit as my only available option to import text as an SVG (path) into paper.js. This allows me to load any font into paper.js. I wish I could code it myself but I am lacking the necessary skills. If there is anything to facilitate the implementation of this feature, please let us know. |
I’ve just proposed OpenType/opentype-layout#4 — which could be an inspiration to provide a compatible function for GlyphRun that would export a JS structure that serializes into JSON the same way as In addition, I shall add that hb-view --font-file="CormorantGaramond-Regular.otf" --features="+liga" \
--text="office" --font-size=256 --output-format=svg outputs: <?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="583.683594pt" height="342pt" viewBox="0 0 583.683594 342" version="1.1">
<defs>
<g>
<symbol overflow="visible" id="glyph0-0">
<path style="stroke:none;" d="M 24.0625 70.40625 L 105.21875 70.40625 L 105.21875 -185.59375 L 24.0625 -185.59375 Z M 43.015625 -160.25 L 43.015625 -168.453125 L 85.25 -168.453125 L 85.25 -160.25 L 68.359375 -160.25 L 68.359375 -150.53125 L 85.25 -150.53125 L 85.25 -142.34375 L 42.75 -142.34375 L 42.75 -150.53125 L 59.640625 -150.53125 L 59.640625 -160.25 Z M 42.75 -109.0625 L 42.75 -135.421875 L 68.359375 -135.421875 L 68.359375 -117.765625 L 85.25 -117.765625 L 85.25 -109.0625 Z M 51.203125 -117.765625 L 59.640625 -117.765625 L 59.640625 -126.71875 L 51.203125 -126.71875 Z M 42.75 -93.4375 L 42.75 -101.890625 L 85.25 -101.890625 L 85.25 -93.4375 L 68.359375 -93.4375 L 68.359375 -75.515625 L 42.75 -75.515625 L 42.75 -84.21875 L 59.640625 -84.21875 L 59.640625 -93.4375 Z M 77.0625 -69.890625 L 77.0625 -86.53125 L 85.25 -86.53125 L 85.25 -61.1875 L 42.75 -61.1875 L 42.75 -69.890625 Z M 59.640625 -40.953125 L 59.640625 -55.296875 L 85.25 -55.296875 L 85.25 -26.375 L 42.75 -26.375 L 42.75 -55.296875 L 51.203125 -55.296875 L 51.203125 -34.8125 L 77.0625 -34.8125 L 77.0625 -47.109375 L 68.359375 -47.109375 L 68.359375 -40.953125 Z M 42.75 17.921875 L 42.75 -11.015625 L 85.25 -11.015625 L 85.25 17.921875 Z M 51.203125 9.46875 L 77.0625 9.46875 L 77.0625 -2.8125 L 51.203125 -2.8125 Z M 42.75 32.25 L 42.75 23.546875 L 85.25 23.546875 L 85.25 32.25 L 77.0625 32.25 L 59.390625 44.28125 L 85.25 44.28125 L 85.25 52.734375 L 42.75 52.734375 L 42.75 44.28125 L 60.671875 32.25 Z M 42.75 32.25 "/>
</symbol>
<symbol overflow="visible" id="glyph0-1">
<path style="stroke:none;" d="M 59.140625 3.328125 C 88.3125 3.328125 113.40625 -16.890625 113.40625 -49.40625 C 113.40625 -76.03125 94.96875 -102.140625 63.75 -102.140625 C 38.90625 -102.140625 9.21875 -86.015625 9.21875 -50.6875 C 9.21875 -21.765625 28.671875 3.328125 59.140625 3.328125 Z M 66.5625 -1.53125 C 42.75 -1.53125 26.375 -27.140625 26.375 -58.625 C 26.375 -83.71875 37.125 -97.28125 55.296875 -97.28125 C 79.109375 -97.28125 96 -74.5 96 -42.234375 C 96 -13.5625 84.484375 -1.53125 66.5625 -1.53125 Z M 66.5625 -1.53125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-2">
<path style="stroke:none;" d="M 153.34375 -181.5 C 177.65625 -181.5 173.0625 -158.71875 186.375 -158.71875 C 190.96875 -158.71875 194.046875 -161.53125 194.046875 -166.65625 C 194.046875 -176.640625 181.25 -185.859375 161.796875 -185.859375 C 144.640625 -185.859375 130.296875 -180.734375 119.8125 -168.953125 C 112.125 -173.0625 100.09375 -176.890625 85.5 -176.890625 C 50.171875 -176.890625 27.640625 -153.59375 25.34375 -107.015625 C 25.09375 -98.296875 21.25 -95.484375 7.171875 -95.484375 C 6.140625 -95.484375 6.140625 -92.15625 7.171875 -92.15625 C 23.546875 -92.15625 25.859375 -90.875 25.859375 -78.34375 L 25.859375 -20.734375 C 25.859375 -5.890625 22.78125 -3.078125 8.1875 -3.078125 C 7.171875 -3.078125 7.171875 0 8.1875 0 C 14.84375 0 23.546875 -0.515625 32.765625 -0.515625 C 44.03125 -0.515625 54.265625 0 65.53125 0 C 66.5625 0 66.5625 -3.078125 65.53125 -3.078125 C 43.515625 -3.078125 40.453125 -5.890625 40.453125 -20.734375 L 40.453125 -91.140625 C 58.375 -91.140625 71.421875 -91.140625 80.125 -90.625 C 100.609375 -90.109375 100.859375 -90.109375 101.375 -78.34375 L 101.375 -20.734375 C 101.375 -5.890625 98.296875 -3.078125 83.71875 -3.078125 C 82.6875 -3.078125 82.6875 0 83.71875 0 C 90.375 0 99.078125 -0.515625 108.28125 -0.515625 C 119.546875 -0.515625 129.796875 0 141.0625 0 C 142.078125 0 142.078125 -3.078125 141.0625 -3.078125 C 119.046875 -3.078125 115.96875 -5.890625 115.96875 -20.734375 L 115.96875 -91.390625 C 126.96875 -90.875 136.703125 -89.09375 147.96875 -85.765625 C 148.734375 -85.25 150.53125 -89.09375 150.53125 -92.421875 C 150.53125 -95.484375 149.765625 -98.5625 148.734375 -98.5625 C 136.703125 -97.28125 126.71875 -96.25 115.96875 -96 L 115.96875 -112.390625 C 115.96875 -160.765625 131.84375 -181.5 153.34375 -181.5 Z M 100.859375 -107.015625 C 100.859375 -104.703125 100.609375 -102.90625 99.84375 -101.375 C 97.796875 -97.53125 92.921875 -96.515625 82.4375 -96.25 C 71.6875 -96 58.875 -96 40.453125 -96 L 40.453125 -112.390625 C 40.453125 -152.578125 55.296875 -172.546875 77.0625 -172.546875 C 90.109375 -172.546875 102.140625 -165.890625 111.109375 -155.640625 C 105.21875 -143.359375 101.625 -127.484375 100.859375 -107.015625 Z M 211.71875 -3.078125 C 198.40625 -3.078125 195.578125 -6.140625 195.578125 -20.734375 L 195.578125 -68.859375 C 195.578125 -85.25 196.09375 -96.25 196.09375 -99.078125 C 196.09375 -100.09375 194.8125 -101.890625 193.28125 -101.125 L 160 -85.5 C 158.46875 -84.734375 159.75 -81.921875 161.28125 -82.4375 C 176.640625 -89.859375 181.25 -86.53125 181.25 -68.609375 L 181.25 -20.734375 C 181.25 -6.140625 178.4375 -3.078125 165.125 -3.078125 C 164.09375 -3.078125 164.09375 0 165.125 0 C 171.265625 0 179.453125 -0.515625 188.421875 -0.515625 C 197.375 -0.515625 205.5625 0 211.71875 0 C 212.734375 0 212.734375 -3.078125 211.71875 -3.078125 Z M 211.71875 -3.078125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-3">
<path style="stroke:none;" d="M 59.140625 3.078125 C 73.46875 3.078125 84.984375 -1.53125 97.03125 -12.28125 C 97.796875 -13.0625 96 -14.84375 95.234375 -14.34375 C 87.296875 -7.9375 78.078125 -5.890625 69.125 -5.890625 C 38.65625 -5.890625 26.109375 -28.671875 26.109375 -55.046875 C 26.109375 -79.359375 37.125 -96 56.578125 -96 C 66.8125 -96 70.90625 -91.90625 74.75 -85.765625 C 77.0625 -81.15625 80.640625 -77.3125 86.015625 -77.3125 C 91.640625 -77.3125 94.96875 -81.15625 94.96875 -85.5 C 94.96875 -94.71875 79.359375 -101.125 65.03125 -101.125 C 38.90625 -101.125 9.21875 -81.65625 9.21875 -47.359375 C 9.21875 -23.296875 24.0625 3.078125 59.140625 3.078125 Z M 59.140625 3.078125 "/>
</symbol>
<symbol overflow="visible" id="glyph0-4">
<path style="stroke:none;" d="M 91.640625 -14.34375 C 84.21875 -9.734375 77.5625 -5.890625 66.046875 -5.890625 C 40.1875 -5.890625 26.109375 -26.875 26.109375 -55.8125 C 26.109375 -59.140625 26.375 -62.203125 26.625 -65.03125 L 90.109375 -65.28125 C 92.421875 -65.28125 92.921875 -67.328125 92.921875 -71.9375 C 92.921875 -90.109375 82.6875 -101.125 63.234375 -101.125 C 33.28125 -101.125 9.21875 -77.0625 9.21875 -46.078125 C 9.21875 -20.984375 25.34375 3.078125 56.0625 3.078125 C 69.125 3.078125 81.40625 -1.28125 93.4375 -11.78125 C 94.203125 -12.796875 92.421875 -14.84375 91.640625 -14.34375 Z M 54.78125 -96 C 67.578125 -96 75.78125 -87.046875 75.78125 -69.375 L 27.140625 -68.359375 C 30.203125 -85.765625 39.9375 -96 54.78125 -96 Z M 54.78125 -96 "/>
</symbol>
</g>
</defs>
<g id="surface1">
<rect x="0" y="0" width="583.683594" height="342" style="fill:rgb(100%,100%,100%);fill-opacity:1;stroke:none;"/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-1" x="16" y="252.75"/>
<use xlink:href="#glyph0-2" x="138.625" y="252.75"/>
<use xlink:href="#glyph0-3" x="359.554688" y="252.75"/>
<use xlink:href="#glyph0-4" x="463.234375" y="252.75"/>
</g>
</g>
</svg> This output is pretty good, using the SVG |
sort of agreed, if it came without those hardcoded |
@Pomax Of course, I agree. In case of |
Cairo SVGs are also problematic when embedded in HTML since the ids used for glyphs are not unique and one needs to post-process them before embedding more than one SVG in an HTML page. |
Could anyone explain like I'm five what the best way to import a whole string as SVG paths is, please? My environment is node.js, paper.js, and fontkit so far. Does OpenType.js allow to import a whole string as SVG paths? Are there any other options? |
OpenType.js definitely lets you get the SVG for a fully typeset string (constrained by its support of the OpenType spec of course). You can see an example of this on their homepage (with the code hosted on github so easy to inspect). FontKit right now does not do SVG generation for fully typeset strings to the best of my knowledge. @devongovett is that correct? |
Thanks, @Pomax ! The problem I had with OpenType.js was that it required a canvas - but I wanted to use it on the server-side rather than in the browser. I still don't know how to get this to work. (I managed with paper.js but not OpenType.) I feel quite dumb at this point. |
No need to feel dumb, paper.js is an amazing library and I fully expect it to be using the canvas as "an afterthought" purely for screen dumping rather than for "doing any actual work". OpenType.js might not be quite as sophisticated. You can try to shim the canvas with something like https://github.com/Automattic/node-canvas although I haven't tried that myself - the alternative is to help figure out how to update the code here in fontkit to serialize post-shaped-sequences to a collection of paths that accurately reflect the shaped text, and then ideally in a way that stays compatible with the universal JSON suggestion by Adam. |
This is a feature, that would be really nice to have. Right now I am iterating through each glyph in the run, getting the commands from it, then applying an offset to the command, and then combining all the commands to an SVG string. This is really painful. |
Agreed. |
Alternatively, you can see if you can write a utility function that ingests the output of layout(), which is a |
This is a good idea, maybe I'll try doing this. |
Old issue. But did anyone end up finding/creating a solution for this? Struggling with this now... |
Created a solution for this, check it out: https://github.com/elron/Font-To-SVG |
Has anyone made a solution that includes kerning support? I’d gladly donate to support this feature |
for what purpose? Because if you just want fully typeset text, as SVG, opentype.js should be able to get you that no problem, with pretty much full opentype feature support (not just kerning) |
It is similar to opentype.js, but I’m trying to support color fonts and additional font formats via fontkit. I can already render single characters as svgs using bounding box calculations—works great. But, rendering kerning is more complex, and after looking into the opentype.js source, I think they use advance width instead of the bbox approach I’ve been using. While I could just hack away at it, I feel like there is more underlying complexity that I may be missing—such as the liga and rlig support you just mentioned. Plus, this kind of workaround would be beneficial for fontkit, and has received interest from other community members too. I’d gladly donate to support examples and workarounds for this feature |
Indeed. Not just kerning literally every opentype feature (GSUB/GPOS, liga, etc.) will won't if you're just combining based on hmtx/vmtx bounding box. You're basically guaranteed to always have the wrong output. At this point, what might be far more useful, and is a route I personally went down, is going "well forget about js, let XeLaTeX do the actual typesetting to PDF, then use PDF2SVG to turn that into the image data I need, because despite the fact that I've literally written more font parsers than I care to remember, XeLaTeX is still the gold standard, and available for literally every CI/CD platform you can imagine". As for benefiting Fontkit: as far as I know, this code is no longer maintained, and the maintainer is not interested in marking it as such, so throwing money at OpenType.js, which very much is still being worked on, might be a better bet. Different holes, but at least a reasonable chance that going "I am willing to pay for someone to implement this" will actually get code landed. |
True, but support for color fonts is exceedingly rare, and is not completely supported in browsers, pdftosvg converters, latex, opentypejs, and other solutions I’ve tried. There’s really not many options to render them, especially as an svg. For me, fontkit has pretty much everything I need except for creating a kerned svg. As for opentype features, I’m sure it’s a rabbit hole, but even opentype.js only supports "liga" and "rlig", so that’s good enough. This package may be unmaintained for now, but it’s used by pdfkit and others, with a download count that keeps growing every day. I still think there’s plenty of features unique to fontkit, and I think having an example of If you’re up for the challenge, I will gladly donate several coffees for you to have a go at it. You were excellent with the OpenType SVG parser. |
Pretty sure opentype.js doesn't support most complex text rendering features: https://rawgit.com/unicode-org/text-rendering-tests/master/reports/OpenType.js.html The last release of that library was also 2 years ago. So claiming "pretty much full opentype feature support" (I don't see any code for it in the repo), and that opentype.js is somehow better maintained seems incorrect. |
As for your actual question, I don't think it would be very difficult. Basic steps:
You can see a similar method to this that shows how these properties work here: https://github.com/foliojs/fontkit/blob/master/src/layout/GlyphRun.js#L89-L107 or in textkit: https://github.com/foliojs/textkit/blob/master/packages/pdf-renderer/index.js#L79-L90 |
Thank you @devongovett ! Not really sure about the viewBox calculation of Here’s the example code I put together: const fs = require("fs-extra");
const fontkit = require("fontkit");
const times = require("lodash.times");
const svgo = require("svgo");
async function start() {
const buffer = await fs.readFile("./public-sans.ttf");
const font = fontkit.create(buffer);
const run = font.layout("Hello World!");
const bbox = run.bbox;
let x = 0;
let y = 0;
const groups = times(run.glyphs.length, (index) => {
const glyph = run.glyphs[index];
const position = run.positions[index];
const fill = "#000000";
const fillOpacity = 1;
const d = glyph.path.scale(-1, 1).rotate(Math.PI).toSVG();
const path = `<path fill="${fill}" fill-opacity="${fillOpacity}" d="${d}" />`;
const group = `<g transform="translate(${x + position.xOffset}, ${
y + position.yOffset
})">${path}</g>`;
x += position.xAdvance;
y += position.yAdvance;
return group;
});
const minX = bbox.minX;
const minY = bbox.minY;
const width = bbox.maxX - bbox.minX;
const height = bbox.maxY - bbox.minY;
const svg = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="${minX} ${
-height + -minY
} ${width} ${height}">
${groups.join("\n")}
</svg>
`;
const optimizedSvg = (await svgo.optimize(svg)).data;
await fs.outputFile("word.svg", Buffer.from(optimizedSvg));
}
start(); |
Fair enough. Last time I brought up that there were lots of open PRs and that there were quite a few issues looking for attention, your response was one that suggested you were not going to be working on fontkit anymore, so if that's changed: fantastic! Happy to help out if you need it. |
Hi @devongovett ! I’d like to extend the script I wrote (with your help!) to support multiple lines, max line length, and appropriate leading / line-height. Do you have an approach or examples that would help point me in the right direction? |
Your code helped me create a solution in my fontkit demo! thank you! This is my code to replicate opentype.js
|
Right now in order to serialize a GlyphRun to something like SVG, one needs to manually run over the glyph array and work with the positional information to generate something that makes sense.
Giving the GlyphRun object its own API in addition to the glyphs and positions properties might be a good idea, so that this works:
The text was updated successfully, but these errors were encountered: