-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
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
toSVG() font outlines #100
Comments
I'm actually working on this at the moment. Text is one of the few remaining things left to polish in SVG export. In browsers, text already works as expected — it's being rendered with correct font. Illustrator, however, doesn't seem to allow linking to fonts. I'm not yet sure how to fix it (without embedding font in SVG itself). Any help would be great. |
It turned out that Illustrator does render text objects properly as long as corresponding fonts are present in the system (and have identical name). So it works pretty well now. Font outlines is something we can look at in the future, but now it's not a priority. |
"fill:none;fill-opacity:1;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;" |
@kangax Is it have been or will be implemented? |
no font in outlines. Writing your on toSVG code is not hard anyway if you know how to handle the path transformation. |
I've tried to understand path transformation, but it's not easy |
can you get from font to path and you have difficulties positioning them on the SVG or you have difficulties moving from text to path? |
Any updates on this? I'm also interested in converting font to path so the users don't need to have the font installed if they want to edit the end result in some other software |
No one is working on that that i know of. |
That's exactly what I want to do, too. |
The solution would be an integration with https://opentype.js.org/ |
Thanks. I will have a look at it. |
@timoostrich wow! good tools! |
i had to look into this for work yesterday by chance and i have to say opentype.js integration is super simple and super useful. |
How did you manage the SVG fonts to paths problem? Could you share some details? That would be awesome. |
from opentype.js docs const font = await opentype.load('fonts/Roboto-Black.ttf');
const path = font.getPath('Hello, World!', 0, 150, 72);
const svgPath = path.toPathData(3); // 3 is decimal places it gives you the outline for that particular font and text. |
Did you use the custom control api? |
Yep!
|
you are missing the control points controls of the beizer curves :) |
🤔 OOO! It's called |
Hi @asturur , not sure if it's because there have some errors while calculating Paths or sub path, or as you said, I missed the control points of the beizer curves, how could I resolve this issue? Could you please provide some examples? Thanks! |
i had to write curve transformation few days ago, i realized that to have full control:
|
https://yqnn.github.io/svg-path-editor/ still have no idea :( |
Could you please provide some links or samples when you are free? |
The SVG paths generated by opentype.js is perfectly shown, but once I transform the paths to points, the Polygon is what https://user-images.githubusercontent.com/2106987/86267366-2c238e80-bbf9-11ea-863f-3f4aa7605768.png like. |
|
ok wait, you can't convert a path to polygon, not easily and not with many many points. You can apply the control api logic to a path. Once the swap is done, you can position the controls following specific values. |
That would be great!! I'm looking forward to those codes! |
This first function makes a path absolute ( Does not take care of removing A yet ) const makePathAbsolute = (path) => {
// x and y represent the last point of the path. the previous command point.
// we add them to each relative command to make it an absolute comment.
// we also swap the v V h H with L, because are easier to transform.
let x = 0;
let y = 0;
let current;
const newPath = path.map((subPath) => subPath.slice(0));
for (let i = 0, len = newPath.length; i < len; ++i) {
current = newPath[i];
switch (current[0]) { // first letter
case 'l': // lineto, relative
current[0] = 'L';
current[1] += x;
current[2] += y;
// falls through
case 'L':
x = current[1];
y = current[2];
break;
case 'h': // horizontal lineto, relative
current[1] += x;
// falls through
case 'H':
current[0] = 'L';
current[2] = y;
x = current[1];
break;
case 'v': // vertical lineto, relative
current[1] += y;
// falls through
case 'V':
current[0] = 'L';
y = current[1];
current[1] = x;
current[2] = y;
break;
case 'm': // moveTo, relative
current[0] = 'M';
current[1] += x;
current[2] += y;
// falls through
case 'M':
x = current[1];
y = current[2];
break;
case 'c': // bezierCurveTo, relative
current[0] = 'C';
current[1] += x;
current[2] += y;
current[3] += x;
current[4] += y;
current[5] += x;
current[6] += y;
// falls through
case 'C':
x = current[5];
y = current[6];
break;
case 's': // shorthand cubic bezierCurveTo, relative
current[0] = 'S';
current[1] += x;
current[2] += y;
current[3] += x;
current[4] += y;
// falls through
case 'S':
x = current[3];
y = current[4];
break;
case 'q': // quadraticCurveTo, relative
current[0] = 'Q';
current[1] += x;
current[2] += y;
current[3] += x;
current[4] += y;
// falls through
case 'Q':
x = current[3];
y = current[4];
break;
case 't': // shorthand quadraticCurveTo, relative
current[0] = 'T';
current[1] += x;
current[2] += y;
// falls through
case 'T':
x = current[1];
y = current[2];
break;
case 'a':
current[0] = 'A';
current[6] += x;
current[7] += y;
// falls through
case 'A':
x = current[6];
y = current[7];
break;
default:
}
}
return newPath;
}; This other transforms the path commands coordinates in absolute coordinates. const getPathScaledForTransform = (path, transform) => {
const absolutePath = makePathAbsolute(path);
const indexesWithXMap = {
L: [1],
M: [1],
C: [1, 3, 5],
S: [1, 3],
Q: [1, 3],
T: [1],
A: [6],
};
absolutePath.forEach((subPath) => {
const command = subPath[0];
if (command === 'z') {
return;
}
const indexes = indexesWithXMap[command];
indexes.forEach((index) => {
const point = { x: subPath[index], y: subPath[index + 1] };
const transformedPoint = fabric.util.transformPoint(point, transform);
subPath[index] = transformedPoint.x;
subPath[index + 1] = transformedPoint.y;
});
});
return pathDataToString(absolutePath);
}; |
wow!! Thanks for your detailed reply! I will have a try tomorrow morning, thanks anyway! |
Morning @asturur :)
|
Here is the cleaned up code that takes care of the elliptic arc swap, i also removed the conversion to string since you do not really need it. Once the path is converted, you have just L, M, C, S, T, Q commands. C S T Q no. C: is made of 'C', cx1, cy1, cx2, cy2, x ,y for S, T, Q is similar, with less points and some constrain, i will try to add an example on how you actually modify those paths with the control api. const fromArcToBeizers = (x, y, aCommand) => {
// shimming ctx to capture commands.
const commands = [];
const ctx = {
bezierCurveTo: (...args) => {
commands.push(['C', ...args]);
},
};
// short way of pulling out the conversion without modifying fabric for now
fabric.util.drawArc(ctx, x, y, [
aCommand[1],
aCommand[2],
aCommand[3],
aCommand[4],
aCommand[5],
aCommand[6],
aCommand[7],
]);
return commands;
};
// taken from my fabricJS things, this is going to go away because will be in fabric
// newer versions and we can get rid of it here.
// this will change the relative parts of a path command to absolute.
const makePathAbsolute = (path) => {
// x and y represent the last point of the path. the previous command point.
// we add them to each relative command to make it an absolute comment.
// we also swap the v V h H with L, because are easier to transform.
let x = 0;
let y = 0;
let current;
const newPath = path.map((subPath) => subPath.slice(0));
const destinationPath = [];
for (let i = 0, len = newPath.length; i < len; ++i) {
let converted = false;
current = newPath[i];
switch (current[0]) { // first letter
case 'l': // lineto, relative
current[0] = 'L';
current[1] += x;
current[2] += y;
// falls through
case 'L':
x = current[1];
y = current[2];
break;
case 'h': // horizontal lineto, relative
current[1] += x;
// falls through
case 'H':
current[0] = 'L';
current[2] = y;
x = current[1];
break;
case 'v': // vertical lineto, relative
current[1] += y;
// falls through
case 'V':
current[0] = 'L';
y = current[1];
current[1] = x;
current[2] = y;
break;
case 'm': // moveTo, relative
current[0] = 'M';
current[1] += x;
current[2] += y;
// falls through
case 'M':
x = current[1];
y = current[2];
break;
case 'c': // bezierCurveTo, relative
current[0] = 'C';
current[1] += x;
current[2] += y;
current[3] += x;
current[4] += y;
current[5] += x;
current[6] += y;
// falls through
case 'C':
x = current[5];
y = current[6];
break;
case 's': // shorthand cubic bezierCurveTo, relative
current[0] = 'S';
current[1] += x;
current[2] += y;
current[3] += x;
current[4] += y;
// falls through
case 'S':
x = current[3];
y = current[4];
break;
case 'q': // quadraticCurveTo, relative
current[0] = 'Q';
current[1] += x;
current[2] += y;
current[3] += x;
current[4] += y;
// falls through
case 'Q':
x = current[3];
y = current[4];
break;
case 't': // shorthand quadraticCurveTo, relative
current[0] = 'T';
current[1] += x;
current[2] += y;
// falls through
case 'T':
x = current[1];
y = current[2];
break;
case 'a':
current[0] = 'A';
current[6] += x;
current[7] += y;
// falls through
case 'A':
converted = true;
destinationPath.push(...fromArcToBeizers(x, y, current));
x = current[6];
y = current[7];
break;
default:
}
if (!converted) {
destinationPath.push(current);
}
}
return destinationPath;
};
const getPathScaledForTransform = (path, transform) => {
const absolutePath = makePathAbsolute(path);
const indexesWithXMap = {
L: [1],
M: [1],
C: [1, 3, 5],
S: [1, 3],
Q: [1, 3],
T: [1],
};
absolutePath.forEach((subPath) => {
const command = subPath[0];
if (command === 'z') {
return;
}
const indexes = indexesWithXMap[command];
indexes.forEach((index) => {
const point = { x: subPath[index], y: subPath[index + 1] };
const transformedPoint = fabric.util.transformPoint(point, transform);
subPath[index] = transformedPoint.x;
subPath[index + 1] = transformedPoint.y;
});
});
return absolutePath;
}; |
Hello, according to above replies, I just made a jsfiddle[1] here. This fiddle aims to transform Text(or Path) to Polygon, Not sure if we could implement this like here[2]:
[1] https://jsfiddle.net/juzhiyuan/f18ta2cp/65/ Test Path:
BTW, in function if (command.toLowerCase() === 'z') {
return;
} |
specific values? With L and M, I created a Polygon here[1] without Bezier Curve UPDATE: [1] is because of without Q command [1] https://jsfiddle.net/juzhiyuan/f18ta2cp/114/ |
Hi @asturur , would you mind having a look at those 2 jsfindle when you have time? Thanks a lot !:) |
https://jsfiddle.net/hdu32bfj/13/ I stop looking at the sword codepen because seems too complex for me and also does not behave perfectly. |
Maybe I could integrate with those correct points with Control API, then action could be implemented, let me have a try :) |
https://jsfiddle.net/hdu32bfj/13/ This demo seems something wrong at var absolutePoint = fabric.util.transformPoint({
x: (fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x),
y: (fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y),
}, fabricObject.calcTransformMatrix()), because the fabricObject doesn't have the |
We may transform Path to Points I think? BTW, it would be great if we have a |
i did not write the anchorWrapper nor the action handler yet. I need some time, i m tryin to do also other fabric stuff. The weekend is the only time i have to really do things other than answering email. |
Got it, I will continue trying on this issue :DD |
Great work on fabric.js, really impressive!! I was just trying the new toSVG feature, fantastic!
I still have a small feature request though: it seems that text is exported as text with a defined font. Maybe it would be nice if font outlines are exported so the svg can be used by people who don't have that particular font installed....
The text was updated successfully, but these errors were encountered: