Skip to content

Commit

Permalink
Add SVG-related API improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
Connum committed Mar 5, 2023
1 parent ca3b438 commit f92ef3f
Show file tree
Hide file tree
Showing 6 changed files with 561 additions and 31 deletions.
32 changes: 28 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,9 @@ Blue lines indicate the glyph bounding box.
Green line indicates the advance width of the glyph.
The arguments are the same as `Glyph.draw`.

##### `Glyph.toPathData(options)`, `Glyph.toDOMElement(options)`, `Glyph.toSVG(options)`, `Glyph.fromSVG(pathData, options)`,
These are currently only wrapper functions for their counterparts on Path objects (see documentation there), but may be extended in the future to pass on Glyph data for automatic calculation.

### The Path object
Once you have a path through `Font.getPath` or `Glyph.getPath`, you can use it.

Expand All @@ -273,14 +276,35 @@ Draw the path on the given 2D context. This uses the `fill`, `stroke` and `strok
Calculate the minimum bounding box for the given path. Returns an `opentype.BoundingBox` object that contains x1/y1/x2/y2.
If the path is empty (e.g. a space character), all coordinates will be zero.

##### `Path.toPathData(decimalPlaces)`
##### `Path.toPathData(options)`
Convert the Path to a string of path data instructions.
See https://www.w3.org/TR/SVG/paths.html#PathData
* `decimalPlaces`: The amount of decimal places for floating-point values. (default: 2)
* `options`:
* `decimalPlaces`: The amount of decimal places for floating-point values. (default: 2)
* `optimize`: apply some optimizations to the path data, e.g. removing unnecessary/duplicate commands (true/false, default: true)
* `flipY`: whether to flip the Y axis of the path data, because SVG and font paths use inverted Y axes. (true: calculate from bounding box, false: disable; default: true)
* `flipYBase`: Base value for the base flipping calculation. You'll probably want to calculate this from the font's ascender and descender values. (default: automatically calculate from the path data's bounding box)


##### `Path.toSVG(decimalPlaces)`
##### `Path.toSVG(options)`
Convert the path to a SVG <path> element, as a string.
* `decimalPlaces`: The amount of decimal places for floating-point values. (default: 2)
* `options`: see Path.toPathData

##### `Path.fromSVG(pathData, options)`
Retrieve path from SVG path data. Either overwriting the path data for an existing path
```js
const path = new Path();
path.fromSVG('M0 0');
```
or creating a new Path directly:
```js
const path = Path.fromSVG('M0 0');
```
* `pathData`: Either a string of SVG path commands, or (only in browser context) an `SVGPathElement`
* `options`:
* `decimalPlaces`, `optimize`, `flipY`, `flipYBase`: see Path.toPathData
* `scale`: scaling value applied to all command coordinates (default: 1)
* `x`/`y`: offset applied to all command coordinates on the x or y axis (default: 0)

#### Path commands
* **Move To**: Move to a new position. This creates a new contour. Example: `{type: 'M', x: 100, y: 200}`
Expand Down
29 changes: 20 additions & 9 deletions bin/test-render
Original file line number Diff line number Diff line change
Expand Up @@ -60,29 +60,40 @@ function renderSVG() {
let svgBody = [];

var glyphSet = new Set();
let x = 0;
const glyphs = font.stringToGlyphs(textToRender);
for (let i = 0; i < glyphs.length; i++) {
const glyph = glyphs[i];
let glyphData = [];

const fontSize = font.unitsPerEm;
let minWidth = 0;
font.forEachGlyph(textToRender, 0, 0, fontSize, {}, function(glyph, gX, gY, gFontSize) {
const glyphPath = glyph.getPath(gX, gY, gFontSize, {}, this);
const glyphWidth = glyph.getMetrics().xMax;
glyphData.push({glyph: glyph, path: glyphPath, gX, gY, w: glyphWidth});
});

for (let i = 0; i < glyphData.length; i++) {
const glyph = glyphData[i].glyph;
const path = glyphData[i].path;
const symbolId = testcase + '.' + glyph.name;
if (!glyphSet.has(glyph)) {
glyphSet.add(glyph);
const svgPath = glyph.path.toSVG();
const svgPath = glyph.path.toSVG({optimize: true, decimalPlaces: 0, flipY: false});
svgSymbols.push(` <symbol id="${symbolId}" overflow="visible">${svgPath}</symbol>`);
}
svgBody.push(` <use xlink:href="#${symbolId}" x="${x}" y="0"/>`);
x += glyph.advanceWidth;
svgBody.push(` <use xlink:href="#${symbolId}" x="${glyphData[i].gX}" y="${glyphData[i].gY}"/>`);
let xMax = glyphData[i].gX + glyph.advanceWidth;
if(xMax > minWidth) {
minWidth = xMax;
}
}

let minX = 0;
let minY = Math.round(font.descender);
let width = Math.round(x);
let height = Math.round(font.ascender - font.descender);
let svgHeader = `<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="${minX} ${minY} ${width} ${height}">`;
viewBox="${minX} ${minY} ${minWidth} ${height}">`;

return svgHeader + svgSymbols.join('\n') + svgBody.join('\n') + SVG_FOOTER;
}
Expand Down
37 changes: 37 additions & 0 deletions src/glyph.js
Original file line number Diff line number Diff line change
Expand Up @@ -374,4 +374,41 @@ Glyph.prototype.drawMetrics = function(ctx, x, y, fontSize) {
draw.line(ctx, x + (advanceWidth * scale), -10000, x + (advanceWidth * scale), 10000);
};

/**
* Convert the Glyph's Path to a string of path data instructions
* @param {object|number} [options={decimalPlaces:2, optimize:true}] - Options object (or amount of decimal places for floating-point values for backwards compatibility)
* @return {string}
* @see Path.toPathData
*/
Glyph.prototype.toPathData = function(options) {
return this.path.toPathData(options);
};

/**
* Sets the path data from an SVG path element or path notation
* @param {string|SVGPathElement}
* @param {object}
*/
Glyph.prototype.fromSVG = function(pathData, options = {}) {
return this.path.fromSVG(pathData, options);
};

/**
* Convert the Glyph's Path to an SVG <path> element, as a string.
* @param {object|number} [options={decimalPlaces:2, optimize:true}] - Options object (or amount of decimal places for floating-point values for backwards compatibility)
* @return {string}
*/
Glyph.prototype.toSVG = function(options) {
return this.path.toSVG(options, this.toPathData.apply(this, [options]));
};

/**
* Convert the path to a DOM element.
* @param {object|number} [options={decimalPlaces:2, optimize:true}] - Options object (or amount of decimal places for floating-point values for backwards compatibility)
* @return {SVGPathElement}
*/
Glyph.prototype.toDOMElement = function(options) {
return this.path.toDOMElement(options);
};

export default Glyph;
Loading

0 comments on commit f92ef3f

Please sign in to comment.