/* Copyright (C) 2014 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. */ using Cairo; using Math; namespace BirdFont { /** A class for converting control points. */ public class PointConverter { Path original_path; Path quadratic_path; public PointConverter (Path path) { original_path = path; } public Path get_quadratic_path () { bool add_more_points = false; quadratic_path = original_path.copy (); estimated_cubic_path (); if (add_more_points) { warning ("Too many points in segment."); } if (quadratic_path.points.size < 2) { return new Path (); } foreach (EditPoint e in quadratic_path.points) { if (e.get_right_handle ().type == PointType.CUBIC) { PenTool.convert_point_segment_type (e, e.get_next (), PointType.DOUBLE_CURVE); } } if (!original_path.is_open ()) { if (quadratic_path.get_last_point ().get_right_handle ().type == PointType.CUBIC) { PenTool.convert_point_segment_type (quadratic_path.get_last_point (), quadratic_path.get_first_point (), PointType.DOUBLE_CURVE); } } quadratic_path.add_hidden_double_points (); return quadratic_path; } public void estimated_cubic_path () { EditPoint segment_start; EditPoint segment_stop; EditPoint quadratic_segment_start; EditPoint quadratic_segment_stop; EditPoint e; double distance, step; int points_in_segment = 0; int size; foreach (EditPoint ep in quadratic_path.points) { ep.set_tie_handle (false); ep.set_reflective_handles (false); } size = quadratic_path.points.size; segment_start = quadratic_path.get_first_point (); if (original_path.is_open ()) { size--; } for (int i = 0; i < size; i++) { segment_stop = (i == size -1) ? quadratic_path.get_first_point () : segment_start.get_next (); quadratic_segment_start = segment_start.copy (); quadratic_segment_stop = segment_stop.copy (); PenTool.convert_point_segment_type (quadratic_segment_start, quadratic_segment_stop, PointType.DOUBLE_CURVE); distance = 0; e = new EditPoint (); if (segment_start.get_right_handle ().is_line () && segment_stop.get_left_handle ().is_line ()) { // skipping line } else if (points_in_segment >= 10) { warning ("Too many points."); } else { find_largest_distance (segment_start, segment_stop, quadratic_segment_start, quadratic_segment_stop, out distance, out e, out step); } if (distance > 0.2) { // range 0.1 - 0.4, quadratic_path.insert_new_point_on_path (e); points_in_segment++; size += 2; // the new point + segment start } else { points_in_segment = 0; segment_start = segment_stop; } } } // TODO: Optimize public static void find_largest_distance (EditPoint a0, EditPoint a1, EditPoint b0, EditPoint b1, out double distance, out EditPoint new_point, out double step) { double max_d; double min_d; int steps = (int) (1.6 * Path.get_length_from (a0, a1)); double x_out, y_out; double step_out; x_out = 0; y_out = 0; step_out = 0; step = 0; distance = 0; new_point = new EditPoint (); new_point.prev = a0; new_point.next = a1; new_point.get_right_handle ().type = PointType.CUBIC; new_point.get_left_handle ().type = PointType.CUBIC; steps = 20; // FIXME: adjust to length if (a0.get_right_handle ().type == PointType.QUADRATIC || a1.get_left_handle ().type == PointType.QUADRATIC || a0.get_right_handle ().type == PointType.LINE_QUADRATIC || a1.get_left_handle ().type == PointType.LINE_QUADRATIC) { return; } max_d = Glyph.CANVAS_MIN; min_d = Glyph.CANVAS_MAX; Path.all_of (a0, a1, (xa, ya, ta) => { min_d = double.MAX; Path.all_of (b0, b1, (xb, yb, tb) => { double d = Path.distance (xa, xb, ya, yb); if (d < min_d) { min_d = d; } return true; }, steps); if (min_d > max_d) { max_d = min_d; x_out = xa; y_out = ya; step_out = ta; } return true; }, steps); distance = max_d; new_point.x = x_out; new_point.y = y_out; step = step_out; } } }