Commit 0402839
quadratic paths with two handles using a hidden point
Changed files (7)
modifiedlibbirdfont/BirdFontFile.valamodifiedlibbirdfont/EditPoint.valamodifiedlibbirdfont/OpenFontFormatWriter.valamodifiedlibbirdfont/Path.valamodifiedlibbirdfont/PenTool.valamodifiedlibbirdfont/Svg.valamodifiedlibbirdfont/Toolbox.vala
Diff
@@ -187,7 +187,10 @@ class BirdFontFile { * S - Start point for path * L - Line * Q - Quadratic Bézier path+ * D - Two quadratic off curve points * C - Cubic Bézier path+ * + * T - Tie handles for previous curve */ private string get_point_data (Path pl) { StringBuilder data = new StringBuilder ();@@ -213,12 +216,23 @@ class BirdFontFile { data.append (" "); add_next_point (m, e, data); n = e;+ + if (e.tie_handles) {+ data.append (" ");+ data.append (@"T");+ }+ i++; } data.append (" "); m = pl.points.first ().data; add_next_point ((!) n, m, data);++ if (m.tie_handles) {+ data.append (" ");+ data.append (@"T");+ } return data.str; }@@ -236,6 +250,12 @@ class BirdFontFile { data.append (@"Q $(h.x ()),$(h.y ()) $(end.x),$(end.y)"); } + private void add_double (EditPoint start, EditPoint end, StringBuilder data) {+ EditPointHandle h1 = start.get_right_handle ();+ EditPointHandle h2 = end.get_left_handle ();+ data.append (@"D $(h1.x ()),$(h1.y ()) $(h2.x ()),$(h2.y ()) $(end.x),$(end.y)");+ }+ private void add_cubic (EditPoint start, EditPoint end, StringBuilder data) { EditPointHandle h1 = start.get_right_handle (); EditPointHandle h2 = end.get_left_handle ();@@ -247,6 +267,8 @@ class BirdFontFile { add_line_to (end, data); } else if (start.right_handle.type == PointType.LINE_CUBIC && end.left_handle.type == PointType.LINE_CUBIC) { add_line_to (end, data);+ } else if (end.left_handle.type == PointType.DOUBLE_CURVE || start.right_handle.type == PointType.DOUBLE_CURVE) {+ add_double (start, end, data); } else if (end.left_handle.type == PointType.QUADRATIC || start.right_handle.type == PointType.QUADRATIC) { add_quadratic (start, end, data); } else {@@ -736,6 +758,50 @@ class BirdFontFile { ep1.recalculate_linear_handles (); } + /** Two quadratic off curve points. */+ private void double_curve (Path path, string px0, string py0, string px1, string py1, string px2, string py2) {+ EditPoint ep1, ep2;+ + double x0 = parse_double (px0);+ double y0 = parse_double (py0);+ double x1 = parse_double (px1);+ double y1 = parse_double (py1);+ double x2 = parse_double (px2);+ double y2 = parse_double (py2);+ + double lx, ly;+ + if (is_null (path.points.last ().data)) {+ warning ("No point");+ return;+ }++ // start with line handles+ ep1 = path.points.last ().data;+ ep1.get_right_handle ().type = PointType.LINE_DOUBLE_CURVE;+ + lx = ep1.x + ((x2 - ep1.x) / 4);+ ly = ep1.y + ((y2 - ep1.y) / 4);+ + ep1.get_right_handle ().move_to_coordinate (lx, ly);+ ep1.recalculate_linear_handles ();+ + // set curve handles+ ep1 = path.points.last ().data;+ ep1.recalculate_linear_handles ();+ ep1.get_right_handle ().type = PointType.DOUBLE_CURVE;+ ep1.get_right_handle ().move_to_coordinate (x0, y0); ++ path.add (x2, y2);+ + ep2 = path.points.last ().data;+ ep2.recalculate_linear_handles ();+ ep2.get_left_handle ().type = PointType.DOUBLE_CURVE;+ ep2.get_left_handle ().move_to_coordinate (x1, y1);+ + ep1.recalculate_linear_handles ();+ }+ private void close (Path path) { EditPoint ep1, ep2; @@ -744,6 +810,8 @@ class BirdFontFile { ep2 = path.points.first ().data; path.points.remove_link (path.points.last ());+ + ep2.tie_handles = ep1.tie_handles; ep2.left_handle.angle = ep1.left_handle.angle; ep2.left_handle.length = ep1.left_handle.length; ep2.left_handle.type = ep1.left_handle.type;@@ -760,6 +828,7 @@ class BirdFontFile { if (d[0] != "S") { warning ("No start point.");+ return path; } if (d[i++] == "S") {@@ -791,6 +860,18 @@ class BirdFontFile { return_val_if_fail (p1.length == 2, path); quadratic (path, p[0], p[1], p1[0], p1[1]);+ } else if (instruction == "D") {+ return_val_if_fail (i + 2 < d.length, path);+ + p = d[i++].split (",");+ p1 = d[i++].split (",");+ p2 = d[i++].split (",");++ return_val_if_fail (p.length == 2, path);+ return_val_if_fail (p1.length == 2, path);+ return_val_if_fail (p2.length == 2, path);+ + double_curve (path, p[0], p[1], p1[0], p1[1], p2[0], p2[1]); } else if (instruction == "C") { return_val_if_fail (i + 2 < d.length, path); @@ -803,6 +884,8 @@ class BirdFontFile { return_val_if_fail (p2.length == 2, path); cubic (path, p[0], p[1], p1[0], p1[1], p2[0], p2[1]);+ } else if (instruction == "T") {+ path.points.last ().data.tie_handles = true; } else { warning (@"invalid instruction $instruction"); return path;@@ -21,12 +21,12 @@ namespace BirdFont { public enum PointType { NONE,- NORMAL,- LINE_QUADRATIC,- LINE_CUBIC,+ LINE_QUADRATIC, // line with quadratic handles+ LINE_DOUBLE_CURVE, // line with two quadratic handles+ LINE_CUBIC, // line with cubic handles CUBIC,+ DOUBLE_CURVE, // two quadratic points with a hidden point half way between the two line handles QUADRATIC,- DOUBLE_CURVE, END, FLOATING }@@ -115,17 +115,28 @@ public class EditPoint { if (h.type == PointType.LINE_CUBIC) { nx = x + ((n.x - x) / 3); ny = y + ((n.y - y) / 3);- h.move_to_coordinate (nx, ny); }- ++ if (h.type == PointType.LINE_DOUBLE_CURVE) {+ nx = x + ((n.x - x) / 4);+ ny = y + ((n.y - y) / 4);+ h.move_to_coordinate (nx, ny);+ }+ // on the other side h = n.get_right_handle (); + if (h.type == PointType.LINE_DOUBLE_CURVE) {+ nx = n.x + ((x - n.x) / 4);+ ny = n.y + ((y - n.y) / 4); + h.move_to_coordinate (nx, ny);+ + }+ if (h.type == PointType.LINE_CUBIC) { nx = n.x + ((x - n.x) / 3);- ny = n.y + ((y - n.y) / 3);- + ny = n.y + ((y - n.y) / 3); h.move_to_coordinate (nx, ny); } }@@ -142,6 +153,13 @@ public class EditPoint { h.move_to_coordinate (nx, ny); } + if (h.type == PointType.LINE_DOUBLE_CURVE) {+ nx = x + ((n.x - x) / 4);+ ny = y + ((n.y - y) / 4);+ + h.move_to_coordinate (nx, ny);+ }+ if (h.type == PointType.LINE_QUADRATIC) { nx = x + ((n.x - x) / 2); ny = y + ((n.y - y) / 2);@@ -157,6 +175,13 @@ public class EditPoint { h.move_to_coordinate (nx, ny); }+ + if (h.type == PointType.LINE_DOUBLE_CURVE) {+ nx = n.x + ((x - n.x) / 4);+ ny = n.y + ((y - n.y) / 4);++ h.move_to_coordinate (nx, ny);+ } } } @@ -186,9 +211,14 @@ public class EditPoint { angle = -acos (a / length) + PI; } - left_handle.type = PointType.CUBIC;- right_handle.type = PointType.CUBIC;- + if (left_handle.type == PointType.LINE_DOUBLE_CURVE || left_handle.type == PointType.QUADRATIC) {+ left_handle.type = PointType.DOUBLE_CURVE;+ right_handle.type = PointType.DOUBLE_CURVE; + } else if (left_handle.type == PointType.LINE_CUBIC) {+ left_handle.type = PointType.CUBIC;+ right_handle.type = PointType.CUBIC;+ }+ right_handle.angle = angle; left_handle.angle = angle; @@ -853,7 +853,126 @@ class GlyfTable : Table { return (uint16)g.unichar_code; } + + public void process_glyph (Glyph g, FontData fd) throws GLib.Error { + uint16 end_point; + uint16 npoints; + int16 ncontours; + int16 nflags; + int glyph_offset; + uint len; + uint coordinate_length; + + fd.seek_end (); // append glyph + + glyph_offset = (int) fd.length (); + + printd (@"glyph_offset: $(glyph_offset)\n"); + + g.remove_empty_paths (); + if (g.path_list.length () == 0) { + // location_offsets == location_offset + 1 to tell parser that this glyf does not have a body + return; + } + + non_zero_glyphs++; + + ncontours = (int16) g.path_list.length (); + fd.add_short (ncontours); + + GlyfData glyf_data = new GlyfData (g); + + // bounding box + fd.add_16 (glyf_data.bounding_box_xmin); + fd.add_16 (glyf_data.bounding_box_ymin); + fd.add_16 (glyf_data.bounding_box_xmax); + fd.add_16 (glyf_data.bounding_box_ymax); + + // end points + foreach (uint16 end in glyf_data.end_points) { + fd.add_u16 (end); + } + + fd.add_u16 (0); // instruction length + + uint glyph_header = 12 + ncontours * 2; + + printd (@"next glyf: $(g.name) ($((uint32)g.unichar_code))\n"); + printd (@"glyf header length: $(glyph_header)\n"); + + end_point = glyf_data.get_end_point (); + ncontours = glyf_data.get_ncontours (); + npoints = (ncontours > 0) ? end_point : 0; // +1? + + if (npoints > max_points) { + max_points = npoints; + } + + if (ncontours > max_contours) { + max_contours = ncontours; + } + + // flags + nflags = glyf_data.get_nflags (); + if (nflags != npoints) { + warning (@"(nflags != npoints) ($nflags != $npoints) in glyph $(g.name). ncontours: $ncontours"); + } + assert (nflags == npoints); + foreach (uint8 flag in glyf_data.flags) { + fd.add_byte (flag); + } + + printd (@"flags: $(nflags)\n"); + + // x coordinates + foreach (int16 x in glyf_data.coordinate_x) { + fd.add_16 (x); + } + + // y coordinates + foreach (int16 y in glyf_data.coordinate_y) { + fd.add_16 (y); + } + + len = fd.length (); + coordinate_length = fd.length () - nflags - glyph_header; + printd (@"coordinate_length: $(coordinate_length)\n"); + printd (@"fd.length (): $(fd.length ())\n"); + assert (fd.length () > nflags + glyph_header); + + printd (@"glyph_offset: $(glyph_offset)\n"); + printd (@"len: $(len)\n"); + + // save bounding box for head table + if (glyf_data.bounding_box_xmin < this.xmin) { + this.xmin = glyf_data.bounding_box_xmin; + } + + if (glyf_data.bounding_box_ymin < this.ymin) { + this.ymin = glyf_data.bounding_box_ymin; + } + + if (glyf_data.bounding_box_xmax > this.xmax) { + this.xmax = glyf_data.bounding_box_xmax; + } + + if (glyf_data.bounding_box_ymax > this.ymax) { + this.ymax = glyf_data.bounding_box_ymax; + } + + // part of average width calculation for OS/2 table + total_width += xmax - xmin; + + printd (@"length before padding: $(fd.length ())\n"); + + // all glyphs needs padding for loca table to be correct + while (fd.length () % 4 != 0) { + fd.add (0); + } + printd (@"length after padding: $(fd.length ())\n"); + } + public Glyph? read_glyph (string name) throws GLib.Error { Glyph? glyph; Glyph g; @@ -1365,172 +1484,137 @@ class GlyfTable : Table { return glyph; } - public void process_glyph (Glyph g, FontData fd) throws GLib.Error { - int16 txmin, tymin, txmax, tymax; - - int16 end_point; - int16 last_end_point; - int16 npoints; - int16 ncontours; - int16 nflags; + /** Data for one entry in the glyf table. */ + class GlyfData : GLib.Object { + public List<Path> paths = new List<Path> (); + public List<uint16> end_points = new List<uint16> (); + public List<uint8> flags = new List<uint8> (); + public List<int16> coordinate_x = new List<int16> (); + public List<int16> coordinate_y = new List<int16> (); - double x, y; - - Font font = OpenFontFormatWriter.get_current_font (); - int glyph_offset; - - uint len; - uint coordinate_length; - Path quadratic; - PointType pt; - - fd.seek_end (); // append glyph + uint16 end_point = 0; + int16 nflags = 0; - glyph_offset = (int) fd.length (); + Glyph glyph; - printd (@"glyph_offset: $(glyph_offset)\n"); + public int16 bounding_box_xmin = 0; + public int16 bounding_box_ymin = 0; + public int16 bounding_box_xmax = 0; + public int16 bounding_box_ymax = 0; - g.remove_empty_paths (); - if (g.path_list.length () == 0) { - // location_offsets == location_offset + 1 to tell parser that this glyf does not have a body - return; + public GlyfData (Glyph g) { + glyph = g; + + foreach (Path p in g.path_list) { + paths.append (p.get_quadratic_points ()); + } + + process_end_points (); + process_flags (); + process_x (); + process_y (); + process_bounding_box (); + } + + public uint16 get_end_point () { + return end_point; + } + + public int16 get_ncontours () { + return (int16) paths.length (); + } + + public int16 get_nflags () { + return nflags; } - non_zero_glyphs++; - - ncontours = (int16) g.path_list.length (); - fd.add_short (ncontours); - - txmin = int16.MAX; - tymin = int16.MAX; - txmax = int16.MIN; - tymax = int16.MIN; - - // bounding box will be set again after coordinate arrays have been created - fd.add_16 (0); - fd.add_16 (0); - fd.add_16 (0); - fd.add_16 (0); - - // end points - end_point = 0; - last_end_point = 0; - foreach (Path p in g.path_list) { - quadratic = p.get_quadratic_points (); + void process_end_points () { + uint16 last_end_point = 0; + PointType type; - if (quadratic.points.length () == 0) { - warning (@"No points in path (quadratic points $(quadratic.points.length ())) (before conversion $(p.points.length ()))"); - continue; - } + end_point = 0; - foreach (EditPoint e in quadratic.points) { - end_point++; - pt = e.get_right_handle ().type; + foreach (Path quadratic in paths) { + if (quadratic.points.length () == 0) { + warning (@"No points in path (before conversion $(quadratic.points.length ()))"); + continue; + } - if (pt == PointType.CUBIC || pt == PointType.QUADRATIC) { + foreach (EditPoint e in quadratic.points) { + end_point++; + type = e.get_right_handle ().type; + + // off curve end_point++; + + if (end_point == 0xFFFF) { + warning ("Too many points"); + } } + end_points.append (end_point - 1); - if (end_point == 0xFFFF) { - warning ("Too many points"); + if (end_point - 1 < last_end_point) { + warning (@"Next endpoint has bad value. (end_point - 1 < last_end_point) ($(end_point - 1) < $last_end_point)"); } - } - fd.add_u16 (end_point - 1); - - if (end_point - 1 < last_end_point) { - warning (@"Next endpoint has bad value. (end_point - 1 < last_end_point) ($(end_point - 1) < $last_end_point)"); + + last_end_point = end_point - 1; } - last_end_point = end_point - 1; + assert (end_point > 0); } - - fd.add_u16 (0); // instruction length - - uint glyph_header = 12 + ncontours * 2; - - printd (@"next glyf: $(g.name) ($((uint32)g.unichar_code))\n"); - printd (@"glyf header length: $(glyph_header)\n"); - - // instructions should go here - - npoints = (ncontours > 0) ? end_point : 0; // +1? - - if (npoints > max_points) { - max_points = npoints; - } - - if (ncontours > max_contours) { - max_contours = ncontours; - } - - // flags - nflags = 0; - List<uint8> flags = new List<uint8> (); - foreach (Path p in g.path_list) { - p = p.get_quadratic_points (); - foreach (EditPoint e in p.points) { - fd.add_byte (Coordinate.ON_PATH); - flags.append (Coordinate.ON_PATH); - nflags++; + + void process_flags () { + flags = new List<uint8> (); + nflags = 0; + + foreach (Path p in paths) { + p = p.get_quadratic_points (); - if (e.get_right_handle ().type == PointType.CUBIC) { - fd.add_byte (Coordinate.NONE); + foreach (EditPoint e in p.points) { + flags.append (Coordinate.ON_PATH); + nflags++; + + // off curve flags.append (Coordinate.NONE); nflags++; } } } - if (nflags != npoints) { - warning (@"(nflags != npoints) ($nflags != $npoints) in glyph $(g.name). ncontours: $ncontours"); - } - assert (nflags == npoints); - - printd (@"flags: $(nflags)\n"); - - // x coordinates - List<int16> coordinate_x = new List<int16> (); - List<int16> coordinate_y = new List<int16> (); - double prev = 0; - - foreach (Path p in g.path_list) { - p = p.get_quadratic_points (); - - foreach (EditPoint e in p.points) { - x = rint (e.x * UNITS - prev - g.left_limit * UNITS); - - fd.add_16 ((int16) x); - coordinate_x.append ((int16) x); - - prev = rint (e.x * UNITS - g.left_limit * UNITS); - - if (e.get_right_handle ().type == PointType.CUBIC) { - x = rint (e.get_right_handle ().x () * UNITS - prev - g.left_limit * UNITS); + void process_x () { + double prev = 0; + double x; + + foreach (Path p in paths) { + foreach (EditPoint e in p.points) { + x = rint (e.x * UNITS - prev - glyph.left_limit * UNITS); + coordinate_x.append ((int16) x); + + prev = rint (e.x * UNITS - glyph.left_limit * UNITS); - fd.add_16 ((int16) x); + // off curve + x = rint (e.get_right_handle ().x () * UNITS - prev - glyph.left_limit * UNITS); coordinate_x.append ((int16) x); - prev = rint (e.get_right_handle ().x () * UNITS - g.left_limit * UNITS); + prev = rint (e.get_right_handle ().x () * UNITS - glyph.left_limit * UNITS); } } } + + void process_y () { + double prev = 0; + double y; + Font font = OpenFontFormatWriter.get_current_font (); + + foreach (Path p in paths) { + foreach (EditPoint e in p.points) { + y = rint (e.y * UNITS - prev + font.base_line * UNITS); + coordinate_y.append ((int16) y); + + prev = rint (e.y * UNITS + font.base_line * UNITS); - // y coordinates - prev = 0; - foreach (Path p in g.path_list) { - p = p.get_quadratic_points (); - foreach (EditPoint e in p.points) { - y = rint (e.y * UNITS - prev + font.base_line * UNITS); - fd.add_16 ((int16) y); - - coordinate_y.append ((int16) y);- - prev = rint (e.y * UNITS + font.base_line * UNITS); - - if (e.get_right_handle ().type == PointType.CUBIC) { + // off curve y = rint (e.get_right_handle ().y () * UNITS - prev + font.base_line * UNITS); - - fd.add_16 ((int16) y); coordinate_y.append ((int16) y); prev = rint (e.get_right_handle ().y () * UNITS + font.base_line * UNITS); @@ -1538,89 +1622,52 @@ class GlyfTable : Table { } } - len = fd.length (); - printd (@"fd.length (): $(fd.length ())\n"); - coordinate_length = fd.length () - nflags - glyph_header; - printd (@"coordinate_length: $(coordinate_length)\n"); - assert (fd.length () > nflags + glyph_header); - - // bounding box - int16 last = 0; - - int i = 0; - foreach (int16 c in coordinate_x) { - c += last; - - // Only on curve points are good for calculating bounding box - if ((flags.nth (i).data & Coordinate.ON_PATH) > 0) { - if (c < txmin) txmin = c; - if (c > txmax) txmax = c; + void process_bounding_box () { + int16 last = 0; + int i = 0; + + bounding_box_xmin = int16.MAX; + bounding_box_ymin = int16.MAX; + bounding_box_xmax = int16.MIN; + bounding_box_ymax = int16.MIN; + + if (coordinate_x.length () == 0) { + warning ("no points in coordinate_y"); } + + foreach (int16 c in coordinate_x) { + c += last; - last = c; - i++; - } + // Only on curve points are good for calculating bounding box + if ((flags.nth (i).data & Coordinate.ON_PATH) > 0) { + if (c < bounding_box_xmin) bounding_box_xmin = c; + if (c > bounding_box_xmax) bounding_box_xmax = c; + } + + last = c; + i++; + } - last = 0; - i = 0; - foreach (int16 c in coordinate_y) { - c += last; - - if ((flags.nth (i).data & Coordinate.ON_PATH) > 0) { - if (c < tymin) tymin = c; - if (c > tymax) tymax = c; + if (coordinate_y.length () == 0) { + warning ("no points in coordinate_y"); } - last = c; - i++; - } - - fd.seek_end (); - - printd (@"glyph_offset: $(glyph_offset)\n"); - printd (@"len: $(len)\n"); - - fd.seek (glyph_offset + 2); // go to box boundries for this glyf - // assert (fd.read_short () == int16.MAX); - - // add bounding box - fd.add_16 (txmin); - fd.add_16 (tymin); - fd.add_16 (txmax); - fd.add_16 (tymax); - fd.seek_end (); - - assert (len == fd.length ()); - - fd.seek (glyph_offset + 2); - assert (fd.read_int16 () == txmin); - assert (fd.read_int16 () == tymin); - assert (fd.read_int16 () == txmax); - assert (fd.read_int16 () == tymax); - fd.seek_end (); - - printd (@"\n"); - printd (@"txmin: $txmin\n"); - printd (@"tymin: $tymin\n"); - printd (@"txmax: $txmax\n"); - printd (@"tymax: $tymax\n"); - - // save this for head table - if (txmin < this.xmin) this.xmin = txmin; - if (tymin < this.ymin) this.ymin = tymin; - if (txmax > this.xmax) this.xmax = txmax; - if (tymax > this.ymax) this.ymax = tymax; + last = 0; + i = 0; + foreach (int16 c in coordinate_y) { + c += last; - // part of average width calculation for OS/2 table - total_width += xmax - xmin; - - printd (@"length before padding: $(fd.length ())\n"); - - // all glyphs needs padding for loca table to be correct - while (fd.length () % 4 != 0) { - fd.add (0); + if ((flags.nth (i).data & Coordinate.ON_PATH) > 0) { + if (c < bounding_box_ymin) bounding_box_ymin = c; + if (c > bounding_box_ymax) bounding_box_ymax = c; + } + + last = c; + i++; + } + + printd (@"Bounding box: $bounding_box_xmin,$bounding_box_ymin $bounding_box_xmax,$bounding_box_ymax\n"); } - printd (@"length after padding: $(fd.length ())\n"); } // necessary in order to have glyphs sorted according to ttf specification @@ -2872,7 +2919,7 @@ class Os2Table : Table { fd.add_16 (200); // SHORT yStrikeoutPosition fd.add_16 (40); // SHORT sFamilyClass - // PANOSE + // FIXME: PANOSE fd.add (0); fd.add (0); fd.add (0); @@ -2884,7 +2931,7 @@ class Os2Table : Table { fd.add (0); fd.add (0); - // FIXA: + // FIXME: fd.add_u32 (0); // ulUnicodeRange1 Bits 0-31 fd.add_u32 (0); // ULONG ulUnicodeRange2 Bits 32-63 fd.add_u32 (0); // ULONG ulUnicodeRange3 Bits 64-95 @@ -4616,7 +4663,7 @@ class GposTable : Table { warning ("No kerning to add to gpos table."); } - printd (@"Adding $num_kerning_values kerning pairs to gpos table."); + printd (@"Adding $num_kerning_values kerning pairs to gpos table.\n"); fd.add_ulong (0x00010000); // table version fd.add_ushort (10); // offset to script list @@ -4776,6 +4823,10 @@ class DirectoryTable : Table { tables.append (this); if (kern_table.kerning_pairs > 0) { + // FIXME: Firefox will ignore the Kern table of GPOS is present + // but not adjust the values according to the GPOS table. + // + // Maybe there is a problem with gpos_table that I am not aware of. tables.append (gpos_table); } @@ -133,7 +133,6 @@ public class Path { if (!is_open () && n != null) { en = (!) n; em = ep.first ().data;- draw_next (en, em, cr); } @@ -196,19 +195,38 @@ public class Path { } private void draw_next (EditPoint e, EditPoint en, Context cr) {- PointType t = e.get_right_handle ().type;+ PointType r = e.get_right_handle ().type;+ PointType l = en.get_left_handle ().type; - if (t == PointType.DOUBLE_CURVE) {- + if (r == PointType.DOUBLE_CURVE || l == PointType.DOUBLE_CURVE) {+ draw_double_curve (e, en, cr); } else { draw_curve (e, en, cr); } } + private static void draw_double_curve (EditPoint e, EditPoint en, Context cr) {+ EditPoint middle;+ double x, y;+ + x = e.get_right_handle ().x () + (en.get_left_handle ().x () - e.get_right_handle ().x ()) / 2;+ y = e.get_right_handle ().y () + (en.get_left_handle ().y () - e.get_right_handle ().y ()) / 2;+ + middle = new EditPoint (x, y, PointType.DOUBLE_CURVE);+ middle.right_handle = en.get_left_handle ().copy ();+ + middle.right_handle.type = PointType.DOUBLE_CURVE;+ middle.left_handle.type = PointType.DOUBLE_CURVE;+ + draw_curve (e, middle, cr);+ draw_curve (middle, en, cr); + }+ private static void draw_curve (EditPoint e, EditPoint en, Context cr, double alpha = 1) { Glyph g = MainWindow.get_current_glyph (); double xa, ya, xb, yb, xc, yc, xd, yd;- PointType t = en.get_right_handle ().type;+ PointType t = e.get_right_handle ().type;+ PointType u = en.get_left_handle ().type; get_bezier_points (e, en, out xa, out ya, out xb, out yb, out xc, out yc, out xd, out yd); @@ -217,7 +235,7 @@ public class Path { cr.line_to (xa, ya); // this point makes sense only if it is the first or last position, the other points are meaningless don't export them - if (t == PointType.QUADRATIC || t == PointType.LINE_QUADRATIC) {+ if (t == PointType.QUADRATIC || t == PointType.LINE_QUADRATIC || t == PointType.DOUBLE_CURVE || u == PointType.QUADRATIC || u == PointType.LINE_QUADRATIC || u == PointType.DOUBLE_CURVE) { cr.curve_to ((xa + 2 * xb) / 3, (ya + 2 * yb) / 3, (xd + 2 * xb) / 3, (yd + 2 * yb) / 3, xd, yd); } else { cr.curve_to (xb, yb, xc, yc, xd, yd);@@ -763,7 +781,9 @@ public class Path { points.delete_link ((!) previous_point); } - if (unlikely (previous_point == null && points.length () != 0)) warning ("previous_point == null");+ if (unlikely (previous_point == null && points.length () != 0)) {+ warning ("previous_point == null");+ } if (points.length () == 0) { points.append (p);@@ -788,10 +808,14 @@ public class Path { if (p.prev != null && p.get_prev ().data.right_handle.type == PointType.QUADRATIC) { p.left_handle.type = PointType.QUADRATIC; p.right_handle.type = PointType.LINE_QUADRATIC;- } else if (Toolbox.use_quadratic_points ()) {+ } else if (Toolbox.get_selected_point_type () == PointType.QUADRATIC) { p.left_handle.type = PointType.LINE_QUADRATIC; p.right_handle.type = PointType.LINE_QUADRATIC; p.type = PointType.LINE_QUADRATIC;+ } else if (Toolbox.get_selected_point_type () == PointType.DOUBLE_CURVE) {+ p.left_handle.type = PointType.LINE_DOUBLE_CURVE;+ p.right_handle.type = PointType.LINE_DOUBLE_CURVE;+ p.type = PointType.DOUBLE_CURVE; } return np;@@ -962,10 +986,18 @@ public class Path { EditPoint middle = new EditPoint (); unowned List<EditPoint> e; EditPointHandle eh;+ EditPointHandle last;+ EditPoint first; quadratic_path = copy ();+ + last = quadratic_path.points.last ().data.get_right_handle ();+ if (last.type == PointType.QUADRATIC) {+ first = quadratic_path.points.first ().data;+ quadratic_path.points.append (first);+ } quadratic_path.close ();- +/* if (quadratic_path.points.length () < 2) { return quadratic_path; }@@ -983,9 +1015,9 @@ public class Path { while (e != quadratic_path.points.last ().prev) { middle.prev = e; middle.next = e.next;- - convert_to_quadratic (quadratic_path, middle); + convert_to_quadratic (quadratic_path, middle); + e = (!) e.next; } @@ -996,7 +1028,7 @@ public class Path { eh = ((!)quadratic_path.points.last ().prev).data.get_right_handle (); eh.set_point_type (PointType.CUBIC); eh.length *= 1.6;-+*/ return quadratic_path; } @@ -1061,6 +1093,7 @@ public class Path { } else { e = (!) e.next; }+ } return need_split; @@ -1077,9 +1110,11 @@ public class Path { EditPoint stop; PointType rt = middle.get_right_handle ().type; + /* // FIXME: DELETE if (rt == PointType.QUADRATIC || rt == PointType.LINE_CUBIC || rt == PointType.LINE_QUADRATIC) { return false; }+ */ start = middle.get_prev ().data; stop = middle.get_next ().data;@@ -178,7 +178,11 @@ public class PenTool : Tool { if (selected_handle.type == PointType.LINE_QUADRATIC) { selected_handle.set_point_type (PointType.QUADRATIC); }- ++ if (selected_handle.type == PointType.LINE_DOUBLE_CURVE) {+ selected_handle.set_point_type (PointType.DOUBLE_CURVE);+ }+ if (GridTool.is_visible ()) { coordinate_x = Glyph.path_coordinate_x (x); coordinate_y = Glyph.path_coordinate_y (y);@@ -84,6 +84,8 @@ class Svg { add_abs_line_to (start, end, svg, g, do_glyph); } else if (end.left_handle.type == PointType.QUADRATIC || start.right_handle.type == PointType.QUADRATIC) { add_quadratic_abs_path (start, end, svg, g, do_glyph);+ } else if (end.left_handle.type == PointType.DOUBLE_CURVE || start.right_handle.type == PointType.DOUBLE_CURVE) {+ add_double_quadratic_abs_path (start, end, svg, g, do_glyph); } else { add_cubic_abs_path (start, end, svg, g, do_glyph); }@@ -133,6 +135,20 @@ class Svg { } } + private static void add_double_quadratic_abs_path (EditPoint start, EditPoint end, StringBuilder svg, Glyph g, bool to_glyph) {+ EditPoint middle;+ double x, y;+ + x = start.get_right_handle ().x () + (end.get_left_handle ().x () - start.get_right_handle ().x ()) / 2;+ y = start.get_right_handle ().y () + (end.get_left_handle ().y () - start.get_right_handle ().y ()) / 2;+ + middle = new EditPoint (x, y, PointType.QUADRATIC);+ middle.right_handle = end.get_left_handle ().copy ();+ + add_quadratic_abs_path (start, middle, svg, g, to_glyph);+ add_quadratic_abs_path (middle, end, svg, g, to_glyph);+ }+ private static void add_quadratic_abs_path (EditPoint start, EditPoint end, StringBuilder svg, Glyph g, bool to_glyph) { double left = g.left_limit; double baseline = BirdFont.get_current_font ().base_line;@@ -47,7 +47,7 @@ public class Toolbox : GLib.Object { ImageSurface? toolbox_background = null; - public static bool use_quadratic_bezierpoints = false;+ public static PointType point_type = PointType.CUBIC; public Toolbox (GlyphCanvas main_glyph_canvas) { glyph_canvas = main_glyph_canvas;@@ -134,16 +134,23 @@ public class Toolbox : GLib.Object { // quadratic Bézier points Tool quadratic_points = new Tool ("quadratic_points", _("Create quadratic Bézier curves")); quadratic_points.select_action.connect ((self) => {- Toolbox.use_quadratic_bezierpoints = true;+ point_type = PointType.QUADRATIC; }); draw_tool_modifiers.add_tool (quadratic_points); // cubic Bézier points Tool cubic_points = new Tool ("cubic_points", _("Create cubic Bézier curves")); cubic_points.select_action.connect ((self) => {- Toolbox.use_quadratic_bezierpoints = false;+ point_type = PointType.CUBIC; }); draw_tool_modifiers.add_tool (cubic_points);++ // two quadratic points off curve for each quadratic control point+ Tool double_points = new Tool ("double_points", _("Quadratic double point"));+ double_points.select_action.connect ((self) => {+ point_type = PointType.DOUBLE_CURVE;+ });+ draw_tool_modifiers.add_tool (double_points); // path tools Tool union_paths_tool = new MergeTool ("union_paths");@@ -598,11 +605,9 @@ public class Toolbox : GLib.Object { idle.attach (null); } - /**- * @return true if new points should be quadratic instead of cubic.- */- public static bool use_quadratic_points () {- return use_quadratic_bezierpoints;+ /** Insert new points of this type. */+ public static PointType get_selected_point_type () {+ return point_type; } public void key_press (uint keyval) {