Commit 2960bf3
export quadratic bezier paths
Changed files (5)
modifiedsrc/Font.valamodifiedsrc/OpenFontFormatWriter.valamodifiedsrc/Path.valamodifiedsrc/TestCases.valamodifiedsrc/Tool.vala
Diff
@@ -284,11 +284,11 @@ class Font : GLib.Object { Path p; Path i; - if (has_glyph ("notdef")) {- return (!) get_glyph ("notdef");+ if (has_glyph (".notdef")) {+ return (!) get_glyph (".notdef"); } - g = new Glyph ("notdef", 0);+ g = new Glyph (".notdef", 0); p = new Path (); i = new Path (); @@ -19,6 +19,8 @@ using Math; namespace Supplement { +const double UNITS = 10; + class OpenFontFormatWriter : Object { DataOutputStream os; @@ -422,6 +424,7 @@ class FontData : Object { class Coordinate { /** TTF coordinate flags. */ + public static const uint8 NONE = 0; public static const uint8 ON_PATH = 1 << 0; public static const uint8 X_SHORT_VECTOR = 1 << 1; public static const uint8 Y_SHORT_VECTOR = 1 << 2; @@ -1330,8 +1333,13 @@ class GlyfTable : Table { end_point = 0; last_end_point = 0; foreach (Path p in g.path_list) { + p = p.get_quadratic_points (); foreach (EditPoint e in p.points) { end_point++; + + if (e.get_right_handle ().type == PointType.CURVE) { + end_point++; + } } fd.add_u16 (end_point - 1); @@ -1359,9 +1367,15 @@ class GlyfTable : Table { // flags nflags = 0; foreach (Path p in g.path_list) { + p = p.get_quadratic_points (); foreach (EditPoint e in p.points) { fd.add_byte (Coordinate.ON_PATH); nflags++; + + if (e.get_right_handle ().type == PointType.CURVE) { + fd.add_byte (Coordinate.NONE); + nflags++; + } } } @@ -1373,30 +1387,53 @@ class GlyfTable : Table { // x coordinates double prev = 0; foreach (Path p in g.path_list) { + p = p.get_quadratic_points (); foreach (EditPoint e in p.points) { - x = e.x - prev - g.left_limit; + x = e.x * UNITS - prev + g.left_limit * UNITS; fd.add_16 ((int16) x); if (x + prev < txmin) txmin = (int16) (x + prev); if (x + prev > txmax) txmax = (int16) (x + prev); - prev = e.x - g.left_limit; + prev = e.x * UNITS + g.left_limit * UNITS; + + if (e.get_right_handle ().type == PointType.CURVE) { + x = e.get_right_handle ().x () * UNITS - prev + g.left_limit * UNITS; + + fd.add_16 ((int16) x); + + if (x + prev < txmin) txmin = (int16) (x + prev); + if (x + prev > txmax) txmax = (int16) (x + prev); + + prev = e.get_right_handle ().x () * UNITS + g.left_limit * UNITS; + } } } - // x coordinates + // y coordinates prev = 0; foreach (Path p in g.path_list) { + p = p.get_quadratic_points (); foreach (EditPoint e in p.points) { - y = e.y - prev + font.base_line; - + y = e.y * UNITS - prev + font.base_line * UNITS; fd.add_16 ((int16) y); if (y + prev < tymin) tymin = (int16) (y + prev); if (y + prev > tymax) tymax = (int16) (y + prev); - prev = e.y + font.base_line; + prev = e.y * UNITS + font.base_line * UNITS; + + if (e.get_right_handle ().type == PointType.CURVE) { + y = e.get_right_handle ().y () * UNITS - prev + font.base_line * UNITS; + + fd.add_16 ((int16) y); + + if (y + prev < tymin) tymin = (int16) (y + prev); + if (y + prev > tymax) tymax = (int16) (y + prev); + + prev = e.get_right_handle ().y () * UNITS + font.base_line * UNITS; + } } } @@ -1442,17 +1479,27 @@ class GlyfTable : Table { //glyphs.append (font.get_null_character ()); //glyphs.append (font.get_nonmarking_return ()); //glyphs.append (font.get_space ()); - + + List<Glyph> unassigned_glyphs = new List<Glyph> (); + // add glyphs, first all assigned then the unassigned ones for (indice = 0; (gl = font.get_glyph_indice (indice)) != null; indice++) { g = (!) gl; - if (g.name == "notdef" || g.name == "null" || g.name == "nonmarkingreturn" || g.name == "space" || g.name == " ") { + if (g.name == ".notdef" || g.unichar_code == 0) { continue; } - glyphs.append (g); - } + if (!g.is_unassigned ()) { + glyphs.append (g); + } else { + unassigned_glyphs.append (g); + } + } + + foreach (Glyph ug in unassigned_glyphs) { + glyphs.append (ug); + } } @@ -1696,17 +1743,8 @@ class CmapSubtableWindowsUnicode : CmapSubtable { uint16 gid_length = 0; uint32 indice; - uint32 first_assigned = 0; - - foreach (Glyph g in glyf_table.glyphs) { - if (!g.is_unassigned ()) { - first_assigned++; - } else { - break; - } - } - printd (@"first_assigned $first_assigned\n"); - first_assigned = 1; + uint32 first_assigned = 1; + foreach (Glyph g in glyf_table.glyphs) { if (!g.is_unassigned ()) { glyph_range.add_single (g.unichar_code); @@ -2082,7 +2120,7 @@ class HeadTable : Table { // font_data.add_u16 (BASELINE_AT_ZERO | LSB_AT_ZERO); font_data.add_u16 (0); // flags - font_data.add_u16 (100); // units per em (should be a power of two for ttf fonts) + font_data.add_u16 (1000); // units per em (should be a power of two for ttf fonts) font_data.add_64 (0); // creation time since 1904-01-01 font_data.add_64 (0); // modified time since 1904-01-01 @@ -2183,10 +2221,9 @@ class HheaTable : Table { Fixed version = 1 << 16; fd.add_fixed (version); // table version - - // TODO: units per em - fd.add_16 ((int16) (-1 * (font.top_position - font.base_line))); // Ascender - fd.add_16 ((int16) (-1 * font.bottom_position)); // Descender + + fd.add_16 ((int16) (-1 * (font.top_position - font.base_line) * UNITS)); // Ascender + fd.add_16 ((int16) (-1 * font.bottom_position * UNITS)); // Descender fd.add_16 (0); // LineGap fd.add_u16 (hmtx_table.max_advance); // maximum advance width value in 'hmtx' table. @@ -2309,9 +2346,9 @@ class HmtxTable : Table { foreach (Glyph g in glyf_table.glyphs) { g.boundries (out xmin, out ymin, out xmax, out ymax); - lsb = (int16) (xmin - g.left_limit + 0.5); - advance = (int16) (g.right_limit - g.left_limit + 0.5); - extent = (int16) (lsb + (xmax - xmin) + 0.5); + lsb = (int16) ((xmin - g.left_limit) * UNITS + 0.5); + advance = (int16) (g.right_limit * UNITS - g.left_limit * UNITS + 0.5); + extent = (int16) (lsb + (xmax * UNITS - xmin * UNITS ) + 0.5); rsb = (int16) (advance - extent); printd (@"$(g.name) advance: $advance = $((int16) (g.right_limit)) - $((int16) (g.left_limit))\n"); @@ -62,6 +62,8 @@ class Path { private static ImageSurface? edit_point_handle_image = null; private static ImageSurface? active_edit_point_handle_image = null; + Path quadratic_path; // quadratic points for ttf export+ public Path () { if (edit_point_image == null) { edit_point_image = Icons.get_icon ("edit_point.png");@@ -1029,13 +1031,163 @@ class Path { return true; } + /** Convert quadratic bezier points to cubic representation of the glyph+ * suitable for ttf-export.+ */ + public Path get_quadratic_points () {+ EditPoint middle = new EditPoint ();+ unowned List<EditPoint> e;+ int i = 0;+ int j = 0;+ + quadratic_path = copy ();+ + if (quadratic_path.points.length () == 0) {+ return quadratic_path;+ }+ + // split all curves in as many regions as we need+ while (split_all_cubic_in_half (quadratic_path)) {+ j++;+ + if (j > 5) {+ warning ("too many iterations in split path");+ break;+ }+ }+ + // estimate quatdratic form+ middle.prev = quadratic_path.points.last ();+ middle.next = quadratic_path.points.first ();++ if (((!)middle.prev).data.get_left_handle ().type == PointType.CURVE) {+ convert_to_quadratic (quadratic_path, middle); + } + + e = (!) quadratic_path.points.first ();+ while (e != quadratic_path.points.last ()) {+ middle.prev = e;+ middle.next = e.next;+ + if (e.data.get_left_handle ().type == PointType.CURVE) {+ convert_to_quadratic (quadratic_path, middle); + }+ + e = (!) e.next;+ }+ + return quadratic_path;+ }++ void convert_to_quadratic (Path cubic_path, EditPoint middle) {+ double curve_x, curve_y, distance; + EditPointHandle eh;+ + EditPoint start = middle.get_prev ().data;+ EditPoint stop = middle.get_next ().data;++ estimate_quadratic (start, stop, out curve_x, out curve_y);+ + eh = start.get_left_handle ();+ eh.set_point_type (PointType.NONE);+ eh.length = 0;+ + eh = start.get_right_handle ();+ eh.set_point_type (PointType.CURVE);+ eh.move_to_coordinate (curve_x, curve_y);+ }++ public bool split_all_cubic_in_half (Path cubic_path) {+ EditPoint middle = new EditPoint ();+ unowned List<EditPoint> e;+ bool need_split = false;+ uint len = cubic_path.points.length ();++ middle.prev = cubic_path.points.last ();+ middle.next = cubic_path.points.first ();+ + if (split_cubic_in_half (cubic_path, middle)) {+ need_split = true;+ }+ + e = (!) cubic_path.points.first ();+ for (uint i = 0; i < len - 1; i++) {+ middle.prev = e;+ middle.next = e.next;+ + if (e.data.get_left_handle ().type == PointType.CURVE + && split_cubic_in_half (cubic_path, middle)) {+ e = (!) e.next.next;+ need_split = true;+ } else {+ e = (!) e.next;+ }+ }+ + return need_split; + }++ public void estimate_quadratic (EditPoint start, EditPoint stop, out double curve_x, out double curve_y) {+ curve_x = -0.25 * start.x + 0.75 * stop.get_left_handle ().x () + 0.75 * start.get_right_handle ().x () - 0.25 * stop.x;+ curve_y = -0.25 * start.y + 0.75 * stop.get_left_handle ().y () + 0.75 * start.get_right_handle ().y () - 0.25 * stop.y; + }++ bool split_cubic_in_half (Path cubic_path, EditPoint middle) {+ double curve_x, curve_y, cx, cy, qx, qy, nx, ny, distance; + + EditPoint start = middle.get_prev ().data;+ EditPoint stop = middle.get_next ().data;+ + estimate_quadratic (start, stop, out curve_x, out curve_y);+ + qx = quadratic_bezier_path (0.75, start.x, curve_x, stop.x);+ qy = quadratic_bezier_path (0.75, start.y, curve_y, stop.y);++ cx = bezier_path (0.75, start.x, start.get_right_handle ().x (), stop.get_left_handle ().x (), stop.x);+ cy = bezier_path (0.75, start.y, start.get_right_handle ().y (), stop.get_left_handle ().y (), stop.y);++ distance = Math.sqrt (Math.pow (cx - qx, 2) + Math.pow (cy - qy, 2));++ nx = bezier_path (0.5, start.x, start.get_right_handle ().x (), stop.get_left_handle ().x (), stop.x);+ ny = bezier_path (0.5, start.y, start.get_right_handle ().y (), stop.get_left_handle ().y (), stop.y);++ if (Math.fabs (distance) > 0.1) {+ EditPoint new_edit_point = new EditPoint (nx, ny, PointType.CURVE);++ new_edit_point.next = middle.get_next ();+ new_edit_point.prev = middle.get_prev ();+ cubic_path.insert_new_point_on_path (new_edit_point);+ cubic_path.create_list ();++ return true; + }+ + return false;+ }++ double distance_to_path (EditPoint start, EditPoint stop, double x, double y) {+ double min = double.MAX;+ + all_of (start, stop, (cx, cy, t) => {+ double n = pow (x - cx, 2) + pow (y - cy, 2);+ + if (n < min) {+ min = n;+ }+ + return true;+ });+ + return min;+ }+ public void insert_new_point_on_path (EditPoint ep) { EditPoint start, stop; double x0, x1, y0, y1; double px, py; double position, t, d, min;- double steps = 100;+ double steps = 200; start = ep.get_prev ().data; stop = ep.get_next ().data;@@ -1123,24 +1275,24 @@ class Path { } all_of (prev.data, i.data, (cx, cy, t) => {- n = pow (x - cx, 2) + pow (y - cy, 2);+ n = pow (x - cx, 2) + pow (y - cy, 2);+ + if (n < min) {+ min = n; - if (n < min) {- min = n;- - ox = cx;- oy = cy;+ ox = cx;+ oy = cy;+ + previous_point = prev;+ next_point = i; - previous_point = prev;- next_point = i;- - step = t;- - g = true;- }+ step = t; - return true;- });+ g = true;+ }+ + return true;+ }); } if (previous_point == null) {@@ -1245,7 +1397,14 @@ class Path { a0 = step * (q1 - q0) + q0; a1 = step * (q2 - q1) + q1; }- ++ public static double quadratic_bezier_path (double step, double p0, double p1, double p2) {+ double q0 = step * (p1 - p0) + p0;+ double q1 = step * (p2 - p1) + p1;+ + return step * (q1 - q0) + q0;+ }+ public void plot (Context cr, Allocation allocation, double view_zoom) { double px = 0, py = 0; double xc = allocation.width / 2.0;@@ -37,6 +37,48 @@ class TestCases { add (test_delete_points, "Delete edit points"); add (test_view_result, "View result in web browser"); add (test_save_backup, "Save backup");+ add (test_convert_to_quadratic_bezier_path, "Convert to quadratic path");+ }++ public static void test_convert_to_quadratic_bezier_path () {+ Tool.test_open_next_glyph ();+ Glyph g = MainWindow.get_current_glyph ();+ Path p = new Path ();+ Path p1 = new Path ();+ + EditPoint e0, e1, e2, e3;+ + p.add (-10, 10);+ p.add (10, 10);+ p.add (10, -10);+ p.add (-10, -10);+ p.close ();+ g.add_path (p);+ g.add_path (p1.get_quadratic_points ());++ e0 = new EditPoint (20, 40);+ e1 = new EditPoint (40, 40);+ e2 = new EditPoint (40, 20);+ e3 = new EditPoint (20, 20);++ p1.add_point (e0);+ p1.add_point (e1);+ p1.add_point (e2);+ p1.add_point (e3);+ p1.close ();++ e0.set_tie_handle (true);+ e1.set_tie_handle (true);+ e2.set_tie_handle (true);+ e3.set_tie_handle (true);++ e0.process_tied_handle ();+ e1.process_tied_handle ();+ e2.process_tied_handle ();+ e3.process_tied_handle ();++ g.add_path (p1);+ g.add_path (p1.get_quadratic_points ()); } public static void test_overview () {@@ -235,49 +235,49 @@ class Tool : GLib.Object { /** Help function to test button press actions. */ public void test_click_action (int b, int x, int y) {- this.yield ();+ Tool.yield (); press_action (this, b, x, y); - this.yield ();+ Tool.yield (); release_action (this, b, x, y); } /** Help function to test select action for this tool. */ public void test_select_action () {- this.yield ();+ Tool.yield (); MainWindow.get_toolbox ().select_tool (this); } public void test_move_action (int x, int y) {- this.yield ();+ Tool.yield (); move_action (this, x, y); } public void test_press_action (int b, int x, int y) {- this.yield ();+ Tool.yield (); press_action (this, b, x, y); } public void test_release_action (int b, int x, int y) {- this.yield ();+ Tool.yield (); release_action (this, b, x, y); } - public void test_open_next_glyph () {- this.yield ();+ public static void test_open_next_glyph () {+ Tool.yield (); OverView o = MainWindow.get_overview (); MainWindow.get_tab_bar ().select_overview (); o.select_next_glyph ();- this.yield ();+ Tool.yield (); o.open_current_glyph ();- this.yield ();+ Tool.yield (); } /** Run pending events in main loop before continue. */- public void @yield () {+ public static void @yield () { int t = 0; var time = new TimeoutSource(500); bool timeout;@@ -313,7 +313,6 @@ class Tool : GLib.Object { } if (unlikely (t > 100000)) {- warning (@"\nToo many interations in test suite main loop for $(get_name ())\n"); assert (false); break; }