mirror of
https://github.com/skishore/makemeahanzi.git
synced 2025-11-01 20:27:44 +08:00
Port basic stroke extraction algorithm to Javascript
This commit is contained in:
@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user