Glyph.vala
/*
Copyright (C) 2012 - 2016 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;
using Gee;
namespace BirdFont {
public class Glyph : FontDisplay {
// Background image
BackgroundImage? background_image = null;
bool background_image_visible = true;
// Glyph zoom level
public double view_zoom = 0.1;
public double view_offset_x = 0;
public double view_offset_y = 0;
Gee.ArrayList<ZoomView> zoom_list = new Gee.ArrayList<ZoomView> ();
int zoom_list_index = 0;
// The point where edit event begun
double pointer_begin_x = 0;
double pointer_begin_y = 0;
// Tap last tap event position in pixels
int last_tap0_y = 0;
int last_tap0_x = 0;
int last_tap1_y = 0;
int last_tap1_x = 0;
double zoom_distance = 0;
bool change_view;
bool ignore_input = false;
// Current pointer position
public double motion_x = 0;
public double motion_y = 0;
// Zoom area
public double zoom_x1 = 0;
public double zoom_y1 = 0;
public double zoom_x2 = 0;
public double zoom_y2 = 0;
public bool zoom_area_is_visible = false;
bool view_is_moving = false;
public double move_offset_x = 0;
public double move_offset_y = 0;
bool move_canvas = false;
public WidgetAllocation allocation = new WidgetAllocation ();
// FIXME: name and unichar should be moved to to glyph collection
public unichar unichar_code = 0;
public string name;
public double left_limit {
get {
return _left_limit;
}
set {
ttf_data = null;
_left_limit = value;
}
}
public double right_limit {
get {
return _right_limit;
}
set {
ttf_data = null;
_right_limit = value;
}
}
private double _right_limit = 0;
private double _left_limit = 0;
// x-height, lsb, etc.
public Gee.ArrayList<Line> vertical_help_lines = new Gee.ArrayList<Line> ();
public Gee.ArrayList<Line> horizontal_help_lines = new Gee.ArrayList<Line> ();
public bool show_help_lines = true;
bool xheight_lines_visible = false;
bool margin_boundaries_visible = false;
string new_guide_name = "";
Gee.ArrayList<Glyph> undo_list = new Gee.ArrayList<Glyph> ();
Gee.ArrayList<Glyph> redo_list = new Gee.ArrayList<Glyph> ();
string glyph_sequence = "";
bool open = true;
public static Glyph? background_glyph = null;
bool empty = false;
/** Id in the version list. */
public int version_id = 0;
/** Cache quadratic form on export. */
GlyfData? ttf_data = null;
Line left_line;
Line right_line;
/** Cache for Cairo surface rendering */
HashMap<string, Surface> glyph_cache = new HashMap<string, Surface> ();
public const double CANVAS_MIN = -10000;
public const double CANVAS_MAX = 10000;
public static bool show_orientation_arrow = false;
public static double orientation_arrow_opacity = 1;
public Layer layers = new Layer ();
public int current_layer = 0;
public Gee.ArrayList<Path> active_paths = new Gee.ArrayList<Path> ();
public Gee.ArrayList<Layer> selected_groups = new Gee.ArrayList<Layer> ();
// used if this glyph is fetched from a fallback font
public double top_limit = 0;
public double baseline = 0;
public double bottom_limit = 0;
public Surface? overview_thumbnail = null;
public Glyph (string name, unichar unichar_code = 0) {
this.name = name;
this.unichar_code = unichar_code;
add_help_lines ();
left_limit = -28;
right_limit = 28;
}
public Glyph.no_lines (string name, unichar unichar_code = 0) {
this.name = name;
this.unichar_code = unichar_code;
}
public Gee.ArrayList<Path> get_active_paths () {
return active_paths;
}
public Layer get_current_layer () {
return_val_if_fail (0 <= current_layer < layers.subgroups.size, new Layer ());
return layers.subgroups.get (current_layer);
}
public void set_current_layer (Layer layer) {
int i = 0;
foreach (Layer l in layers.subgroups) {
if (likely (l == layer)) {
current_layer = i;
return;
}
i++;
}
warning ("Layer is not added to glyph.");
}
public Gee.ArrayList<Path> get_visible_paths () {
return layers.get_visible_paths ().paths;
}
public PathList get_visible_path_list () {
return layers.get_visible_paths ();
}
public Gee.ArrayList<Path> get_paths_in_current_layer () {
return get_current_layer ().get_all_paths ().paths;
}
public Gee.ArrayList<Path> get_all_paths () {
return layers.get_all_paths ().paths;
}
public void add_new_layer () {
layers.add_layer (new Layer ());
current_layer = layers.subgroups.size - 1;
}
public int get_layer_index (Layer layer) {
return layers.index_of (layer);
}
public GlyfData get_ttf_data () {
if (ttf_data == null) {
ttf_data = new GlyfData (this);
}
return (!) ttf_data;
}
public PathList get_quadratic_paths () {
PointConverter pc;
PathList pl;
PathList stroke;
pl = new PathList ();
foreach (Path p in get_visible_paths ()) {
if (p.stroke > 0) {
stroke = p.get_completed_stroke ();
foreach (Path stroke_part in stroke.paths) {
pc = new PointConverter (stroke_part);
pl.add (pc.get_quadratic_path ());
}
} else {
pc = new PointConverter (p);
pl.add (pc.get_quadratic_path ());
}
}
return pl;
}
public override void close () {
undo_list.clear ();
redo_list.clear ();
}
public void set_empty_ttf (bool e) {
empty = e;
}
public bool is_empty_ttf () {
return empty;
}
public void clear_active_paths () {
selected_groups.clear ();
active_paths.clear ();
}
public void add_active_path (Layer? group, Path? p) {
Path path;
Layer g;
if (p != null) {
path = (!) p;
if (Toolbox.get_move_tool ().is_selected ()) {
if (path.stroke > 0) {
Toolbox.set_object_stroke (path.stroke);
}
}
if (!active_paths.contains (path)) {
active_paths.add (path);
}
PenTool.active_path = path;
}
if (group != null) {
g = (!) group;
if (!selected_groups.contains (g)) {
selected_groups.add (g);
}
}
}
public void delete_background () {
store_undo_state ();
background_image = null;
GlyphCanvas.redraw ();
}
public Path? get_active_path () {
return_val_if_fail (active_paths.size > 0, null);
return active_paths.get (active_paths.size - 1);
}
public bool boundaries (out double x1, out double y1, out double x2, out double y2) {
var paths = get_all_paths ();
if (paths.size == 0) {
x1 = 0;
y1 = 0;
x2 = 0;
y2 = 0;
return false;
}
x1 = CANVAS_MAX;
x2 = CANVAS_MIN;
y1 = CANVAS_MAX;
y2 = CANVAS_MIN;
foreach (Path p in paths) {
p.update_region_boundaries ();
if (p.points.size > 1) {
if (p.xmin < x1) {
x1 = p.xmin;
}
if (p.xmax > x2) {
x2 = p.xmax;
}
if (p.ymin < y1) {
y1 = p.ymin;
}
if (p.ymax > y2) {
y2 = p.ymax;
}
}
}
return x1 != double.MAX;
}
public void selection_boundaries (out double x, out double y, out double w, out double h) {
double px, py, px2, py2;
px = 10000;
py = 10000;
px2 = -10000;
py2 = -10000;
foreach (Path p in active_paths) {
if (p.xmin < px) {
px = p.xmin;
}
if (p.ymin < py) {
py = p.ymin;
}
if (p.xmax > px2) {
px2 = p.xmax;
}
if (p.ymax > py2) {
py2 = p.ymax;
}
}
if (px2 == -10000 || px == 10000) {
warning (@"No box for selected paths. ($(active_paths.size))");
px = 0;
py = 0;
px2 = 0;
py2 = 0;
}
x = px;
y = py2;
w = px2 - px;
h = py2 - py;
}
/** @return centrum pixel for x coordinates. */
public static double xc () {
double c = MainWindow.get_current_glyph ().allocation.width / 2.0;
return c;
}
/** @return centrum pixel for y coordinates. */
public static double yc () {
double c = MainWindow.get_current_glyph ().allocation.height / 2.0;
return c;
}
/** @return 1/view_zoom */
public static double ivz () {
return 1 / MainWindow.get_current_glyph ().view_zoom;
}
public void resized (WidgetAllocation alloc) {
double a, b, c, d;
a = Glyph.path_coordinate_x (0);
b = Glyph.path_coordinate_y (0);
this.allocation = alloc;
c = Glyph.path_coordinate_x (0);
d = Glyph.path_coordinate_y (0);
view_offset_x -= c - a;
view_offset_y -= b - d;
}
public void set_background_image (BackgroundImage? b) {
BackgroundImage bg;
if (b == null) {
background_image = null;
} else {
bg = (!) b;
background_image = bg;
}
BirdFont.get_current_font ().touch ();
}
public BackgroundImage? get_background_image () {
return background_image;
}
public override void scroll_wheel (double x, double y,
double pixeldelta_x, double pixeldelta_y) {
if (pixeldelta_y > 0) {
zoom_in_at_point (x, y, pixeldelta_y);
} else {
zoom_out_at_point (x, y, pixeldelta_y);
}
GlyphCanvas.redraw ();
}
public virtual void add_path (Path p) {
if (layers.subgroups.size == 0) {
layers.add_layer (new Layer ());
}
get_current_layer ().add_path (p);
}
public override void selected_canvas () {
TimeoutSource input_delay;
ttf_data = null; // recreate quatradic path on export
overview_thumbnail = null;
ignore_input = true; // make sure that tripple clicks in overview are ignored
input_delay = new TimeoutSource (250);
input_delay.set_callback(() => {
ignore_input = false;
return false;
});
input_delay.attach (null);
add_help_lines ();
KeyBindings.set_require_modifier (false);
glyph_sequence = Preferences.get ("glyph_sequence");
GridTool.update_lines ();
if (!is_null (MainWindow.native_window)) {
MainWindow.scrollbar.set_size (0);
}
update_zoom_bar ();
Font font = BirdFont.get_current_font ();
string index = font.settings.get_setting (@"Active Layer $(get_name ())");
if (index != "") {
int i = int.parse (index);
if (0 <= i < layers.subgroups.size) {
current_layer = i;
}
}
DrawingTools.update_layers ();
MainWindow.get_toolbox ().update_expanders ();
Tool current_tool = MainWindow.get_toolbox ().get_current_tool ();
current_tool.select_action (current_tool);
}
void update_zoom_bar () {
if (!is_null (Toolbox.drawing_tools)
&& !is_null (Toolbox.drawing_tools.zoom_bar)) {
Toolbox.drawing_tools.zoom_bar.set_zoom ((view_zoom - 1) / 20);
}
}
public void remove_lines () {
vertical_help_lines.clear ();
horizontal_help_lines.clear ();
}
public void add_help_lines () {
remove_lines ();
return_if_fail (!is_null (BirdFont.get_current_font ()));
double bgt = BirdFont.get_current_font ().top_limit;
Line top_margin_line = new Line ("top margin", t_("top margin"), bgt, false);
top_margin_line.set_color_theme ("Guide 2");
top_margin_line.position_updated.connect ((pos) => {
BirdFont.get_current_font ().top_limit = pos;
});
double thp = BirdFont.get_current_font ().top_position;
Line top_line = new Line ("top", t_("top"), thp, false);
top_line.position_updated.connect ((pos) => {
Font f = BirdFont.get_current_font ();
f.top_position = pos;
});
double xhp = BirdFont.get_current_font ().xheight_position;
Line xheight_line = new Line ("x-height", t_("x-height"), xhp, false);
xheight_line.set_color_theme ("Guide 3");
xheight_line.dashed = true;
xheight_line.position_updated.connect ((pos) => {
Font f = BirdFont.get_current_font ();
f.xheight_position = pos;
});
double xbl = BirdFont.get_current_font ().base_line;
Line base_line = new Line ("baseline", t_("baseline"), xbl, false);
base_line.position_updated.connect ((pos) => {
Font f = BirdFont.get_current_font ();
f.base_line = pos;
Overview o = MainWindow.get_overview ();
o.reset_cache ();
});
double bp = BirdFont.get_current_font ().bottom_position;
Line bottom_line = new Line ("bottom", t_("bottom"), bp, false);
bottom_line.position_updated.connect ((pos) => {
BirdFont.get_current_font ().bottom_position = pos;
});
double bgb = BirdFont.get_current_font ().bottom_limit;
Line bottom_margin_line = new Line ("bottom margin", t_("bottom margin"), bgb, false);
bottom_margin_line.set_color_theme ("Guide 2");
bottom_margin_line.position_updated.connect ((pos) => {
BirdFont.get_current_font ().bottom_limit = pos;
});
left_line = new Line ("left", t_("left"), left_limit, true);
left_line.lsb = true;
left_line.position_updated.connect ((pos) => {
left_limit = pos;
update_other_spacing_classes ();
left_line.set_metrics (get_left_side_bearing ());
});
left_line.set_metrics (get_left_side_bearing ());
right_line = new Line ("right", t_("right"), right_limit, true);
right_line.rsb = true;
right_line.position_updated.connect ((pos) => {
right_limit = pos;
update_other_spacing_classes ();
right_line.set_metrics (get_right_side_bearing ());
});
right_line.set_metrics (get_right_side_bearing ());
// lists of lines are sorted and lines are added only if
// they are relevant for a particular glyph.
// left to right
add_line (left_line);
add_line (right_line);
bool glyph_has_top = has_top_line ();
// top to bottom
add_line (top_margin_line);
top_margin_line.set_visible (margin_boundaries_visible);
add_line (top_line);
top_line.set_visible (glyph_has_top || xheight_lines_visible);
add_line (xheight_line);
xheight_line.set_visible (!glyph_has_top || xheight_lines_visible);
add_line (base_line);
add_line (bottom_line);
bottom_line.set_visible (CharDatabase.has_descender (unichar_code) || xheight_lines_visible);
add_line (bottom_margin_line);
bottom_margin_line.set_visible (margin_boundaries_visible);
foreach (Line guide in BirdFont.get_current_font ().custom_guides) {
add_line (guide);
}
}
public double get_left_side_bearing () {
double x1, y1, x2, y2;
if (get_boundaries (out x1, out y1, out x2, out y2)) {
return x1 - left_limit;
} else {
return right_limit - left_limit;
}
}
public double get_right_side_bearing () {
double x1, y1, x2, y2;
if (get_boundaries (out x1, out y1, out x2, out y2)) {
return right_limit - x2;
} else {
return right_limit - left_limit;
}
}
public bool get_boundaries (out double x1, out double y1,
out double x2, out double y2) {
double max_x, min_x, max_y, min_y;
PathList pl;
var paths = get_all_paths ();
if (paths.size == 0) {
x1 = 0;
y1 = 0;
x2 = 0;
y2 = 0;
return false;
}
max_x = CANVAS_MIN;
min_x = CANVAS_MAX;
max_y = CANVAS_MIN;
min_y = CANVAS_MAX;
// FIXME: optimize
foreach (Path p in paths) {
if (p.stroke > 0) {
pl = p.get_stroke_fast ();
foreach (Path part in pl.paths) {
boundaries_for_path (part, ref min_x, ref max_x, ref min_y, ref max_y);
}
} else {
boundaries_for_path (p, ref min_x, ref max_x, ref min_y, ref max_y);
}
}
x1 = min_x;
y1 = max_y;
x2 = max_x;
y2 = min_y;
return max_x != CANVAS_MIN;
}
void boundaries_for_path (Path p, ref double min_x, ref double max_x,
ref double min_y, ref double max_y) {
double max_x2, max_y2, min_x2, min_y2;
max_x2 = max_x;
min_x2 = min_x;
max_y2 = max_y;
min_y2 = min_y;
p.all_of_path ((x, y, t) => {
if (x > max_x2) {
max_x2 = x;
}
if (y > max_y2) {
max_y2 = y;
}
if (x < min_x2) {
min_x2 = x;
}
if (y < min_y2) {
min_y2 = y;
}
return true;
});
max_x = max_x2;
min_x = min_x2;
max_y = max_y2;
min_y = min_y2;
}
bool has_top_line () {
return !unichar_code.islower () || CharDatabase.has_ascender (unichar_code);
}
public bool get_show_help_lines () {
return show_help_lines;
}
/** Show both x-height and top lines. */
public bool get_xheight_lines_visible () {
return xheight_lines_visible;
}
/** Show both x-height and top lines. */
public void set_xheight_lines_visible (bool x) {
xheight_lines_visible = x;
add_help_lines ();
}
public void set_margin_lines_visible (bool m) {
margin_boundaries_visible = m;
add_help_lines ();
}
public bool get_margin_lines_visible () {
return margin_boundaries_visible;
}
public void remove_empty_paths () {
foreach (Path p in get_all_paths ()) {
if (p.points.size < 2) {
delete_path (p);
remove_empty_paths ();
return;
}
}
}
public void delete_path (Path p) {
layers.remove_path(p);
}
public string get_svg_data () {
return Svg.to_svg_glyph (this);
}
public int get_height () {
Font f = BirdFont.get_current_font ();
return (int) Math.fabs (f.top_limit - f.bottom_limit);
}
public double get_width () {
return Math.fabs (right_limit - left_limit);
}
public unichar get_unichar () {
return unichar_code;
}
public string get_unichar_string () {
string? s = (!)get_unichar ().to_string ();
if (unlikely (s == null)) {
warning ("Invalid character.");
return "".dup ();
}
return (!) s;
}
public void redraw_help_lines () {
redraw_area (0, 0, allocation.width, allocation.height);
}
public void set_show_help_lines (bool hl) {
show_help_lines = hl;
}
private void add_line (Line line) {
if (line.is_vertical ()) {
vertical_help_lines.add (line);
} else {
horizontal_help_lines.add (line);
}
sort_help_lines ();
line.queue_draw_area.connect ((x, y, w, h) => {
this.redraw_area (x, y, w, h);
});
}
public void sort_help_lines () {
vertical_help_lines.sort ((a, b) => {
Line first, next;
first = (Line) a;
next = (Line) b;
return (int) (first.get_pos () - next.get_pos ());
});
horizontal_help_lines.sort ((a, b) => {
Line first, next;
first = (Line) a;
next = (Line) b;
return (int) (first.get_pos () - next.get_pos ());
});
}
public override string get_name () {
return name;
}
public override string get_label () {
return name;
}
private void help_line_event (int x, int y) {
bool m = false;
foreach (Line line in vertical_help_lines) {
if (!m && line.event_move_to (x, y, allocation)) {
m = true;
}
}
foreach (Line line in horizontal_help_lines) {
if (!m && line.event_move_to (x, y, allocation)) {
m = true;
}
}
}
public override void key_release (uint keyval) {
Tool t;
t = MainWindow.get_toolbox ().get_current_tool ();
t.key_release_action (t, keyval);
if (keyval == (uint)' ') {
move_canvas = false;
}
}
public override void key_press (uint keyval) {
Tool t = MainWindow.get_toolbox ().get_current_tool ();
t.key_press_action (t, keyval);
if (keyval == (uint)' ') {
move_canvas = true;
}
switch (keyval) {
case Key.NUM_PLUS:
zoom_in ();
break;
case Key.NUM_MINUS:
zoom_out ();
break;
}
}
/** Delete edit point from path.
* @return false if no points was deleted
*/
public bool process_deleted () {
Gee.ArrayList<Path> deleted_paths = new Gee.ArrayList<Path> ();
foreach (Path p in get_all_paths ()) {
if (p.points.size > 0) {
if (process_deleted_points_in_path (p)) {
return true;
}
} else {
deleted_paths.add (p);
}
}
foreach (Path p in deleted_paths) {
delete_path (p);
}
return false;
}
private bool process_deleted_points_in_path (Path p) {
PathList remaining_points;
remaining_points = p.process_deleted_points ();
foreach (Path path in remaining_points.paths) {
add_path (path);
path.reopen ();
path.create_list ();
add_active_path (null, path);
}
if (remaining_points.paths.size > 0) {
delete_path (p);
return true;
}
return false;
}
public override void motion_notify (double x, double y) {
Tool t = MainWindow.get_toolbox ().get_current_tool ();
if (view_is_moving) {
move_view_offset (x, y);
return;
}
help_line_event ((int) x, (int) y);
t.move_action (t, (int) x, (int) y);
motion_x = x * ivz () - xc () + view_offset_x;
motion_y = yc () - y * ivz () - view_offset_y;
}
public override void button_release (int button, double ex, double ey) {
bool line_moving = false;
view_is_moving = false;
foreach (Line line in get_all_help_lines ()) {
if (!line.set_move (false)) {
line_moving = true;
}
}
if (!line_moving) {
Tool t = MainWindow.get_toolbox ().get_current_tool ();
t.release_action (t, (int) button, (int) ex, (int) ey);
}
update_view ();
}
private Gee.ArrayList<Line> get_all_help_lines () {
Gee.ArrayList<Line> all_lines = new Gee.ArrayList<Line> ();
foreach (Line l in vertical_help_lines) {
all_lines.add (l);
}
foreach (Line l in horizontal_help_lines) {
all_lines.add (l);
}
if (GridTool.is_visible ()) {
foreach (Line l in GridTool.get_vertical_lines ()) {
all_lines.add (l);
}
foreach (Line l in GridTool.get_horizontal_lines ()) {
all_lines.add (l);
}
}
return all_lines;
}
public void update_view () {
GlyphCanvas.redraw ();
}
public override void double_click (uint button, double ex, double ey) {
Tool t = MainWindow.get_toolbox ().get_current_tool ();
t.double_click_action (t, (int) button, (int) ex, (int) ey);
}
public override void button_press (uint button, double ex, double ey) {
bool moving_lines = false;
pointer_begin_x = ex;
pointer_begin_y = ey;
foreach (Line line in horizontal_help_lines) {
if (!moving_lines && line.is_visible () && line.button_press (button)) {
moving_lines = true;
}
}
foreach (Line line in vertical_help_lines) {
if (!moving_lines && line.is_visible () && line.button_press (button)) {
moving_lines = true;
}
}
if (moving_lines) {
return;
}
if (move_canvas
|| DrawingTools.move_canvas.is_selected ()
|| (KeyBindings.has_ctrl () && KeyBindings.has_shift ())) {
view_is_moving = true;
move_offset_x = view_offset_x;
move_offset_y = view_offset_y;
} else {
Tool t = MainWindow.get_toolbox ().get_current_tool ();
t.press_action (t, (int) button, (int) ex, (int) ey);
}
}
/** Add new points to this path. */
public void set_active_path (Path p) {
p.reopen ();
clear_active_paths ();
add_active_path (null, p);
}
/** Move view port centrum to this coordinate. */
public void set_center (double x, double y) {
x -= allocation.width / 2.0;
y -= allocation.height / 2.0;
view_offset_x += x / view_zoom;
view_offset_y += y / view_zoom;
}
public void set_zoom_from_area () {
double x = Math.fmin (zoom_x1, zoom_x2);
double y = Math.fmin (zoom_y1, zoom_y2);
double w = Math.fabs (zoom_x1 - zoom_x2);
double h = Math.fabs (zoom_y1 - zoom_y2);
double view_zoom_x, view_zoom_y;
double ivz, off;
if (move_canvas) {
return;
}
if (Path.distance (x, x + w, y, y + h) < 7) {
zoom_in ();
} else {
view_offset_x += x / view_zoom;
view_offset_y += y / view_zoom;
if (unlikely (allocation.width == 0 || allocation.height == 0)) {
return;
}
view_zoom_x = allocation.width * view_zoom / w;
view_zoom_y = allocation.height * view_zoom / h;
// TODO: there is a max zoom level
if (view_zoom_x * allocation.width < view_zoom_y * allocation.height) {
view_zoom = view_zoom_x;
ivz = 1 / view_zoom;
off = (view_zoom / view_zoom_y) * allocation.height / view_zoom;
off = allocation.height/view_zoom - off;
off /= 2;
view_offset_y -= off;
} else {
view_zoom = view_zoom_y;
ivz = 1 / view_zoom;
off = (view_zoom / view_zoom_x) * allocation.width / view_zoom;
off = allocation.width / view_zoom - off;
off /= 2;
view_offset_x -= off;
}
zoom_area_is_visible = false;
store_current_view ();
}
update_zoom_bar ();
}
public void show_zoom_area (int sx, int sy, int nx, int ny) {
set_zoom_area (sx, sy, nx, ny);
zoom_area_is_visible = true;
GlyphCanvas.redraw ();
}
public void set_zoom_area (int sx, int sy, int nx, int ny) {
zoom_x1 = sx;
zoom_y1 = sy;
zoom_x2 = nx;
zoom_y2 = ny;
}
public static void validate_zoom () {
Glyph g = MainWindow.get_current_glyph ();
if (unlikely (g.view_zoom == 0)) {
warning ("Zoom is zero.");
g.reset_zoom ();
if (g.view_zoom == 0) {
g.view_zoom = 1;
g.view_offset_x = 0;
g.view_offset_y = 0;
}
}
}
public static double path_coordinate_x (double x) {
Glyph g = MainWindow.get_current_glyph ();
validate_zoom ();
return x * ivz () - xc () + g.view_offset_x;
}
public static int reverse_path_coordinate_x (double x) {
Glyph g = MainWindow.get_current_glyph ();
validate_zoom ();
return (int) Math.rint ((x - g.view_offset_x + xc ()) * g.view_zoom);
}
public static double precise_reverse_path_coordinate_x (double x) {
Glyph g = MainWindow.get_current_glyph ();
validate_zoom ();
return (x - g.view_offset_x + xc ()) * g.view_zoom;
}
public static double path_coordinate_y (double y) {
Glyph g = MainWindow.get_current_glyph ();
validate_zoom ();
return yc () - y * ivz () - g.view_offset_y;
}
public static int reverse_path_coordinate_y (double y) {
Glyph g = MainWindow.get_current_glyph ();
validate_zoom ();
y = Math.rint ((y + g.view_offset_y - yc ()) * g.view_zoom);
return (int) (-y);
}
public static double precise_reverse_path_coordinate_y (double y) {
Glyph g = MainWindow.get_current_glyph ();
validate_zoom ();
y = (y + g.view_offset_y - yc ()) * g.view_zoom;
return -y;
}
public Layer? get_path_at (double x, double y) {
Layer? group = null;
bool found = false;
var subgroups = get_current_layer ().subgroups;
if (is_null (subgroups)) {
warning ("Expecting a sub group.");
return null;
}
foreach (Layer layer in subgroups) {
if (is_null (layer.paths) || is_null (layer.paths.paths)) {
warning ("No paths in get_path_at");
continue;
}
foreach (Path pt in layer.paths.paths) {
if (pt.is_over (x, y)) {
found = true;
group = layer;
}
}
}
if (!found) {
var current_layer_paths = get_paths_in_current_layer ();
if (is_null (current_layer_paths)) {
warning ("Expecting current_layer_paths.");
return null;
}
foreach (Path pt in current_layer_paths) {
if (pt.is_over (x, y)) {
Layer layer = new Layer ();
layer.is_counter = true;
layer.single_path = true;
layer.add_path (pt);
group = layer;
}
}
}
return group;
}
public bool select_path (double x, double y) {
Path? p = null;
bool found = false;
foreach (Path pt in get_paths_in_current_layer ()) {
if (pt.is_over (x, y)) {
p = pt;
found = true;
}
}
if (!KeyBindings.has_shift ()) {
clear_active_paths ();
}
add_active_path (null, p);
return found;
}
public bool is_over_selected_path (double x, double y) {
foreach (Path pt in active_paths) {
if (pt.is_over (x, y)) {
return true;
}
}
return false;
}
public void queue_redraw_path (Path path) {
redraw_path (path.xmin, path.ymin, path.xmax, path.ymax);
}
private void redraw_path (double xmin, double ymin, double xmax, double ymax) {
int yc = (int)(allocation.height / 2.0);
double yta = yc - ymin - view_offset_y;
double ytb = yc - ymax - view_offset_y;
double xta = -view_offset_x - xmin;
double xtb = -view_offset_x - xmax;
redraw_area ((int)xtb - 10, (int)yta - 10, (int)(xtb - xta) + 10, (int) (yta - ytb) + 10);
}
public Path get_closeset_path (double x, double y) {
double d;
EditPoint ep = new EditPoint ();
Path min_point = new Path ();
double min_distance = double.MAX;
double xt = path_coordinate_x (x);
double yt = path_coordinate_y (y);
var paths = get_visible_paths ();
foreach (Path p in paths) {
if (p.is_over (xt, yt)) {
return p;
}
}
foreach (Path p in paths) {
if (p.points.size == 0) continue;
p.get_closest_point_on_path (ep, xt, yt);
d = Math.pow (ep.x - xt, 2) + Math.pow (ep.y - yt, 2);
if (d < min_distance) {
min_distance = d;
min_point = p;
}
}
// a path without any editpoints
if (paths.size > 0) {
return paths.get (0);
}
if (unlikely (min_distance == double.MAX)) {
warning (@"No path found in path_list.");
}
return min_point;
}
public void move_selected_edit_point_coordinates (EditPoint selected_point, double xt, double yt) {
double x, y;
BirdFont.get_current_font ().touch ();
x = reverse_path_coordinate_x (xt);
y = reverse_path_coordinate_y (yt);
// redraw control point
redraw_area ((int)(x - 4*view_zoom), (int)(y - 4*view_zoom), (int)(x + 3*view_zoom), (int)(y + 3*view_zoom));
// update position of selected point
selected_point.set_position (xt, yt);
if (view_zoom >= 2) {
redraw_area (0, 0, allocation.width, allocation.height);
} else {
redraw_last_stroke (x, y);
}
}
public void move_selected_edit_point (EditPoint selected_point, double x, double y) {
double xt = path_coordinate_x (x);
double yt = path_coordinate_y (y);
move_selected_edit_point_coordinates (selected_point, xt, yt);
}
public void redraw_segment (EditPoint a, EditPoint b) {
double margin = 10;
double x = Math.fmin (reverse_path_coordinate_x (a.x), reverse_path_coordinate_x (b.x)) - margin;
double y = Math.fmin (reverse_path_coordinate_y (a.y), reverse_path_coordinate_y(b.y)) - margin;
double w = Math.fabs (reverse_path_coordinate_x (a.x) - reverse_path_coordinate_x(b.x)) + 2 * margin;
double h = Math.fabs (reverse_path_coordinate_y (a.y) - reverse_path_coordinate_y (b.y)) + 2 * margin;
redraw_area ((int)x, (int)y, (int)w, (int)h);
}
private void redraw_last_stroke (double x, double y) {
// redraw line, if we have more than one new point on path
double px = 0;
double py = 0;
int tw = 0;
int th = 0;
double xc = (allocation.width / 2.0);
if (active_paths.size == 0) {
return;
}
foreach (Path path in active_paths) {
EditPoint p;
EditPoint pl = path.get_last_point ();
if (pl.prev != null) {
p = pl.get_prev ();
px = p.x + xc;
py = p.y - xc;
tw = (px > x) ? (int) (px - x) : (int) (x - px);
th = (py > y) ? (int) (py - y) : (int) (y - py);
if (px > x) px -= tw + 60;
if (py > y) py -= th + 60;
} else {
px = x - 60;
py = y - 60;
tw = 0;
th = 0;
}
}
redraw_area ((int)px - 20, (int)py - 20, tw + 120, th + 120);
}
public bool has_active_path () {
return active_paths.size > 0;
}
public bool is_open () {
return open;
}
/** Close all editable paths and return false if no path have been closed. */
public bool close_path () {
bool r = false;
foreach (Path p in get_all_paths ()) {
if (p.is_editable ()) {
r = true;
p.set_editable (false);
}
}
open = false;
clear_active_paths ();
GlyphCanvas.redraw ();
MainWindow.set_cursor (NativeWindow.VISIBLE);
return r;
}
public void open_path () {
foreach (Path p in get_visible_paths ()) {
p.set_editable (true);
p.recalculate_linear_handles ();
if (p.is_open () && p.points.size > 0) {
p.get_first_point ().set_reflective_handles (false);
p.get_first_point ().set_tie_handle (false);
p.get_last_point ().set_reflective_handles (false);
p.get_last_point ().set_tie_handle (false);
}
}
open = true;
redraw_area (0, 0, allocation.width, allocation.height);
}
public void redraw_path_region (Path p) {
int x, y, w, h;
p.update_region_boundaries ();
x = reverse_path_coordinate_x (p.xmin);
y = reverse_path_coordinate_x (p.xmin);
w = reverse_path_coordinate_x (p.xmax) - x;
h = reverse_path_coordinate_x (p.ymax) - y; // FIXME: redraw path
redraw_area (x, y, w, h);
}
public Line get_line (string name) {
foreach (Line line in vertical_help_lines) {
if (likely (line.get_label () == name)) {
return line;
}
}
foreach (Line line in horizontal_help_lines) {
if (likely (line.get_label () == name)) {
return line;
}
}
warning (@"No line with label $name found");
return new Line ("Err");
}
public override void zoom_in () {
if (move_canvas) {
return;
}
set_zoom_area (10, 10, allocation.width - 10, allocation.height - 10);
set_zoom_from_area ();
update_view ();
update_zoom_bar ();
}
public override void zoom_out () {
double w = allocation.width;
int n = (int) (10 * ((w - 10) / allocation.width));
set_zoom_area (-n, -n, allocation.width + n, allocation.height + n);
set_zoom_from_area ();
update_view ();
update_zoom_bar ();
}
public override void zoom_max () {
default_zoom ();
update_zoom_bar ();
}
public override void zoom_min () {
double ax = 1000;
double ay = 1000;
double bx = -1000;
double by = -1000;
int iax, iay, ibx, iby;
reset_zoom ();
foreach (var p in get_visible_paths ()) {
p.update_region_boundaries ();
if (p.points.size > 2) {
if (p.xmin < ax) ax = p.xmin;
if (p.ymin < ay) ay = p.ymin;
if (p.xmax > bx) bx = p.xmax;
if (p.ymax > by) by = p.ymax;
}
}
if (ax == 1000) return; // empty page
iax = (int) ((ax + view_offset_x + allocation.width / 2.0) * view_zoom);
iay = (int) ((-ay + view_offset_y + allocation.height / 2.0) * view_zoom);
ibx = (int) ((bx + view_offset_x + allocation.width / 2.0) * view_zoom);
iby = (int) ((-by + view_offset_y + allocation.height / 2.0) * view_zoom);
show_zoom_area (iax, iay, ibx, iby); // set this later on button release
set_zoom_from_area ();
zoom_out (); // add some margin
redraw_area (0, 0, allocation.width, allocation.height);
update_zoom_bar ();
}
public override void store_current_view () {
ZoomView n;
if (zoom_list_index + 1 < zoom_list.size) {
n = zoom_list.get (zoom_list_index);
while (n != zoom_list.get (zoom_list.size - 1)) {
zoom_list.remove_at (zoom_list.size - 1);
}
}
zoom_list.add (new ZoomView (view_offset_x, view_offset_y, view_zoom, allocation));
zoom_list_index = (int) zoom_list.size - 1;
if (zoom_list.size > 50) {
zoom_list.remove_at (0);
}
}
public override void restore_last_view () {
if (zoom_list.size == 0 || zoom_list_index - 1 < 0) {
return;
}
zoom_list_index--;
ZoomView z = zoom_list.get (zoom_list_index);
view_offset_x = z.x;
view_offset_y = z.y;
view_zoom = z.zoom;
allocation = z.allocation;
update_zoom_bar ();
}
public override void next_view () {
ZoomView z;
if (zoom_list.size == 0 || zoom_list_index + 1 >= zoom_list.size) {
return;
}
zoom_list_index++;
z = zoom_list.get (zoom_list_index);
view_offset_x = z.x;
view_offset_y = z.y;
view_zoom = z.zoom;
allocation = z.allocation;
update_zoom_bar ();
}
public override void reset_zoom () {
view_offset_x = 0;
view_offset_y = 0;
set_zoom (1);
store_current_view ();
update_zoom_bar ();
}
/** Get x-height or top line. */
public Line get_upper_line () {
if (has_top_line () || xheight_lines_visible) {
return get_line ("top");
}
return get_line ("x-height");
}
/** Get base line. */
public Line get_lower_line () {
return get_line ("baseline");
}
/** Set default zoom. See default_zoom. */
public void set_default_zoom () {
int bottom = 0;
int top = 0;
int left = 0;
int right = 0;
return_if_fail (vertical_help_lines.size != 0);
return_if_fail (horizontal_help_lines.size != 0);
reset_zoom ();
bottom = get_lower_line ().get_position_pixel ();
top = get_upper_line ().get_position_pixel ();
left = vertical_help_lines.get (vertical_help_lines.size - 1).get_position_pixel ();
right = vertical_help_lines.get (0).get_position_pixel ();
set_zoom_area (left + 10, top - 10, right - 10, bottom + 10);
set_zoom_from_area ();
}
/** Set default zoom and redraw canvas. */
public void default_zoom () {
set_default_zoom ();
update_view ();
}
public bool is_empty () {
foreach (Path p in get_visible_paths ()) {
if (p.points.size > 0) {
return false;
}
}
return true;
}
public void set_zoom (double z)
requires (z > 0)
{
view_zoom = z;
}
public void set_background_visible (bool visibility) {
background_image_visible = visibility;
}
public bool get_background_visible () {
return background_image_visible;
}
public void draw_coordinate (Context cr) {
Theme.color (cr, "Table Border");
cr.set_font_size (12);
cr.move_to (0, 10);
cr.show_text (@"($motion_x, $motion_y)");
cr.stroke ();
}
/** Draw filled paths. */
public void draw_paths (Context cr, Color? c = null) {
PathList stroke;
Color color;
bool open;
cr.save ();
cr.new_path ();
foreach (Path p in get_visible_paths ()) {
if (c != null) {
color = (!) c;
} else if (p.color != null) {
color = (!) p.color;
} else {
color = Color.black ();
}
if (p.stroke > 0) {
stroke = p.get_stroke_fast ();
draw_path_list (stroke, cr, color);
} else {
open = p.is_open ();
if (open) {
p.close ();
p.recalculate_linear_handles ();
}
p.draw_path (cr, this, color);
if (open) {
p.reopen ();
}
}
}
cr.fill ();
cr.restore ();
}
public void draw_path (Context cr) {
PathList stroke;
Color color;
cr.save ();
cr.new_path ();
foreach (Path p in get_visible_paths ()) {
if (p.stroke > 0) {
stroke = p.get_stroke_fast ();
if (p.is_editable ()) {
color = Theme.get_color ("Filled Stroke");
color.a = 0.8;
} else {
color = Color.black ();
}
draw_path_list (stroke, cr, color);
}
}
cr.fill ();
cr.restore ();
if (!(MainWindow.get_toolbox ().get_current_tool () is PenTool)
&& !(MainWindow.get_toolbox ().get_current_tool () is PointTool)
&& !(MainWindow.get_toolbox ().get_current_tool () is TrackTool)
&& !(MainWindow.get_toolbox ().get_current_tool () is BezierTool)) {
cr.save ();
cr.new_path ();
foreach (Path p in active_paths) {
if (p.stroke > 0) {
stroke = p.get_stroke_fast ();
color = Theme.get_color ("Selected Objects");
draw_path_list (stroke, cr, color);
}
}
cr.fill ();
cr.restore ();
}
if (is_open () && Path.fill_open_path) {
cr.save ();
cr.new_path ();
foreach (Path p in get_visible_paths ()) {
if (p.stroke == 0) {
color = p.color == null ? get_path_fill_color () : (!) p.color;
p.draw_path (cr, this, color);
}
}
cr.fill ();
cr.restore ();
}
if (is_open ()) {
cr.save ();
cr.new_path ();
foreach (Path p in get_visible_paths ()) {
p.draw_outline (cr);
p.draw_edit_points (cr);
}
cr.restore ();
}
if (!is_open ()) {
// This was good for testing but it is way too slow:
// Svg.draw_svg_path (cr, get_svg_data (), Glyph.xc () + left, Glyph.yc () - baseline);
cr.save ();
cr.new_path ();
foreach (Path p in get_visible_paths ()) {
if (p.stroke == 0) {
color = p.color == null ? Color.black () : (!) p.color;
p.draw_path (cr, this, color);
}
}
cr.close_path ();
cr.fill ();
cr.restore ();
foreach (Path p in active_paths) {
cr.save ();
cr.new_path ();
if (p.stroke == 0) {
p.draw_path (cr, this);
}
cr.close_path ();
cr.fill ();
cr.restore ();
}
}
if (show_orientation_arrow) {
foreach (Path p in get_visible_paths ()) {
if (p.stroke > 0) {
stroke = p.get_stroke_fast ();
foreach (Path ps in stroke.paths) {
ps.draw_orientation_arrow (cr, orientation_arrow_opacity);
}
} else {
p.draw_orientation_arrow (cr, orientation_arrow_opacity);
}
}
}
}
private Color get_path_fill_color () {
return Theme.get_color ("Fill Color");
}
public void draw_path_list (PathList pl, Context cr, Color? c = null) {
foreach (Path p in pl.paths) {
p.draw_path (cr, this, c);
}
}
public void draw_background_color (Context cr, double opacity) {
if (opacity > 0) {
cr.save ();
cr.rectangle (0, 0, allocation.width, allocation.height);
Theme.color (cr, "Canvas Background");
cr.fill ();
cr.restore ();
}
}
public void draw_help_lines (Context cr) {
foreach (Line line in get_all_help_lines ()) {
cr.save ();
line.draw (cr, allocation);
cr.restore ();
}
}
public void set_allocation (WidgetAllocation a) {
allocation = a;
}
public override void draw (WidgetAllocation allocation, Context cmp) {
Tool tool;
this.allocation = allocation;
cmp.save ();
draw_background_color (cmp, 1);
cmp.restore ();
if (background_image != null && background_image_visible) {
((!)background_image).draw (cmp, allocation, view_offset_x, view_offset_y, view_zoom);
}
if (unlikely (Preferences.draw_boundaries)) {
foreach (Path p in get_visible_paths ()) {
p.draw_boundaries (cmp);
}
}
draw_background_glyph (allocation, cmp);
juxtapose (allocation, cmp);
if (BirdFont.show_coordinates) {
draw_coordinate (cmp);
}
if (show_help_lines) {
cmp.save ();
draw_help_lines (cmp);
cmp.restore ();
}
if (!is_empty ()) {
cmp.save ();
cmp.scale (view_zoom, view_zoom);
cmp.translate (-view_offset_x, -view_offset_y);
draw_path (cmp);
cmp.restore ();
}
cmp.save ();
tool = MainWindow.get_toolbox ().get_current_tool ();
tool.draw_action (tool, cmp, this);
cmp.restore ();
}
private void zoom_in_at_point (double x, double y, double amount = 15) {
int n = (int) (-amount);
zoom_at_point (x, y, n);
}
private void zoom_out_at_point (double x, double y, double amount = 15) {
double a = -amount;
int n = (int) (a * ((allocation.width - a) / allocation.width));
zoom_at_point (x, y, n);
}
public void zoom_tap (double distance) {
int w = (int) (distance);
if (distance != 0) {
show_zoom_area (-w , -w, allocation.width + w, allocation.height + w);
set_zoom_from_area ();
}
}
/** Zoom in @param n pixels. */
private void zoom_at_point (double x, double y, int n) {
double w = allocation.width;
double h = allocation.height;
double rx = Math.fabs (w / 2 - x) / (w / 2);
double ry = Math.fabs (h / 2 - y) / (h / 2);
int xd = (x < w / 2) ? (int) (n * rx) : (int) (-n * rx);
int yd = (y < h / 2) ? (int) (n * ry) : (int) (-n * ry);
show_zoom_area (-n + xd, -n + yd, allocation.width + n + xd, allocation.height + n + yd);
set_zoom_from_area ();
}
private void move_view_offset (double x, double y) {
view_offset_x = move_offset_x + (pointer_begin_x - x) * (1/view_zoom);
view_offset_y = move_offset_y + (pointer_begin_y - y) * (1/view_zoom);
GlyphCanvas.redraw ();
}
public void store_undo_state (bool clear_redo = false) {
Glyph g = copy ();
undo_list.add (g);
if (clear_redo) {
redo_list.clear ();
}
}
public void store_redo_state () {
Glyph g = copy ();
redo_list.add (g);
}
public Glyph copy () {
Glyph g = new Glyph.no_lines (name, unichar_code);
g.current_layer = current_layer;
g.left_limit = left_limit;
g.right_limit = right_limit;
g.remove_lines ();
foreach (Line line in get_all_help_lines ()) {
g.add_line (line.copy ());
}
g.layers = layers.copy ();
foreach (Path p in active_paths) {
g.active_paths.add (p);
}
if (background_image != null) {
g.background_image = ((!) background_image).copy ();
}
g.empty = empty;
g.open = open;
g.version_id = version_id;
return g;
}
public void reload () {
Font f = BirdFont.get_current_font ();
if (f.has_glyph (name)) {
set_glyph_data ((!) f.get_glyph (name));
}
}
public override void undo () {
Glyph g;
Tool tool;
if (undo_list.size == 0) {
return;
}
tool = MainWindow.get_toolbox ().get_current_tool ();
tool.before_undo ();
g = undo_list.get (undo_list.size - 1);
store_redo_state ();
set_glyph_data (g);
undo_list.remove_at (undo_list.size - 1);
DrawingTools.update_layers ();
PenTool.update_selected_points ();
clear_active_paths ();
tool.after_undo ();
}
public override void redo () {
Glyph g;
if (redo_list.size == 0) {
return;
}
g = redo_list.get (redo_list.size - 1);
store_undo_state (false);
set_glyph_data (g);
redo_list.remove_at (redo_list.size - 1);
DrawingTools.update_layers ();
PenTool.update_selected_points ();
clear_active_paths ();
}
void set_glyph_data (Glyph g) {
current_layer = g.current_layer;
layers = g.layers.copy ();
left_limit = g.left_limit;
right_limit = g.right_limit;
remove_lines ();
foreach (Line line in g.get_all_help_lines ()) {
add_line (line.copy ());
}
add_help_lines ();
if (g.background_image != null) {
background_image = ((!) g.background_image).copy ();
}
clear_active_paths ();
foreach (Path p in g.active_paths) {
add_active_path (null, p);
}
redraw_area (0, 0, allocation.width, allocation.height);
}
/** Split curve in two parts and add a new point in between.
* @return the new point
*/
public void insert_new_point_on_path (double x, double y) {
double min, distance;
Path? p = null;
Path path;
EditPoint? np = null;
EditPoint lep;
double xt;
double yt;
xt = x * ivz () - xc () + view_offset_x;
yt = yc () - y * ivz () - view_offset_y;
min = double.MAX;
foreach (Path pp in get_visible_paths ()) {
lep = new EditPoint ();
pp.get_closest_point_on_path (lep, xt, yt);
distance = Math.sqrt (Math.pow (Math.fabs (xt - lep.x), 2) + Math.pow (Math.fabs (yt - lep.y), 2));
if (distance < min) {
min = distance;
p = pp;
np = lep;
}
}
if (p == null) {
return;
}
path = (!) p;
lep = new EditPoint ();
path.get_closest_point_on_path (lep, xt, yt);
path.insert_new_point_on_path (lep);
}
static bool in_range (double offset_x, double coordinate_x1, double coordinate_x2) {
return coordinate_x1 <= offset_x <= coordinate_x2;
}
public void juxtapose (WidgetAllocation allocation, Context cr) {
string glyph_sequence = Preferences.get ("glyph_sequence");
unichar c;
Font font = BirdFont.get_current_font ();
Glyph glyph = MainWindow.get_current_glyph ();
Glyph juxtaposed;
StringBuilder current = new StringBuilder ();
int pos;
string name;
double x, kern;
double left, baseline;
string last_name;
double box_x1, box_x2, box_y1, box_y2;
double marker_x, marker_y;
KerningClasses classes = font.get_kerning_classes ();
x = 0;
box_x1 = path_coordinate_x (0);
box_y1 = path_coordinate_y (0);
box_x2 = path_coordinate_x (allocation.width);
box_y2 = path_coordinate_y (allocation.height);
current.append_unichar (glyph.unichar_code);
pos = glyph_sequence.index_of (current.str);
baseline = font.base_line;;
left = glyph.get_line ("left").pos;
x = glyph.get_width ();
last_name = glyph.name;
for (int i = pos + 1; i < glyph_sequence.char_count (); i++) {
c = glyph_sequence.get_char (i);
name = (!) c.to_string ();
juxtaposed = (font.has_glyph (name)) ? (!) font.get_glyph (name) : font.get_space ().get_current ();
if (font.has_glyph (last_name) && font.has_glyph (name)) {
kern = classes.get_kerning (last_name, name);
} else {
kern = 0;
}
if (!juxtaposed.is_empty ()
&& (in_range (left + x + kern, box_x1, box_x2) // the letter is visible
|| in_range (left + x + kern + juxtaposed.get_width (), box_x1, box_x2))) {
marker_x = Glyph.xc () + left + x + kern - glyph.view_offset_x;
marker_y = Glyph.yc () - baseline - glyph.view_offset_y;
cr.save ();
cr.scale (glyph.view_zoom, glyph.view_zoom);
Theme.color (cr, "Foreground 1");
Svg.draw_svg_path (cr, juxtaposed.get_svg_data (), marker_x, marker_y);
cr.restore ();
}
x += juxtaposed.get_width () + kern;
last_name = name;
}
x = 0;
last_name = glyph.name;
for (int i = pos - 1; i >= 0; i--) {
c = glyph_sequence.get_char (i);
name = (!) c.to_string ();
juxtaposed = (font.has_glyph (name)) ? (!) font.get_glyph (name) : font.get_space ().get_current ();
if (font.has_glyph (last_name) && font.has_glyph (name)) {
kern = classes.get_kerning (name, last_name);
} else {
kern = 0;
}
x -= juxtaposed.get_width ();
x -= kern;
marker_x = Glyph.xc () + left + x;
marker_y = Glyph.yc () - baseline;
if (!juxtaposed.is_empty ()
&&(in_range (left + x, box_x1, box_x2)
|| in_range (left + x + juxtaposed.get_width (), box_x1, box_x2))) {
cr.save ();
cr.scale (glyph.view_zoom, glyph.view_zoom);
cr.translate (-glyph.view_offset_x, -glyph.view_offset_y);
Theme.color (cr, "Foreground 1");
Svg.draw_svg_path (cr, juxtaposed.get_svg_data (), marker_x, marker_y);
cr.restore ();
}
last_name = name;
}
}
/** @return left side bearing */
public double get_lsb () {
return get_line ("left").pos;
}
/** @return bottom line */
public double get_baseline () {
Font font = BirdFont.get_current_font ();
return font.base_line;
}
void draw_background_glyph (WidgetAllocation allocation, Context cr) {
double left, baseline, current_left;
Glyph g;
Font font = BirdFont.get_current_font ();
current_left = get_line ("left").pos;
if (background_glyph != null) {
g = (!) background_glyph;
baseline = font.base_line;
left = g.get_line ("left").pos;
cr.save ();
cr.scale (view_zoom, view_zoom);
cr.translate (-view_offset_x, -view_offset_y);
Theme.color (cr, "Background Glyph");
Svg.draw_svg_path (cr, g.get_svg_data (),
Glyph.xc () + left - (left - current_left) ,
Glyph.yc () - baseline);
cr.restore ();
}
}
public string get_hex () {
return Font.to_hex_code (unichar_code);
}
public override void move_view (double x, double y) {
view_offset_x += x / view_zoom;
view_offset_y += y / view_zoom;
GlyphCanvas.redraw ();
}
/** Scroll or zoom from tap events. */
public void change_view_event (int finger, int x, int y) {
double dx, dy;
double last_distance, new_distance;
dx = 0;
dy = 0;
new_distance = 0;
if (last_tap0_y == -1 || last_tap0_x == -1 || last_tap1_y == -1 || last_tap1_x == -1) {
return;
}
if (finger == 0) {
dx = last_tap0_x - x;
dy = last_tap0_y - y;
new_distance = Path.distance (last_tap1_x, x, last_tap1_y, y);
}
if (finger == 1) {
dx = last_tap1_x - x;
dy = last_tap1_y - y;
new_distance = Path.distance (last_tap0_x, x, last_tap0_y, y);
}
last_distance = Path.distance (last_tap0_x, last_tap1_x, last_tap0_y, last_tap1_y);
if (zoom_distance != 0) {
zoom_tap (zoom_distance - new_distance);
}
if (finger == 1) {
warning (@"dx $dx dy $dy last_tap1_x $last_tap1_x last_tap1_y $last_tap1_y x $x y $y");
move_view (dx, dy);
}
zoom_distance = new_distance;
}
public override void tap_down (int finger, int x, int y) {
TimeoutSource delay;
if (finger == 0) {
delay = new TimeoutSource (400); // wait for second finger
delay.set_callback(() => {
if (!change_view && !ignore_input) {
button_press (1, x, y);
}
return false;
});
delay.attach (null);
last_tap0_x = x;
last_tap0_y = y;
}
if (finger == 1) {
change_view = true;
last_tap1_x = x;
last_tap1_y = y;
}
}
public override void tap_up (int finger, int x, int y) {
if (finger == 0) {
button_release (1, x, y);
last_tap0_x = -1;
last_tap0_y = -1;
}
if (finger == 1) {
last_tap1_x = -1;
last_tap1_y = -1;
change_view = false;
zoom_distance = 0;
}
}
public override void tap_move (int finger, int x, int y) {
if (!change_view) {
motion_notify (x, y);
} else {
change_view_event (finger, x, y);
}
if (finger == 0) {
last_tap0_x = x;
last_tap0_y = y;
}
if (finger == 1) {
last_tap1_x = x;
last_tap1_y = y;
}
}
public void update_spacing_class () {
Font font = BirdFont.get_current_font ();
GlyphCollection? g;
GlyphCollection gc;
Glyph glyph;
Gee.ArrayList<string> s;
SpacingData sd;
sd = font.get_spacing ();
s = sd.get_all_connections ((!) unichar_code.to_string ());
foreach (string l in s) {
if (l != (!) unichar_code.to_string ()) {
g = font.get_glyph_collection (l);
if (g != null) {
gc = (!) g;
glyph = gc.get_current ();
if (glyph.left_limit == glyph.right_limit) {
warning ("Zero width glyph in kerning class.");
}
left_limit = glyph.left_limit;
right_limit = glyph.right_limit;
break;
}
}
}
add_help_lines ();
}
public void update_other_spacing_classes () {
Font font = BirdFont.get_current_font ();
GlyphCollection? g;
GlyphCollection gc;
Glyph glyph;
Gee.ArrayList<string> s;
SpacingData sd;
sd = font.get_spacing ();
s = sd.get_all_connections (get_name ());
foreach (string l in s) {
if (l != (!) unichar_code.to_string ()) {
g = font.get_glyph_collection (l);
if (g != null) {
gc = (!) g;
glyph = gc.get_current ();
glyph.left_limit = left_limit;
glyph.right_limit = right_limit;
}
}
}
}
public void set_cache (string key, Surface cache) {
glyph_cache.set (key, cache);
}
public bool has_cache (string key) {
return glyph_cache.has_key (key);
}
public Surface get_cache (string key) {
if (unlikely (!has_cache (key))) {
warning ("No cache for glyph.");
return new ImageSurface (Cairo.Format.ARGB32, 1, 1);
}
return glyph_cache.get (key);
}
public void add_custom_guide () {
TextListener listener;
listener = new TextListener (t_("Guide"), "", t_("Add"));
listener.signal_text_input.connect ((text) => {
new_guide_name = text;
});
listener.signal_submit.connect (() => {
Line guide;
double position;
position = path_coordinate_y (allocation.height / 2.0);
guide = new Line (new_guide_name, new_guide_name, position);
horizontal_help_lines.add (guide);
BirdFont.get_current_font ().custom_guides.add (guide);
TabContent.hide_text_input ();
GlyphCanvas.redraw ();
});
TabContent.show_text_input (listener);
}
public Path? get_last_path () {
var paths = get_all_paths ();
return_val_if_fail (paths.size > 0, null);
return paths.get (paths.size - 1);
}
public void move_layer_up () {
Layer layer = get_current_layer ();
if (current_layer + 2 <= layers.subgroups.size) {
return_if_fail (0 <= current_layer + 2 <= layers.subgroups.size);
layers.subgroups.insert (current_layer + 2, layer);
return_if_fail (0 <= current_layer + 1 < layers.subgroups.size);
layers.subgroups.remove_at (current_layer);
set_current_layer (layer);
}
}
public void move_layer_down () {
Layer layer = get_current_layer ();
if (current_layer >= 1) {
return_if_fail (0 <= current_layer - 1 < layers.subgroups.size);
layers.subgroups.insert (current_layer - 1, layer);
return_if_fail (0 <= current_layer + 1 < layers.subgroups.size);
layers.subgroups.remove_at (current_layer + 1);
set_current_layer (layer);
}
}
public void fix_curve_orientation () {
int inside_count;
bool inside;
Path outline;
foreach (Path p1 in get_visible_paths ()) {
inside_count = 0;
foreach (Path p2 in get_visible_paths ()) {
if (p1 != p2) {
inside = true;
outline = p2.flatten (100);
foreach (EditPoint ep in p1.points) {
if (!SvgParser.is_inside (ep, outline)) {
inside = false;
}
}
if (inside) {
inside_count++;
}
}
}
if (inside_count % 2 == 0) {
p1.force_direction (Direction.CLOCKWISE);
} else {
p1.force_direction (Direction.COUNTER_CLOCKWISE);
}
}
}
public override void magnify (double magnification) {
double x_center = path_coordinate_x (xc ());
double y_center = path_coordinate_y (yc ());
view_zoom *= 1 + magnification;
view_offset_x -= path_coordinate_x (xc ()) - x_center;
view_offset_y += path_coordinate_y (yc ()) - y_center;
GlyphCanvas.redraw ();
}
public Glyph self_interpolate (double weight) {
Glyph g1 = copy ();
Glyph g2 = copy ();
g1.fix_curve_orientation ();
g2.layers = new Layer (); // remove all paths
foreach (Path p in g1.get_visible_paths ()) {
bool counter = !p.is_clockwise ();
g2.add_path (p.copy ());
p.remove_points_on_points ();
Path master = p.get_self_interpolated_master (counter, weight);
p = p.interpolate_estimated_path (master, weight);
p.recalculate_linear_handles ();
g2.add_path (p);
g2.add_path (master);
}
return g2;
}
}
}