diff --git a/lib/svg.js b/lib/svg.js index f98bde05..b660399b 100644 --- a/lib/svg.js +++ b/lib/svg.js @@ -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. diff --git a/server/migration.js b/server/migration.js index c67b3b72..e63bda8c 100644 --- a/server/migration.js +++ b/server/migration.js @@ -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.