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

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

	This library is distributed in the hope that it will be useful, but 
	WITHOUT ANY WARRANTY; without even the implied warranty of 
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
	Lesser General Public License for more details.
*/

using Cairo;
using Math;

namespace BirdFont {

public class GridTool : Tool {
	
	static Gee.ArrayList<Line> horizontal;
	static Gee.ArrayList<Line> vertical;
	
	static bool grid_visible = false;
	public static bool ttf_units = false;
	
	public static double size_x;
	public static double size_y;
	
	public static Gee.ArrayList<SpinButton> sizes;
	
	/** Lock grid and guides. */
	public static bool lock_grid = false;
	
	public GridTool (string n) {
		string units;
		base (n, t_("Show grid"));
		
		units = Preferences.get ("ttf_units");
		if (units == "true") {
			ttf_units = true;
		}
		
		horizontal = new Gee.ArrayList <Line> ();
		vertical = new Gee.ArrayList <Line> ();
		
		sizes = new Gee.ArrayList <SpinButton> ();
		
		size_x = 2;
		size_y = 2;
	
		update_lines ();
			
		select_action.connect((self) => {
			grid_visible = !grid_visible;
			update_lines ();
			GlyphCanvas.redraw ();
			
			self.set_selected (grid_visible);
			
			Toolbox tb = MainWindow.get_toolbox ();
			Tool t = tb.get_tool ("help_lines");
			
			if (grid_visible && !t.is_selected ()) {
				MainWindow.get_toolbox ().select_tool (t);
			}
		});
		
		press_action.connect((self, b, x, y) => {
		});

		release_action.connect((self, b, x, y) => {
		});
		
		move_action.connect ((self, x, y)	 => {
		});
		
		draw_action.connect ((self, cairo_context, glyph) => {
		});
			
	}
	
	public static void set_grid_width (double w) {
		double t = 0;
		
		if (ttf_units) {
			ttf_grid_coordinate (ref w, ref t);	
		}
		
		size_x = w;
		size_y = w;
		
		update_lines ();
	}

	public static void ttf_grid_coordinate (ref double x, ref double y) {
		x = GlyfData.tie_to_ttf_grid_x (MainWindow.get_current_glyph (), x);
		y = GlyfData.tie_to_ttf_grid_y (BirdFont.get_current_font (), y);
	}
		
	public static void update_lines () {
		Glyph g = MainWindow.get_current_glyph ();
		double step = size_y;
		Color color = Theme.get_color ("Grid");
		double i;
		int max_lines = 600;
		int n;
		Line t, l, u;


		Line baseline = g.get_line ("baseline");
		Line bottom_margin = g.get_line ("bottom margin");
		Line top_margin = g.get_line ("top margin");
		
		Line left = g.get_line ("left");
		Line right = g.get_line ("right");

		vertical.clear ();
		horizontal.clear ();
	
		n = 0;
		for (i = left.get_pos () - 7 * step; i <= right.get_pos () + 7 * step; i += step) {	
			l = new Line ("grid", "", i, Line.VERTICAL);
			l.set_moveable (false);
			l.set_color (color.r, color.g, color.b, color.a);
			vertical.add (l);
			
			if (++n >= max_lines) {
				break;
			}
		}
		
		n = 0;
		for (i = baseline.get_pos () - step; i >= bottom_margin.get_pos () - 7 * step; i -= step) {
			t = new Line ("grid", "", i, Line.HORIZONTAL);
			t.set_moveable (false);
			t.set_color (color.r, color.g, color.b, color.a);
			horizontal.insert (0, t);	

			if (++n >= max_lines) {
				break;
			}

		}
		
		for (i = baseline.get_pos (); i <= top_margin.get_pos () + 7 * step; i += step) {
			u = new Line ("grid", "", i, Line.HORIZONTAL);
			u.set_moveable (false);
			u.set_color (color.r, color.g, color.b, color.a);
			horizontal.add (u);	

			if (++n >= max_lines) {
				break;
			}

		}	
	}

	public static void tie_coordinate (ref double x, ref double y) {
		tie_point (ref x, ref y, true);
	}

	public static void tie_point (ref double x, ref double y, bool coordinate) {
		x = tie_point_x (x, coordinate);
		y = tie_point_y (y, coordinate);
	}

	public static double tie_point_x (double x, bool coordinate)
		requires (vertical.size >= 2)
	{
		double d, m;
		Line xmin = vertical.get (0);
		Line xpos;
		Line startx = vertical.get (0);
		Line stopx = vertical.get (vertical.size - 1);

		// outside of the grid
		if (!coordinate) {
			if (!(startx.pos < Glyph.path_coordinate_x (x) < stopx.pos)) {
				return x;
			}
		} else {
			if (!(startx.pos < x < stopx.pos)) {
				return x;
			}
		}
			
		if (!coordinate) {
			xpos = new Line ("", "", 0, Line.VERTICAL);
			xpos.pos = Glyph.path_coordinate_x (x);
		} else {
			xpos = new Line ("", "", x, Line.VERTICAL);
		}

		m = double.MAX;
		foreach (Line line in vertical) {
			d = Math.fabs (line.get_pos () - xpos.get_pos ());
			
			if (d <= m) {
				m = d;
				xmin = line;
			}
			
		}
		
		if (!coordinate) {
			x = Glyph.reverse_path_coordinate_x (xmin.get_pos ());
		} else {
			x = xmin.get_pos ();
		}
		
		return x;
	}

	public static double tie_point_y (double y, bool coordinate)
		requires (horizontal.size >= 2)
	{
		double d, m;
		Line ymin = horizontal.get (0);
		Line ypos;
		Line starty = horizontal.get (0);
		Line stopy = horizontal.get (horizontal.size - 1);
		
		// outside of the grid
		if (!coordinate) {
			if (!(starty.pos < Glyph.path_coordinate_y (y) < stopy.pos)) {
				return y;
			}
		} else {
			if (!(starty.pos < y < stopy.pos)) {
				return y;
			}
		}
		
		if (!coordinate) {
			ypos = new Line ("", "", 0, Line.HORIZONTAL);
			ypos.pos = Glyph.path_coordinate_y (y);
		} else {
			ypos = new Line ("", "", y, Line.HORIZONTAL);
		}

		m = double.MAX;
		foreach (Line line in horizontal) {
			d = Math.fabs (line.get_pos () - ypos.get_pos ());
			
			if (d <= m) {
				m = d;
				ymin = line;
			}
			
		}
		
		if (!coordinate) {
			y = Glyph.reverse_path_coordinate_y (ymin.get_pos ());
		} else {
			y = ymin.get_pos ();
		}
		
		return y;
	}
	
	public static bool has_ttf_grid () {
		return ttf_units;
	}
	
	public static bool is_visible () {
		return grid_visible;
	}
	
	public static Gee.ArrayList<Line> get_horizontal_lines () {
		return horizontal;
	}
	
	public static Gee.ArrayList<Line> get_vertical_lines () {
		return vertical;
	}

}

}

Contact

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