Improve area calculation for glyph orientation

This commit is contained in:
Shaunak Kishore
2015-09-28 02:20:44 -04:00
parent fc4b206045
commit 62a66c1c6a
2 changed files with 47 additions and 10 deletions

View File

@ -17,17 +17,40 @@ this.svg = {};
// Returns twice the area contained in the path. The result is positive iff the
// path winds in the counter-clockwise direction.
const get2xArea = (path) => {
let area = 0;
//
// The approximation error is an upper-bound on the distance between consecutive
// points in the polygon approximation used to compute the area. The default
// error of 64 is chosen so that the sign will be correct with good likelihood.
const get2xArea = (path, approximation_error) => {
const polygon_approximation = [path[0].start];
approximation_error = approximation_error || 64;
for (let x of path) {
area += (x.end[0] + x.start[0])*(x.end[1] - x.start[1]);
let num_points = 0;
if (x.control !== undefined) {
const distance = Math.sqrt(Point.distance2(x.start, x.end));
num_points = Math.floor(distance/approximation_error);
}
for (let i = 0; i < num_points; i++) {
const t = (i + 1)/(num_points + 1);
const s = 1 - t;
polygon_approximation.push(
[s*s*x.start[0] + 2*s*t*x.control[0] + t*t*x.end[0],
s*s*x.start[1] + 2*s*t*x.control[1] + t*t*x.end[1]]);
}
polygon_approximation.push(x.end);
}
let area = 0;
for (var i = 0; i < polygon_approximation.length; i++) {
const p1 = polygon_approximation[i];
const p2 = polygon_approximation[(i + 1) % polygon_approximation.length];
area += (p2[0] + p1[0])*(p2[1] - p1[1]);
}
return area;
}
// Takes a list of paths and orients them so that exterior contours are oriented
// counter-clockwise and interior contours clockwise.
const orientPaths = (paths, reverse) => {
const orientPaths = (paths, approximation_error) => {
for (var i = 0; i < paths.length; i++) {
const path = paths[i];
let contains = 0;
@ -43,7 +66,7 @@ const orientPaths = (paths, reverse) => {
// other paths. It is counter-clockwise iff its area is positive. The path
// should be reversed if (CCW && internal) || (CW && external).
const should_reverse = (area > 0) !== (contains % 2 === 0);
if (should_reverse && !reverse) {
if (should_reverse) {
for (let segment of path) {
[segment.start, segment.end] = [segment.end, segment.start];
}
@ -163,10 +186,8 @@ svg.convertCommandsToPath = (commands) => {
// Converts a normal-form SVG path string to a list of paths. The paths obey an
// orientation constraint: the external paths are oriented counter-clockwise,
// while the internal paths are oriented clockwise.
//
// If 'reverse' is true, the paths will all be oriented opposite this rule.
svg.convertSVGPathToPaths = (path, reverse) => {
return orientPaths(splitPath(path), reverse);
svg.convertSVGPathToPaths = (path) => {
return orientPaths(splitPath(path));
}
// Takes the given list of paths and returns a normal-form SVG path string.

View File

@ -2,7 +2,23 @@
const completionCallback = undefined;
const perGlyphCallback = undefined;
const perGlyphCallback = (glyph) => {
assert(glyph.stages.strokes !== undefined);
let changed = 0;
for (var i = 0; i < glyph.stages.strokes.length; i++) {
const stroke = glyph.stages.strokes[i];
const paths = svg.convertSVGPathToPaths(stroke);
assert(paths.length === 1);
const path = svg.convertPathsToSVGPath(paths);
if (path !== stroke) {
glyph.stages.strokes[i] = path;
changed += 1;
}
}
if (changed) {
console.log(`Flipped ${changed} path(s) in glyph: ${glyph.character}`);
}
}
// Runs the given per-glyph callback for each glyph in the database.
// When all the glyphs are migrated, runs the completion callback.