Port basic stroke extraction algorithm to Javascript

This commit is contained in:
Shaunak Kishore
2015-09-08 10:52:18 -04:00
parent c353bea385
commit 707fe91da6
2 changed files with 130 additions and 5 deletions

View File

@ -255,6 +255,129 @@ function Endpoint(paths, index) {
return this;
}
// Code for the stroke extraction step follows.
function add_edge_to_adjacency(edge, adjacency) {
assert(edge.length === 2);
adjacency[edge[0]] = adjacency[edge[0]] || [];
if (adjacency[edge[0]].indexOf(edge[1]) < 0) {
adjacency[edge[0]].push(edge[1]);
}
}
function get_svg_path(stroke) {
assert(stroke.length > 0);
var terms = ['M', stroke[0].start[0], stroke[0].start[1]];
for (var i = 0; i < stroke.length; i++) {
var segment = stroke[i];
if (segment.control === undefined) {
terms.push('L');
} else {
terms.push('Q');
terms.push(segment.control[0]);
terms.push(segment.control[1]);
}
terms.push(segment.end[0]);
terms.push(segment.end[1]);
}
terms.push('Z');
return terms.join(' ');
}
function extract_stroke(paths, endpoint_map, bridge_adjacency,
extracted_indices, start) {
var current = start;
var result = [];
var visited = {};
function advance(index) {
return [index[0], (index[1] + 1) % paths[index[0]].length];
}
function angle(endpoint, index) {
var diff = Point.subtract(endpoint_map[Point.key(index)].point,
endpoint.point);
assert(diff[0] !== 0 || diff[1] !== 0);
var angle = Math.atan2(diff[1], diff[0]);
return Angle.subtract(angle, endpoint.angles[0]);
}
while (true) {
// Add the current path segment to the path.
result.push(paths[current[0]][current[1]]);
visited[Point.key(current)] = true;
current = advance(current);
// If there are bridges at the start of the next path segment, follow the
// one that makes the largest angle with the current path. The ordering
// criterion enforce that we try to cross aligned bridges.
var key = Point.key(current);
if (bridge_adjacency.hasOwnProperty(key)) {
var endpoint = endpoint_map[key];
var next = bridge_adjacency[key].sort(function(a, b) {
return angle(endpoint, a) - angle(endpoint, b);
})[0];
result.push({
start: Point.clone(endpoint.point),
end: Point.clone(endpoint_map[Point.key(next)].point),
control: undefined,
});
current = next;
}
// Check if we have either closed the loop or hit an extracted segment.
var key = Point.key(current);
if (Point.equal(current, start)) {
for (var index in visited) {
extracted_indices[index] = true;
}
return result;
} else if (extracted_indices[key] || visited[key]) {
return undefined;
}
}
}
function extract_strokes(paths, endpoints, bridges) {
// Build up the necessary hash tables and adjacency lists needed to run the
// stroke extraction loop.
var endpoint_map = {};
var endpoint_position_map = {};
for (var i = 0; i < endpoints.length; i++) {
var endpoint = endpoints[i];
endpoint_map[Point.key(endpoint.index)] = endpoint;
endpoint_position_map[Point.key(endpoint.point)] = endpoint;
}
bridges.map(check_bridge);
var bridge_adjacency = {};
for (var i = 0; i < bridges.length; i++) {
var keys = bridges[i].map(Point.key);
assert(endpoint_position_map.hasOwnProperty(keys[0]));
assert(endpoint_position_map.hasOwnProperty(keys[1]));
var xs = keys.map(function(x) { return endpoint_position_map[x].index; });
add_edge_to_adjacency([Point.key(xs[0]), xs[1]], bridge_adjacency);
add_edge_to_adjacency([Point.key(xs[1]), xs[0]], bridge_adjacency);
}
// Actually extract strokes. Any given path segment index should appear on
// exactly one stroke; if it is not on a stroke, we log a warning.
var extracted_indices = {};
var strokes = [];
for (var i = 0; i < paths.length; i++) {
for (var j = 0; j < paths[i].length; j++) {
var index = [i, j];
if (extracted_indices[Point.key(index)]) {
continue;
}
var stroke = extract_stroke(paths, endpoint_map, bridge_adjacency,
extracted_indices, index);
if (stroke === undefined) {
console.log('Stroke extraction missed an index!');
continue;
}
strokes.push(stroke);
}
}
return strokes;
}
// Exports go below this fold.
this.get_glyph_render_data = function(glyph) {
@ -265,9 +388,12 @@ this.get_glyph_render_data = function(glyph) {
endpoints.push(new Endpoint(paths, [i, j]));
}
}
var bridges = get_bridges(endpoints);
var strokes = extract_strokes(paths, endpoints, bridges);
return {
bridges: get_bridges(endpoints),
bridges: bridges,
d: Glyphs.get_svg_path(glyph),
endpoints: endpoints,
strokes: strokes.map(get_svg_path),
};
}