Clone in a terminal:
ResizeTool.vala
/*
    Copyright (C) 2012 Johan Mattsson

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

using Cairo;

namespace Supplement {

class ResizeTool : Tool {

	ImageSurface move_icon;
	ImageSurface move_icon_active;

	ImageSurface rotate_icon;
	ImageSurface rotate_icon_active;
		
	int active_handle = -1;
	
	Path selected_path = new Path ();
	
	bool resize = false;
	double resize_begin_x = 0;
	double resize_begin_y = 0;
	Path? original_path = null;
	
	public ResizeTool (string n) {
		base (n, "resize paths");

		select_action.connect((self) => {
			active_handle = -1;
			redraw_handle ();
		});
			
		deselect_action.connect((self) => {
			active_handle = -1;
			redraw_handle ();
		});
		
		press_action.connect((self, b, x, y) => {
			if (get_handle_at (x, y) > -1) {
				resize_begin_x = x;
				resize_begin_y = y;
				resize = true;
				original_path = null;
			}
		});

		release_action.connect((self, b, x, y) => {
			resize = false;
			original_path = null;
		});
		
		move_action.connect ((self, x, y)	 => {
			high_light (x, y);
			
			if (resize) {
				resize_process (x, y);
			}
		});
		
		draw_action.connect ((self, cairo_context, glyph) => {
				draw_handle (cairo_context, glyph);
			});
			
		move_icon = (!) Icons.get_icon ("move_handle.png");
		move_icon_active = (!) Icons.get_icon ("move_handle_active.png");

		rotate_icon = (!) Icons.get_icon ("rotate_icon.png");
		rotate_icon_active = (!) Icons.get_icon ("rotate_icon_active.png");		
		
	}

	private void resize_process (double x, double y) {
		double a, b, c, nc, tx, ty;
		
		Path p;
		
		// TODO: this function resizes only one path not a group of paths
		Path po = selected_path;
		
		if (po.empty ()) return;
				
		if (original_path == null) original_path = po.copy ();
		
		p = ((!)original_path).copy ();
		
		a = p.xmax - p.xmin;
		b = p.ymax - p.ymin;
		
		tx = x - resize_begin_x;
		ty = y - resize_begin_y;
		
		c  =  Math.sqrt (Math.pow (a, 2) + Math.pow (b, 2));
		nc =  Math.sqrt (Math.pow (a + tx, 2) + Math.pow (b + ty, 2));

		p.resize (nc / c);
		po.replace_path (p);

		MainWindow.get_current_glyph ().queue_redraw_path (po);
		MainWindow.get_current_glyph ().queue_redraw_path (p);
	}

	private void high_light (double x, double y) {
		int i = get_handle_at (x, y);

		if (active_handle != i) {
			redraw_handle ();
			active_handle = i;
		}
	}

	private int get_handle_at (double x, double y) {
		Glyph g = MainWindow.get_current_glyph ();
		int hx, hy;
		double ivz = 1/g.view_zoom;
				
		int i = 0;
		foreach (var p in g.path_list) {
			hx = (int) (p.xmax * ivz + g.view_offset_x + (g.allocation.width / 2) * ivz);
			hy = (int) (p.ymax * ivz + g.view_offset_y + (g.allocation.height / 2) * ivz);
		
			if (p.points.length () > 0 && hx <= x <= hx + move_icon.get_width () && hy <= y <= hy + move_icon.get_height ()) {
				selected_path = p;
				return i;
			}
			
			i++;
		}
		
		selected_path = new Path ();
		return -1;
	}

	private void draw_handle (Context cr, Glyph g) {
		double x, y;
		int i = 0;	
		double ivz = 1/g.view_zoom;

		if (resize) return;
						
		foreach (var p in g.path_list) {
			x = p.xmax * ivz + g.view_offset_x + (g.allocation.width / 2) * ivz;
			y = p.ymax * ivz + g.view_offset_y + (g.allocation.height / 2) * ivz;
			
			if (p.points.length () > 0) {
				ImageSurface s = (i == active_handle) ? move_icon_active : move_icon;
				cr.set_source_surface (s, x, y);
				cr.paint ();

				ImageSurface r = (i == active_handle) ? rotate_icon_active : rotate_icon;
				cr.set_source_surface (r, x, y - 20);
				cr.paint ();
			}
			
			i++;
		}
	}

	private void redraw_handle () {
		Glyph g = MainWindow.get_current_glyph ();
		int x, y;
		double ivz = 1/g.view_zoom;
		
		foreach (var p in g.path_list) {
			x = (int) (p.xmax * ivz + g.view_offset_x + (g.allocation.width / 2) * ivz);
			y = (int) (p.ymax * ivz + g.view_offset_y + (g.allocation.height / 2) * ivz);

			g.redraw_area (x, y, (int) move_icon.get_width (), (int) move_icon.get_height ());
			g.redraw_area (0, 0, g.allocation.width, g.allocation.height);
		}
	}

	public override bool test () {
		// draw and resize glyphs 
		
		draw_test_glyph (0, 100); // 1:1

		draw_test_glyph (0, 89); 
		resize_test_glyph (0.99); // 99%
				
		for (int i = 2; i < 10; i++) {
			draw_test_glyph (10 * (i - 1), 100);
			resize_test_glyph (1.0 / i);
		}

		int m = 100;
		for (int i = 2; i < 6; i++) {
			m += ((i - 1) * 30) + 5;
			draw_test_glyph (0, m);
			resize_test_glyph (i);
		}

		this.yield ();
		
		return true;
	}

	private void resize_test_glyph (double ratio_percent) {
		test_select_action ();
		
		this.yield ();
		
		Glyph g = MainWindow.get_current_glyph ();
		Path? pn = g.get_last_path ();
		return_if_fail (pn != null);

		Path p = (!) pn;		
		p.resize (ratio_percent);

	}
	
	private void draw_test_glyph (int x_offset, int y_offset) {
		// open a new glyph
		Tool pen_tool = MainWindow.get_toolbox ().get_tool ("pen_tool");

		this.yield ();
		MainWindow.get_tab_bar ().select_overview ();

		this.yield ();
		MainWindow.get_overview ().open_current_glyph ();
		
		pen_tool.test_select_action ();
		
		// paint
		pen_tool.test_click_action (1, 10 + x_offset, 10 + y_offset); 
		pen_tool.test_click_action (1, 20 + x_offset, 10 + y_offset);
		pen_tool.test_click_action (1, 20 + x_offset, 20 + y_offset);
		pen_tool.test_click_action (1, 10 + x_offset, 20 + y_offset);
		
		// close
		pen_tool.test_click_action (3, 0, 0);
	}
	
}

}

Contact

Email: johan.mattsson.m@gmail.com
Mastodon: typo.social/@birdfont
GitHub: johanmattssonm
LinkedIn: johan-mattsson-2ab324112