Commit b0488ce
new directory structure
Changed files (141)
addedbirdfont-export/BirdfontExport.valaaddedbirdfont-export/wscriptaddedbirdfont/GtkWindow.valaaddedbirdfont/Main.valaaddedbirdfont/wscriptmodifiedconfiguremodifiedicons/zoom_1_1.pngaddedlibbirdfont/Allocation.valaaddedlibbirdfont/Argument.valaaddedlibbirdfont/BackgroundSelection.valaaddedlibbirdfont/BackgroundTool.valaaddedlibbirdfont/Birdfont.valaaddedlibbirdfont/CharDatabase.valaaddedlibbirdfont/ClipTool.valaaddedlibbirdfont/Context.valaaddedlibbirdfont/CutBackgroundTool.valaaddedlibbirdfont/CutTool.valaaddedlibbirdfont/DropMenu.valaaddedlibbirdfont/EditPoint.valaaddedlibbirdfont/EditPointHandle.valaaddedlibbirdfont/EotWriter.valaaddedlibbirdfont/Expander.valaaddedlibbirdfont/ExportTool.valaaddedlibbirdfont/Font.valaaddedlibbirdfont/FontDisplay.valaaddedlibbirdfont/Glyph.valaaddedlibbirdfont/GlyphBackgroundImage.valaaddedlibbirdfont/GlyphCanvas.valaaddedlibbirdfont/GlyphCollection.valaaddedlibbirdfont/GlyphRange.valaaddedlibbirdfont/GlyphTable.valaaddedlibbirdfont/GridTool.valaaddedlibbirdfont/IOThread.valaaddedlibbirdfont/Icons.valaaddedlibbirdfont/Intersection.valaaddedlibbirdfont/IntersectionList.valaaddedlibbirdfont/Kerning.valaaddedlibbirdfont/KeyBindings.valaaddedlibbirdfont/Line.valaaddedlibbirdfont/MainWindow.valaaddedlibbirdfont/MenuAction.valaaddedlibbirdfont/MenuTab.valaaddedlibbirdfont/MergeTool.valaaddedlibbirdfont/MoveTool.valaaddedlibbirdfont/NativeWindow.valaaddedlibbirdfont/OpenFontFormatReader.valaaddedlibbirdfont/OpenFontFormatWriter.valaaddedlibbirdfont/OverView.valaaddedlibbirdfont/Path.valaaddedlibbirdfont/PenTool.valaaddedlibbirdfont/Preferences.valaaddedlibbirdfont/Preview.valaaddedlibbirdfont/ResizeTool.valaaddedlibbirdfont/SaveDialog.valaaddedlibbirdfont/Scrollbar.valaaddedlibbirdfont/ShrinkTool.valaaddedlibbirdfont/SpinButton.valaaddedlibbirdfont/Supplement.valaaddedlibbirdfont/Svg.valaaddedlibbirdfont/SvgFontFormatWriter.valaaddedlibbirdfont/Tab.valaaddedlibbirdfont/TabBar.valaaddedlibbirdfont/TestCases.valaaddedlibbirdfont/TestSupplement.valaaddedlibbirdfont/Tool.valaaddedlibbirdfont/Toolbox.valaaddedlibbirdfont/TooltipArea.valaaddedlibbirdfont/UniRange.valaaddedlibbirdfont/UserActions.valaaddedlibbirdfont/VersionList.valaaddedlibbirdfont/ZoomTool.valaaddedlibbirdfont/wscriptdeletedsrc/Allocation.valadeletedsrc/Argument.valadeletedsrc/BackgroundSelection.valadeletedsrc/BackgroundTool.valadeletedsrc/Birdfont.valadeletedsrc/BirdfontExport.valadeletedsrc/CharDatabase.valadeletedsrc/ClipTool.valadeletedsrc/Context.valadeletedsrc/CutBackgroundTool.valadeletedsrc/CutTool.valadeletedsrc/DropMenu.valadeletedsrc/EditPoint.valadeletedsrc/EditPointHandle.valadeletedsrc/EotWriter.valadeletedsrc/Expander.valadeletedsrc/ExportTool.valadeletedsrc/Font.valadeletedsrc/FontDisplay.valadeletedsrc/Glyph.valadeletedsrc/GlyphBackgroundImage.valadeletedsrc/GlyphCanvas.valadeletedsrc/GlyphCollection.valadeletedsrc/GlyphRange.valadeletedsrc/GlyphTable.valadeletedsrc/GridTool.valadeletedsrc/GtkWindow.valadeletedsrc/IOThread.valadeletedsrc/Icons.valadeletedsrc/Intersection.valadeletedsrc/IntersectionList.valadeletedsrc/Kerning.valadeletedsrc/KeyBindings.valadeletedsrc/Line.valadeletedsrc/Main.valadeletedsrc/MainWindow.valadeletedsrc/MenuAction.valadeletedsrc/MenuTab.valadeletedsrc/MergeTool.valadeletedsrc/MoveTool.valadeletedsrc/NativeWindow.valadeletedsrc/OpenFontFormatReader.valadeletedsrc/OpenFontFormatWriter.valadeletedsrc/OverView.valadeletedsrc/Path.valadeletedsrc/PenTool.valadeletedsrc/Preferences.valadeletedsrc/Preview.valadeletedsrc/ResizeTool.valadeletedsrc/SaveDialog.valadeletedsrc/Scrollbar.valadeletedsrc/ShrinkTool.valadeletedsrc/SpinButton.valadeletedsrc/Supplement.valadeletedsrc/Svg.valadeletedsrc/SvgFontFormatWriter.valadeletedsrc/Tab.valadeletedsrc/TabBar.valadeletedsrc/TestCases.valadeletedsrc/TestSupplement.valadeletedsrc/Tool.valadeletedsrc/Toolbox.valadeletedsrc/TooltipArea.valadeletedsrc/UniRange.valadeletedsrc/UserActions.valadeletedsrc/VersionList.valadeletedsrc/ZoomTool.valadeletedsrc/wscriptmodifiedwscript
Diff
new file mode 100644@@ -0,0 +1,23 @@+/*+ 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 Birdfont;++/** Stand alone command line utility for exporting fonts. */+public static int main (string[] arg) { + return run_export (arg);+}new file mode 100644@@ -0,0 +1,23 @@+def build (bld):++ bld (+ rule = "valac -C ./libbirdfont/birdfont.vapi ../birdfont-export/BirdfontExport.vala -X ../build/libbirdfont/libbirdfont.so -X ./birdfont.h --pkg gtk+-2.0 --pkg libxml-2.0 --pkg gdk-2.0 --pkg webkit-1.0",+ depends = 'libbirdfont',+ target = '../BirdfontExport.c',+ name = 'birdfont_export'+ )++ bld (+ rule = 'gcc ../build/BirdfontExport.c -I../build/ -L ../build/libbirdfont -l birdfont -o ${TGT} \+ $(pkg-config --cflags --libs glib-2.0) \+ $(pkg-config --cflags --libs libxml-2.0) \+ $(pkg-config --cflags --libs gio-2.0) \+ $(pkg-config --cflags --libs libsoup-2.4) \+ $(pkg-config --cflags --libs gtk+-2.0) \+ $(pkg-config --cflags --libs webkit-1.0)',+ source = ['../BirdfontExport.c'],+ target = 'birdfont-export',+ depends = 'birdfont_export'+ )+ + bld.install_files('${PREFIX}/bin/', ['../build/birdfont-export/birdfont-export'], chmod=755)new file mode 100644@@ -0,0 +1,808 @@+/*+ 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;+using Gtk;+using Gdk;+using Supplement;+using WebKit;++namespace Supplement {++public class GtkWindow : Gtk.Window, NativeWindow {++ HBox list_box;+ HBox canvas_box;+ + WebView html_canvas;+ ScrolledWindow html_box;++ VBox tab_box;++ static DrawingArea margin_bottom;+ static DrawingArea margin_right;+ + GlyphCanvasArea glyph_canvas_area;+ + static List<uint> key_pressed = new List<uint> ();+ + public GtkWindow (string title) {+ // set_title (title);+ }+ + public void init () {+ margin_bottom = new DrawingArea ();+ margin_right = new DrawingArea ();+ + margin_bottom.set_size_request (0, 0);+ margin_right.set_size_request (0, 0);+ + delete_event.connect (quit);+ + set_size_and_position ();+ + html_canvas = new WebView ();+ WebKit.set_cache_model (CacheModel.DOCUMENT_VIEWER);+ html_canvas.get_settings ().enable_default_context_menu = false;+ + html_canvas.title_changed.connect ((p, s) => {+ webkit_callback (s);+ });+ + html_box = new ScrolledWindow (null, null);+ html_box.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);+ html_box.add (html_canvas);+ html_canvas.set_editable (true);+ + MainWindow.get_tab_bar ().signal_tab_selected.connect ((f, tab) => {+ bool n;+ File layout_dir;+ File layout_uri;+ string uri = "";+ FontDisplay fd = tab.get_display ();+ + MainWindow.glyph_canvas.set_current_glyph (fd);+ n = fd.is_html_canvas ();+ + if (n) {+ layout_dir = FontDisplay.find_layout_dir ();+ uri = fd.get_uri ();+ + if (uri == "") {+ layout_uri = layout_dir.get_child (fd.get_html_file ());+ uri = fd.path_to_uri ((!) layout_uri.get_path ());+ }+ + if (fd.get_html () == "") {+ + if (fd.get_name () == "Preview") {+ // hack: force webkit to ignore cache in preview + html_box.set_visible (false);+ glyph_canvas_area.set_visible (true);+ + try {+ Preview preview = (Preview) fd;+ DataInputStream dis = new DataInputStream (preview.get_html_file ().read ());+ string? line;+ StringBuilder sb = new StringBuilder ();+ uint rid = Random.next_int ();+ Font font = Supplement.get_current_font ();+ + File preview_directory = Supplement.get_preview_directory ();+ + File f_ttf = font.get_folder ().get_child (@"$(font.get_name ()).ttf");+ File f_eot = font.get_folder ().get_child (@"$(font.get_name ()).eot");+ File f_svg = font.get_folder ().get_child (@"$(font.get_name ()).svg");++ if (f_ttf.query_exists ()) {+ f_ttf.delete ();+ }+ + if (f_eot.query_exists ()) {+ f_eot.delete ();+ }+ + if (f_svg.query_exists ()) {+ f_svg.delete ();+ }+ + ExportTool.export_ttf_font (); + ExportTool.export_svg_font ();+ + File r_ttf = preview_directory.get_child (@"$(font.get_name ())$rid.ttf");+ File r_svg = preview_directory.get_child (@"$(font.get_name ())$rid.svg");+ + if (Supplement.win32) {+ f_ttf.copy (r_ttf, FileCopyFlags.NONE);+ }+ + f_svg.copy (r_svg, FileCopyFlags.NONE);++ while ((line = dis.read_line (null)) != null) {+ line = ((!) line).replace (@"$(font.get_name ()).ttf", @"$(FontDisplay.path_to_uri ((!) f_ttf.get_path ()))?$rid");+ line = ((!) line).replace (@"$(font.get_name ()).eot", @"$(FontDisplay.path_to_uri ((!) f_eot.get_path ()))?$rid");+ line = ((!) line).replace (@"$(font.get_name ()).svg", @"$(FontDisplay.path_to_uri ((!) f_svg.get_path ()))?$rid");+ sb.append ((!) line);+ }+ + html_canvas.load_html_string (sb.str, uri); + } catch (Error e) {+ warning (e.message);+ warning ("Failed to load html into canvas.");+ }+ + } else {+ // normal way to load a uri for all other pages+ html_canvas.load_uri (uri);+ html_canvas.reload_bypass_cache (); + }+ + } else {+ html_canvas.load_html_string (fd.get_html (), uri);+ }+ + html_box.set_visible (n);+ glyph_canvas_area.set_visible (!n);+ } else {+ html_box.set_visible (false);+ glyph_canvas_area.set_visible (true);+ }+ });++ // Hide this canvas when window is realized and flip canvas + // visibility in tab selection signal.+ html_canvas.expose_event.connect ((t, e) => {+ glyph_canvas_area.set_visible (false);+ return false;+ });+ + MainWindow.tabs.add_unique_tab (MainWindow.menu_tab, 60, true);+ + MainWindow.tabs.select_tab_name ("Menu");++ glyph_canvas_area = new GlyphCanvasArea (MainWindow.glyph_canvas);++ canvas_box = new HBox (false, 0);+ canvas_box.pack_start (glyph_canvas_area, true, true, 0);+ canvas_box.pack_start (html_box, true, true, 0);+ + tab_box = new VBox (false, 0);+ tab_box.pack_start (new TabbarCanvas (MainWindow.tabs), false, false, 0); + + tab_box.pack_start (canvas_box, true, true, 0);++ tab_box.pack_start (new TooltipCanvas (MainWindow.tool_tip), false, false, 0);+ tab_box.pack_start (margin_bottom, false, false, 0);+ + list_box = new HBox (false, 0);+ list_box.pack_start (tab_box, true, true, 0);+ list_box.pack_start (new ToolboxCanvas (MainWindow.tools), false, false, 0);+ list_box.pack_start (margin_right, false, false, 0);++ VBox vbox = new VBox (false, 0);+ + vbox.pack_start(create_menu (), false, false, 0);+ vbox.pack_start(list_box, true, true, 0);++ add (vbox);+ + try {+ set_icon_from_file ((!) Icons.find_icon ("window_icon.png").get_path ());+ } catch (GLib.Error e) {+ warning (e.message);+ }++ key_press_event.connect ((t, event) => {+ MainWindow window = MainWindow.get_singleton ();+ FontDisplay fd = window.glyph_canvas.current_display;+ + if (fd is Glyph) {+ window.tools.key_press (event.keyval);+ }+ + window.glyph_canvas.key_press (event.keyval);+ return false;+ });+ + key_release_event.connect ((t, event) => {+ MainWindow window = MainWindow.get_singleton ();+ if (window.glyph_canvas is Glyph) {+ window.glyph_canvas.key_release (event.keyval);+ }+ return false;+ });+ + show_all ();+ }++ MenuBar create_menu () {+ MenuBar menubar = new MenuBar ();+ Gtk.Menu file_menu = new Gtk.Menu ();+ Gtk.Menu edit_menu = new Gtk.Menu ();+ Gtk.Menu tab_menu = new Gtk.Menu ();+ Gtk.Menu tool_menu = new Gtk.Menu ();++ AccelGroup accel_group = new Gtk.AccelGroup();+ add_accel_group (accel_group);+ + // File+ Gtk.MenuItem new_item = new Gtk.MenuItem.with_mnemonic (_("_New"));+ file_menu.append (new_item);+ new_item.activate.connect (() => { MenuTab.new_file (); });+ new_item.add_accelerator ("activate", accel_group, 'N', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);++ Gtk.MenuItem load_item = new Gtk.MenuItem.with_mnemonic (_("_Open"));+ file_menu.append (load_item);+ load_item.activate.connect (() => { MenuTab.load (); });+ load_item.add_accelerator ("activate", accel_group, 'O', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);++ Gtk.MenuItem save_item = new Gtk.MenuItem.with_mnemonic (_("_Save"));+ file_menu.append (save_item);+ save_item.activate.connect (() => { MenuTab.save (); });+ save_item.add_accelerator ("activate", accel_group, 'S', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);++ Gtk.MenuItem save_as_item = new Gtk.MenuItem.with_mnemonic (_("Save _as"));+ file_menu.append (save_as_item);+ save_as_item.activate.connect (() => { MenuTab.save_as (); });+ + Gtk.MenuItem export_item = new Gtk.MenuItem.with_mnemonic (_("_Export"));+ file_menu.append (export_item);+ export_item.activate.connect (() => { ExportTool.export_all (); });+ export_item.add_accelerator ("activate", accel_group, 'E', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);++ Gtk.MenuItem preview_item = new Gtk.MenuItem.with_mnemonic(_("_Preview"));+ file_menu.append (preview_item);+ preview_item.activate.connect (() => { MenuTab.preview (); });+ preview_item.add_accelerator ("activate", accel_group, 'P', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);++ Gtk.MenuItem kerning_item = new Gtk.MenuItem.with_mnemonic (_("_Kerning"));+ file_menu.append (kerning_item);+ kerning_item.activate.connect (() => { MenuTab.show_kerning_context (); });+ kerning_item.add_accelerator ("activate", accel_group, 'K', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);++ Gtk.MenuItem quit_item = new Gtk.MenuItem.with_mnemonic (_("_Quit"));+ file_menu.append (quit_item);+ quit_item.activate.connect (() => { quit(); });++ // Edit+ Gtk.MenuItem undo_item = new Gtk.MenuItem.with_mnemonic (_("_Undo"));+ edit_menu.append (undo_item);+ undo_item.activate.connect (() => { MainWindow.get_current_display ().undo (); }); + undo_item.add_accelerator ("activate", accel_group, 'Z', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);+ + Gtk.MenuItem copy_item = new Gtk.MenuItem.with_mnemonic (_("_Copy"));+ edit_menu.append (copy_item);+ copy_item.activate.connect (() => { ClipTool.copy (); }); + copy_item.add_accelerator ("activate", accel_group, 'C', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);++ Gtk.MenuItem paste_item = new Gtk.MenuItem.with_mnemonic (_("_Paste"));+ edit_menu.append (paste_item);+ paste_item.activate.connect (() => { ClipTool.paste (); }); + paste_item.add_accelerator ("activate", accel_group, 'V', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);+ + // Tab+ Gtk.MenuItem next_tab_item = new Gtk.MenuItem.with_mnemonic (_("_Next tab"));+ tab_menu.append (next_tab_item);+ next_tab_item.activate.connect (() => { + TabBar tb = MainWindow.get_tab_bar ();+ int n = tb.get_selected () + 1;+ + if (!(0 <= n < tb.get_length ())) {+ return;+ }+ + tb.select_tab (n);+ }); ++ Gtk.MenuItem prevoius_tab_item = new Gtk.MenuItem.with_mnemonic (_("_Previous tab"));+ tab_menu.append (prevoius_tab_item);+ prevoius_tab_item.activate.connect (() => { + TabBar tb = MainWindow.get_tab_bar ();+ int n = tb.get_selected () - 1;++ if (!(0 <= n < tb.get_length ())) {+ return;+ }+ + tb.select_tab (n);+ }); ++ Gtk.MenuItem close_tab_item = new Gtk.MenuItem.with_mnemonic (_("_Close tab"));+ tab_menu.append (close_tab_item);+ close_tab_item.activate.connect (() => { + TabBar tb = MainWindow.get_tab_bar ();+ int n = tb.get_selected ();++ if (!(0 <= n < tb.get_length ())) {+ return;+ }+ + tb.close_tab (n);+ }); + close_tab_item.add_accelerator ("activate", accel_group, 'W', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);++ // Tool+ Gtk.MenuItem pen_item = new Gtk.MenuItem.with_mnemonic (_("_Create path"));+ tool_menu.append (pen_item);+ pen_item.activate.connect (() => { + Toolbox.select_tool_by_name ("pen_tool");+ });+ pen_item.add_accelerator ("activate", accel_group, ',', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);++ Gtk.MenuItem zoom_item = new Gtk.MenuItem.with_mnemonic (_("_Zoom"));+ tool_menu.append (zoom_item);+ zoom_item.activate.connect (() => { + Toolbox.select_tool_by_name ("zoom_tool");+ });++ Gtk.MenuItem counter_item = new Gtk.MenuItem.with_mnemonic (_("_Create counter path"));+ tool_menu.append (counter_item);+ counter_item.activate.connect (() => { + Toolbox.select_tool_by_name ("cut");+ });+ counter_item.add_accelerator ("activate", accel_group, 'U', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);++ Gtk.MenuItem move_item = new Gtk.MenuItem.with_mnemonic (_("_Move"));+ tool_menu.append (move_item);+ move_item.activate.connect (() => { + Toolbox.select_tool_by_name ("move");+ });+ move_item.add_accelerator ("activate", accel_group, 'M', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);++ Gtk.MenuItem full_unicode_item = new Gtk.MenuItem.with_mnemonic (_("Show _full unicode characters set"));+ tool_menu.append (full_unicode_item);+ full_unicode_item.activate.connect (() => { + Toolbox.select_tool_by_name ("utf_8");+ });+ full_unicode_item.add_accelerator ("activate", accel_group, 'F', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);++ Gtk.MenuItem default_charset_item = new Gtk.MenuItem.with_mnemonic (_("Show de_fault characters set"));+ tool_menu.append (default_charset_item);+ default_charset_item.activate.connect (() => { + Toolbox.select_tool_by_name ("custom_character_set");+ });+ default_charset_item.add_accelerator ("activate", accel_group, 'R', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);++ Gtk.MenuItem avalilable_characters_item = new Gtk.MenuItem.with_mnemonic (_("Show characters in font"));+ tool_menu.append (avalilable_characters_item);+ avalilable_characters_item.activate.connect (() => { + Toolbox.select_tool_by_name ("available_characters");+ });+ avalilable_characters_item.add_accelerator ("activate", accel_group, 'D', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);++ Gtk.MenuItem add_grid_item = new Gtk.MenuItem.with_mnemonic (_("Add new _grid item"));+ tool_menu.append (add_grid_item);+ add_grid_item.activate.connect (() => { + MainWindow.get_toolbox ().add_new_grid ();+ });++ Gtk.MenuItem remove_grid_item = new Gtk.MenuItem.with_mnemonic (_("Remove gr_id item"));+ tool_menu.append (remove_grid_item);+ remove_grid_item.activate.connect (() => { + MainWindow.get_toolbox ().remove_current_grid ();+ });+ + Gtk.MenuItem zoom_in_item = new Gtk.MenuItem.with_mnemonic (_("_Zoom in"));+ tool_menu.append (zoom_in_item);+ zoom_in_item.activate.connect (() => { + Toolbox.select_tool_by_name ("zoom_in");+ });+ zoom_in_item.add_accelerator ("activate", accel_group, '+', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);++ Gtk.MenuItem zoom_out_item = new Gtk.MenuItem.with_mnemonic (_("Zoom _out"));+ tool_menu.append (zoom_out_item);+ zoom_out_item.activate.connect (() => { + Toolbox.select_tool_by_name ("zoom_out");+ });+ zoom_out_item.add_accelerator ("activate", accel_group, '-', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);++ Gtk.MenuItem zoom_1_1_item = new Gtk.MenuItem.with_mnemonic (_("Zoom to _scale 1:1"));+ tool_menu.append (zoom_1_1_item);+ zoom_1_1_item.activate.connect (() => { + Toolbox.select_tool_by_name ("zoom_1_1");+ });+ zoom_1_1_item.add_accelerator ("activate", accel_group, '0', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);++ // Add menus+ Gtk.MenuItem file_launcher = new Gtk.MenuItem.with_mnemonic (_("_File"));+ file_launcher.set_submenu (file_menu);++ Gtk.MenuItem edit_launcher = new Gtk.MenuItem.with_mnemonic (_("_Edit"));+ edit_launcher.set_submenu (edit_menu);++ Gtk.MenuItem tab_launcher = new Gtk.MenuItem.with_mnemonic (_("_Tab"));+ tab_launcher.set_submenu (tab_menu);++ Gtk.MenuItem tool_launcher = new Gtk.MenuItem.with_mnemonic (_("T_ool"));+ tool_launcher.set_submenu (tool_menu);+ + menubar.append (file_launcher);+ menubar.append (edit_launcher);+ menubar.append (tab_launcher);+ menubar.append (tool_launcher);+ + return menubar; + }++ internal void toggle_expanded_margin_bottom () {+ int w, h;+ margin_bottom.get_size_request (out w, out h);+ + if (h == 1) h = 2; + else h = 1;+ + margin_bottom.set_size_request (w, h);+ }+ + internal void toggle_expanded_margin_right () { + int w, h;+ margin_right.get_size_request (out w, out h);++ if (w == 1) w = 2; + else w = 1;++ margin_right.set_size_request (w, h);+ }++ public static void hide_cursor () {+ /*+ Pixmap pixmap = new Pixmap (null, 1, 1, 1);+ Color color = { 0, 0, 0, 0 };+ Cursor cursor = new Cursor.from_pixmap (pixmap, pixmap, color, color, 0, 0);+ */+ + // Fixa: But why?+ // (Supplement.exe:1300): Gdk-CRITICAL **: gdk_window_set_cursor: assertion `GDK_IS_WINDOW (window)' failed + // singleton.frame.set_cursor (cursor);+ }++ public void update_window_size () {+ int w, h;+ get_size (out w, out h);+ + Preferences.set ("window_width", @"$w");+ Preferences.set ("window_height", @"$h");+ }+ + private void set_size_and_position () {+ int w = Preferences.get_window_width ();+ int h = Preferences.get_window_height ();+ + set_default_size (w, h);+ // move (10, 240);+ }+ + public bool quit () {+ bool added;+ SaveDialog s = new SaveDialog ();+ + if (Supplement.get_current_font ().is_modified ()) {+ added = MainWindow.get_tab_bar ().add_unique_tab (s, 50);+ } else {+ added = false;+ }+ + if (!added) {+ Supplement.get_current_font ().save_backup ();+ Gtk.main_quit ();+ }+ + s.finished.connect (() => {+ Supplement.get_current_font ().delete_backup ();+ Gtk.main_quit ();+ });+ + return true;+ }+ + public string? file_chooser (string title) {+ string? fn = null;+ FileChooserDialog file_chooser = new FileChooserDialog (title, this, FileChooserAction.SAVE, Stock.CANCEL, ResponseType.CANCEL, Stock.SAVE, ResponseType.ACCEPT);+ Font font = Supplement.get_current_font ();+ + try {+ file_chooser.set_current_folder_file (font.get_folder ());+ } catch (GLib.Error e) {+ stderr.printf (e.message);+ }+ + if (file_chooser.run () == ResponseType.ACCEPT) { + MainWindow.get_glyph_canvas ().redraw ();+ fn = file_chooser.get_filename ();+ }++ file_chooser.destroy ();+ + return fn;+ } +}++class TabbarCanvas : DrawingArea {+ TabBar tabbar;+ + public TabbarCanvas (TabBar tb) { + tabbar = tb;++ set_extension_events (ExtensionMode.CURSOR | EventMask.POINTER_MOTION_MASK);+ add_events (EventMask.BUTTON_PRESS_MASK | EventMask.POINTER_MOTION_MASK | EventMask.LEAVE_NOTIFY_MASK);+ + motion_notify_event.connect ((t, e)=> {+ Gtk.Allocation alloc;+ tabbar.motion (e.x, e.y);+ get_allocation (out alloc);+ queue_draw_area (0, 0, alloc.width, alloc.height);+ return true;+ }); + + button_press_event.connect ((t, e)=> {+ Gtk.Allocation alloc;+ get_allocation (out alloc);+ tabbar.select_tab_click (e.x, e.y, alloc.width, alloc.height);+ queue_draw_area (0, 0, alloc.width, alloc.height);+ return true;+ });++ expose_event.connect ((t, e)=> {+ Context cr = cairo_create (get_window ());++ Gtk.Allocation alloc;+ get_allocation (out alloc);++ tabbar.draw (cr, alloc.width, alloc.height);+ return true;+ });+ + tabbar.signal_tab_selected.connect ((t) => {+ Gtk.Allocation alloc;+ get_allocation (out alloc);+ queue_draw_area (0, 0, alloc.width, alloc.height); + });+ + set_size_request (20, 25);+ }+ +}++class ToolboxCanvas : DrawingArea {+ Toolbox tb;+ + public ToolboxCanvas (Toolbox toolbox) {+ tb = toolbox;+ + tb.redraw.connect ((x, y, w, h) => {+ queue_draw_area (x, y, w, h);+ });+ + button_press_event.connect ((se, e)=> {+ tb.press (e.button, e.x, e.y);+ return true;+ }); + + button_release_event.connect ((se, e)=> {+ tb.release (e.button, e.x, e.y);+ return true;+ });++ motion_notify_event.connect ((sen, e)=> {+ tb.move (e.x, e.y);+ return true;+ });+ + expose_event.connect ((t, e)=> {+ Gtk.Allocation allocation;+ get_allocation (out allocation);+ + Context cw = cairo_create(get_window());+ tb.allocation_width = allocation.width;+ tb.allocation_height = allocation.height;+ tb.draw (allocation.width, allocation.height, cw);+ + return true;+ });+ + add_events (EventMask.BUTTON_PRESS_MASK | EventMask.BUTTON_RELEASE_MASK | EventMask.POINTER_MOTION_MASK | EventMask.LEAVE_NOTIFY_MASK);++ set_size_request (160, 100);++ leave_notify_event.connect ((t, e)=> {+ tb.reset_active_tool ();+ return true;+ });+ + }+}++public class GlyphCanvasArea : DrawingArea {+ GlyphCanvas glyph_canvas;+ Gtk.Allocation alloc;++ /* State on linux */+ public static const int L_SHIFT = 17;+ public static const int L_CTRL = 20;+ public static const int L_SHIFT_CTRL = 21;++ /* State on windows */+ public static const int W_SHIFT = 1;+ public static const int W_CTRL = 4;+ public static const int W_SHIFT_CTRL = 5;+ + public GlyphCanvasArea (GlyphCanvas gc) {+ int event_flags;+ + glyph_canvas = gc;+ + event_flags = EventMask.BUTTON_PRESS_MASK;+ event_flags |= EventMask.BUTTON_RELEASE_MASK;+ event_flags |= EventMask.POINTER_MOTION_MASK;+ event_flags |= EventMask.LEAVE_NOTIFY_MASK;+ event_flags |= EventMask.SCROLL_MASK;+ + add_events (event_flags);++ glyph_canvas.signal_redraw_area.connect ((x, y, w, h) => {+ queue_draw_area ((int)x, (int)y, (int)w, (int)h);+ });++ expose_event.connect ((t, e)=> { + Gtk.Allocation allocation;+ get_allocation (out allocation);+ + glyph_canvas.allocation.width = allocation.width;+ glyph_canvas.allocation.height = allocation.height;+ glyph_canvas.allocation.y = allocation.x;+ glyph_canvas.allocation.y = allocation.y;+ + if (unlikely (allocation != alloc && alloc.width != 0)) {+ // Set size of glyph widget to an even number and notify + // set new allocation for glyph+ bool ug = false;+ + if (allocation.height % 2 != 0) {+ MainWindow.native_window.toggle_expanded_margin_bottom ();+ ug = true;+ }+ + if (allocation.width % 2 != 0) {+ MainWindow.native_window.toggle_expanded_margin_right ();+ ug = true;+ } + + if (ug) {+ //redraw_area (1, 1, 2, 2);+ } else if (unlikely (allocation.width % 2 != 0 || allocation.height % 2 != 0)) {+ warning (@"\nGlyph canvas is not divisible by two.\nWidth: $(allocation.width)\nHeight: $(allocation.height)");+ }+ + Supplement.current_glyph.resized ();+ }+ + alloc = allocation;+ + Context cw = cairo_create (get_window());+ + Surface s = new Surface.similar (cw.get_target (), Cairo.Content.COLOR_ALPHA, allocation.width, allocation.height);+ Context c = new Context (s); ++ glyph_canvas.current_display.draw (glyph_canvas.allocation, c);++ cw.save ();+ cw.set_source_surface (c.get_target (), 0, 0);+ cw.paint ();+ cw.restore ();+ + return true;+ });++ button_press_event.connect ((t, e)=> {+ set_modifier ((int) e.state);+ + if (e.type == EventType.BUTTON_PRESS) {+ glyph_canvas.current_display.button_press (e.button, e.x, e.y); + } else if (e.type == EventType.2BUTTON_PRESS) {+ glyph_canvas.current_display.double_click (e.button, e.x, e.y);+ }+ + return true;+ });+ + button_release_event.connect ((t, e)=> {+ set_modifier ((int) e.state);+ glyph_canvas.current_display.button_release ((int) e.button, e.x, e.y);+ return true;+ });+ + motion_notify_event.connect ((t, e)=> {+ glyph_canvas.current_display.motion_notify (e.x, e.y); + return true;+ });+ + scroll_event.connect ((t, e)=> {+ set_modifier ((int) e.state);+ + if (e.direction == Gdk.ScrollDirection.UP) {+ glyph_canvas.current_display.scroll_wheel_up (e.x, e.y);+ } else if (e.direction == Gdk.ScrollDirection.DOWN) {+ glyph_canvas.current_display.scroll_wheel_down (e.x, e.y);+ }+ + glyph_canvas.current_display.button_release (2, e.x, e.y);+ return true;+ }); + }++ static void set_modifier (int k) {+ switch (k) {+ case L_SHIFT:+ KeyBindings.set_modifier (SHIFT);+ break;+ case L_CTRL:+ KeyBindings.set_modifier (CTRL);+ break;+ case L_SHIFT_CTRL:+ KeyBindings.set_modifier (SHIFT | CTRL);+ break;+ case W_SHIFT:+ KeyBindings.set_modifier (SHIFT);+ break;+ case W_CTRL:+ KeyBindings.set_modifier (CTRL);+ break;+ case W_SHIFT_CTRL:+ KeyBindings.set_modifier (SHIFT | CTRL);+ break;++ default:+ KeyBindings.set_modifier (NONE);+ break;+ } + }+}++public class TooltipCanvas : DrawingArea {+ TooltipArea tooltip_area;+ + public TooltipCanvas (TooltipArea ta) {+ tooltip_area = ta;++ expose_event.connect ((t, e)=> {+ Allocation allocation = {0, 0, 0, 0};+ Gtk.Allocation alloc;+ Context cr = cairo_create (get_window ());+ + get_allocation (out alloc);++ allocation.width = alloc.width;+ allocation.height = alloc.height;+ allocation.y = alloc.x;+ allocation.y = alloc.y;+ + tooltip_area.draw (cr, allocation);++ return true;+ });+ + tooltip_area.redraw.connect (() => {+ Gtk.Allocation alloc;+ get_allocation (out alloc);+ queue_draw_area (0, 0, alloc.width, alloc.height);+ });+ + set_size_request (10, 20);+ }+}++}new file mode 100644@@ -0,0 +1,53 @@+/*+ 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 Birdfont;+using Supplement;++const string GETTEXT_PACKAGE = "birdfont"; ++void parse_gtk_rc () {+ File f = FontDisplay.find_file ("layout", "birdfont.rc");+ Gtk.rc_parse ((!) f.get_path ());+}++public static int main (string[] arg) {+ GtkWindow native_window;+ MainWindow window;+ string file;+ Supplement.Supplement supplement = new Supplement.Supplement ();+ supplement.init (arg);+ + Gtk.init (ref arg);+ parse_gtk_rc ();++ native_window = new GtkWindow ("birdfont"); + window = new MainWindow ();+ + window.set_native (native_window);+ native_window.init ();++ file = supplement.args.get_file ();+ if (file != "") {+ window.menu_tab.load_font (file);+ }+ + Gtk.main ();++ return 0;+}+new file mode 100644@@ -0,0 +1,26 @@+def build (bld): + bld (+ rule = 'valac -C ./libbirdfont/birdfont.vapi ../birdfont/Main.vala ../birdfont/GtkWindow.vala -X ../build/libbirdfont/libbirdfont.so -X ./birdfont.h --pkg gtk+-2.0 --pkg libxml-2.0 --pkg gdk-2.0 --pkg webkit-1.0',+ name = 'main_c',+ source = ['../birdfont/Main.vala', '../birdfont/GtkWindow.vala'],+ target = '../Main.c',+ depends = 'libbirdfont'+ )++ bld (+ rule = """gcc ../build/Main.c ../build/GtkWindow.c \+ -D 'GETTEXT_PACKAGE="birdfont"' \+ -I ../build/ -I ../build/libbirdfont/ -L ../build/libbirdfont -l birdfont \+ -o ${TGT} \+ $(pkg-config --cflags --libs glib-2.0) \+ $(pkg-config --cflags --libs libxml-2.0) \+ $(pkg-config --cflags --libs gio-2.0) \+ $(pkg-config --cflags --libs libsoup-2.4) \+ $(pkg-config --cflags --libs gtk+-2.0) \+ $(pkg-config --cflags --libs webkit-1.0)""",+ source = ['../Main.c'],+ target = 'birdfont',+ depends = 'main_c'+ )++ bld.install_files('${PREFIX}/bin/', ['../build/birdfont/birdfont'], chmod=755)@@ -3,4 +3,4 @@ cd waf-* && \ ./configure && \ cp ./waf ../ && \ cd .. && \-./waf configure+./waf configure $*Binary files a/icons/zoom_1_1.png and b/icons/zoom_1_1.png differnew file mode 100644@@ -0,0 +1,27 @@+/*+ 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/>.+*/++namespace Supplement {++public struct Allocation {+ public int width;+ public int height;+ public int x;+ public int y;+}++}new file mode 100644@@ -0,0 +1,241 @@+/*+ 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/>.+*/++namespace Supplement {+ +public class Argument : GLib.Object {+ + List<string> args;+ + public Argument (string line) {+ args = new List<string> ();+ set_argument (line);+ }+ + public Argument.command_line (string[] arg) { + args = new List<string> ();++ foreach (string a in arg) {+ args.append (a);+ }+ }+ + /** returns 0 if valid or index the bad parameter. */ + public int validate () {+ string prev = "";+ int i = 0;+ foreach (string a in args) {+ + // program name+ if (i == 0) {+ prev = a;+ i++;+ continue;+ }+ + // file name+ if (i == 1 && !a.has_prefix ("-")) {+ prev = a;+ i++;+ continue;+ }++ // a single character, like -t+ if (!a.has_prefix ("--") && a.has_prefix ("-")) {+ a = expand_param (a);+ }+ + // valid parameter+ if (a == "--exit" || a == "--slow" || a == "--help" || a == "--test" || a == "--fatal-warning" || a == "--show-coordinates") {+ prev = a;+ i++;+ continue;+ } else if (a.has_prefix ("--")) {+ return i;+ }+ + // not argument to parameter+ if (!(prev == "--test")) {+ return i;+ }+ + prev = a;+ i++;+ }+ + return 0;+ }+ + /** Return the font file parameter. */+ public string get_file () {+ string f = "";+ + if (args.length () >= 2) {+ f = args.nth (1).data;+ }++ if (f.has_prefix ("-")) {+ return "";+ }+ + return f;+ }+ + public void print_all () {+ print (@"$(args.length ()) arguments:\n");+ + foreach (string p in args) {+ print (@"$p\n");+ }+ }+ + public bool has_argument (string param) {+ return (get_argument (param) != null);+ }+ + /** Get commandline argument. */+ public string? get_argument (string param) {+ int i = 0;+ string? n;+ string p;++ if (param.substring (0, 1) != "-") {+ warning (@"parameters must begin with \"-\" got $param");+ return null;+ }++ foreach (string s in args) {++ // this is content not a parameter + if (s.substring (0, 1) != "-") continue;++ // we might need to expand -t to test fo instance+ if (s.substring (0, 2) != "--") {+ p = expand_param (s);+ } else { + p = s;+ }+ + if (param == p) {+ if (i + 2 >= args.length ()) {+ return "";+ }+ + n = args.nth (i + 2).data;+ if (n == null) {+ return "";+ }+ + if (args.nth (i + 2).data.substring (0, 1) == "-") {+ return "";+ }+ + return args.nth (i + 2).data;+ }+ + i++;+ }+ + return null;+ }++ private void print_padded (string cmd, string desc) {+ int l = 25 - cmd.char_count ();++ stdout.printf (cmd);+ + for (int i = 0; i < l; i++) {+ stdout.printf (" ");+ }+ + stdout.printf (desc);+ stdout.printf ("\n");+ }++ /** Return full command line parameter for the abbrevation.+ * -t becomes --test.+ */+ private string expand_param (string? param) {+ if (param == null) return "";+ var p = (!) param;+ + if (p.length == 0) return "";+ if (p.get_char (0) != '-') return "";+ if (p.char_count () != 2) return "";+ + switch (p.get_char (1)) {+ case 'c':+ return "--show-coordinates";+ case 'e': + return "--exit";+ case 'f': + return "--fatal-warning";+ case 'h': + return "--help";+ case 's': + return "--slow";+ case 't': + return "--test";+ }+ + return "";+ }++ private void set_argument (string arg) {+ int i = 0;+ int a;+ string n;+ + if (arg.char_count () <= 1) {+ return;+ }+ + do {+ a = arg.index_of (" ", i + 1);+ n = arg.substring (i, a - i);+ + if (n.index_of ("\"") == 0) {+ a = arg.index_of ("\"", i + 1);+ n = arg.substring (i, a - i + 1);+ }+ + args.append (n);+ + i += n.char_count () + 1;+ } while (i < arg.char_count ());+ }++ public void print_help () + requires (args.length () > 0)+ {+ stdout.printf (_("Usage") + ": ");+ stdout.printf (args.nth (0).data);+ stdout.printf (" [" + _("FILE") + "] [" + _("OPTION") + " ...]\n");++ print_padded ("-c, --show-coordinates", _("show coordinate in glyph view"));+ print_padded ("-e, --exit", _("exit if a test case failes"));+ print_padded ("-f, --fatal-warning", _("treat warnings as fatal"));+ print_padded ("-h, --help", _("show this message"));+ print_padded ("-s, --slow", _("sleep between each command in test suite"));+ print_padded ("-t, --test [TEST]", _("run test case"));+ + stdout.printf ("\n");+ }++}+ +}new file mode 100644@@ -0,0 +1,302 @@+/*+ 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;+using Gdk;++namespace Supplement {++class BackgroundSelection : FontDisplay {++ int active_box = -1;+ ImageSurface add_icon;+ List <string> background_images = new List <string> ();+ Allocation allocation;+ Glyph glyph;+ + public BackgroundSelection () {+ ImageSurface? i;+ glyph = MainWindow.get_current_glyph ();+ + i = Icons.get_icon ("add_background_image.png");+ return_if_fail (i != null);+ add_icon = (!) i;+ }++ public override string get_name () {+ return "Backgrounds";+ }++ public override void draw (Allocation allocation, Context cr) { + double x, y, zoom;+ int box_index = 0;+ Font font;++ this.allocation = allocation;++ // bg color+ cr.save ();+ cr.set_source_rgba (255/255.0, 255/255.0, 255/255.0, 1);+ cr.rectangle (0, 0, allocation.width, allocation.height);+ cr.fill ();+ cr.restore ();+ + draw_box (box_index, 20, 20, cr);+ cr.set_source_surface (add_icon, 62, 62);+ cr.paint ();+ + cr.save ();+ x = 160;+ y = 20;+ zoom = 0.04;+ + font = Supplement.get_current_font ();+ foreach (string file in background_images) {+ box_index++;++ if (x > allocation.width - 140) {+ x = 20;+ y += 140;+ }++ draw_box (box_index, x, y, cr);+ draw_thumbnail (file, x, y, cr, box_index);+ + x += 140; + }+ cr.restore ();+ + }+ + private ImageSurface create_thumbnail (string file) throws GLib.Error {+ Pixbuf pixbuf;+ + Font font = Supplement.get_current_font ();+ File folder = font.get_backgrounds_folder ();+ string fn = Checksum.compute_for_string (ChecksumType.SHA1, file);+ File original = File.new_for_path (file);+ File thumbnail_path = folder.get_child (@"thumbnail");+ File png_image = thumbnail_path.get_child (@"$(fn).png");+ + if (!thumbnail_path.query_exists ()) {+ DirUtils.create ((!) thumbnail_path.get_path (), 0xFFFFFF);+ }+ + if (!png_image.query_exists ()) {+ pixbuf = new Pixbuf.from_file_at_scale ((!) original.get_path (), 100, 100, true);+ if (!pixbuf.save ((!) png_image.get_path (), "png")) {+ warning (@"can't save $((!) png_image.get_path ())");+ }+ }+ + return new ImageSurface.from_png ((!) png_image.get_path ());+ }+ + private bool draw_thumbnail (string file, double x, double y, Context cr, int box_index) {+ ImageSurface img;+ + try {+ img = create_thumbnail (file);+ + if (img.status () == Cairo.Status.SUCCESS) {+ cr.set_source_surface (img, x + 10, y + 10);+ cr.paint ();+ }+ } catch (GLib.Error e) {+ warning (e.message);+ return false;+ }+ + return true; + }+ + private void draw_box (int index, double x, double y, Context cr) {+ cr.save ();+ cr.set_line_join (LineJoin.ROUND);+ cr.set_line_width (7);+ + if (index == active_box) {+ cr.set_source_rgba (203/255.0, 220/255.0, 249/255.0, 1);+ } else {+ cr.set_source_rgba (183/255.0, 200/255.0, 223/255.0, 1);+ }+ + cr.rectangle (x, y, 120, 120);+ cr.fill_preserve ();+ cr.stroke ();+ + cr.restore (); + }+ + public override void button_release (int button, double ex, double ey) {+ motion (ex, ey);+ + if (active_box == 0) {+ add_image ();+ } else {+ select_image ();+ }+ }++ private void add_image () {+ string? file;++ file = MainWindow.file_chooser ("Add image");++ if (file != null) {+ Supplement.get_current_font ().add_background_image ((!) file);+ }+ + MainWindow.get_glyph_canvas ().redraw (); + + collect_background_images ();+ }++ private void select_image () {+ Font font;+ string file;+ GlyphBackgroundImage bg;+ TabBar tb;+ Tool zoom_background;++ font = Supplement.get_current_font ();+ + if (!(0 <= active_box - 1 < background_images.length ())) {+ return;+ }+ + file = background_images.nth (active_box - 1).data; // Fixa: check bounds+ bg = new GlyphBackgroundImage (file);+ tb = MainWindow.get_tab_bar ();+ + bg.reset_scale (glyph);+ + glyph.set_background_visible (true);+ + MainWindow.get_current_glyph ().set_background_image (bg);+ tb.select_tab_name (MainWindow.get_current_glyph ().get_name ());++ zoom_background = MainWindow.get_tool ("zoom_background_image");+ zoom_background.select_action (zoom_background);++ MainWindow.get_toolbox ().select_tool_by_name ("cut_background");+ }++ private void add_unique_background (string file) {+ foreach (var f in background_images) {+ if (f == file) {+ return;+ }+ }+ + background_images.prepend (file);+ }++ public override void selected_canvas () {+ Font font = Supplement.get_current_font ();+ File folder = font.get_backgrounds_folder ();+ + if (!folder.query_exists ()) {+ DirUtils.create ((!) folder.get_path (), 0xFFFFFF);+ }+ + collect_background_images ();+ }++ bool is_image (string file_name) {+ return file_name.index_of (".png") != -1 + || file_name.index_of (".jpg") != -1+ || file_name.index_of (".jpeg") != -1;+ }++ void collect_background_images () {+ Font font = Supplement.get_current_font ();+ File directory = font.get_folder ();+ FileEnumerator enumerator;+ FileInfo? file_info;+ FileInfo fi;+ File img_file;++ while (background_images.length () > 0) {+ background_images.delete_link (background_images.first ());+ }+ + foreach (string file in font.background_images) {+ add_unique_background (file);+ }+ + try {+ enumerator = directory.enumerate_children (FILE_ATTRIBUTE_STANDARD_NAME, 0);+ + while ((file_info = enumerator.next_file ()) != null) {+ fi = (!) file_info;+ + if (is_image (fi.get_name ())) {+ img_file = directory.get_child (fi.get_name ()); + add_unique_background ((!)img_file.get_path ());+ }+ + }+ } catch (GLib.Error e) {+ warning (e.message);+ }+ }+ + public override void key_press (uint e) {+ }++ public override void key_release (uint e) {+ }+ + public override void motion_notify (double x, double y) {+ motion (x, y);+ }+ + public void motion (double x, double y) {+ int active;+ + active = (int) (x / 140); // column+ active += (int) (y / 140) * (int) (allocation.width / 140.0); // row+ + if (active != active_box) {+ MainWindow.get_glyph_canvas ().redraw ();+ active_box = active;+ }+ }++ public override void button_press (uint button, double ex, double ey) {+ motion (ex, ey);+ }++ public override void store_current_view () {+ }+ + public override void restore_last_view () {+ }++ public override void next_view () {+ }+ + public override void scroll_wheel_up (double x, double y) {+ }++ public override void scroll_wheel_down (double x, double y) {+ }++}++}new file mode 100644@@ -0,0 +1,158 @@+/*+ 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/>.+*/++namespace Supplement {++class BackgroundTool : Tool {++ double begin_x = 0;+ double begin_y = 0;++ public double img_offset_x = 0;+ public double img_offset_y = 0;++ public double img_width = 0;+ public double img_height = 0;+ + public double img_scale_x = 0;++ public static double top_limit;+ public static double bottom_limit;+ + bool move_bg;+ bool resize_bg; // DEL+ + public BackgroundTool (string name) {+ base (name, _("Move, resize and rotate background image"));+ + top_limit = 0;+ bottom_limit = 0;++ select_action.connect((self) => {+ });++ deselect_action.connect ((self) => {+ });+ + press_action.connect((self, b, x, y) => {+ Glyph g = MainWindow.get_current_glyph ();+ GlyphBackgroundImage? bg = g.get_background_image ();+ GlyphBackgroundImage background;+ + if (bg == null) {+ return;+ }+ + background = (!) bg;+ + g.store_undo_state ();+ + background.handler_press (x, y);+ + begin_x = x;+ begin_y = y;+ + img_offset_x = background.img_offset_x;+ img_offset_y = background.img_offset_y;+ + img_scale_x = background.img_scale_x;+ + img_width = background.get_img ().get_width () * background.img_scale_x;+ img_height = background.get_img ().get_height () * background.img_scale_y;+ + move_bg = true;+ });++ release_action.connect((self, b, x, y) => {+ Glyph g = MainWindow.get_current_glyph ();+ GlyphBackgroundImage? bg = g.get_background_image ();+ + if (bg == null) {+ return;+ }+ + img_offset_x = ((!)bg).img_offset_x;+ img_offset_y = ((!)bg).img_offset_y;+ + ((!)bg).handler_release (x, y);+ + move_bg = false;+ resize_bg = false;+ });++ move_action.connect((self, x, y) => { + move (x, y);+ });+ + draw_action.connect ((self, cairo_context, glyph) => {+ Glyph g = MainWindow.get_current_glyph ();+ GlyphBackgroundImage? background_image = g.get_background_image ();+ if (background_image == null) return;+ + ((!) background_image).draw_handle (cairo_context, glyph);+ });+ }+ + void move (double x, double y) {+ Glyph g = MainWindow.get_current_glyph ();+ GlyphBackgroundImage? background_image = g.get_background_image ();+ GlyphBackgroundImage bg = (!) background_image;+ + double xscale, yscale, dx, dy;++ if (background_image == null) return;+ + bg.handler_move (x, y);+ + if (!(move_bg || resize_bg)) return;+ + dx = x - begin_x;+ dy = y - begin_y;+ + dx *= 1/g.view_zoom;+ dy *= 1/g.view_zoom;+ + dx *= PenTool.precision;+ dy *= PenTool.precision;+ + if (bg.selected_handle == 2) {+ bg.set_img_rotation_from_coordinate (x, y);+ }+ + if (bg.selected_handle == 1) {+ xscale = img_scale_x * ((img_width - dx) / img_width); + yscale = xscale;++ bg.set_img_scale (xscale, yscale);+ bg.set_img_offset (this.img_offset_x + dx, this.img_offset_y);+ } + + if (move_bg && bg.selected_handle <= 0) {+ bg.set_img_offset (this.img_offset_x + dx, this.img_offset_y + dy);+ }++ MainWindow.get_glyph_canvas ().redraw ();+ }+ + public override bool test () {+ return false;+ }++}++}+new file mode 100644@@ -0,0 +1,169 @@+/*+ 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 Supplement;++namespace Birdfont {++static void print_export_help (string[] arg) {+ stdout.printf (_("Usage:"));+ stdout.printf (arg[0]);+ stdout.printf (" [OPTION ...] FILE\n");+ stdout.printf ("-h, --help " + _("print this message\n"));+ stdout.printf ("-o, --output [DIRECTORY] " + _("write files to this directory\n"));+ stdout.printf ("-s, --svg " + _("write svg file\n"));+ stdout.printf ("-t, --ttf " + _("write ttf and eot files\n"));+ stdout.printf ("\n");+}++public static int run_export (string[] arg) {+ string output_directory = ".";+ string file_name = "";+ bool specific_formats = false; + bool write_ttf = false;+ bool write_svg = false;+ Supplement.Supplement supplement = new Supplement.Supplement ();+ + File directory;+ + if (arg.length < 2) {+ print_export_help (arg);+ }+ + for (int i = 1; i < arg.length; i++) {++ if (arg[i] == "-f" || arg[i] == "--fatal-warnings") {+ supplement.fatal_wanings = true;+ return 0;+ }++ if (arg[i] == "-h" || arg[i] == "--help") {+ print_export_help (arg);+ return 0;+ }+ + if ((arg[i] == "-o" || arg[i] == "--output") && i + 1 < arg.length) {+ output_directory = arg[i + 1];+ i++;+ continue;+ }++ if (arg[i] == "-s" || arg[i] == "--svg") {+ write_svg = true;+ specific_formats = true;+ continue;+ }+ + if (arg[i] == "-t" || arg[i] == "--ttf") {+ write_ttf = true;+ specific_formats = true;+ continue;+ }+ + if (arg[i].has_prefix ("-")) {+ print_export_help (arg);+ return 1;+ }+ + if (!arg[i].has_prefix ("-")) {+ file_name = arg[i];+ + if (i != arg.length - 1) {+ print_export_help (arg);+ return 1;+ }+ + break;+ }+ }++ if (supplement.fatal_wanings) {+ LogLevelFlags levels = LogLevelFlags.LEVEL_ERROR | LogLevelFlags.LEVEL_CRITICAL | LogLevelFlags.LEVEL_WARNING;+ Log.set_handler (null, levels, supplement.fatal_warning); + }++ Preferences preferences = new Preferences ();+ preferences.load ();+ + supplement.args = new Argument ("");+ supplement.current_font = new Font ();+ supplement.current_glyph = new Glyph ("");+ + if (!supplement.get_current_font ().load (file_name, false)) {+ stderr.printf (@"Failed to load font $file_name.\n");+ + if (!file_name.has_suffix (".ffi")) {+ stderr.printf (@"Is it a .ffi file?\n");+ }+ + return 1;+ }++ directory = File.new_for_path (output_directory);+ + if (!directory.query_exists ()) {+ stderr.printf (_("Can't find output directory") + @"$((!)directory.get_path ())\n");+ return 1;+ }++ if (!specific_formats || write_svg) {+ print (_("Writing") + @" $(supplement.current_font.get_name ()).svg to $output_directory\n");+ ExportTool.export_svg_font_path (File.new_for_path (output_directory));+ }++ if (!specific_formats || write_ttf) {+ print (_("Writing") + @" $(supplement.current_font.get_name ()).ttf to $output_directory\n");+ ExportTool.export_ttf_font_path (File.new_for_path (output_directory), false);+ }+ + return 0;+}++public static string wine_to_unix_path (string exec_path) {+ bool drive_c, drive_z;+ int i;+ string p, q;++ p = exec_path;+ p = p.replace ("\\", "/");+ + drive_c = exec_path.index_of ("C:") == 0;+ drive_z = exec_path.index_of ("Z:") == 0;+ + i = p.index_of (":");+ + if (i != -1) {+ p = p.substring (i + 2);+ }++ if (drive_c) {+ q = @"/home/$(Environment.get_user_name ())/.wine/drive_c/" + p;+ + if (File.new_for_path (q).query_exists ()) {+ return q;+ } else {+ return p;+ }+ }+ + if (drive_z) {+ return ("/" + p).dup ();+ }++ return exec_path.dup ();+}++}new file mode 100644@@ -0,0 +1,62 @@+/*+ 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/>.+*/++namespace Supplement {++class CharDatabase {++ public CharDatabase () {+ }++ public static bool has_ascender (unichar c) {+ if (!c.islower()) return true;+ + // todo: their derrivatives+ switch (c) {+ case 'b': return true;+ case 'd': return true;+ case 'f': return true;+ case 'h': return true;+ case 'i': return true;+ case 'j': return true;+ case 'k': return true;+ case 'l': return true; + }+ + if ('à' <= c <= 'å') return true;+ if ('è' <= c <= 'ö') return true;+ if ('ù' <= c <= 'ă') return true;+ if ('ć' <= c <= 'ė') return true;++ return false;+ }++ public static bool has_descender (unichar c) {+ // todo: their derrivatives+ switch (c) {+ case 'g': return true;+ case 'j': return true;+ case 'p': return true;+ case 'q': return true;+ }+ + return false; + }+ +}++}new file mode 100644@@ -0,0 +1,102 @@+/*+ 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/>.+*/++namespace Supplement {++public class ClipTool : Tool {++ static Glyph? glyph = null; + static List<Path> path = new List<Path> ();++ public static void copy () {+ Font font = Supplement.get_current_font ();+ OverView overview = MainWindow.get_overview ();+ FontDisplay fd = MainWindow.get_current_display ();+ string gc;+ Glyph g = MainWindow.get_current_glyph ();++ glyph = null;++ while (path.length () > 0) {+ path.remove_link (path.first ());+ }+ + if (fd is OverView) {+ gc = overview.get_selected_char ();+ glyph = font.get_glyph (gc);+ } + + if (fd is Glyph) {+ foreach (Path p in g.active_paths) {+ path.append (p.copy ());+ }++ if (path.length () == 0) {+ glyph = g.copy ();+ }+ }++ }++ public static void paste () {+ Font font = Supplement.get_current_font ();+ OverView overview = MainWindow.get_overview ();+ FontDisplay fd = MainWindow.get_current_display ();+ string gc;+ Glyph? destination = null;+ unichar new_char;+ Path inserted;+ + if (fd is OverView) {+ gc = overview.get_selected_char ();+ destination = font.get_glyph (gc);+ + if (destination == null) {+ new_char = gc.get_char (0);+ destination = new Glyph (gc, new_char) ;+ font.add_glyph ((!) destination);+ }+ }+ + if (fd is Glyph) {+ destination = (Glyph) fd;++ if (destination == glyph) {+ return;+ }+ + ((!)destination).store_undo_state ();+ + if (glyph != null) {+ foreach (Path p in ((!)glyph).path_list) {+ inserted = p.copy ();+ ((!)destination).add_path (inserted);+ }+ }++ foreach (Path p in path) {+ inserted = p.copy ();+ ((!)destination).add_path (inserted);+ }+ + ((!)destination).update_view ();+ }+ }++}++}new file mode 100644@@ -0,0 +1,335 @@+/*+ 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 {++/** Kerning context. */+public class ContextDisplay : FontDisplay {++ List <Word> row;+ int active_handle = -1;+ int selected_handle = -1;+ + double begin_handle_x = 0;+ double begin_handle_y = 0;+ + double last_handle_x = 0;+ + public ContextDisplay () {+ Word w = new Word ();+ row = new List <Word> ();+ row.append (w);+ }++ public override string get_name () {+ return "Kerning";+ }+ + public override void draw (Allocation allocation, Context cr) {+ Glyph? g;+ Glyph glyph;+ double x, y, w, kern, alpha;+ double x2;+ int i, wi;+ string prev;+ + i = 0;+ + // bg color+ cr.save ();+ cr.set_source_rgba (1, 1, 1, 1);+ cr.rectangle (0, 0, allocation.width, allocation.height);+ cr.fill ();+ cr.restore ();+ + alpha = 1;+ y = 100;+ x = 20;+ w = 0;+ prev = "";+ + foreach (Word word in row) {+ wi = 0;+ + foreach (string s in word.glyph) { + g = (!) Supplement.get_current_font ().get_glyph (s); + + if (s == "") {+ continue;+ }+ + kern = (prev != "") ? get_kerning (prev, s) : 0;+ + // draw glyph + if (g == null) {+ w = 50;+ alpha = 1;+ } else {+ alpha = 0;+ glyph = (!) g;+ Svg.draw_svg_path (cr, glyph.get_svg_data (), x + kern, y);+ w = glyph.get_width ();+ }++ // handle+ if (active_handle == i) {+ //x2 = x + kern;+ x2 = x + kern / 2.0;+ + cr.save ();+ cr.set_source_rgba (153/255.0, 153/255.0, 173/255.0, 1);+ cr.move_to (x2 - 5, y + 20);+ cr.line_to (x2 + 0, y + 20 - 5);+ cr.line_to (x2 + 5, y + 20);+ cr.fill ();+ + cr.set_font_size (10);+ cr.show_text (s);+ cr.restore ();+ } + + x += w + kern;+ + // caption+ if (g == null || ((!)g).is_empty ()) {+ cr.save ();+ cr.set_source_rgba (153/255.0, 153/255.0, 153/255.0, alpha);+ cr.move_to (x - w / 2.0 - 5, y + 20);+ cr.set_font_size (10);+ cr.show_text (s);+ cr.restore ();+ }+ + prev = s;+ + wi++;+ i++;+ }+ + if (y > allocation.height && i > 10) {+ row.remove (word);+ }+ + y += MainWindow.get_current_glyph ().get_height () + 20;+ x = 20; + }+ }+ + private void set_kerning (int handle, double val) {+ string a, b;+ Font font;+ int wi = 0;+ double kern;+ + font = Supplement.get_current_font ();++ font.touch ();++ a = "";+ b = "";++ foreach (Word word in row) {+ foreach (string s in word.glyph) {+ + if (s == "") {+ continue;+ }+ + b = s;+ + if (handle == wi) {+ kern = font.get_kerning (a, b) + val;+ font.set_kerning (a, b, kern);+ }+ + wi++;+ + a = b;+ }+ }+ + + }+ + private double get_kerning (string a, string b) {+ Font font = Supplement.get_current_font ();+ return font.get_kerning (a, b);+ }+ + public override void selected_canvas () {+ Glyph g;+ Word w;+ unowned List<Word>? r;+ unowned List<string>? rw;+ StringBuilder s = new StringBuilder ();+ bool append_char = false;+ + KeyBindings.singleton.set_require_modifier (true);+ + g = MainWindow.get_current_glyph ();+ s.append_unichar (g.get_unichar ());++ r = row;+ return_if_fail (r != null);++ if (row.length () == 0) {+ append_char = true;+ }+ + if (row.length () > 0) {+ rw = row.first ().data.glyph;+ return_if_fail (rw != null);+ append_char = (row.first ().data.glyph.data.index_of (s.str) == -1);+ }+ + if (append_char) {+ w = new Word ();+ row.append (w);+ w.glyph.prepend (s.str);+ } + }+ + public override void key_press (uint keyval) {+ unichar c = (unichar) keyval;+ Font f = Supplement.get_current_font ();+ string name;+ + if (KeyBindings.singleton.modifier == NONE || KeyBindings.singleton.modifier == SHIFT) {+ + if (keyval == Key.BACK_SPACE && row.length () > 0) { + row.first ().data.glyph.remove_link (row.first ().data.glyph.last ());+ }+ + if (row.length () == 0 || c == Key.ENTER) {+ row.prepend (new Word ());+ }+ + if (!is_modifier_key (c) && c.validate ()) {+ name = f.get_name_for_character (c);+ row.first ().data.glyph.append (name);+ }+ }+ + MainWindow.get_glyph_canvas ().redraw ();+ }+ + public override void motion_notify (double ex, double ey) {+ double k, y;+ + if (selected_handle == -1) {+ set_active_handle (ex, ey);+ } else {+ y = 1;+ + if (Math.fabs (ey - begin_handle_y) > 20) {+ y = ((Math.fabs (ey - begin_handle_y) / 100) + 1);+ }+ + k = (ex - last_handle_x) / y; // y-axis is variable precision+ set_kerning (selected_handle, k);+ MainWindow.get_glyph_canvas ().redraw ();+ }+ + last_handle_x = ex;+ }+ + public void set_active_handle (double ex, double ey) {+ double y = 100;+ double x = 20;+ double w = 0;+ double d, kern;+ double min = double.MAX;+ int i = 0;+ int row_index = 0;+ int col_index = 0;+ Glyph? g;+ Glyph glyph = new Glyph.no_lines ("");+ + string prev = "";+ string gl_name = "";+ + foreach (Word word in row) {+ col_index = 0;+ foreach (string s in word.glyph) {+ + // draw glyph+ g = (!) Supplement.get_current_font ().get_glyph (s); + kern = (prev != "") ? get_kerning (prev, s) : 0;+ + if (g == null) {+ w = 50; + } else {+ glyph = (!) g;+ w = glyph.get_width ();+ + }+ + d = Math.pow (x + kern - ex, 2) + Math.pow (y - ey, 2);+ + if (d < min) {+ min = d;+ gl_name = glyph.get_name ();+ + if (active_handle != i - row_index) {+ active_handle = i - row_index;+ MainWindow.get_glyph_canvas ().redraw ();+ }++ if (col_index == word.glyph.length () || col_index == 0) {+ active_handle = -1;+ }+ }+ + prev = s;+ x += w + kern;+ i++;+ col_index++;+ }+ + row_index++;+ y += MainWindow.get_current_glyph ().get_height () + 20;+ x = 20;+ }+ }+ + public override void button_release (int button, double ex, double ey) {+ set_active_handle (ex, ey);+ selected_handle = -1;+ }+ + public override void button_press (uint button, double ex, double ey) {+ set_active_handle (ex, ey);+ selected_handle = active_handle;+ begin_handle_x = ex;+ begin_handle_y = ey;+ last_handle_x = ex;+ }++}++class Word {+ public List <string> glyph;+ + public Word () {+ glyph = new List <string> ();+ glyph.append ("");+ }+}++}new file mode 100644@@ -0,0 +1,246 @@+/*+ 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;+using Math;++namespace Supplement {++class CutBackgroundTool : Tool {++ double x1 = 0;+ double y1 = 0;+ double x2 = 0;+ double y2 = 0;+ + bool is_visible = false;+ bool is_set = false;+ bool is_done = false;+ + public signal void new_image (GlyphBackgroundImage file);+ + public CutBackgroundTool (string name) {+ base (name, _("Crop background image"));++ select_action.connect((self) => {+ });++ deselect_action.connect ((self) => {+ });+ + press_action.connect((self, b, x, y) => { + if (!is_over_rectangle (x, y) || !is_visible) {+ x1 = x;+ y1 = y;+ + x2 = x;+ y2 = y;+ + is_visible = true;+ is_set = false;+ is_done = false;+ }+ + if (is_set && is_visible && is_over_rectangle (x, y)) {+ do_cut ();+ is_set = false;+ is_visible = false;+ is_done = true;+ + x1 = 0;+ y1 = 0;+ x2 = 0;+ y2 = 0;+ }+ });++ release_action.connect((self, b, x, y) => {+ if (!is_set && !is_done) {+ x2 = x;+ y2 = y;+ is_set = true;+ }+ + is_done = false;+ });++ move_action.connect((self, x, y) => {+ if (is_visible && !is_set) {+ x2 = x;+ y2 = y;+ + MainWindow.get_glyph_canvas ().redraw ();+ }+ });+ + draw_action.connect ((self, cr, glyph) => {+ if (is_visible) {+ cr.save ();+ cr.set_line_width (2.0);+ cr.set_source_rgba (0, 0, 1, 0.3);+ cr.rectangle (fmin (x1, x2), fmin (y1, y2), fabs (x1 - x2), fabs (y1 - y2));+ cr.stroke ();+ cr.restore ();+ }+ });++ new_image.connect ((file) => {+ Glyph glyph = MainWindow.get_current_glyph ();+ TabBar tb = MainWindow.get_tab_bar ();+ + glyph.store_undo_state ();+ + glyph.set_background_image (file);+ tb.select_tab_name (glyph.get_name ());+ + glyph.set_background_visible (true);+ });++ }++ bool is_over_rectangle (double x, double y) {+ return fmin (x1, x2) + 1 < x < fmax (x1, x2) - 1 && fmin (y1, y2) + 1 < y < fmax (y1, y2) - 1;+ }++ double get_width () {+ return fabs (x1 - x2);+ }++ double get_height () {+ return fabs (y1 - y2);+ }++ void do_cut () {+ double x, y;+ int h, w;+ double wc, hc;+ + Glyph g = MainWindow.get_current_glyph ();+ GlyphBackgroundImage? b = g.get_background_image ();+ GlyphBackgroundImage bg = (!) b;+ + ImageSurface img;+ + Surface sr;+ Context cr;+ + double tx, ty, vx, vy; + if (b == null) {+ return;+ }++ // Add margin+ Surface sg = new Surface.similar (bg.get_img (), bg.get_img ().get_content (), bg.size_margin, bg.size_margin);+ Context cg = new Context (sg);+ + wc = bg.get_margin_width ();+ hc = bg.get_margin_height ();+ + cg.set_source_rgba (1, 1, 1, 1);+ cg.rectangle (0, 0, bg.size_margin, bg.size_margin);+ cg.fill ();+ + cg.set_source_surface (bg.get_img (), wc, hc);+ cg.paint ();++ // find start+ tx = bg.img_offset_x - g.view_offset_x;+ ty = bg.img_offset_y - g.view_offset_y;+ + ty *= g.view_zoom;+ tx *= g.view_zoom;++ vx = g.path_coordinate_x (tx) - g.path_coordinate_x (fmin (x1, x2)); + vy = g.path_coordinate_y (ty) - g.path_coordinate_y (fmin (y1, y2));+ + x = (int) (vx / bg.img_scale_x);+ y = (int) (-vy / bg.img_scale_y);++ // do the cut+ img = bg.get_img ();++ w = (int) (get_width () / g.view_zoom);+ h = (int) (get_height () / g.view_zoom);++ sr = new Surface.similar (sg, img.get_content (), w, h);+ cr = new Context (sr);++ cr.set_source_rgba (1, 1, 1, 1);+ cr.rectangle (0, 0, w, h);+ cr.fill ();+ + cr.scale (bg.img_scale_x, bg.img_scale_y);+ + cr.set_source_surface (sg, x, y);+ cr.paint ();+ + save_img (sr, g);+ + cr.restore ();+ }++ void save_img (Surface sr, Glyph g) {+ GlyphBackgroundImage newbg;+ Font f = Supplement.get_current_font ();+ File img_dir;+ File img_file;+ File img_file_next;+ string fn;+ double wc, hc;+ + img_dir = f.get_backgrounds_folder ().get_child ("parts");++ if (!img_dir.query_exists ()) {+ DirUtils.create ((!) img_dir.get_path (), 0xFFFFFF);+ }+ + img_file = img_dir.get_child (@"NEW_BACKGROUND.png");+ fn = (!) img_file.get_path ();+ + sr.write_to_png (fn);+ + newbg = new GlyphBackgroundImage (fn);+ + fn = newbg.get_sha1 () + ".png";++ img_file_next = img_dir.get_child (fn);+ + try {+ if (img_file_next.query_exists ()) {+ img_file_next.delete ();+ }++ img_file.set_display_name (fn);+ } catch (GLib.Error e) {+ warning (e.message);+ return;+ }+ + newbg = new GlyphBackgroundImage ((!) f.get_backgrounds_folder ().get_child ("parts").get_child (fn).get_path ());+ + // set position for the new background+ wc = newbg.get_margin_width ();+ hc = newbg.get_margin_height ();+ + newbg.img_x = g.path_coordinate_x (fmin (x1, x2)) - wc - 0.5;+ newbg.img_y = g.path_coordinate_y (fmin (y1, y2)) + hc + 0.5;++ new_image (newbg);+ }+}++}new file mode 100644@@ -0,0 +1,61 @@+/*+ 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/>.+*/++namespace Supplement {++class CutTool : Tool {++ PenTool pen;+ + public CutTool (string n) {+ base (n, "Create counter path", 'u', CTRL);+ + select_action.connect((self) => {+ pen = (PenTool) MainWindow.get_tool ("pen_tool");+ });+ + press_action.connect((self, b, x, y) => {+ pen.press_action (pen, b, x, y);+ });++ release_action.connect((self, b, x, y) => {+ pen.release_action (pen, b, x, y);+ });+ + move_action.connect ((self, x, y) => {+ pen.move_action (pen, x, y);+ });+ }++ public static void force_direction () {+ Glyph g = MainWindow.get_current_glyph ();+ Tool current_tool = MainWindow.get_toolbox ().get_current_tool ();+ + foreach (Path p in g.active_paths) {+ if (p.is_open () && !p.has_direction ()) {+ if (current_tool is PenTool) {+ p.force_direction (Direction.CLOCKWISE);+ } else if (current_tool is CutTool) {+ p.force_direction (Direction.COUNTER_CLOCKWISE);+ } + }+ }+ }+ +}++}new file mode 100644@@ -0,0 +1,201 @@+/*+ 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 {++public enum MenuDirection {+ DROP_DOWN,+ POP_UP;+}++public class DropMenu : GLib.Object {++ public delegate void Selected (MenuAction self);+ public signal void selected (DropMenu self);+ + double x = -1;+ double y = -1;+ bool menu_visible = false;++ List <MenuAction> actions = new List <MenuAction> ();++ const int item_height = 25;+ + MenuDirection direction = MenuDirection.DROP_DOWN;+ + ImageSurface? icon = null;+ + public DropMenu (string icon_file = "") {+ if (icon_file != "") {+ icon = Icons.get_icon (@"$icon_file.png");+ }+ }+ + public void deselect_all () {+ foreach (MenuAction m in actions) {+ m.set_selected (false);+ }+ }+ + public void set_direction (MenuDirection d) {+ direction = d;+ }+ + public void close () {+ menu_visible = false;+ }+ + public MenuAction add_item (string label) {+ MenuAction m = new MenuAction (label);+ add_menu_item (m);+ return m;+ }+ + public void add_menu_item (MenuAction m) {+ m.parent = this;+ actions.append (m);+ }+ + public bool is_over_icon (double px, double py) {+ if (x == -1 || y == -1) {+ return false;+ }+ + return x - 5 < px < x + 12 + 5 && y - 5 < py < y + 12 + 5;+ }++ public bool menu_item_action (double px, double py) {+ MenuAction? action;++ if (menu_visible) {+ action = get_menu_action_at (px, py);+ + if (action != null) {+ ((!) action).action ((!)action);+ selected (this);+ menu_visible = false;+ return true;+ }+ }+ + return false;+ }+ + public bool menu_icon_action (double px, double py) { + menu_visible = is_over_icon (px, py);+ return menu_visible;+ }+ + MenuAction? get_menu_action_at (double px, double py) {+ double n = 0;+ double ix, iy;+ + foreach (MenuAction item in actions) {+ ix = x - 100 + 12;+ + if (direction == MenuDirection.DROP_DOWN) {+ iy = y + 12 + n * item_height;+ } else {+ iy = y - 24 - n * item_height;+ }+ + if (ix <= px <= ix + 100 && iy <= py <= iy + item_height) {+ return item;+ }+ + n++; + }++ return null;+ }+ + public void set_position (double px, double py) {+ x = px;+ y = py;+ }+ + public void draw_menu (Context cr) {+ double ix, iy;+ int n;+ + if (likely (!menu_visible)) {+ return;+ }+ + cr.save ();+ cr.set_source_rgba (122/255.0, 150/255.0, 169/255.0, 1);+ cr.set_line_join (LineJoin.ROUND);+ cr.set_line_width (12);+ + if (direction == MenuDirection.DROP_DOWN)+ cr.rectangle (x - 100 + 18, y + 18, 88, actions.length () * item_height - 12);+ else+ cr.rectangle (x - 100 + 18, y + 6 - actions.length () * item_height, 88, actions.length () * item_height - 12);+ + cr.fill_preserve ();+ cr.stroke ();+ cr.restore ();+ + cr.save ();+ + n = 0;+ foreach (MenuAction item in actions) {+ + if (direction == MenuDirection.DROP_DOWN)+ iy = y + 33 + n * item_height - 5;+ else + iy = y - 8 - n * item_height;+ + ix = x - 100 + 20;+ + item.draw (ix, iy, cr);+ n++;+ }+ + cr.restore ();+ }+ + public void draw_icon (Context cr) {+ double alpha = 1;+ ImageSurface i = (!) icon;+ + if (!menu_visible) {+ alpha = 0;+ }+ + cr.save ();+ + cr.set_source_rgba (122/255.0, 150/255.0, 169/255.0, alpha);+ + cr.rectangle (x, y, 12, 12);+ cr.fill_preserve ();+ cr.stroke ();+ + if (likely (icon != null && i.status () == Cairo.Status.SUCCESS)) {+ cr.set_source_surface (i, x, y + (12 - i.get_height ()) / 2.0);+ cr.paint ();+ } else {+ warning ("Failed to load icon.");+ }+ + cr.restore ();+ }+}++}new file mode 100644@@ -0,0 +1,343 @@+/*+ 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 Math;++namespace Supplement {++public enum PointType {+ NONE,+ NORMAL,+ LINE,+ CURVE,+ DOUBLE_CURVE,+ END,+ FLOATING+}++public class EditPoint {+ + public double x;+ public double y;+ public PointType type;+ + public double r = 1;+ public double g = 0;+ public double b = 0;+ public double a = 1;+ + public unowned List<EditPoint>? prev = null;+ public unowned List<EditPoint>? next = null;++ public bool active = false;+ public bool selected = false;+ + public int selected_handle = 0;+ + public EditPointHandle right_handle;+ public EditPointHandle left_handle;+ + public bool tie_handles = false;+ + public EditPoint (double nx = 0, double ny = 0, PointType nt = PointType.LINE) {+ x = nx;+ y = ny;+ type = nt;+ active = false;+ + set_active (true);+ + if (nt == PointType.FLOATING) {+ r = 1;+ g = 1;+ b = 0;+ a = 1;+ active = false;+ }+ + right_handle = new EditPointHandle (this, 0, 7);+ left_handle = new EditPointHandle (this, PI, 7);++ if (unlikely (nx.is_nan () || ny.is_nan ())) {+ warning (@"Invalid point at ($nx,$ny).");+ x = 0;+ y = 0;+ }+ }++ /** Flip handles if next point on path is in the other direction. + * Used to recalculate handles after new point is inserted on a path.+ */+ public void recalculate_handles (double px, double py) {+ double dr, dl;+ EditPointHandle t;+ + if (next == null || ((!)next).length () < 2) {+ return;+ }+ + px = get_next ().nth (1).data.x;+ py = get_next ().nth (1).data.y;+ + dr = Math.sqrt (Math.pow (px - right_handle.x (), 2) + Math.pow (py - right_handle.y (), 2));+ dl = Math.sqrt (Math.pow (px - left_handle.x (), 2) + Math.pow (py - left_handle.y (), 2));++ // flip handles+ if (dl < dr) {+ t = right_handle;+ right_handle = left_handle;+ left_handle = t;+ } + }+ + /** Set bezier points for linear paths. */+ public void recalculate_linear_handles () {+ unowned EditPointHandle h;+ unowned EditPoint n;+ double nx, ny;++ if (prev == null && next != null) {+ prev = get_next ().last ();+ }++ // left handle+ if (prev != null) {+ n = get_prev ().data;+ h = get_left_handle ();+ + if (h.type == PointType.LINE) {+ nx = x + ((n.x - x) / 3);+ ny = y + ((n.y - y) / 3);+ + h.move_to_coordinate (nx, ny);+ }++ h = n.get_right_handle ();+ + // on the other side+ h = n.get_right_handle ();+ + if (h.type == PointType.LINE) {+ nx = n.x + ((x - n.x) / 3);+ ny = n.y + ((y - n.y) / 3);+ + h.move_to_coordinate (nx, ny);+ }+ }++ // right handle+ if (next != null) {+ n = get_next ().data;+ h = get_right_handle ();+ + if (h.type == PointType.LINE) {+ nx = x + ((n.x - x) / 3);+ ny = y + ((n.y - y) / 3);+ + h.move_to_coordinate (nx, ny);+ }++ h = n.get_left_handle ();+ + if (h.type == PointType.LINE) {+ nx = n.x + ((x - n.x) / 3);+ ny = n.y + ((y - n.y) / 3);++ h.move_to_coordinate (nx, ny);+ }+ }+ }+ + public void set_tie_handle (bool t) {+ tie_handles = t;+ }++ public void process_tied_handle () {+ double a, b, c, length, angle;+ EditPointHandle eh;+ + eh = right_handle;+ + a = left_handle.x () - right_handle.x ();+ b = left_handle.y () - right_handle.y ();+ c = a * a + b * b;+ + if (c == 0) {+ return;+ }+ + length = sqrt (fabs (c));+ + if (right_handle.y () < left_handle.y ()) {+ angle = acos (a / length) + PI;+ } else {+ angle = -acos (a / length) + PI;+ }+ + left_handle.type = PointType.CURVE;+ right_handle.type = PointType.CURVE;+ + right_handle.angle = angle;+ left_handle.angle = angle;++ set_tie_handle (true);+ eh.move_to_coordinate (right_handle.x (), right_handle.y ());+ }++ public EditPoint copy () {+ EditPoint new_point = new EditPoint ();+ + new_point.x = x;+ new_point.y = y;+ + new_point.type = type;+ new_point.set_color (r, g, b, a);+ + new_point.tie_handles = tie_handles;+ + new_point.right_handle.angle = right_handle.angle;+ new_point.right_handle.length = right_handle.length;+ new_point.right_handle.type = right_handle.type;++ new_point.left_handle.angle = left_handle.angle;+ new_point.left_handle.length = left_handle.length; + new_point.left_handle.type = left_handle.type;++ return new_point;+ }++ public double get_distance (double x, double y) {+ return Path.distance (this.x, x, this.y, y);+ }++ public unowned EditPointHandle get_left_handle () {+ return left_handle;+ }+ + public unowned EditPointHandle get_right_handle () {+ return right_handle;+ }+ + public void set_color (double r, double g, double b, double a) {+ this.r = r;+ this.g = g;+ this.b = b;+ this.a = a;+ }+ + public unowned List<EditPoint> get_prev () {+ if (prev == null && next != null && get_next ().prev == get_next ().first ()) {+ prev = get_next ().last ();+ }+ + if (unlikely (prev == null)) {+ warning ("EditPoint.prev is null");+ }+ + return (!) prev;+ }++ public unowned List<EditPoint> get_next () {+ if (unlikely (next == null)) {+ warning ("EditPoint.next is null");+ }+ + return (!) next;+ }+ + public unowned List<EditPoint> get_list () {+ return get_next ().first ();+ }+ + public void set_position (double tx, double ty) {+ x = tx;+ y = ty;++ if (unlikely (tx.is_nan () || ty.is_nan ())) {+ warning (@"Invalid point at ($tx,$ty).");+ x = 0;+ y = 0;+ }+ }+ + public static void to_coordinate (ref double x, ref double y) {+ double xc, yc, xt, yt, ivz;+ Glyph g = MainWindow.get_current_glyph ();+ + ivz = 1 / g.view_zoom;+ + xc = (g.allocation.width / 2.0);+ yc = (g.allocation.height / 2.0);+ + x *= ivz;+ y *= ivz;++ xt = x - xc + g.view_offset_x;+ yt = yc - y - g.view_offset_y; + + x = xt;+ y = yt;+ }+ + public bool is_selected () {+ return selected;+ }+ + public void set_selected (bool s) {+ selected = s;+ }+ + public bool set_active (bool active) {+ bool update = (this.active != active);+ + if (update) {+ this.active = active;+ + if (active) {+ r = 0.5;+ g = 0;+ b = 1;+ a = 1;+ } else {+ r = 0;+ g = 1;+ b = 0.5;+ a = 1;+ }+ }+ + return update;+ }+ + public int get_index () {+ int i = -1;+ + if (next == null) return i;+ + foreach (var d in get_next ().first ()) {+ i++;+ + if (this == d) {+ return i;+ }+ }+ + return i;+ }+}++}new file mode 100644@@ -0,0 +1,167 @@+/*+ 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 Math;++namespace Supplement {+ +public class EditPointHandle {+ + public double angle;+ public double length;+ + public EditPoint parent;+ + public PointType type;+ + EditPoint? visual_handle = null;++ static EditPoint none = new EditPoint ();++ public bool active;++ public EditPointHandle.empty() {+ this.parent = none;+ this.angle = 0;+ this.length = 10;+ this.type = PointType.LINE;+ this.active = false;+ }+ + public EditPointHandle (EditPoint parent, double angle, double length) {+ this.parent = parent;+ this.angle = angle;+ this.length = length;+ this.type = PointType.LINE;+ this.active = false;+ }++ public EditPointHandle copy () {+ EditPointHandle n = new EditPointHandle.empty ();+ n.angle = angle;+ n.length = length;+ n.parent = parent;+ n.type = type;+ n.active = active;+ return n;+ }++ public void set_point_type (PointType point_type) {+ type = point_type;+ }++ public double x () {+ double r = px ();+ + if (unlikely (r <= -100000)) {+ print_position ();+ move_to (0, 0);+ }+ + return r;+ }+ + public double y () {+ double r = py ();+ + if (unlikely (r <= -100000)) {+ print_position ();+ move_to (0, 0);+ }+ + return r;+ }+ + double px () {+ assert ((EditPoint?) parent != null);+ return cos (angle) * length + parent.x;+ }++ double py () {+ assert ((EditPoint?) parent != null);+ return sin (angle) * length + parent.y;+ }+ + void print_position () {+ warning (@"\nEdit point handle at position $(px ()),$(py ()) is not valid.\n"+ + @"Type: $(parent.type), "+ + @"Index: $(parent.get_index ()) of $(parent.get_list ().length ())\n"+ + @"Angle: $angle, Length: $length."); + }+ + public EditPoint get_point () {+ EditPoint p;+ + if (visual_handle == null) {+ visual_handle = new EditPoint (0, 0);+ }+ + p = (!) visual_handle;+ p.x = x ();+ p.y = y ();+ + return p;+ }+ + public void move_to_coordinate (double x, double y) {+ double a, b, c;++ a = parent.x - x;+ b = parent.y - y;+ c = a * a + b * b;+ + if (c == 0) {+ return;+ }+ + length = sqrt (fabs (c)); + + if (c < 0) length = -length;+ + if (y < parent.y) {+ angle = acos (a / length) + PI;+ } else {+ angle = -acos (a / length) + PI;+ }+ + if (parent.tie_handles) {+ tie_handle ();+ }+ }++ private void tie_handle () {+ if (this == parent.get_left_handle ()) {+ parent.get_right_handle ().angle = angle - PI;+ } else {+ parent.get_left_handle ().angle = angle - PI;+ }+ }+ + public void move_delta (double dx, double dy) {+ double px = px () + dx * Glyph.ivz ();+ double py = py () - dy * Glyph.ivz ();+ + move_to_coordinate (px, py);+ }+ + public void move_to (double x, double y) {+ EditPoint.to_coordinate (ref x, ref y);+ move_to_coordinate (x, y);+ }+}++}new file mode 100644@@ -0,0 +1,174 @@+/*+ 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 Math;++namespace Supplement {++class EotWriter : GLib.Object {+ + string ttf_file_name;+ string eot_file_name;+ + public EotWriter (string ttf_file, string eot_file) {+ ttf_file_name = ttf_file;+ eot_file_name = eot_file;+ }+ + public void write () throws Error {+ FontData fd = new FontData ();+ FontData ttf_data = new FontData ();+ OpenFontFormatReader input; + uint32 ttf_length;+ File ttf_file;+ File eot_file;+ FileInfo file_info;+ OtfInputStream dis;+ DataOutputStream os;+ uint8* data;+ uint32 l;+ NameTable names;+ string tn;+ + ttf_file = File.new_for_path (ttf_file_name);+ + if (!ttf_file.query_exists ()) {+ warning (@"EotWriter: file does not exist. $((!) ttf_file.get_path ())");+ return;+ }++ eot_file = File.new_for_path (eot_file_name);+ + if (eot_file.query_exists ()) {+ warning ("File exists in eot export.");+ return;+ }+ + os = new DataOutputStream(eot_file.create (FileCreateFlags.REPLACE_DESTINATION));++ file_info = ttf_file.query_info ("*", FileQueryInfoFlags.NONE);+ ttf_length = (uint32) file_info.get_size ();+ + if (ttf_length == 0) {+ warning ("TTF file is of zero length.");+ return;+ }++ dis = new OtfInputStream (ttf_file.read ());+ ttf_data.write_table (dis, 0, ttf_length);++ // parse file to find head checksum+ input = new OpenFontFormatReader ();+ input.parse_index (ttf_file_name);+ + names = input.directory_table.name_table;+ + fd.add_littleendian_u32 (0); // table length+ fd.add_littleendian_u32 (ttf_length);+ + fd.add_littleendian_u32 (0x00020001); // table format version+ fd.add_littleendian_u32 (0); // flags+ + // panose+ for (int i = 0; i < 10; i++) {+ fd.add (0);+ }+ + // according to specification should charset and italic be added the + // other way around, but it does not work+ + fd.add (1); // default charset+ fd.add (0); // italic set to regular+ + fd.add_littleendian_u32 (400); // weight+ fd.add_littleendian_u16 (0); // 0 drm, designer must know that notified+ fd.add_littleendian_u16 (0x504C); // magic number++ // FIXA:+ // unicode ranges + fd.add_littleendian_u32 (0);+ fd.add_littleendian_u32 (0);+ fd.add_littleendian_u32 (0);+ fd.add_littleendian_u32 (0);+ + // FIXA:+ // code page range+ fd.add_littleendian_u32 (0);+ fd.add_littleendian_u32 (0);+ + fd.add_littleendian_u32 (input.get_head_checksum ()); // head checksum adjustment+ + // reserved+ fd.add_littleendian_u32 (0);+ fd.add_littleendian_u32 (0);+ fd.add_littleendian_u32 (0);+ fd.add_littleendian_u32 (0);+ + fd.add_littleendian_u16 (0); // padding+ + tn = names.get_name (names.FONT_NAME);+ fd.add_littleendian_u16 ((uint16) (2 * tn.char_count ())); // strlen of family name+ fd.add_str_littleendian_utf16 (tn);+ + fd.add_littleendian_u16 (0); // padding+ + tn = names.get_name (names.SUBFAMILY_NAME);+ fd.add_littleendian_u16 ((uint16) (2 * tn.char_count ())); // strlen of family name+ fd.add_str_littleendian_utf16 (tn);++ fd.add_littleendian_u16 (0); // padding+ + // FIXA version or name + version+ tn = names.get_name (names.VERSION);+ fd.add_littleendian_u16 ((uint16) (2 * tn.char_count ())); // strlen of family name+ fd.add_str_littleendian_utf16 (tn);+ + fd.add_littleendian_u16 (0); // padding+ + tn = names.get_name (names.FULL_FONT_NAME);+ fd.add_littleendian_u16 ((uint16) (2 * tn.char_count ())); // strlen of family name+ fd.add_str_littleendian_utf16 (tn);+ + fd.add_littleendian_u16 (0); // padding+ + fd.add_littleendian_u16 (0); // length of root string+ // the root string goes here.+ + // update length of this table+ fd.seek (0);+ fd.add_littleendian_u32 (fd.length () + ttf_length);+ + l = fd.length_with_padding ();+ data = fd.table_data;+ for (int i = 0; i < l; i++) {+ os.put_byte (data[i]);+ }++ l = ttf_data.length_with_padding ();+ data = ttf_data.table_data;+ for (int i = 0; i < l; i++) {+ os.put_byte (data[i]);+ }+ + dis.close ();+ os.close ();+ input.close ();+ }+ +}+ +}new file mode 100644@@ -0,0 +1,225 @@+/*+ 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;+using Math;++namespace Supplement {++public class Expander : GLib.Object {+ + public double x = 7;+ public double y = 5;++ public double w = 6;+ public double h = 5;+ + public double margin = 0;+ + protected double rotation = 0;+ protected double opacity = 0.5;+ + protected bool active = false;+ protected bool open = false;++ public List<Tool> tool;++ bool persist = false;+ bool unique = false;+ + public Expander () {+ }++ /** Returns true if tools can be used with the current canvas after+ * they have been selectes and false if they are a commands to be executed.+ */+ public bool is_persistent () {+ return persist;+ }++ /** Returns true if all other tools in thid expander should be deselected + * when a tool is selected.+ */+ public bool is_unique () {+ return unique;+ }++ public void set_persistent (bool p) {+ persist = p;+ }++ public void set_unique (bool u) {+ unique = u;+ }+ + private void update_tool_position () {+ int i = 0;+ double xt = x + 10;+ double yt = y - 4;+ foreach (var t in tool) {+ t.set_properties (xt, yt, w, h);+ + if (i == 3) {+ yt += 31;+ i = 0;+ xt = x + 10;+ } else {+ i++;+ xt += w + 3 + 25;+ }+ }+ }+ + public void set_offset (double ty) {+ y = ty;+ + if (open) {+ update_tool_position ();+ }+ }+ + public void add_tool (Tool t) {+ tool.append (t);+ update_tool_position ();+ + t.select_action.connect ((selected) => {+ if (!selected.new_selection) {+ if (is_persistent ()) {+ selected.set_selected (true);+ } else {+ selected.set_selected (false);+ }+ }+ + MainWindow.get_toolbox ().redraw ((int) x, (int) y, (int) w + 300, (int) (h + margin));+ + if (is_unique ()) {+ foreach (var deselected in tool) {+ if (selected.get_id () != deselected.get_id ()) {+ deselected.set_selected (false);+ }+ }+ }++ if (!is_persistent ()) {+ var time = new TimeoutSource(200);+ time.set_callback(() => {+ selected.set_selected (false);+ MainWindow.get_toolbox ().redraw ((int) x, (int) y, (int) w + 300, (int) (h + margin));+ return false;+ });+ time.attach(null);+ }++ selected.new_selection = false;+ });+ }+ + public bool is_over (double xp, double yp) { + return (x - 7/2.0 <= xp <= x + w + 7/2.0 && y - 7/2.0<= yp <= y + w + 7/2.0); + }+ + public bool set_active (bool a) {+ bool r = (active != a);+ opacity = (a) ? 1 : 0;+ active = a;+ return r;+ }+ + public bool is_open () {+ return open;+ }+ + public virtual bool set_open (bool o) {+ bool r = (open != o);+ rotation = (o) ? Math.PI_2 : 0;+ + if (o) {+ margin = 35 * (int)((tool.length () / 4.0) + 1) ;+ rotation = Math.PI_2; + if (tool.length () % 4 == 0) {+ margin -= 35;+ }+ } else {+ margin = 0;+ rotation = 0; + }+ + open = o;+ return r;+ }+ + public void draw (int wd, int hd, Context cr) {+ double lx, ly;+ double ih2 = 5.4 / 2;+ double iw2 = 5.4 / 2;+ + // box+ cr.save ();+ cr.set_line_join (LineJoin.ROUND);+ cr.set_line_width(7);+ cr.set_source_rgba (176/255.0, 211/255.0, 230/255.0, opacity);+ cr.rectangle (x, y, w, h);+ cr.stroke ();+ cr.restore ();+ + // arrow+ cr.save ();+ + cr.translate (x + w/2, y + h/2);+ cr.rotate (rotation);++ cr.set_line_width (1);+ cr.set_source_rgba (0, 0, 0, opacity);++ cr.new_path ();+ cr.move_to (-iw2, -ih2);+ cr.line_to (iw2, 0); + cr.line_to (-iw2, +ih2);++ cr.close_path();+ cr.stroke ();+ cr.restore ();+ + // separator+ cr.save ();+ lx = x + w + 7;+ ly = y + ih2;+ if (lx < wd) {+ cr.set_line_width(1);+ cr.set_source_rgba (0, 0, 0, 0.2);+ cr.move_to (lx, ly);+ cr.line_to (wd - w - x + 4, ly); + cr.stroke ();+ }+ cr.restore ();++ }+ + public void draw_content (int w, int h, Context cr) {+ if (open) {+ cr.save ();+ foreach (var t in tool) {+ t.draw (cr);+ }+ cr.restore ();+ }+ }+ +}++}new file mode 100644@@ -0,0 +1,538 @@+/*+ 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/>.+*/++namespace Supplement {++public class ExportTool : Tool {+ private static string? prefered_browser = null;+ private static ExportThread export_thread;+ + private static unowned Thread<void*> export_thread_handle;++ private static bool stop_export_thread = false;++ public ExportTool (string n) {+ base (n, _("Export glyph to svg file"), 'e', CTRL);+ + select_action.connect((self) => {+ export_svg_font ();+ // Fixa: add export_glyph_to_svg (); as tool in toolbox+ });+ + press_action.connect((self, b, x, y) => {+ });++ release_action.connect((self, b, x, y) => {+ });+ + move_action.connect ((self, x, y) => {+ });+ }++ public static bool export_all () {+ Font font = Supplement.get_current_font ();+ bool f;+ + if (font.get_ttf_export ()) {+ f = export_ttf_font ();+ + if (!f) {+ warning ("Failed to export font");+ return false;+ }+ }+ + if (font.get_svg_export ()) {+ f = export_svg_font ();++ if (!f) {+ warning ("Failed to export font");+ return false;+ }+ }+ + return true;+ }++ public void export_glyph_to_svg () {+ Glyph glyph = MainWindow.get_current_glyph ();+ Font font = Supplement.get_current_font ();+ FontDisplay fd = MainWindow.get_current_display ();+ string glyph_svg;+ string svg_file = @"$(glyph.get_name ()).svg";+ File file = font.get_folder ().get_child (svg_file);+ DataOutputStream os;+ string name = glyph.get_name ();+ int pid = 0;+ + if (!(fd is Glyph)) {+ return;+ }+ + try {+ + if (file.query_exists ()) {+ file.delete ();+ }+ + os = new DataOutputStream (file.create(FileCreateFlags.REPLACE_DESTINATION)); + + _("Alphabet");+ + os.put_string (+"""<?xml version="1.0" standalone="no"?>+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >+<svg """);++ os.put_string (@"xmlns=\"http://www.w3.org/2000/svg\"\n");+ os.put_string (@"\twidth=\"$(glyph.get_width () / Glyph.SCALE)\"\n");+ os.put_string (@"\theight=\"$(glyph.get_height () / Glyph.SCALE)\"\n");+ os.put_string (@"\tid=\"glyph_$(name)\"\n");+ os.put_string (@"\tversion=\"1.1\">\n");+ + os.put_string (@"\n");+ + os.put_string (@"<g id=\"$(name)\">\n");+ + foreach (Path p in glyph.path_list) {+ + glyph_svg = Svg.to_svg_path (p, glyph);+ + os.put_string (@"<path ");+ os.put_string (@"style=\"fill:#000000;stroke-width:0px\" ");+ os.put_string (@"d=\"$(glyph_svg)\" id=\"path_$(name)$(pid++)\" />\n");+ + }+ + os.put_string ("""</g>+ +</svg>""");+ + } catch (Error e) {+ stderr.printf (@"Export \"$svg_file\" \n");+ critical (@"$(e.message)");+ status (_("Can't export") + @" $svg_file.");+ }+ + status (_("Wrote") + @" $svg_file.");+ }++ public static void generate_html_document (string html_file, Font font) {+ File file = File.new_for_path (html_file);+ DataOutputStream os;+ string name = font.get_name ();++ try {+ os = new DataOutputStream (file.create(FileCreateFlags.REPLACE_DESTINATION));++os.put_string (+"""<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">+<html>+<head>++ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">+ <title>Lorem ipsum</title>+ + <style type="text/css">++ @font-face {+ font-family: '"""); os.put_string (@"$(name)"); os.put_string ("""SVG';+ src: url('"""); os.put_string (@"$(name).svg#$(name)"); os.put_string ("""') format('svg');+ }+ """);++ os.put_string ("""+ @font-face {+ font-family: '"""); os.put_string (@"$(name)"); os.put_string ("""TTF';+ src: url('"""); os.put_string (@"$(name).ttf"); os.put_string ("""') format('truetype');+ } + """);++ os.put_string ("""+ @font-face {+ font-family: '"""); os.put_string (@"$(name)"); os.put_string ("""EOT';+ src: url('"""); os.put_string (@"$(name).eot"); os.put_string ("""');+ } + + """);+ + os.put_string (""" + h1 {+ font-weight:normal;+ margin: 0 0 5px 0;+ color: #afafaf;+ }++ h2 {+ font-weight:normal;+ margin: 0 0 5px 0;+ color: #afafaf;+ }+ + h3 {+ font-weight:normal;+ margin: 0 0 5px 0;+ color: #afafaf;+ }+ + h4 {+ font-weight:normal;+ margin: 0 0 5px 0;+ color: #afafaf;+ }+ + p {+ margin: 0 0 5px 0;+ color: #000000;+ }+ + body {+ margin: 30px 0 0 30px;+ }+ + div {+ font-family: """);++ os.put_string ("'");+ os.put_string (@"$(name)");+ os.put_string ("EOT'");+ + os.put_string (", '");+ os.put_string (@"$(name)");+ os.put_string ("SVG'");+ + os.put_string (", '");+ os.put_string (@"$(name)");+ os.put_string ("TTF'");++ os.put_string (";");+ os.put_string ("""+ margin: 0 0 30px 0;+ }++ h1.bigger {+ font-size: 58pt;+ }++ h2.big {+ font-size: 40pt;+ }+ + h3.small {+ font-size: 32pt;+ }++ h4.smaller {+ font-size: 18pt;+ }++ h1.percent {+ font-size: 100%;+ } + + p.bigger {+ font-size: 32pt;+ }++ p.big {+ font-size: 24pt;+ }+ + p.small {+ font-size: 18pt;+ }++ p.smaller {+ font-size: 12pt;+ }++ </style>+""");+ + os.put_string (+""" +</head>+<body>++<div>+ <h1 class="bigger">Lorem ipsum</h1>+ <p class="bigger">Dolor sit amet!</p>+</div>++<div>+ <h2 class="big">Hamburgerfonstiv</h2>+ <p class="big">Inspirerande</p>+</div>++<div>+ <h3 class="small">Handgloves & Mittoms</h3>+ <p class="small">Wind blows in Hauges poetry.</p>+</p>+</div>++<div>+ <h4 class="smaller">Headline 16pt</h4>+ <p class="smaller">Ett litet stycke svalhjärta.</p> +</div>++<div>+ <h4 class="smaller">""" + _("Alphabet") + """</h4>+ <p class="smaller">""" + _("a b c d e f g h i j k l m n o p q r s t u v w x y z") + """</p>+ <p class="smaller">""" + _("A B C D E F G H I J K L M N O P Q R S T U V W X Y Z") + """</p>+ <p class="smaller">0 1 2 3 4 5 6 7 8 9</p>+</div>++</body>+</html>+""");++ } catch (GLib.Error e) {+ warning (e.message);+ }+ }++ public static bool export_ttf_font () {+ Font font = Supplement.get_current_font ();+ File file = font.get_folder ();+ return export_ttf_font_path (file);+ }+ + /** Stop a running export thread.*/+ public static void stop_export () {+ lock (stop_export_thread) {+ stop_export_thread = true;+ }+ }+ + public static bool should_stop () {+ bool r;+ lock (stop_export_thread) {+ r = stop_export_thread;+ }+ return r;+ } + + public static string get_birdfont_export () {+ File f;+ + f = File.new_for_path ("birdfont-export.sh");+ if (f.query_exists ()) {+ return "sh birdfont-export.sh";+ }++ f = File.new_for_path ("../birdfont-export.sh"); + if (f.query_exists ()) {+ return "sh ../birdfont-export.sh";+ }++ f = File.new_for_path ("birdfont-export.exe"); + if (f.query_exists ()) {+ return (!) f.get_path ();+ }++ f = File.new_for_path (@"$PREFIX/birdfont-export");+ if (f.query_exists ()) {+ return @"$PREFIX/bin/birdfont-export";+ }++ warning ("Can't find birdfont-export.");+ + return "birdfont-export";+ }+ + public static bool export_ttf_font_path (File folder, bool async = true) {+ Font current_font = Supplement.get_current_font ();+ File ttf_file;+ File eot_file;+ string temp_file;+ bool done = true;+ string export_command;+ + if (Supplement.win32) {+ VersionList.set_suppress_signal (true);+ async = false;+ }+ + try {+ // create a copy of current font and use it in a separate + // export thread+ temp_file = current_font.save_backup ();+ ttf_file = folder.get_child (current_font.get_name () + ".ttf");+ eot_file = folder.get_child (current_font.get_name () + ".eot");++ if (ttf_file.query_exists ()) {+ ttf_file.delete ();+ }++ if (eot_file.query_exists ()) {+ eot_file.delete ();+ }+ + assert (!is_null (temp_file));+ assert (!is_null (ttf_file.get_path ()));+ assert (!is_null (eot_file.get_path ()));++ export_thread = new ExportThread (temp_file, (!) ttf_file.get_path (), (!) eot_file.get_path ());++ try {+ if (async) {+ export_command = @"$(get_birdfont_export ()) --ttf -o $((!) folder.get_path ()) $temp_file";+ + try {+ Process.spawn_command_line_async (export_command);+ } catch (Error e) {+ stderr.printf (@"Failed to execute \"$export_command\" \n");+ critical (@"$(e.message)");+ }+ }+ + if (!async) {+ export_thread.run ();+ }+ } catch (ThreadError e) {+ warning (e.message);+ done = false;+ } + + } catch (Error e) {+ critical (@"$(e.message)");+ done = false;+ }++ if (Supplement.win32) {+ VersionList.set_suppress_signal (false);+ }+ + return done; + }+ + public static bool export_svg_font () {+ Font font = Supplement.get_current_font ();+ return export_svg_font_path (font.get_folder ());+ }+ + public static bool export_svg_font_path (File folder) {+ Font font = Supplement.get_current_font ();+ string file_name = @"$(font.get_name ()).svg";+ File file;+ SvgFontFormatWriter fo;+ + try {+ file = folder.get_child (file_name);+ + if (file.query_exists ()) {+ file.delete ();+ }+ + fo = new SvgFontFormatWriter ();+ fo.open (file);+ fo.write_font_file (font);+ fo.close ();+ } catch (Error e) {+ critical (@"$(e.message)");+ status (e.message);+ return false;+ }+ + status (_ ("Wrote font files"));+ return true;+ }++ public class ExportThread : GLib.Object {+ private static string ffi;+ private static string ttf;+ private static string eot;++ public ExportThread (string ffi, string ttf, string eot) {+ this.ffi = ffi.dup ();+ this.ttf = ttf.dup ();+ this.eot = eot.dup ();+ }+ + public void* run () {+ assert (!is_null (ffi));+ assert (!is_null (ttf));+ assert (!is_null (eot));+ + if (!should_stop ()) { + write_ttf ();+ }+ + if (!should_stop ()) { + write_eof ();+ }++ return null;+ }+ + void write_ttf () {+ OpenFontFormatWriter fo = new OpenFontFormatWriter ();+ Font f = new Font ();+ File file = (!) File.new_for_path (ttf);++ return_if_fail (!is_null (ffi)); + return_if_fail (!is_null (ttf));+ return_if_fail (!is_null (file));+ return_if_fail (!is_null (f));+ return_if_fail (!is_null (fo));+ + try {+ if (!f.load (ffi, false)) {+ warning (@"Can't read $ffi");+ }+ + fo.open (file);+ fo.write_ttf_font (f);+ fo.close ();+ } catch (Error e) {+ critical (@"$(e.message)");+ }+ }+ + void write_eof () {+ EotWriter fo;++ return_if_fail (!is_null (this));+ return_if_fail (!is_null (ttf));+ return_if_fail (!is_null (eot));+ + fo = new EotWriter (ttf, eot);++ return_if_fail (!is_null (fo));++ try {+ fo.write ();+ } catch (Error e) {+ warning ("EOF conversion falied.");+ critical (@"$(e.message)");+ }+ }++ }+ + private static void status (string s) {+ TooltipArea status = MainWindow.get_tool_tip ();+ + if (is_null (status)) {+ return;+ }+ + status.show_text (s);+ }+}++}new file mode 100644@@ -0,0 +1,1439 @@+/*+ 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;+using Xml;++namespace Supplement {++public class Font : GLib.Object {+ + GlyphTable glyph_cache = new GlyphTable ();+ + /** Glyphs that not are tied to a unichar value. */+ GlyphTable unassigned_glyphs = new GlyphTable ();++ List<string> glyph_names = new List<string> ();++ /** Last unassigned index */+ int next_unindexed = 0;+ + public List <string> background_images = new List <string> ();+ public string background_scale = "1";+ + /** Top margin */+ public double top_limit;+ + /** Height of upper case letters. */+ public double top_position;++ /** x-height upper bearing from origo. */+ public double xheight_position;++ /** Base line coordinate from origo. */+ public double base_line;+ + /** Descender position */+ public double bottom_position;+ + /** Bottom margon */+ public double bottom_limit;+ + public string? backup_file = null;+ public string? font_file = null;+ + bool modified = false;+ + private string name = "typeface";+ + bool ttf_export = true;+ bool svg_export = true;++ OpenFontFormatReader otf;+ bool otf_font = false;+ + public List<string> grid_width = new List<string> ();+ + public Font () {+ // positions in pixels at first zoom level+ // default x-height should be 60 in 1:1+ top_limit = -66 * Glyph.SCALE;+ top_position = -54 * Glyph.SCALE;+ xheight_position = -38 * Glyph.SCALE;+ base_line = 18 * Glyph.SCALE;+ bottom_position = 38 * Glyph.SCALE;+ bottom_limit = 45 * Glyph.SCALE;+ }++ public void touch () {+ modified = true;+ }++ public bool get_ttf_export () {+ return ttf_export;+ }++ public bool get_svg_export () {+ return svg_export;+ }++ public File get_backgrounds_folder () {+ string fn = @"$(get_name ()) backgrounds";+ File f = Supplement.get_settings_directory ().get_child (fn);+ return f;+ }++ /** Retuns true if the current font has be modified */+ public bool is_modified () {+ return modified;+ }++ /** Full path to this font file. */+ public string get_path () {+ int i = 0;+ + if (font_file != null) {+ return (!) font_file;+ }+ + StringBuilder sb = new StringBuilder ();+ sb.append (Environment.get_home_dir ());+ sb.append (@"/$(get_name ()).ffi");+ + File f = File.new_for_path (sb.str);++ while (f.query_exists ()) {+ sb.erase ();+ sb.append (Environment.get_home_dir ());+ sb.append (@"/$(get_name ())$(++i).ffi");+ f = File.new_for_path (sb.str);+ }+ + return sb.str;+ }++ public string get_file_name () {+ string p = get_path ();+ int i = p.last_index_of ("/");+ + if (i == -1) {+ i = p.last_index_of ("\\");+ }+ + p = p.substring (i + 1);+ + return p;+ }+ + public File get_folder () {+ string p = get_path ();+ int i = p.last_index_of ("/");+ + if (i == -1) {+ i = p.last_index_of ("\\");+ }+ + p = p.substring (0, i);+ + return File.new_for_path (p);+ }+ + public double get_height () {+ double r = base_line - top_position;+ return (r > 0) ? r : -r;+ }+ + public void set_name (string name) {+ this.name = name;+ }+ + public string get_name () {+ return name;+ }++ public void print_all () {+ stdout.printf ("Assigned:\n"); + glyph_cache.for_each((g) => {+ stdout.printf (@"$(g.get_name ())\n");+ });++ stdout.printf ("\n");+ stdout.printf ("Unssigned:\n"); + unassigned_glyphs.for_each ((g) => {+ stdout.printf (@"$(g.get_name ())\n");+ });++ }++ public bool has_glyph (string n) {+ return get_glyph (n) != null;+ }++ public Glyph get_nonmarking_return () {+ Glyph ret;+ + if (has_glyph ("nonmarkingreturn")) {+ return (!) get_glyph ("nonmarkingreturn");+ }+ + ret = new Glyph ("nonmarkingreturn", '\r');+ ret.set_unassigned (false);+ ret.left_limit = 0;+ ret.right_limit = 0;+ ret.remove_empty_paths ();+ + assert (ret.path_list.length () == 0);+ + return ret;+ }+ + public Glyph get_null_character () {+ Glyph n;+ + if (has_glyph ("null")) {+ return (!) get_glyph ("null");+ }+ + n = new Glyph ("null", '\0');+ n.set_unassigned (false);+ n.left_limit = 0;+ n.right_limit = 0;+ n.remove_empty_paths ();+ + assert (n.path_list.length () == 0);+ + return n;+ }+ + public Glyph get_space () {+ Glyph n;+ + if (has_glyph (" ")) {+ return (!) get_glyph (" ");+ }++ if (has_glyph ("space")) {+ return (!) get_glyph ("space");+ }+ + n = new Glyph ("space", ' ');+ n.set_unassigned (false);+ n.left_limit = 0;+ n.right_limit = 27;+ n.remove_empty_paths ();+ + assert (n.path_list.length () == 0);+ + return n; + }+ + public Glyph get_not_def_character () {+ Glyph g;++ Path p;+ Path i;+ + if (has_glyph (".notdef")) {+ return (!) get_glyph (".notdef");+ }+ + g = new Glyph (".notdef", 0);+ p = new Path ();+ i = new Path ();+ + g.set_unassigned (true);+ g.left_limit = -33;+ g.right_limit = 33;+ + p.add (-20, -top_position - 5);+ p.add (20, -top_position - 5);+ p.add (20, -base_line + 5);+ p.add (-20, -base_line + 5);+ p.close ();+ + i.add (-15, -top_position - 10);+ i.add (15, -top_position - 10);+ i.add (15, -base_line + 10);+ i.add (-15, -base_line + 10);+ i.reverse ();+ i.close ();++ g.add_path (i);+ g.add_path (p);++ return g;+ }+ + public void add_glyph (Glyph glyph) {+ GlyphCollection? gc = get_glyph_collection (glyph.get_name ());++ if (gc == null) {+ add_glyph_collection (new GlyphCollection (glyph));+ }+ }++ public void add_glyph_collection (GlyphCollection glyph_collection) {+ GlyphCollection? gc = get_cached_glyph_collection (glyph_collection.get_name ());+ + if (glyph_collection.get_name () == "") {+ warning ("Refusing to insert glyph with name \"\", null character should be named null.");+ return;+ }+ + if (gc != null) {+ warning ("glyph has already been added");+ return;+ }+ + if (glyph_collection.get_current ().is_unassigned ()) {+ unassigned_glyphs.insert (glyph_collection);+ } else {+ glyph_cache.insert (glyph_collection);+ }++ if (!has_name (glyph_collection.get_name ())) {+ glyph_names.append (glyph_collection.get_name ());+ glyph_names.sort (strcmp);+ }+ }+ + public string get_name_for_character (unichar c) {+ // if some glyph is already mapped to unichar, return it's name+ uint i = 0;+ Glyph? gl;+ Glyph g;+ StringBuilder sb;+ + while ((gl = get_glyph_indice (i++)) != null) {+ g = (!) gl;+ + if (g.unichar_code == c) {+ return g.name;+ }+ }+ + // otherwise return some default name, possibly from unicode database+ if (c == 0) {+ return ".null".dup ();+ }+ + sb = new StringBuilder ();+ sb.append_unichar (c);+ return sb.str; + }+ + public bool has_name (string name) {+ foreach (string n in glyph_names) {+ if (n == name) {+ return true;+ }+ }+ + return false;+ }+ + public void delete_glyph (string glyph) {+ glyph_names.remove_all (glyph);+ glyph_cache.remove (glyph);+ }+ + /** Obtain all versions and alterntes for this glyph. */+ public GlyphCollection? get_glyph_collection (string glyph) {+ GlyphCollection? gc = get_cached_glyph_collection (glyph);+ Glyph? g;+ + if (gc == null && otf_font) {+ // load it from otf file if we need to+ g = otf.read_glyph (glyph);+ + if (g != null) {+ add_glyph_callback ((!) g);+ return get_cached_glyph_collection (glyph);+ }+ }+ + return gc;+ }++ public GlyphCollection? get_cached_glyph_collection (string glyph) {+ GlyphCollection? gc = null;+ + gc = glyph_cache.get (glyph);+ + if (gc == null) {+ gc = unassigned_glyphs.get (glyph);+ }+ + return gc;+ }+ + public Glyph? get_glyph (string glyph) {+ GlyphCollection? gc = get_glyph_collection (glyph);+ + if (gc == null) {+ return null;+ }+ + return ((!)gc).get_current ();+ }+ + public Glyph? get_glyph_indice (unichar glyph_indice) {+ GlyphCollection? gc;+ string n;+ + if (!(0 <= glyph_indice < glyph_names.length ())) {+ return null;+ }+ + n = glyph_names.nth (glyph_indice).data;+ gc = get_glyph_collection (n);+ + if (gc != null) {+ return ((!) gc).get_current ();+ }+ + return null;+ }+ + public void add_background_image (string file) {+ background_images.append (file);+ }++ public double get_kerning (string a, string b) {+ Glyph? gl = get_glyph (a);+ Glyph g;+ + if (gl == null) {+ warning (@"glyph \"$a\" does not exist cannot obtain kerning");+ return 0;+ }+ + g = (!) gl;+ + return g.get_kerning (b);+ }++ public void set_kerning (string a, string b, double val) {+ Glyph? gl;+ Glyph g;+ + gl = get_glyph (a);+ + if (gl == null) {+ warning ("glyph is not parsed yet cannot add kerning");+ return;+ }+ + g = (!) gl;+ g.add_kerning (b, val);+ }+ + /** Returns path to backup file. */+ public string save_backup () {+ File dir = Supplement.get_backup_directory ();+ File? temp_file = null;+ int i = 0;++ if (backup_file == null) {+ temp_file = dir.get_child (@"current_font_$i.ffi");+ + while (((!) temp_file).query_exists ()) {+ i++;+ temp_file = dir.get_child (@"current_font_$i.ffi");+ }+ + backup_file = ((!) temp_file).get_path ();+ }++ assert (backup_file != null); + write_font_file ((!) backup_file);+ + return (!) backup_file;+ }+ + public bool save (string path) {+ bool r = write_font_file (path);+ + if (r) {+ font_file = path;+ }+ + modified = false;+ add_thumbnail ();+ Preferences.add_recent_files (get_path ());+ + return r;+ }++ public bool write_font_file (string path) {+ try {+ File file = File.new_for_path (path);++ if (file.query_file_type (0) == FileType.DIRECTORY) {+ stderr.printf (@"Can not save font. $path is a directory.");+ return false;+ }+ + if (file.query_exists ()) {+ file.delete ();+ }+ + DataOutputStream os = new DataOutputStream(file.create(FileCreateFlags.REPLACE_DESTINATION));+ + os.put_string ("""<?xml version="1.0" encoding="utf-8" standalone="yes"?>""");+ os.put_string ("\n");+ + os.put_string ("<font>\n");+ + os.put_string ("\n");+ os.put_string (@"<name>$(get_name ())</name>\n");+ + os.put_string ("\n");+ os.put_string (@"<ttf-export>$(ttf_export)</ttf-export>\n");++ os.put_string ("\n");+ os.put_string (@"<svg-export>$(svg_export)</svg-export>\n");+ + os.put_string ("\n");+ os.put_string ("<lines>\n");+ + os.put_string (@"\t<top_limit>$top_limit</top_limit>\n");+ os.put_string (@"\t<top_position>$top_position</top_position>\n");+ os.put_string (@"\t<x-height>$xheight_position</x-height>\n");+ os.put_string (@"\t<base_line>$base_line</base_line>\n");+ os.put_string (@"\t<bottom_position>$bottom_position</bottom_position>\n");+ os.put_string (@"\t<bottom_limit>$bottom_limit</bottom_limit>\n");+ + os.put_string ("</lines>\n\n");++ foreach (string gv in grid_width) {+ os.put_string (@"<grid width=\"$(gv)\"/>\n");+ }+ + if (GridTool.sizes.length () > 0) {+ os.put_string ("\n");+ }+ + os.put_string (@"<background scale=\"$(background_scale)\" />\n");+ os.put_string ("\n");+ + if (background_images.length () > 0) {+ os.put_string (@"<images>\n");+ + foreach (string f in background_images) {+ os.put_string (@"\t<img src=\"$f\"/>\n");+ }+ + os.put_string (@"</images>\n");+ os.put_string ("\n");+ }+ + glyph_cache.for_each ((gc) => {+ try {+ bool selected;+ foreach (Glyph g in gc.get_version_list ().glyphs) {+ selected = (g == gc.get_current ());+ write_glyph (g, selected, os);+ }+ } catch (GLib.Error ef) {+ stderr.printf (@"Failed to save $path \n");+ stderr.printf (@"$(ef.message) \n");+ }+ });+ + glyph_cache.for_each ((gc) => {+ try {+ Glyph glyph = gc.get_current ();+ + foreach (Kerning k in glyph.kerning) {+ string l, r;+ Glyph? gr = get_glyph (k.glyph_right);+ Glyph glyph_right;+ + if (gr == null) {+ warning ("kerning glyph that does not exist.");+ }+ + glyph_right = (!) gr;+ + l = Font.to_hex_code (glyph.unichar_code);+ r = Font.to_hex_code (glyph_right.unichar_code);+ + os.put_string (@"<hkern left=\"U+$l\" right=\"U+$r\" kerning=\"$(k.val)\"/>\n");+ }+ } catch (GLib.Error e) {+ warning (e.message);+ }+ });++ glyph_cache.for_each ((gc) => {+ GlyphBackgroundImage bg;+ + try {+ string data;+ + foreach (Glyph g in gc.get_version_list ().glyphs) {++ if (g.get_background_image () != null) {+ bg = (!) g.get_background_image ();+ data = bg.get_png_base64 ();+ + if (!bg.is_valid ()) {+ continue;+ }+ + os.put_string (@"<background-image sha1=\"");+ os.put_string (bg.get_sha1 ());+ os.put_string ("\" ");+ os.put_string (" data=\"");+ os.put_string (data);+ os.put_string ("");+ os.put_string ("\" />\n"); + }+ }+ } catch (GLib.Error ef) {+ stderr.printf (@"Failed to save $path \n");+ stderr.printf (@"$(ef.message) \n");+ }+ });+ + os.put_string ("</font>");+ + } catch (GLib.Error e) {+ stderr.printf (@"Failed to save $path \n");+ stderr.printf (@"$(e.message) \n");+ return false;+ } catch (GLib.IOError e) {+ stderr.printf (@"Failed to save $path \n");+ stderr.printf (@"$(e.message) \n");+ return false;+ }+ + return true;+ }++ private void write_glyph (Glyph g, bool selected, DataOutputStream os) throws GLib.Error {+ os.put_string (@"<glyph unicode=\"$(to_hex (g.unichar_code))\" selected=\"$selected\" left=\"$(g.left_limit)\" right=\"$(g.right_limit)\">\n");++ foreach (var p in g.path_list) {+ if (p.points.length () == 0) {+ continue;+ }+ + os.put_string ("\t<object>");++ foreach (var ep in p.points) {+ os.put_string (@"<point x=\"$(ep.x)\" y=\"$(ep.y)\" ");+ + if (ep.right_handle.type == PointType.CURVE) {+ os.put_string (@"right_angle=\"$(ep.right_handle.angle)\" ");+ os.put_string (@"right_length=\"$(ep.right_handle.length)\" ");+ }+ + if (ep.left_handle.type == PointType.CURVE) {+ os.put_string (@"left_angle=\"$(ep.left_handle.angle)\" ");+ os.put_string (@"left_length=\"$(ep.left_handle.length)\" "); + }+ + if (ep.right_handle.type == PointType.CURVE || ep.left_handle.type == PointType.CURVE) {+ os.put_string (@"tie_handles=\"$(ep.tie_handles)\" ");+ }+ + os.put_string ("/>");+ + }+ + os.put_string ("</object>\n");+ }+ + GlyphBackgroundImage? bg = g.get_background_image ();+ + if (bg != null) {+ GlyphBackgroundImage background_image = (!) bg;++ double pos_x = background_image.img_x;+ double pos_y = background_image.img_y;+ + double scale_x = background_image.img_scale_x;+ double scale_y = background_image.img_scale_y;+ + double rotation = background_image.img_rotation;+ + if (background_image.is_valid ()) {+ os.put_string (@"\t<background sha1=\"$(background_image.get_sha1 ())\" x=\"$pos_x\" y=\"$pos_y\" scale_x=\"$scale_x\" scale_y=\"$scale_y\" rotation=\"$rotation\"/>\n");+ }+ }+ + os.put_string ("</glyph>\n\n"); ++ }++ public void set_font_file (string path) {+ font_file = path;+ modified = false;+ }++ public uint length () {+ return glyph_names.length ();+ }++ public bool is_empty () {+ return (glyph_names.length () == 0);+ }++ public bool load (string path, bool recent = true) {+ bool loaded = false;+ + try {+ otf_font = false;++ while (grid_width.length () > 0) {+ grid_width.remove_link (grid_width.first ());+ }++ while (glyph_names.length () > 0) {+ glyph_names.remove_link (glyph_names.first ());+ }+ + glyph_cache.remove_all ();+ unassigned_glyphs.remove_all ();+ + if (path.has_suffix (".ffi")) {+ loaded = parse_file (path);+ font_file = path;+ }+ + if (Supplement.experimental) {+ if (path.has_suffix (".ttf")) {+ loaded = parse_otf_file (path);+ font_file = path;+ }+ }+ + if (recent) {+ add_thumbnail ();+ Preferences.add_recent_files (get_path ());+ }+ } catch (GLib.Error e) {+ warning (e.message);+ return false;+ }+ + return loaded;+ }++ private void add_thumbnail () {+ File f = Supplement.get_thumbnail_directory ().get_child (@"$((!) get_file_name ()).png");+ Glyph? gl = get_glyph ("a");+ Glyph g;+ ImageSurface img;+ ImageSurface img_scale;+ Context cr;+ double scale;+ + if (gl == null) {+ gl = get_glyph_indice (4);+ } + + if (gl == null) {+ gl = get_not_def_character ();+ }+ + g = (!) gl;++ img = g.get_thumbnail ();+ scale = 70.0 / img.get_width ();+ + if (scale > 70.0 / img.get_height ()) {+ scale = 70.0 / img.get_height ();+ }+ + if (scale > 1) {+ scale = 1;+ }++ img_scale = new ImageSurface (Format.ARGB32, (int) (scale * img.get_width ()), (int) (scale * img.get_height ()));+ cr = new Context (img_scale);+ + cr.scale (scale, scale);++ cr.save ();+ cr.set_source_surface (img, 0, 0);+ cr.paint ();+ cr.restore ();+ + img_scale.write_to_png ((!) f.get_path ());+ }++ /** Callback function for loading glyph in a separate thread. */+ public void add_glyph_callback (Glyph g) {+ GlyphCollection? gcl;+ GlyphCollection gc;+ + gcl = get_cached_glyph_collection (g.get_name ());+ + if (gcl != null) {+ warning (@"glyph \"$(g.get_name ())\" does already exist");+ }+ + if (g.is_unassigned ()) {+ gc = new GlyphCollection (g);+ + if (g.name == "") {+ warning ("refusing to insert glyph without name");+ g.name = @"($(++next_unindexed))";+ return;+ }+ + add_glyph_collection ((!) gc);+ } else if (gcl == null) {+ gc = new GlyphCollection (g);+ add_glyph_collection ((!) gc);+ } else {+ stderr.printf (@"Glyph collection does already have an entry for $(g.get_name ()) char $((uint64) g.unichar_code).\n");+ }+ + // take xheight from appropriate lower case letter+ // xheight_position = estimate_xheight ();+ }++ public bool parse_otf_file (string path) throws GLib.Error {+ otf = new OpenFontFormatReader ();+ otf_font = true;+ + otf.parse_index (path);+ + foreach (string n in otf.get_all_names ()) {+ glyph_names.append (n);+ }+ + return true;+ }+ + public bool parse_file (string path) throws GLib.Error {+ Parser.init ();+ + Xml.Doc* doc;+ Xml.Node* root;+ Xml.Node* node;++ // set this path as file for this font+ font_file = path;+ + // empty cache and fill it with new glyphs from disk+ glyph_cache.remove_all ();+ unassigned_glyphs.remove_all ();++ while (background_images.length () > 0) {+ background_images.remove_link (background_images.first ());+ }++ create_background_files (path);++ doc = Parser.parse_file (path);+ root = doc->get_root_element ();+ + if (root == null) {+ delete doc;+ return false;+ }++ node = root;+ + for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {+ + if (iter->name == "glyph") {+ parse_glyph (iter);+ }++ if (iter->name == "lines") {+ parse_font_boundries (iter);+ }++ if (iter->name == "grid") {+ parse_grid (iter);+ }++ if (iter->name == "background") {+ parse_background (iter);+ }+ + if (iter->name == "images") {+ parse_background_selection (iter);+ }++ if (iter->name == "name") {+ name = iter->children->content;+ }++ if (iter->name == "hkern") {+ parse_kerning (iter);+ }++ if (iter->name == "ttf-export") {+ ttf_export = bool.parse (iter->children->content);+ } ++ if (iter->name == "svg-export") {+ svg_export = bool.parse (iter->children->content);+ }+ + }+ + delete doc;+ Parser.cleanup ();++ return true;+ }+ + private void create_background_files (string path) {+ Xml.Doc* doc = Parser.parse_file (path);+ Xml.Node* root;+ Xml.Node* node;+ + root = doc->get_root_element ();+ + if (root == null) {+ delete doc;+ return;+ }++ node = root;+ + for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {+ if (iter->name == "name") {+ name = iter->children->content;+ }+ + if (iter->name == "background-image") {+ parse_background_image (iter);+ }+ }+ + delete doc;+ Parser.cleanup ();+ }+ + private void parse_kerning (Xml.Node* node) {+ string attr_name;+ string attr_content;+ string left = "";+ string right = "";+ string kern = "";++ StringBuilder b;+ + for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {+ attr_name = prop->name;+ attr_content = prop->children->content;+ + if (attr_name == "left") {+ b = new StringBuilder ();+ b.append_unichar (to_unichar (attr_content));+ left = @"$(b.str)";+ }++ if (attr_name == "right") {+ b = new StringBuilder ();+ b.append_unichar (to_unichar (attr_content));+ right = @"$(b.str)";+ }+ + if (attr_name == "kerning") {+ kern = attr_content;+ }+ }+ + set_kerning (left, right, double.parse (kern));+ }+ + private void parse_background_image (Xml.Node* node) + requires (node != null)+ {+ string attr_name;+ string attr_content;+ + string file = "";+ string data = "";+ + File img_dir;+ File img_file;+ FileOutputStream file_stream;+ DataOutputStream png_stream;+ + for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {+ return_if_fail (!is_null (prop->name));+ return_if_fail (!is_null (prop->children));+ return_if_fail (!is_null (prop->children->content));+ + attr_name = prop->name;+ attr_content = prop->children->content;+ + if (attr_name == "sha1") {+ file = attr_content;+ }+ + if (attr_name == "data") {+ data = attr_content;+ }+ }+ + img_dir = get_backgrounds_folder ().get_child ("parts");++ if (!img_dir.query_exists ()) {+ DirUtils.create ((!) img_dir.get_path (), 0xFFFFFF);+ }+ + img_file = img_dir.get_child (@"$(file).png");+ + if (img_file.query_exists ()) {+ return;+ }+ + try {+ file_stream = img_file.create (FileCreateFlags.REPLACE_DESTINATION);+ png_stream = new DataOutputStream (file_stream);++ png_stream.write (Base64.decode (data));+ png_stream.close (); + } catch (GLib.Error e) {+ warning (e.message);+ }+ }+ + private void parse_background (Xml.Node* node) + requires (node != null)+ {+ string attr_name;+ string attr_content;+ + for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {+ + return_if_fail (!is_null (prop->name));+ return_if_fail (!is_null (prop->children));+ return_if_fail (!is_null (prop->children->content));+ + attr_name = prop->name;+ attr_content = prop->children->content;+ + if (attr_name == "scale") {+ background_scale = attr_content;+ }+ }+ }+ + private void parse_background_selection (Xml.Node* node) {+ string attr_name = "";+ string attr_content;+ + return_if_fail (node != null);+ + for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {+ if (iter->name == "img") {+ for (Xml.Attr* prop = iter->properties; prop != null; prop = prop->next) {+ attr_name = prop->name;+ attr_content = prop->children->content;+ + if (attr_name == "src") {+ background_images.append (attr_content);+ }+ }+ }+ }+ }+ + private void parse_grid (Xml.Node* node) {+ for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {+ string attr_name = prop->name;+ string attr_content = prop->children->content;+ + if (attr_name == "width") {+ grid_width.append (attr_content);+ }+ } + }+ + private void parse_font_boundries (Xml.Node* node) {+ for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {+ if (iter->name == "top_limit") top_limit = parse_double_from_node (iter);+ if (iter->name == "top_position") top_position = parse_double_from_node (iter);+ if (iter->name == "x-height") xheight_position = parse_double_from_node (iter);+ if (iter->name == "base_line") base_line = parse_double_from_node (iter);+ if (iter->name == "bottom_position") bottom_position = parse_double_from_node (iter);+ if (iter->name == "bottom_limit") bottom_limit = parse_double_from_node (iter);+ } + }+ + private double parse_double_from_node (Xml.Node* iter) {+ double d;+ bool r = double.try_parse (iter->children->content, out d);+ + if (unlikely (!r)) {+ string? s = iter->content;+ if (s == null) warning (@"Content is null for node $(iter->name)\n");+ else warning (@"Failed to parse double for \"$(iter->content)\"\n");+ }+ + return (r) ? d : 0;+ }+ + /** Parse one glyph. */+ public void parse_glyph (Xml.Node* node) {+ string name = "";+ int left = 0;+ int right = 0;+ unichar uni = 0;+ int version = 0;+ bool selected = false;+ Glyph g;+ GlyphCollection? gc;+ + for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {+ string attr_name = prop->name;+ string attr_content = prop->children->content;+ StringBuilder b;+ + if (attr_name == "unicode") {+ uni = to_unichar (attr_content);+ b = new StringBuilder ();+ b.append_unichar (uni);+ name = b.str;+ }++ if (attr_name == "left") {+ left = int.parse (attr_content);+ }+ + if (attr_name == "right") {+ right = int.parse (attr_content);+ }+ + if (attr_name == "version") {+ version = int.parse (attr_content);+ }+ + if (attr_name == "selected") {+ selected = bool.parse (attr_content);+ }+ }++ g = new Glyph (name, uni);+ + g.left_limit = left;+ g.right_limit = right;++ parse_content (g, node);+ + // todo: use disk thread and idle add this:+ + gc = get_glyph_collection (g.get_name ());+ + if (g.get_name () == "") {+ warning ("No name set for glyph.");+ }+ + if (gc == null) {+ gc = new GlyphCollection (g);+ add_glyph_collection ((!) gc);+ } else {+ ((!)gc).insert_glyph (g, selected);+ }+ + }+ + /** Parse visual objects and paths */+ private void parse_content (Glyph g, Xml.Node* node) {+ Xml.Node* i;+ return_if_fail (node != null);+ + for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {+ if (iter->name == "object") {+ Path p = new Path ();+ + for (i = iter->children; i != null; i = i->next) {+ if (i->name == "point") {+ parse_point (p, i);+ } + }++ p.close ();+ g.add_path (p);+ }+ + if (iter->name == "background") {+ parse_background_scale (g, iter);+ }+ }+ }+ + private void parse_background_scale (Glyph g, Xml.Node* node) {+ GlyphBackgroundImage img;+ GlyphBackgroundImage? new_img = null;+ + string attr_name = "";+ string attr_content;+ + File img_file = get_backgrounds_folder ().get_child ("parts");+ + for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {+ attr_name = prop->name;+ attr_content = prop->children->content;+ + if (attr_name == "sha1") {+ img_file = img_file.get_child (attr_content + ".png");++ if (!img_file.query_exists ()) {+ warning (@"Background file has not been created yet. $((!) img_file.get_path ())");+ }+ + new_img = new GlyphBackgroundImage ((!) img_file.get_path ());+ g.set_background_image ((!) new_img);+ }+ }+ + if (unlikely (new_img == null)) {+ warning (@"No source for image found for $attr_name in $(g.name)");+ return;+ }+ + img = (!) new_img;+ + for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {+ attr_name = prop->name;+ attr_content = prop->children->content;+ + if (attr_name == "x") {+ img.img_x = double.parse (attr_content);+ }+ + if (attr_name == "y") {+ img.img_y = double.parse (attr_content);+ } ++ if (attr_name == "scale_x") {+ img.img_scale_x = double.parse (attr_content);+ }++ if (attr_name == "scale_y") {+ img.img_scale_y = double.parse (attr_content);+ }+ + if (attr_name == "rotation") {+ img.img_rotation = double.parse (attr_content);+ }+ }+ + img.set_position(img.img_x, img.img_y); + }+ + private void parse_point (Path p, Xml.Node* iter) {+ double x = 0;+ double y = 0;+ + PointType type = PointType.LINE;+ + double angle_right = 0;+ double angle_left = 0;+ + double length_right = 0;+ double length_left = 0;+ + PointType type_right = PointType.LINE;+ PointType type_left = PointType.LINE;+ + bool tie_handles = false;+ + EditPoint ep;+ + for (Xml.Attr* prop = iter->properties; prop != null; prop = prop->next) {+ string attr_name = prop->name;+ string attr_content = prop->children->content;+ + if (attr_name == "x") x = double.parse (attr_content);+ if (attr_name == "y") y = double.parse (attr_content);+ + if (attr_name == "right_angle") {+ type = PointType.CURVE; // FIXA: delete, maybe+ } + + if (attr_name == "right_angle") {+ type_right = PointType.CURVE;+ } ++ if (attr_name == "left_angle") {+ type_left = PointType.CURVE;+ }+ + if (attr_name == "right_angle") angle_right = double.parse (attr_content);+ if (attr_name == "right_length") length_right = double.parse (attr_content);+ if (attr_name == "left_angle") angle_left = double.parse (attr_content);+ if (attr_name == "left_length") length_left = double.parse (attr_content);+ + if (attr_name == "tie_handles") tie_handles = bool.parse (attr_content);+ }+ + ep = new EditPoint (x, y);+ + ep.type = type;+ + ep.right_handle.angle = angle_right;+ ep.right_handle.length = length_right;+ ep.right_handle.type = type_right;+ + ep.left_handle.angle = angle_left;+ ep.left_handle.length = length_left;+ ep.left_handle.type = type_left;+ + ep.tie_handles = tie_handles;+ + p.add_point (ep);+ }+ + public bool restore_backup () {+ string? b = present_backup_file ();+ + if (b == null) return false;+ + return parse_file ((!)b);+ }+ + private string? present_backup_file () {+ try {+ File dir = Supplement.get_settings_directory ();+ var files = dir.enumerate_children (FILE_ATTRIBUTE_STANDARD_NAME, 0);++ // What if we have more than one backup file left?+ FileInfo? file_info;+ while ((file_info = files.next_file ()) != null) {+ FileInfo fi = (!) file_info;+ if (fi.get_name ().index_of ("current_font_") != -1) {+ File f = dir.get_child (fi.get_name ());+ backup_file = f.get_path ();+ return backup_file;+ }+ }+ } catch (GLib.Error e) {+ stderr.printf (@"Failed to load backup\n");+ stderr.printf (@"$(e.message) \n");+ }+ + return null;+ }+ + /** Delete temporary rescue files. */+ public void delete_backup () {+ if (backup_file == null) return;++ try {+ File f = File.new_for_path ((!) backup_file);+ f.delete (); + } catch (GLib.Error e) {+ stderr.printf (@"Failed to delete backup\n");+ stderr.printf (@"$(e.message) \n");+ }+ }+ + public static string to_hex (unichar ch) {+ StringBuilder s = new StringBuilder ();+ s.append ("U+");+ s.append (to_hex_code (ch));+ return s.str;+ }++ public static string to_hex_code (unichar ch) {+ StringBuilder s = new StringBuilder ();+ + uint8 a = (uint8)(ch & 0x00000F);+ uint8 b = (uint8)((ch & 0x0000F0) >> 4 * 1);+ uint8 c = (uint8)((ch & 0x000F00) >> 4 * 2);+ uint8 d = (uint8)((ch & 0x00F000) >> 4 * 3);+ uint8 e = (uint8)((ch & 0x0F0000) >> 4 * 4);+ uint8 f = (uint8)((ch & 0xF00000) >> 4 * 5);+ + if (e != 0 || f != 0) {+ s.append (oct_to_hex (f));+ s.append (oct_to_hex (e));+ }+ + if (c != 0 || d != 0) {+ s.append (oct_to_hex (d));+ s.append (oct_to_hex (c));+ }+ + s.append (oct_to_hex (b));+ s.append (oct_to_hex (a));+ + return s.str;+ }++ public static unichar to_unichar (string unicode) {+ int index = 2;+ int i = 0;+ unichar c;+ unichar rc = 0;+ bool r;++ if (unlikely (unicode.index_of ("U+") != 0)) {+ warning ("All unicode values must begin with U+");+ }+ + while (r = unicode.get_next_char (ref index, out c)) {+ rc <<= 4;+ rc += hex_to_oct (c);+ + return_val_if_fail (++i <= 6, '\0');+ }++ return rc;+ }+ + private static string oct_to_hex (uint8 o) {+ switch (o) {+ case 10: return "a";+ case 11: return "b";+ case 12: return "c";+ case 13: return "d";+ case 14: return "e";+ case 15: return "f";+ }++ return_val_if_fail (0 <= o <= 9, "-".dup ());+ + return o.to_string ();+ }++ private static uint8 hex_to_oct (unichar o) {+ StringBuilder s = new StringBuilder ();+ s.append_unichar (o);+ + switch (o) {+ case 'a': return 10;+ case 'b': return 11;+ case 'c': return 12;+ case 'd': return 13;+ case 'e': return 14;+ case 'f': return 15;+ }+ + return_val_if_fail ('0' <= o <= '9', 0);+ + return (uint8) (o - '0');+ }++}++}new file mode 100644@@ -0,0 +1,239 @@+/*+ 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;+using Birdfont;++namespace Supplement {++/** Javascripts in webkit do use this callback. */+public class PropertyFunction {+ public delegate void PropertyCallback (string val);+ public PropertyCallback call;+}++public abstract class FontDisplay : GLib.Object {+ + List<string> property = new List<string> ();+ List<PropertyFunction> call = new List<PropertyFunction> ();+ + /** Queue redraw area */+ public signal void redraw_area (double x, double y, double w, double h);+ + /** Name of symbol or set of symbols in display selections like the tab panel. */+ public virtual string get_name () {+ return "";+ }+ + public virtual bool is_html_canvas () {+ return false;+ }++ public static string path_to_uri (string path) {+ string uri = path;+ string wp;+ + // wine uri hack+ if (Supplement.win32) {+ wp = wine_to_unix_path (uri);+ + if (find_file (wp, "").query_exists ()) {+ uri = wp;+ }++ if (uri.index_of ("\\") > -1) {+ uri = uri.replace ("\\", "/");+ }+ }++ if (uri.index_of ("/") == 0) {+ uri = @"file://$uri";+ } else {+ uri = @"file:///$uri";+ }+ + return uri;+ }++ public virtual string get_uri () {+ return "";+ }+ + public virtual string get_html_file () {+ return "index.html";+ }+ + public virtual string get_html () {+ return "";+ }+ + public virtual void draw (Allocation allocation, Context cr) {+ }+ + public virtual void selected_canvas () {+ }+ + public virtual void key_press (uint keyval) {+ }+ + public virtual void key_release (uint keyval) {+ }+ + public virtual void motion_notify (double x, double y) {+ }+ + public virtual void button_release (int button, double x, double y) {+ }+ + public virtual void button_press (uint button, double x, double y) {+ }++ public virtual void double_click (uint button, double ex, double ey) {+ } ++ public virtual void zoom_in () {+ }+ + public virtual void zoom_out () {+ }+ + public virtual void zoom_max () {+ }+ + public virtual void zoom_min () {+ }+ + public virtual void reset_zoom () {+ }+ + public virtual void store_current_view () {+ warning ("store_current_view not implemented for this class.");+ }+ + public virtual void restore_last_view () {+ warning ("restore_last_view not implemented for this class.");+ }++ public virtual void next_view () {+ warning ("next_view not implemented for this class.");+ }+ + public virtual void scroll_wheel_up (double x, double y) {+ }+ + public virtual void scroll_wheel_down (double x, double y) {+ }++ public virtual void undo () {+ }++ public static File find_layout_dir () {+ return find_file (null, "layout");+ }++ public void add_html_callback (string prop, PropertyFunction.PropertyCallback cb) {+ PropertyFunction pf = new PropertyFunction ();+ pf.call = cb;+ property.append (prop);+ call.append (pf);+ }+ + public void process_property (string prop) {+ string k, v;+ int i, j;+ PropertyFunction cb;+ + if (prop == "" || prop == "done") {+ return;+ }+ + i = prop.index_of (":");+ + if (i <= 0) {+ return;+ }+ + k = prop.substring (0, i);+ v = prop.substring (i + 1);+ + j = 0;+ + foreach (string p in property) {+ if (p == k) {+ break;+ }+ j++;+ }+ + if (j >= property.length ()) {+ warning (@"key \"$k\" not found in property list, value: $v");+ return;+ }+ + cb = call.nth (j).data;+ cb.call (v);+ }++ public static File find_file (string? dir, string name) {+ File f;+ string d = (dir == null) ? "" : (!) dir;+ + f = get_file (Supplement.exec_path + "/", name + "/");+ if (likely (f.query_exists ())) return f;+ + f = get_file (Supplement.exec_path + "/" + d + "/", name);+ if (likely (f.query_exists ())) return f;+ + f = get_file ("./" + d + "/", name);+ if (likely (f.query_exists ())) return f; ++ f = get_file ("../" + d + "/", name);+ if (likely (f.query_exists ())) return f;++ f = get_file (".\\" + d + "\\", name);+ if (likely (f.query_exists ())) return f;++ f = get_file ("", name);+ if (likely (f.query_exists ())) return f;++ f = get_file (d + "\\", name);+ if (likely (f.query_exists ())) return f;++ f = get_file ("/usr/local/share/birdfont/" + d + "/", name);+ if (likely (f.query_exists ())) return f;++ f = get_file ("/usr/share/birdfont/" + d + "/", name);+ if (likely (f.query_exists ())) return f;++ f = get_file (@"$PREFIX/share/birdfont/" + d + "/", name);+ if (likely (f.query_exists ())) return f;+ + warning (@"Did not find file $name in $d");+ + return f; + }++ public static File get_file (string? path, string name) {+ StringBuilder fn = new StringBuilder ();+ string p = (path == null) ? "" : (!) path;+ fn.append (p);+ fn.append ((!) name);+ return File.new_for_path (fn.str);+ }+}++}new file mode 100644@@ -0,0 +1,1719 @@+/*+ 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;+using Math;++namespace Supplement {++public class Glyph : FontDisplay {++ public static const double SCALE = 1.0;++ // Background image+ GlyphBackgroundImage? background_image = null;+ bool background_image_visible = true;+ + // Glyph zoom level+ public double view_zoom = 0.01;+ public double view_offset_x = 0;+ public double view_offset_y = 0;+ List<ZoomView> zoom_list = new List<ZoomView> ();+ int zoom_list_index = 0;+ + // Paths+ public List<Path> path_list;+ public List<Path> active_paths = new List<Path> ();++ // Control points+ public EditPoint? new_point_on_path = null;+ public EditPoint? flipping_point_on_path = null;+ public EditPoint? last_added_edit_point = null;+ + // The point where edit event begun + double pointer_begin_x = 0;+ double pointer_begin_y = 0;++ // Current pointer position+ double motion_x = 0;+ double motion_y = 0;+ + // Zoom area+ double zoom_x1 = 0;+ double zoom_y1 = 0;+ double zoom_x2 = 0;+ double zoom_y2 = 0;+ bool zoom_area_is_visible = false;+ + bool move_view = false;+ public double move_offset_x = 0;+ public double move_offset_y = 0;++ public Allocation allocation;+ + public string name;++ public double left_limit;+ public double right_limit;+ + // x-height, lsb, etc.+ public List<Line> vertical_help_lines = new List<Line> ();+ public List<Line> horizontal_help_lines = new List<Line> ();+ List<Line> all_lines = new List<Line> (); // vertical, horzontal and grid lines+ bool show_help_lines = true;+ bool xheight_lines_visible = false;+ bool margin_boundries_visible = false;+ + bool unassigned = false;+ public unichar unichar_code = 0;+ + List<Glyph> undo_list = new List<Glyph> ();+ + public List<Kerning> kerning = new List<Kerning> ();+ + string glyph_sequence = "";+ bool open = true;+ + public Glyph (string name, unichar unichar_code = 0) {+ this.name = name;+ this.unichar_code = unichar_code;+ + path_list.append (new Path ());+ + add_help_lines ();++ left_limit = -28 * SCALE;+ right_limit = 28 * SCALE;+ }++ public Glyph.no_lines (string name, unichar unichar_code = 0) {+ this.name = name;+ this.unichar_code = unichar_code;++ path_list.append (new Path ());+ }+ + public void clear_active_paths () {+ while (active_paths.length () > 0) {+ active_paths.remove_link (active_paths.first ());+ }+ }+ + public void add_active_path (Path? p) {+ if (p != null) {+ + foreach (Path pi in active_paths) {+ if (pi == p) {+ return;+ }+ }+ + active_paths.append ((!) p);+ }+ }+ + public Path? get_active_path () {+ return_if_fail (!is_null(active_paths));+ + if (!is_null(active_paths.last ())) {+ return null;+ }+ + return active_paths.last ().data;+ }+ + + public void add_kerning (string right_glyph, double val) {+ Kerning? kl = null;++ foreach (Kerning k in kerning) {+ if (k.glyph_right == right_glyph) {+ kl = k;+ }+ }+ + if (kl == null) {+ kerning.append (new Kerning (right_glyph, val));+ } else {+ ((!)kl).val = val;+ }+ }++ public double get_kerning (string right_glyph) {+ foreach (Kerning k in kerning) {+ if (k.glyph_right == right_glyph) {+ return k.val;+ }+ }+ + return 0;+ }+ + public void set_unassigned (bool u) {+ unassigned = u;+ }+ + public bool is_unassigned () {+ return unassigned;+ }++ public void boundries (out double x1, out double y1, out double x2, out double y2) {+ if (path_list.length () == 0) {+ x1 = 0;+ y1 = 0;+ x2 = 0;+ y2 = 0;+ return;+ }++ x1 = path_list.first ().data.xmin;+ y1 = path_list.first ().data.ymin;+ x2 = path_list.first ().data.xmax;+ y2 = path_list.first ().data.ymax;+ + foreach (Path p in path_list) {+ p.update_region_boundries ();+ + 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 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 () {+ double a, b, c, d;+ + if (view_zoom > 1) { + a = vertical_help_lines.first ().data.get_coordinate ();+ c = horizontal_help_lines.first ().data.get_coordinate ();+ + b = vertical_help_lines.first ().data.get_coordinate ();+ d = horizontal_help_lines.first ().data.get_coordinate ();+ + view_offset_x -= a - b;+ view_offset_y -= c - d;+ }+ }++ public void set_background_image (GlyphBackgroundImage? b) {+ GlyphBackgroundImage bg;+ + if (b == null) {+ background_image = null;+ }+ + bg = (!) b;+ + background_image = bg;+ + Supplement.get_current_font ().touch ();+ }+ + public GlyphBackgroundImage? get_background_image () {+ return (!) background_image;+ }+ + public override void scroll_wheel_up (double x, double y) {+ if (KeyBindings.has_ctrl ()) {+ zoom_in_at_point (x, y);+ } else if (KeyBindings.has_alt ()) { + view_offset_x -= 10 / view_zoom;+ } else {+ view_offset_y -= 10 / view_zoom;+ }+ + redraw_area (0, 0, allocation.width, allocation.height);+ }+ + public override void scroll_wheel_down (double x, double y) {+ if (KeyBindings.has_ctrl ()) {+ zoom_out_at_point (x, y);+ } else if (KeyBindings.has_alt ()) { + view_offset_x += 10 / view_zoom;+ } else {+ view_offset_y += 10 / view_zoom;+ }+ + redraw_area (0, 0, allocation.width, allocation.height);+ }+ + public void add_path (Path p) {+ path_list.append (p);+ }+ + public override void selected_canvas () {+ add_help_lines ();+ KeyBindings.singleton.set_require_modifier (false);+ glyph_sequence = Preferences.get ("glyph_sequence");+ }+ + private void remove_lines () {+ while (vertical_help_lines.length () != 0) {+ vertical_help_lines.remove_link (vertical_help_lines.first ());+ }++ while (horizontal_help_lines.length () != 0) {+ horizontal_help_lines.remove_link (horizontal_help_lines.first ());+ } + }+ + public void add_help_lines () {+ remove_lines ();++ assert (!is_null (Supplement.get_current_font ()));++ double bgt = Supplement.get_current_font ().top_limit;+ Line top_margin_line = new Line ("top margin", bgt, false);+ top_margin_line.set_color (0.7, 0, 0, 0.5);+ top_margin_line.position_updated.connect ((pos) => {+ Supplement.get_current_font ().top_limit = pos;+ });+ + double thp = Supplement.get_current_font ().top_position;+ Line top_line = new Line ("top", thp, false);+ top_line.position_updated.connect ((pos) => {+ Font f = Supplement.get_current_font ();+ f.top_position = pos;+ });+ + double xhp = Supplement.get_current_font ().xheight_position;+ Line xheight_line = new Line ("x-height", xhp, false);+ xheight_line.set_color (33 / 255.0, 68 / 255.0, 120 / 255.0, 166 / 255.0);+ xheight_line.position_updated.connect ((pos) => { + Font f = Supplement.get_current_font ();+ f.xheight_position = pos;+ });++ double xbl = Supplement.get_current_font ().base_line;+ Line base_line = new Line ("baseline", xbl, false);+ base_line.position_updated.connect ((pos) => {+ Font f = Supplement.get_current_font ();+ f.base_line = pos;+ });++ + double bp = Supplement.get_current_font ().bottom_position;+ Line bottom_line = new Line ("bottom", bp, false);+ bottom_line.set_color (33 / 255.0, 68 / 255.0, 120 / 255.0, 166 / 255.0);+ bottom_line.position_updated.connect ((pos) => {+ Supplement.get_current_font ().bottom_position = pos;+ });++ double bgb = Supplement.get_current_font ().bottom_limit;+ Line bottom_margin_line = new Line ("bottom margin", bgb, false);+ bottom_margin_line.set_color (0.7, 0, 0, 0.5);+ bottom_margin_line.position_updated.connect ((pos) => {+ Supplement.get_current_font ().bottom_limit = pos;+ });+ + Line left_line = new Line ("left", left_limit, true);+ left_line.position_updated.connect ((pos) => {+ left_limit = pos;+ });+ + Line right_line = new Line ("right", right_limit, true);+ right_line.position_updated.connect ((pos) => {+ right_limit = pos;+ });+ + // lists of help lines are sorted and lines are added only if + // they are important 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_boundries_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_boundries_visible);+ }+ + bool has_top_line () {+ return !unichar_code.islower () || CharDatabase.has_ascender (unichar_code);+ }+ + /** 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_boundries_visible = m;+ add_help_lines ();+ }+ + public bool get_margin_lines_visible () {+ return margin_boundries_visible;+ }+ + public void remove_empty_paths () {+ foreach (var p in path_list) {+ if (p.points.length () < 2) {+ delete_path (p);+ }+ }+ }+ + public void delete_path (Path p) {+ path_list.remove_all (p);+ }+ + public string get_svg_data () {+ return Svg.to_svg_glyph (this, 1 / SCALE);+ }+ + public int get_height () + requires (vertical_help_lines.length () >= 2)+ {+ unowned List<Line> a = horizontal_help_lines.first ();+ unowned List<Line> b = horizontal_help_lines.last ();+ + return (int) Math.fabs (a.data.pos / SCALE - b.data.pos / SCALE); + }+ + public double get_width () {+ return Math.fabs (right_limit / SCALE - left_limit / SCALE);+ }++ public unichar get_unichar () {+ return unichar_code;+ }++ public bool get_show_help_lines () {+ return show_help_lines;+ }+ + 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.append (line);+ line.list_item = vertical_help_lines.last ();+ } else {+ horizontal_help_lines.append (line);+ line.list_item = horizontal_help_lines.last ();+ }++ 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) => {+ return (int) (a.get_pos () - b.get_pos ());+ });+ + horizontal_help_lines.sort ((a, b) => {+ return (int) (a.get_pos () - b.get_pos ());+ });+ }+ + public override string get_name () {+ return name;+ }+ + private void help_line_event (double x, double y) {+ double mx = x;+ double my = y;+ + if (GridTool.is_visible ()) {+ GridTool.tie (ref mx, ref my);+ }+ + foreach (Line line in get_all_help_lines ()) {+ line.event_move_to (mx, my, x, y, allocation);+ }+ }++ public override void key_release (uint keyval) { + Tool t;+ t = MainWindow.get_toolbox ().get_current_tool ();+ t.key_release_action (t, keyval); + }++ public override void key_press (uint keyval) { + Tool t = MainWindow.get_toolbox ().get_current_tool ();+ t.key_press_action (t, keyval);+ }+ + /** Delete edit point from path. */+ public void delete_edit_point (EditPoint ep) {+ foreach (Path p in path_list) {+ if (p.points.length () > 0) {+ p.delete_edit_point (ep);+ } else {+ delete_path (p);+ }+ }+ }+ + public override void motion_notify (double x, double y) {+ Tool t;+ t = MainWindow.get_toolbox ().get_current_tool ();+ + if (move_view && KeyBindings.has_ctrl ()) {+ move_view_offset (x, y);+ return;+ }++ t.move_action (t, (int) x, (int) y);++ if (Supplement.show_coordinates) {+ motion_x = x * ivz () - xc () + view_offset_x;+ motion_y = yc () - y * ivz () - view_offset_y;+ }+ + help_line_event (x, y); + }+ + public override void button_release (int button, double ex, double ey) {+ bool line_moving = false;+ + if (KeyBindings.has_ctrl ()) {+ move_view = false;+ return;+ }+ + 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 unowned List<Line> get_all_help_lines () {+ while (all_lines.length () > 0) all_lines.delete_link (all_lines.first ());+ + all_lines.concat (vertical_help_lines.copy ());+ all_lines.concat (horizontal_help_lines.copy ());+ + if (GridTool.is_visible ()) {+ all_lines.concat (GridTool.get_vertical_lines ().copy ());+ all_lines.concat (GridTool.get_horizontal_lines ().copy ());+ }+ + return all_lines;+ }+ + public void update_view () {+ MainWindow.get_glyph_canvas ().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) { + pointer_begin_x = ex;+ pointer_begin_y = ey;+ + foreach (Line line in get_all_help_lines ()) {+ if (line.button_press ()) {+ return;+ }+ }+ + if (KeyBindings.has_ctrl ()) {+ move_view = 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);+ }+ }++ /** Insert new edit point for current path on the appropriate zoom+ * level.+ */+ public EditPoint add_new_edit_point (int x, int y) {+ insert_edit_point (x, y);+ + return_if_fail (active_paths.length () > 0);+ + last_added_edit_point = active_paths.last ().data.get_last_point ();+ return (!) last_added_edit_point;+ }++ public EditPoint get_last_edit_point () {+ return_val_if_fail (last_added_edit_point != null, new EditPoint ());+ return (!) last_added_edit_point;+ }++ /** 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;++ view_offset_x += x / view_zoom;+ view_offset_y += y / view_zoom;+ + view_zoom_x = allocation.width * view_zoom / w;+ view_zoom_y = allocation.height * view_zoom / h;+ + // TODO: there is a max zoom level: probably ivz > 0.1+ + 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 ();+ }+ + public void show_zoom_area (int sx, int sy, int nx, int ny) {+ double x, y, w, h;+ + set_zoom_area (sx, sy, nx, ny);+ + zoom_area_is_visible = true;+ + x = Math.fmin (zoom_x1, zoom_x2) - 50;+ y = Math.fmin (zoom_y1, zoom_y2) - 50;++ w = Math.fabs (zoom_x1 - zoom_x2) + 100;+ h = Math.fabs (zoom_y1 - zoom_y2) + 100;+ + redraw_area ((int)x, (int)y, (int)w, (int)h);+ }++ 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 double path_coordinate_x (double x) {+ Glyph g = MainWindow.get_current_glyph ();+ return_if_fail (g.view_zoom != 0);+ return x * g.ivz () - g.xc () + g.view_offset_x;+ }++ public static int reverse_path_coordinate_x (double x) {+ Glyph g = MainWindow.get_current_glyph ();+ return_if_fail (g.view_zoom != 0);+ return (int) ((x - g.view_offset_x + g.xc ()) * g.view_zoom);+ }++ public static double path_coordinate_y (double y) {+ Glyph g = MainWindow.get_current_glyph ();+ return g.yc () - y * g.ivz () - g.view_offset_y;+ }++ public static int reverse_path_coordinate_y (double y) {+ Glyph g = MainWindow.get_current_glyph ();+ y = ((y + g.view_offset_y - g.yc ()) * g.view_zoom);+ return (int) (-y);+ }++ public bool select_path (double x, double y) {+ Path? p = null;+ bool found = false;+ + foreach (Path pt in path_list) {+ if (pt.is_over (x, y)) {+ p = pt;+ found = true;+ }+ }+ + if (!KeyBindings.has_shift ()) {+ clear_active_paths ();+ }+ + add_active_path (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);++ foreach (Path p in path_list) {+ if (p.is_over (xt, yt)) {+ return p;+ }+ }++ foreach (Path p in path_list) {+ if (p.points.length () == 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 (path_list.length () > 0) {+ return path_list.first ().data;+ }++ if (unlikely (min_distance == double.MAX)) {+ warning (@"No path found in path_list. Length: $(path_list.length ())");+ + if (path_list.length () > 0) {+ stderr.printf (@"p.points.length () $(path_list.first ().data.points.length ()) \n");+ }+ }+ + return min_point;+ }+ + private EditPoint insert_edit_point (double x, double y) {+ unowned List<Path> paths;+ double xt, yt;+ bool added;+ Path np;+ EditPoint inserted;+ + if (active_paths.length () == 0) {+ np = new Path ();+ clear_active_paths ();+ active_paths.append (np);+ path_list.append (np);+ }+ + paths = path_list;+ + xt = path_coordinate_x (x);+ yt = path_coordinate_y (y);+ + added = false;++ if (new_point_on_path != null) {+ return_if_fail (active_paths.length () > 0);++ Path p = new Path ();+ EditPoint e = (!) new_point_on_path;++ return_if_fail (e.prev != null);+ + p.add_point (e);+ e.type = PointType.CURVE;+ + e.right_handle.type = PointType.CURVE;+ e.left_handle.type = PointType.CURVE;+ + paths.append (p);+ add_active_path (p);+ flipping_point_on_path = new_point_on_path;+ new_point_on_path = null;+ added = true;+ return e;+ }+ + if (!added) {+ foreach (Path p in paths) {+ if (p.is_open ()) {+ p.add (xt, yt);+ clear_active_paths ();+ add_active_path (p);+ added = true;+ break;+ }+ }+ }++ if (!added) {+ foreach (Path p in paths) {+ if (p.is_over (xt, yt)) {+ p.add (xt, yt);+ paths.append (p);+ added = true;+ redraw_area (0, 0, allocation.width, allocation.height);+ break;+ }+ }+ }+ + if (!added) {+ if (paths.length () > 0 && paths.last ().data.is_open ()) {+ paths.last().data.add (xt, yt);+ }+ + foreach (var p in path_list) {+ p.close ();+ }+ + np = new Path ();+ paths.append (np);+ np.add (xt, yt);+ + clear_active_paths ();+ add_active_path (np);+ }+ + assert (active_paths.length () > 0);+ assert (active_paths.last ().data.points.length () > 0);+ + inserted = active_paths.last ().data.points.data;+ + return inserted;+ }+ + public void move_selected_edit_point_delta (EditPoint selected_point, double dx, double dy) {+ double px = selected_point.x + xc () - view_offset_x;+ double py = -1 * (selected_point.y - yc () + view_offset_y);+ + px *= view_zoom;+ py *= view_zoom;+ + move_selected_edit_point (selected_point, dx + px, dy + py);+ }+ + public void move_selected_edit_point (EditPoint selected_point, double x, double y) { + double xc, yc, xt, yt;+ double ivz = 1 / view_zoom;+ EditPoint p;+ + Supplement.get_current_font ().touch ();++ xc = (allocation.width / 2.0);+ yc = (allocation.height / 2.0);+ + x *= ivz;+ y *= ivz;+ + xt = x - xc + view_offset_x;+ yt = yc - y - view_offset_y;+ + // 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);+ }+ + if (flipping_point_on_path != null) {+ p = (!) flipping_point_on_path;+ p.recalculate_handles (x, y);+ redraw_area (0, 0, allocation.width, allocation.height);+ } + }+ + 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.length () == 0) {+ return;+ }+ + foreach (Path path in active_paths) {+ EditPoint? pt = path.get_second_last_point ();+ if (pt != null) {+ EditPoint p = (!) pt;+ + 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 Path? get_last_path () + ensures (result != null)+ {+ return_if_fail (path_list.length () > 0);+ return path_list.last ().data;+ }+ + public bool has_active_path () {+ return active_paths.length () > 0;+ }+ + public bool is_open () {+ return open;+ }+ + /** Close all editable paths and return false if no path have boon closed. + * Paths without area (points and lines) will be deleted.+ */+ public bool close_path () {+ bool r = false;+ + foreach (var p in path_list) {+ if (p.is_editable ()) {+ r = true;+ p.close ();+ }+ }+ + clear_active_paths ();+ new_point_on_path = null;+ flipping_point_on_path = null;+ + delete_invisible_paths ();+ + redraw_area (0, 0, allocation.width, allocation.height);+ + open = false;+ + return r;+ }++ public void open_path () { + foreach (var p in path_list) {+ p.set_editable (true);+ }+ + 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_boundries ();+ + 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); + }++ /** Delete all paths without area. */+ private void delete_invisible_paths () {+ foreach (var p in path_list) {+ + if (p.points.length () < 2) {+ path_list.remove (p);+ continue;+ }+ + if (p.points.length () == 2) {+ if (p.points.first ().data.type == PointType.LINE && p.points.last ().data.type == PointType.LINE) {+ path_list.remove (p);+ continue;+ }+ }+ + }+ }++ 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 () {+ set_zoom_area (10, 10, allocation.width - 10, allocation.height - 10);+ set_zoom_from_area ();+ update_view ();+ }+ + 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 ();+ }+ + public override void zoom_max () {+ default_zoom ();+ }+ + 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 path_list) {+ p.update_region_boundries ();+ + if (p.points.length () > 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);+ + }++ public override void store_current_view () {+ if (zoom_list_index + 1 < zoom_list.length ()) {+ unowned List<ZoomView> n = zoom_list.nth (zoom_list_index);+ while (n != zoom_list.last ()) zoom_list.delete_link (zoom_list.last ());+ }+ + zoom_list.append (new ZoomView (view_offset_x, view_offset_y, view_zoom, allocation));+ zoom_list_index = (int) zoom_list.length () - 1;+ if (zoom_list.length () > 50) zoom_list.delete_link (zoom_list.first ());+ }+ + public override void restore_last_view () + requires (zoom_list.length () > 0)+ { + if (zoom_list_index - 1 < 0 || zoom_list.length () == 0)+ return;+ + zoom_list_index--;+ + ZoomView z = zoom_list.nth (zoom_list_index).data;+ + view_offset_x = z.x;+ view_offset_y = z.y;+ view_zoom = z.zoom;+ allocation = z.allocation;+ }++ public override void next_view () + requires (zoom_list.length () > 0)+ {+ if (zoom_list_index + 1 >= zoom_list.length ())+ return;+ + zoom_list_index++;+ + ZoomView z = zoom_list.nth (zoom_list_index).data;+ + view_offset_x = z.x;+ view_offset_y = z.y;+ view_zoom = z.zoom;+ allocation = z.allocation; + }+ + public override void reset_zoom () {+ view_offset_x = 0;+ view_offset_y = 0;+ + set_zoom (1);+ + store_current_view ();+ }+ + /** Get x-height or top line. */+ public Line get_upper_line () + requires (horizontal_help_lines.length () > 2)+ {+ + if (has_top_line () || xheight_lines_visible) {+ return get_line ("top");+ }+ + return get_line ("x-height");+ }++ /** Get base line. */+ public Line get_lower_line () + requires (horizontal_help_lines.length () > 2)+ {+ return horizontal_help_lines.last ().prev.data;+ }+ + /** Set default zoom. See default_zoom. */+ public void set_default_zoom () {+ int l, t, b, r;+ + double bottom = 0;+ double top = 0;+ double left = 0;+ double right = 0;+ + unowned List<Line>? n = horizontal_help_lines;+ unowned List<Line>? v = vertical_help_lines;+ + if (unlikely (n == null)) {+ warning ("n == null");+ warning (@"Can not set default zoom for $name, help lines are not available.");+ }+ + return_if_fail (v != null);+ return_if_fail (vertical_help_lines.length () != 0);+ return_if_fail (horizontal_help_lines.length () != 0);++ reset_zoom ();+ + bottom = -get_lower_line ().get_pos ();+ top = -get_upper_line ().get_pos ();++ left = vertical_help_lines.last ().data.get_pos ();+ right = vertical_help_lines.first ().data.get_pos ();+ + l = reverse_path_coordinate_x (left);+ t = reverse_path_coordinate_y (top);+ r = reverse_path_coordinate_x (right); + b = reverse_path_coordinate_y (bottom);+ + set_zoom_area (l + 10, t - 10, r - 10, b + 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 path_list) {+ if (p.points.length () > 0) {+ return false;+ }+ }+ + return true;+ }+ + private 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;+ }+ + private void plot_outline (Context cr) {+ foreach (unowned Path p in path_list) {+ p.plot (cr, allocation, view_zoom);+ } + }+ + private void draw_coordinate (Context cr) {+ cr.set_source_rgba (0.5, 0.5, 0.5, 1);+ cr.set_font_size (12);+ cr.move_to (0, 10);+ cr.show_text (@"($motion_x, $motion_y)");+ cr.stroke (); + }+ + public void draw_path (Context cr) {+ double left, baseline;++ // plot_outline (cr);+ + baseline = get_line ("baseline").pos;+ left = get_line ("left").pos;+ + if (!is_open ()) {+ Svg.draw_svg_path (cr, get_svg_data (), Glyph.xc () + left, Glyph.yc () + baseline, SCALE);+ }+ + if (is_open ()) {+ foreach (unowned Path p in path_list) {+ p.draw_outline (cr, allocation, view_zoom);+ p.draw_edit_points (cr, allocation, view_zoom);+ }+ }++ if (!is_open ()) {+ foreach (unowned Path p in active_paths) {+ if (!p.is_editable ()) {+ p.fill_path (cr, allocation, view_zoom);+ }+ }+ }++ if (new_point_on_path != null) {+ Path.draw_edit_point_center ((!) new_point_on_path, cr);+ }+ }+ + private void draw_zoom_area(Context cr) {+ cr.save ();+ cr.set_line_width (2.0);+ cr.set_source_rgba (0, 0, 1, 0.3);+ cr.rectangle (Math.fmin (zoom_x1, zoom_x2), Math.fmin (zoom_y1, zoom_y2), Math.fabs (zoom_x1 - zoom_x2), Math.fabs (zoom_y1 - zoom_y2));+ cr.stroke ();+ cr.restore ();+ }++ private void draw_background_color (Context cr, double opacity) {+ cr.save ();+ cr.rectangle (0, 0, allocation.width, allocation.height);+ cr.set_line_width (0);+ cr.set_source_rgba (1, 1, 1, opacity);+ cr.fill ();+ cr.stroke ();+ cr.restore ();+ }+ + private void draw_help_lines (Context cr) {+ foreach (Line line in get_all_help_lines ()) {+ cr.save ();+ line.draw (cr, allocation);+ cr.restore ();+ }+ }+ + public override void draw (Allocation allocation, Context cr) {+ Tool tool;+ + this.allocation = allocation;++ ImageSurface ps = new ImageSurface (Format.ARGB32, allocation.width, allocation.height);+ Context cmp = new Context (ps);++ cr.save ();+ draw_background_color (cr, 1);+ cr.restore ();++ if (background_image != null && background_image_visible) {+ ((!)background_image).draw (cr, allocation, view_offset_x, view_offset_y, view_zoom);+ }+ + juxtapose (allocation, cr);++ if (Supplement.show_coordinates) {+ draw_coordinate (cmp);+ }++ if (show_help_lines) {+ cmp.save ();+ cmp.scale (view_zoom, view_zoom);+ cmp.translate (-view_offset_x, -view_offset_y);+ draw_help_lines (cmp);+ cmp.restore ();+ }++ if (zoom_area_is_visible) {+ cmp.save ();+ draw_zoom_area (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 ();+ + cr.save ();+ cr.set_source_surface (ps, 0, 0);+ cr.paint ();+ cr.restore ();+ } ++ private void zoom_in_at_point (double x, double y) {+ int n = -10;+ zoom_at_point (x, y, n);+ }+ + private void zoom_out_at_point (double x, double y) {+ int n = (int) (10.0 * ((allocation.width - 10.0) / allocation.width));+ zoom_at_point (x, y, n);+ }+ + /** 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);+ redraw_area (0, 0, allocation.width, allocation.height);+ }++ public void store_undo_state () {+ Glyph g = copy ();+ undo_list.append (g); + }++ public Glyph copy () {+ Glyph g = new Glyph.no_lines (name, unichar_code);+ + 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 ());+ }++ foreach (Path p in path_list) {+ g.add_path (p.copy ());+ }+ + foreach (Path p in active_paths) {+ g.active_paths.append (p);+ }++ if (background_image != null) {+ g.background_image = ((!) background_image).copy ();+ }+ + return g;+ }++ public void reload () {+ Glyph g;+ Font f = Supplement.get_current_font ();+ + if (f.has_glyph (name)) {+ set_glyph_data ((!) f.get_glyph (name));+ }+ }++ public override void undo () {+ Glyph g;+ + if (undo_list.length () == 0) {+ return;+ }+ + g = undo_list.last ().data; + set_glyph_data (g);+ + undo_list.remove_link (undo_list.last ());+ }+ + void set_glyph_data (Glyph g) {+ while (path_list.length () > 0) {+ path_list.remove_link (path_list.last ());+ }+ + foreach (Path p in g.path_list) {+ add_path (p);+ }++ 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 (p);+ }+ + open_path ();+ close_path ();+ + redraw_area (0, 0, allocation.width, allocation.height); + }+ + public ImageSurface get_thumbnail () {+ ImageSurface img;+ Context cr;+ double gx, gy;+ double x1, x2, y1, y2;+ Font font = Supplement.get_current_font ();++ remove_empty_paths ();+ boundries (out x1, out y1, out x2, out y2);++ if (x2 - x1 < 1 || y2 - y1 < 1) { // create an empty thumbnail + img = new ImageSurface (Format.ARGB32, 100, 100);+ } else {+ img = new ImageSurface (Format.ARGB32, (int) (x2 - x1), (int) (y2 - y1));+ }+ + gx = left_limit - x1;+ gy = (y2 - y1) + font.base_line + y1;+ + cr = new Context (img);+ + Svg.draw_svg_path (cr, get_svg_data (), gx, gy, 1.0); + + return img;+ }++ /** Split curve in two parts and add a new point in between. */+ public void insert_new_point_on_path (double x, double y) {+ double min, distance;+ Path? p = null;+ 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;+ + delete_invisible_paths ();+ + foreach (Path pp in path_list) {+ 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;+ }++ lep = new EditPoint ();+ ((!)p).get_closest_point_on_path (lep, xt, yt);+ ((!)p).insert_new_point_on_path (lep);+ }+ + /** Merge selected paths. */+ public void merge_all () {+ store_undo_state ();+ delete_invisible_paths ();+ + if (active_paths.length () < 2) {+ return;+ }+ + foreach (Path p0 in active_paths) {+ merge_path (p0);+ }+ + //open_path ();+ close_path ();++ foreach (Path p in path_list) {+ p.update_region_boundries ();+ } + + redraw_area (0, 0, allocation.width, allocation.height);+ }+ + public bool merge_path (Path p0) {+ Path mp;+ PathList path_list;+ + foreach (Path p1 in active_paths) {+ if (p0 == p1) {+ p0 = p1;+ continue;+ }+ + path_list = Path.merge (p1, p0);++ if (path_list.paths.length () == 0) {+ p0 = p1;+ continue;+ }+ + mp = path_list.paths.first ().data;++ delete_path (p0);+ delete_path (p1);+ + // add new path + it's counter paths+ foreach (Path p in path_list.paths) {+ p.close (); + add_path (p.copy ());+ }+ + add_active_path (mp);+ + active_paths.remove_all (p0);+ active_paths.remove_all (p1);++ p0 = mp;+ + mp.create_list ();+ + return true;+ }+ + return false;+ }++ public void juxtapose (Allocation allocation, Context cr) {+ string glyph_sequence = Preferences.get ("glyph_sequence");+ unichar c;+ Font font = Supplement.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;+ + x = 0;+ + current.append_unichar (glyph.unichar_code);+ pos = glyph_sequence.index_of (current.str);+ + baseline = glyph.get_line ("baseline").pos;+ 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 = font.get_name_for_character (c); + juxtaposed = (font.has_glyph (name)) ? (!) font.get_glyph (name) : font.get_space ();+ + if (font.has_glyph (last_name) && font.has_glyph (name)) {+ kern = font.get_kerning (last_name, name);+ } else {+ kern = 0;+ }++ if (!juxtaposed.is_empty ()) {+ cr.save ();+ cr.scale (glyph.view_zoom, glyph.view_zoom);+ cr.translate (-glyph.view_offset_x, -glyph.view_offset_y);+ Svg.draw_svg_path (cr, juxtaposed.get_svg_data (), Glyph.xc () + left + x + kern, Glyph.yc () + baseline, Glyph.SCALE);+ cr.restore ();+ }+ + x += juxtaposed.get_width () + font.get_kerning (glyph.name, name) + 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 = font.get_name_for_character (c); + juxtaposed = (font.has_glyph (name)) ? (!) font.get_glyph (name) : font.get_space ();+ + if (font.has_glyph (last_name) && font.has_glyph (name)) {+ kern = font.get_kerning (name, last_name);+ } else {+ kern = 0;+ }+ + x -= juxtaposed.get_width ();+ x -= kern;+ + if (!juxtaposed.is_empty ()) {+ cr.save ();+ cr.scale (glyph.view_zoom, glyph.view_zoom);+ cr.translate (-glyph.view_offset_x, -glyph.view_offset_y);++ Svg.draw_svg_path (cr, juxtaposed.get_svg_data (), Glyph.xc () + left + x, Glyph.yc () + baseline, Glyph.SCALE);+ cr.restore ();+ }+ + last_name = name;+ } + }+}++}new file mode 100644@@ -0,0 +1,793 @@+/*+ 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;+using Math;+using Gdk;++namespace Supplement {+ +public class GlyphBackgroundImage {+ + public double img_x = 0;+ public double img_y = 0;+ + public double img_scale_x = 1;+ public double img_scale_y = 1;+ public double img_rotation = 0;+ private int size = -1;++ public int active_handle = -1;+ public int selected_handle = -1;+ + private ImageSurface? background_image = null;+ private ImageSurface? original_image = null;+ + private string path;++ private double contrast = 1.0;+ private bool desaturate = false;+ private double threshold = 1.0;+ + private bool background_image_is_processing = false;++ public signal void updated ();++ public double img_offset_x {+ get { return img_x + Glyph.xc (); }+ set { img_x = value - Glyph.xc (); }+ }++ public double img_offset_y {+ get { return Glyph.yc () - img_y; }+ set { img_y = Glyph.yc () - value; }+ }+ + public int size_margin {+ get {+ if (unlikely (size == -1)) {+ size = (int) (Math.sqrt (Math.pow (get_img ().get_height (), 2) + Math.pow (get_img ().get_width (), 2)) + 0.5);+ }+ + return size;+ }+ }+ + public GlyphBackgroundImage (string fn) {+ path = fn;+ }++ public GlyphBackgroundImage copy () {+ GlyphBackgroundImage bg = new GlyphBackgroundImage (path);+ + bg.img_x = img_x;+ bg.img_y = img_y;++ bg.img_scale_x = img_scale_x;+ bg.img_scale_y = img_scale_y;+ bg.img_rotation = img_rotation;++ bg.contrast = contrast;+ bg.desaturate = desaturate;+ bg.threshold = threshold;++ return bg;+ + }++ public double get_margin_width () {+ return ((size_margin - get_img ().get_width ()) / 2.0);+ }++ public double get_margin_height () {+ return ((size_margin - get_img ().get_height ()) / 2.0);+ }++ public void set_img_offset (double x, double y) {+ img_offset_x = x;+ img_offset_y = y;+ }++ public void set_position (double coordinate_x, double coordinate_y) {+ img_x = coordinate_x;+ img_y = coordinate_y;+ }+ + public ImageSurface get_img () {+ if (path.index_of (".png") == -1) {+ create_png ();+ }+ + if (background_image == null) {+ background_image = new ImageSurface.from_png (path);+ original_image = new ImageSurface.from_png (path);+ }+ + return (!) background_image;+ }++ public bool is_valid () {+ FileInfo file_info;+ File file = File.new_for_path (path);+ + if (!file.query_exists ()) {+ return false;+ }+ + try {+ file_info = file.query_info ("*", FileQueryInfoFlags.NONE);+ + if (file_info.get_size () == 0) {+ return false;+ }+ } catch (GLib.Error e) {+ warning (e.message);+ return false;+ }+ + return true;+ }++ public string get_png_base64 () {+ try {+ File file = File.new_for_path (path);+ FileInfo file_info = file.query_info ("*", FileQueryInfoFlags.NONE);+ uint8[] buffer = new uint8[file_info.get_size ()];+ FileInputStream file_stream;+ DataInputStream png_stream;+ + if (!file.query_exists ()) {+ warning (@"Failed to save image $path, file does not exist.");+ return "";+ }+ + if (is_null (buffer)) {+ warning (@"Colud not allocate a buffer of $(file_info.get_size ()) bytes to store $path.");+ return "";+ }+ + file_stream = file.read ();+ + png_stream = new DataInputStream (file_stream);+ png_stream.read (buffer);+ + return Base64.encode (buffer);+ } catch (GLib.Error e) {+ warning (e.message);+ }+ + return "";+ }++ public string get_sha1 () {+ try {+ File file = File.new_for_path (path);+ FileInfo file_info;+ uint8[] buffer;+ FileInputStream file_stream;+ DataInputStream png_stream;+ + if (!file.query_exists ()) {+ warning (@"Can't save $path file does not exist.");+ return "";+ }+ + file_info = file.query_info ("*", FileQueryInfoFlags.NONE);+ + if (file_info.get_size () == 0) {+ warning (@"length of image $path is zero");+ return "";+ }+ + buffer = new uint8[file_info.get_size ()];+ file_stream = file.read ();+ png_stream = new DataInputStream (file_stream);++ png_stream.read (buffer);+ + return Checksum.compute_for_data (ChecksumType.SHA1, buffer);+ } catch (GLib.Error e) {+ warning (e.message);+ }+ + return "";+ }+ + private void create_png () {+ string fn = @"$path.png";+ + Font font = Supplement.get_current_font ();+ File folder = font.get_backgrounds_folder ();+ File original = File.new_for_path (fn);+ File png_image = folder.get_child (@"full_$((!)original.get_basename ())");++ Pixbuf pixbuf;+ + if (png_image.query_exists ()) {+ path = (!) png_image.get_path ();+ return;+ }+ + try {+ pixbuf = new Pixbuf.from_file (path);+ pixbuf.save ((!) png_image.get_path (), "png");+ path = (!) png_image.get_path ();+ } catch (GLib.Error e) {+ warning (e.message);+ }+ }+ + public void set_img_rotation_from_coordinate (double x, double y) {+ double bcx, bcy;+ double a, b, c, length;++ img_center (out bcx, out bcy);++ a = bcx - x;+ b = bcy - y;+ c = a * a + b * b;+ + if (c == 0) {+ return;+ }+ + length = sqrt (fabs (c)); + + if (c < 0) {+ length = -length;+ }+ + img_rotation = (y < bcy) ? acos (a / length) + PI : -acos (a / length) + PI;+ }+ + public void set_img_scale (double xs, double ys) {+ img_scale_x = xs;+ img_scale_y = ys;+ }++ public void reset_scale (Glyph g) {+ double w, h;++ w = g.get_width ();+ h = g.get_height ();++ img_scale_x = 1; + img_scale_y = 1;+ + img_offset_x = g.get_line ("left").pos;+ img_offset_y = g.get_line ("top").pos; + }++ public void draw (Context cr, Allocation allocation, double view_offset_x, double view_offset_y, double view_zoom) {+ double x, y;+ double iw, ih;+ int h, w;+ double scale_x, scale_y;+ double oy, ox;+ + Surface s;+ Context c;++ Surface sg;+ Context cg;++ Surface st;+ Context ct;++ double wc, hc;++ if (unlikely (get_img ().status () != Cairo.Status.SUCCESS)) {+ stderr.printf (@"Background image is invalid. (\"$path\")\n");+ MainWindow.get_current_glyph ().set_background_visible (false);+ return;+ }+ + // add margin+ sg = new Surface.similar (get_img (), get_img ().get_content (), size_margin, size_margin);+ cg = new Context (sg);+ + wc = get_margin_width ();+ hc = get_margin_height ();+ + cg.set_source_rgba (1, 1, 1, 1);+ cg.rectangle (0, 0, size_margin, size_margin);+ cg.fill ();+ + cg.set_source_surface (get_img (), wc, hc);+ cg.paint ();++ x = Glyph.reverse_path_coordinate_x (img_offset_x);+ y = Glyph.reverse_path_coordinate_y (img_offset_y);+ + ih = (int) get_img ().get_height ();+ iw = (int) get_img ().get_width ();+ + w = (int) iw;+ h = (int) ih;++ oy = size_margin;+ ox = size_margin;++ // rotate image+ s = new Surface.similar (sg, sg.get_content (), (int) (ox), (int) (oy));+ c = new Context (s);+ + c.save ();++ c.set_source_rgba (1, 1, 1, 1);+ c.rectangle (0, 0, size_margin, size_margin);+ c.fill ();+ + c.translate (size_margin * 0.5, size_margin * 0.5);+ c.rotate (img_rotation);+ c.translate (-size_margin * 0.5, -size_margin * 0.5);++ c.set_source_surface (cg.get_target (), 0, 0);+ c.paint (); + c.restore ();++ // mask+ st = new Surface.similar (s, s.get_content (), allocation.width, allocation.height);+ ct = new Context (st);+ ct.save ();++ ct.set_source_rgba (1, 1, 1, 1);+ ct.rectangle (0, 0, allocation.width, allocation.height);+ ct.fill ();++ // scale both canvas and image at the same time; rather tricky.+ scale_x = view_zoom * img_scale_x;+ scale_y = view_zoom * img_scale_y;+ + ct.scale (scale_x, scale_y);+ ct.translate (-view_offset_x / img_scale_x, -view_offset_y / img_scale_y);+ + ct.set_source_surface (s, img_offset_x / img_scale_x, img_offset_y / img_scale_y);++ ct.paint ();+ ct.restore ();+ + // append it+ cr.save ();+ cr.set_source_surface (st, 0, 0);+ cr.paint ();+ cr.restore ();+ }+ + public void handler_release (double nx, double ny) { + selected_handle = 0;+ handler_move (nx, ny);+ }+ + public void handler_press (double nx, double ny) { + if (is_over_rotate (nx, ny)) {+ selected_handle = 2;+ } else if (is_over_resize (nx, ny)) {+ selected_handle = 1;+ } else {+ selected_handle = 0;+ } + }++ void img_center (out double x, out double y) {+ Glyph g = MainWindow.get_current_glyph ();+ + x = img_offset_x - g.view_offset_x + (size_margin / 2) * img_scale_x;+ y = img_offset_y - g.view_offset_y + (size_margin / 2) * img_scale_y;++ y *= g.view_zoom;+ x *= g.view_zoom;+ }++ bool is_over_rotate (double nx, double ny) {+ double x, y, d;++ img_center (out x, out y);++ x += cos (img_rotation) * 75;+ y += sin (img_rotation) * 75;++ d = sqrt (pow (x - nx, 2) + pow (y - ny, 2));+ + return d < 10;+ }++ bool is_over_resize (double nx, double ny) {+ Glyph g = MainWindow.get_current_glyph ();+ double x, y, size;+ bool inx, iny;++ size = 12 * g.view_zoom;++ x = img_offset_x - g.view_offset_x;+ y = img_offset_y - g.view_offset_y + (size_margin) * img_scale_y;+ + y *= g.view_zoom;+ x *= g.view_zoom;+ + inx = x - size <= nx <= x + size;+ iny = y - size <= ny <= y + size;+ + return inx && iny;+ }++ public void handler_move (double nx, double ny) {+ int prev_handle = active_handle;++ if (is_over_rotate (nx, ny)) {+ active_handle = 2;+ } else if (is_over_resize (nx, ny)) {+ active_handle = 1;+ } else {+ active_handle = 0;+ }+ + if (prev_handle != active_handle) {+ MainWindow.get_glyph_canvas ().redraw ();+ }+ }+ + public void draw_handle (Context cr, Glyph g) {+ draw_resize_handle (cr, g);+ draw_rotate_handle (cr, g);+ }++ public void draw_resize_handle (Context cr, Glyph g) {+ double x, y;+ cr.save ();+ + cr.set_source_rgba (1, 0, 0.3, 1);++ x = img_offset_x - g.view_offset_x;+ y = img_offset_y - g.view_offset_y + (size_margin) * img_scale_y;+ + y *= g.view_zoom;+ x *= g.view_zoom;+ + draw_handle_triangle (x, y, cr, g, 6);+ + cr.restore ();+ }+ + public void draw_rotate_handle (Context cr, Glyph g) {+ double x, y, hx, hy, x2, y2;+ + double ivz = 1.0 / (g.view_zoom);+ + cr.save ();+ + cr.scale (g.view_zoom, g.view_zoom);+ + if (selected_handle == 2) cr.set_source_rgba (1, 0, 0.3, 1);+ else if (active_handle == 2) cr.set_source_rgba (0, 0, 0.3, 1);+ else cr.set_source_rgba (0.7, 0.7, 0.8, 1);++ x = img_offset_x - g.view_offset_x + (size_margin / 2) * img_scale_x;+ y = img_offset_y - g.view_offset_y + (size_margin / 2) * img_scale_y;+ + cr.rectangle (x, y, 5 * ivz, 5 * ivz);+ cr.fill ();++ hx = cos (img_rotation) * 75 * ivz;+ hy = sin (img_rotation) * 75 * ivz;+ + x2 = x + hx;+ y2 = y + hy;++ cr.rectangle (x2, y2, 5 * ivz, 5 * ivz);+ cr.fill ();++ cr.set_line_width (ivz);+ cr.move_to (x + 2.5* ivz, y + 2.5* ivz);+ cr.line_to (x2 + 2.5* ivz, y2 + 2.5* ivz);+ cr.stroke ();+ + cr.restore ();+ }++ void draw_handle_triangle (double x, double y, Context cr, Glyph g, int direction, double s = 1) + requires (0 < direction < 8)+ {+ double ivz = 1.0 / (g.view_zoom);+ double size;+ + cr.save ();+ cr.set_line_width (ivz);+ + if (selected_handle == 1) cr.set_source_rgba (1, 0, 0.3, 1);+ else if (active_handle == 1) cr.set_source_rgba (0, 0, 0.3, 1);+ else cr.set_source_rgba (0.7, 0.7, 0.8, 1); ++ size = (8) * s;+ + cr.scale (1, 1);+ cr.new_path ();+ + // up + left+ if (direction == 1) {+ cr.move_to (x - size, y - size);+ cr.line_to (x + size, y - size);+ cr.line_to (x - size, y + size); + }+ + if (direction == 6) {+ cr.move_to (x + size, y + size); + cr.line_to (x - size, y + size);+ cr.line_to (x - size, y - size); + }+ + cr.close_path();+ cr.fill ();++ cr.restore ();+ + }++ private static uchar ftu (double c) {+ if (c < 0) return 0;+ if (c > 255) return 255;+ return (uchar) c;+ }++ private static ImageSurface? process_background_colors (ImageSurface img, double contrast, bool desaturate, double threshold) {+ int i;+ unowned uchar[]? pix_buff_t;+ unowned uchar[] pix_buff;+ int len;+ uchar[] contrast_img;+ ImageSurface contrast_surface;+ double c, thres;+ uchar nc;++ int w = img.get_width();+ int h = img.get_height();++ if (unlikely (img.status () != Cairo.Status.SUCCESS)) {+ warning ("Err");+ return null;+ }+ + len = 4 * w * h;+ + pix_buff_t = img.get_data ();+ return_if_fail (pix_buff_t != null);+ pix_buff = pix_buff_t;+ + contrast_img = new uchar[len];++ pix_buff_t = contrast_img;+ return_if_fail (pix_buff_t != null);+ + contrast += 70;+ contrast /= 71;+ + if (desaturate) {+ i = 0;+ + while (i < len) {+ c = (pix_buff[i] + pix_buff[i+1] + pix_buff[i+2]) / 3.0;+ nc = ftu (Math.pow (c, contrast));++ contrast_img[i] = nc;+ contrast_img[i+1] = nc; + contrast_img[i+2] = nc;+ contrast_img[i+3] = 255;+ + i += 4;+ }+ } else {+ i = 0;+ while (i < len) {+ contrast_img[i] = ftu (Math.pow (pix_buff[i], contrast) - Math.pow (100, contrast) + 100);+ contrast_img[i+1] = ftu (Math.pow (pix_buff[i+1], contrast) - Math.pow (100, contrast) + 100);+ contrast_img[i+2] = ftu (Math.pow (pix_buff[i+2], contrast) - Math.pow (100, contrast) + 100);+ contrast_img[i+3] = 255;+ + i += 4;+ }+ }++ if (true) {+ thres = (threshold - 0.5) * 255;+ + i = 0;+ while (i < len) {+ c = (pix_buff[i] + pix_buff[i+1] + pix_buff[i+2]) / 3.0;+ nc = (c > thres) ? 255 : 0;++ contrast_img[i] = nc;+ contrast_img[i+1] = nc; + contrast_img[i+2] = nc;+ contrast_img[i+3] = 255;+ + i += 4;+ }+ }++ contrast_surface = new ImageSurface.for_data (contrast_img, img.get_format(), w, h, img.get_stride());++ if (unlikely (contrast_surface.status () != Cairo.Status.SUCCESS)) {+ warning ("Err");+ return null;+ }+ + contrast_surface.mark_dirty ();+ + return contrast_surface; + }++ async ImageSurface? do_process_background_colors () {+ SourceFunc callback = do_process_background_colors.callback;+ ImageSurface? output = null;+ double contrast = get_contrast ();+ bool desaturate = get_desaturate_background ();+ double threshold = this.threshold;++ if (original_image == null) {+ return null;+ }+ + // Fixa: Add multi threading+ //ThreadFunc<void*> run = () => {+ output = process_background_colors ((!)original_image, contrast, desaturate, threshold);+ Idle.add ((owned) callback);+ //return null;+ //};+ + try {+ //Thread.create<void*> (run, false);+ yield;+ } catch (ThreadError e) {+ stderr.printf ("Thread error: %s\n", e.message);+ }++ return output;+ }++ public void update_background (double contrast, bool desaturate) {+ background_image_is_processing = true;+ + do_process_background_colors.begin ((obj, res) => {+ ImageSurface? img;++ img = do_process_background_colors.end (res);+ + if (img != null) {+ background_image = (!) img;+ updated ();+ } else {+ stderr.printf ("Error: No background image"); + }+ + // update image if new value was set while background task was running + if (get_contrast () != contrast || get_desaturate_background () != desaturate) {++ do_process_background_colors.begin ((obj, res) => {+ img = do_process_background_colors.end (res);+ if (likely (img != null)) {+ background_image = (!) img;+ updated ();+ } else {+ stderr.printf ("Error: No background image"); + }+ });+ }++ background_image_is_processing = false;+ updated ();+ + });+ }++ public double get_contrast () {+ double bc;+ lock (contrast) {+ bc = contrast;+ }+ return bc;+ }+ + public bool get_desaturate_background () {+ bool bw;+ lock (desaturate) {+ bw = desaturate;+ }+ return bw;+ }+ + public void set_desaturate_background (bool bw) {+ lock (desaturate) {+ desaturate = bw;+ }+ + if (background_image_is_processing) {+ return;+ }+ + background_image_is_processing = true;+ + update_background (get_contrast (), bw);+ }+ + public void set_threshold (double t) {+ threshold = t;+ }+ + public Path auto_trace () {+ ImageSurface img;+ int len, w, h, i;+ unowned uchar[] pix_buff;+ Path p = new Path ();+ double x, y;++ if (background_image == null) {+ return p;+ }++ img = (!) background_image;++ w = img.get_width();+ h = img.get_height();++ if (unlikely (img.status () != Cairo.Status.SUCCESS)) {+ warning ("Err");+ return p;+ }+ + len = 4 * w * h;++ pix_buff = img.get_data ();++ i = 0;+ while (i < len - 4) {+ if (is_edge (pix_buff[i], pix_buff[i + 4])) {+ y = i / w;+ x = i - y;+ + p.add (x, y);+ + if (p.points.length () > 10) {+ return p;+ }+ }+ + i += 4;+ }+ + return p;+ }+ + private bool is_edge (uchar a, uchar b) {+ return (a == 255 && b == 0);+ }+ + public void set_contrast (double contrast) {+ lock (this.contrast) {+ this.contrast = contrast;+ }+ + if (background_image_is_processing) {+ return;+ }+ + background_image_is_processing = true;+ + update_background (contrast, get_desaturate_background ());+ }+}+ +}new file mode 100644@@ -0,0 +1,82 @@+/* + 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; +using Math; + +namespace Supplement { + +public class GlyphCanvas : GLib.Object { + + public FontDisplay current_display; + public signal void signal_redraw_area (int x, int y, int w, int h); + public Allocation allocation; + + public GlyphCanvas () { + } + + public void key_release (uint e) { + current_display.key_release (e); + } + + public void key_press (uint e) { + current_display.key_press (e); + } + + public void set_current_glyph (FontDisplay fd) { + if (fd is Glyph) { + Glyph g = (Glyph) fd; + + Supplement.current_glyph = g; + Supplement.current_glyph.resized (); + + g.allocation = allocation; + + warn_if_fail (g.allocation.width != 0 && g.allocation.height != 0); + } + + current_display = fd; + + fd.selected_canvas (); + + fd.redraw_area.connect ((x, y, w, h) => { + signal_redraw_area ((int)x, (int)y, (int)w, (int)h); + }); + + redraw (); + + MainWindow.native_window.update_window_size (); + } + + public static Glyph get_current_glyph () { + return Supplement.current_glyph; + } + + public FontDisplay get_current_display () { + return current_display; + } + + public void redraw_area (int x, int y, int w, int h) { + signal_redraw_area (x, y, w, h); + } + + public void redraw () { + signal_redraw_area (0, 0, allocation.width, allocation.height); + } +} + +} new file mode 100644@@ -0,0 +1,52 @@+/*+ 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;+using Math;++namespace Supplement {++public class GlyphCollection : GLib.Object {+ VersionList versions;+ + public GlyphCollection (Glyph? current = null) {+ versions = new VersionList (current);+ }+ + public VersionList get_version_list () {+ return versions;+ }+ + public Glyph get_current () {+ return versions.get_current ();+ }+ + public void insert_glyph (Glyph g, bool selected) {+ versions.add_glyph (g, selected); + assert (versions.glyphs.length () > 0);+ }+ + public uint length () {+ return versions.glyphs.length ();+ }+ + public string get_name () {+ return get_current ().get_name ();+ }+}+ +}new file mode 100644@@ -0,0 +1,262 @@+/*+ 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/>.+*/++namespace Supplement {++public class GlyphRange {+ + List<UniRange> ranges;+ + public unowned List<string> unassigned = new List<string> ();+ + uint32 len = 0;+ + public GlyphRange () {+ }+ + public unowned List<UniRange> get_ranges () {+ return ranges;+ }+ + public void use_default_range () {+ /// All lower case letters in alphabetic order separated by space+ string lower_case = _("a b c d e f g h i j k l m n o p q r s t u v w x y z");+ + /// All upper case letters in alphabetic order separated by space+ string upper_case = _("A B C D E F G H I J K L M N O P Q R S T U V W X Y Z");++ foreach (string c in lower_case.split (" ")) {+ add_single (c.get_char ());+ }++ foreach (string c in upper_case.split (" ")) {+ add_single (c.get_char ());+ }+ + add_range ('0', '9');+ + add_single (' '); // TODO: add all spaces here.+ + add_single ('.');+ add_single ('?');+ + add_single (',');+ + add_single ('’');++ add_range ('“', '”');++ add_single ('&');+ + add_range (':', ';');+ + add_single ('/'); + + add_range ('!', '/');+ + add_single ('-');+ add_range ('‐', '—');+ add_range ('<', '@');+ add_range ('(', ')');+ }+ + public void use_full_unicode_range () {+ add_range ('\0', (unichar) 0xFFF8);+ }+ + // Todo: complete localized alphabetical sort åäö is the right order for example.+ public void sort () {+ ranges.sort ((a, b) => {+ bool r = a.start > b.start;+ + return_if_fail (a.start != b.start);+ + return (r) ? 1 : -1;+ });+ }+ + public void add_single (unichar c) {+ add_range (c, c);+ }+ + public uint32 get_length () {+ unichar l = len;+ l += unassigned.length ();+ return l;+ }+ + public void add_range (unichar start, unichar stop) {+ unichar b, s;+ if (unique (start, stop)) {+ append_range (start, stop);+ } else {+ + // make sure this range does not overlap existing ranges+ b = start;+ s = b;+ if (!unique (b, b)) { + while (b < stop) {+ if (!unique (b, b)) {+ b++;+ } else {+ if (s != b) {+ add_range (b, stop);+ }+ + b++;+ s = b;+ }+ }+ } else {+ while (b < stop) {+ if (unique (b, b)) {+ b++;+ } else {+ if (s != b) {+ add_range (start, b - 1);+ }+ + b++;+ s = b;+ }+ } + }+ }+ }+ + private void append_range (unichar start, unichar stop) {+ UniRange r;+ + StringBuilder s = new StringBuilder ();+ StringBuilder e = new StringBuilder ();+ s.append_unichar (start);+ e.append_unichar (stop);+ + r = insert_range (start, stop); // insert a unique range+ merge_range (r); // join connecting ranges+ }+ + private void merge_range (UniRange r) {+ foreach (UniRange u in ranges) {+ if (u == r) {+ continue;+ } + + if (u.start == r.stop + 1) {+ u.start = r.start;+ ranges.remove_all (r);+ merge_range (u);+ }+ + if (u.stop == r.start - 1) {+ u.stop = r.stop;+ ranges.remove_all (r);+ merge_range (u);+ }+ }+ }++ public string get_char (uint32 index) {+ int64 ti;+ string chr;+ UniRange r;+ StringBuilder sb;+ unichar c;+ + if (index > len + unassigned.length ()) { + return "\0".dup();+ }+ + if (index >= len) {+ + if (index - len >= unassigned.length ()) {+ return "\0".dup();+ } + + chr = ((!) unassigned.nth (index - len)).data;+ return chr;+ }++ r = ranges.first ().data;+ ti = index;++ foreach (UniRange u in ranges) {+ ti -= u.length ();+ + if (ti < 0) {+ r = u;+ break;+ }+ }+ + sb = new StringBuilder ();+ c = r.get_char ((unichar) (ti + r.length ()));+ sb.append_unichar (c);++ return sb.str;+ }+ + public uint32 length () {+ return len;+ }++ private bool unique (unichar start, unichar stop) {+ foreach (UniRange u in ranges) {+ + if (inside (start, u.start, u.stop)) return false;+ if (inside (stop, u.start, u.stop)) return false;+ if (inside (u.start, start, stop)) return false;+ if (inside (u.stop, start, stop)) return false;+ + }++ return true;+ }++ private bool inside (unichar start, unichar u_start, unichar u_stop) {+ return (u_start <= start <= u_stop);+ }+ + private UniRange insert_range (unichar start, unichar stop) {+ if (unlikely (start > stop)) {+ warning ("start > stop");+ stop = start;+ }+ + UniRange ur = new UniRange (start, stop);+ len += ur.length ();+ ranges.append (ur);+ + return ur;+ }++ public void print_all () {+ StringBuilder s;+ + stdout.printf ("Ranges:\n");+ foreach (UniRange u in ranges) {+ s = new StringBuilder ();+ s.append_unichar (u.start);+ s.append (" - ");+ s.append_unichar (u.stop);+ s.append ("\n");+ stdout.printf (s.str);+ }+ }+}++}new file mode 100644@@ -0,0 +1,412 @@+/*+ 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/>.+*/++namespace Supplement {++/** A sorted table of glyphs with search index. */+class GlyphTable : GLib.Object {+ List<Item> data = new List<Item> ();+ + List<RowItem> row1 = new List<RowItem> ();+ List<RowItem> row2 = new List<RowItem> ();+ List<RowItem> row3 = new List<RowItem> ();+ + public GlyphTable () {+ }++ public void remove_all () {+ while (data.length () > 0) {+ data.remove_link (data.first ());+ }+ }++ public void @for_each (Func<GlyphCollection> func) {+ data.foreach ((v) => {+ func (v.glyph_collection);+ });+ }++ unowned List<Item>? find (string n) {+ return find_index (n);+ }++ unowned List<Item>? find_next (string n) {+ return find_next_index (n);+ }+ + public void remove (string name) {+ unowned List<Item>? d = find (name);+ + if (d == null) {+ warning (@"did not find $name");+ return;+ }+ + data.remove_link ((!) d);+ build_index ();+ }++ public GlyphCollection? nth (uint i) {+ unowned List<Item>? d = data.nth (i);+ + if (d == null) {+ warning (@"no glyph for index $i");+ return null;+ }+ + return ((!) d).data.glyph_collection;+ }++ public uint length () {+ return data.length ();+ }++ public new GlyphCollection? @get (string name) {+ unowned List<Item>? d = find_index (name);+ + if (d == null) {+ return null;+ }+ + return ((!) d).data.glyph_collection;+ }++ public bool insert (GlyphCollection g) {+ Item item;+ unowned List<Item>? next;+ + if (g.get_name () == "") {+ warning (@"No proper name for glyph collection. g.get_name (): \"$(g.get_name ())\"");+ return false;+ }++ if (find (g.get_name ()) != null) {+ warning (@"Table does already contain character \"$(g.get_name ())\".");+ return false;+ }+ + next = find_next (g.get_name ());+ + item = new Item (g.get_name (), g);+ + if (next == null) {+ data.append (item);+ } else {+ data.insert_before ((!) next, item); // _sorted+ }+ + build_index ();+ + if (find (g.get_name ()) == null) {+ warning (@"Can not find glyph \"$(g.get_name ())\".");+ print_all ();+ return false;+ }+ + return true;+ }++ public void print_all () {+ int indice = 0;+ foreach (Item i in data) {+ print (@"$(i.name) indice: $(indice)\n");+ indice++;+ }+ + print ("Index:\n");+ print ("\n");+ print ("row3\n");+ foreach (RowItem i in row3) {+ print (@"$(i.item.data.name)\n");+ }+ + print ("\n");+ print ("row2\n");+ foreach (RowItem i in row2) {+ print (@"$(i.item.data.name)\n");+ }+ + print ("\n");+ print ("row1\n");+ foreach (RowItem i in row1) {+ print (@"$(i.item.data.name)\n");+ } + }++ static int compare (string an, Item b) {+ string? bt = b.glyph_collection.get_name ();+ string bn = (!) bt;+ + uint32 ac = 0;+ uint32 bc = 0;+ + int i1 = 0;+ int i2 = 0;+ + return_if_fail (bt != null);+ + int ca = an.char_count ();+ int cb = bn.char_count ();+ + if (ca != cb) {+ return ca < cb ? 1 : -1;+ }+ + while (true) {+ an.get_next_char (ref i1, out ac);+ bn.get_next_char (ref i2, out bc);+ + if (ac == bc && ac != 0) {+ continue;+ }+ + if (ac == bc) return 0;+ + return (ac < bc) ? 1 : -1;+ }+ }+ + public void build_index () {+ uint len;+ RowItem r;+ + while (row1.length () > 0) {+ row1.remove_link (row1.first ());+ }++ while (row2.length () > 0) {+ row2.remove_link (row2.first ());+ }++ while (row3.length () > 0) {+ row3.remove_link (row3.first ());+ }++ if (data.length () == 0) {+ return;+ }+ + // row 1+ len = data.length () / 5;+ if (len == 0) len++;+ for (int i = 0; i < len; i++) {+ r = new RowItem ();+ r.item = data.nth (i * 5);+ r.level = 1;+ row1.append (r);+ }+ r = new RowItem ();+ r.item = data.last ();+ r.level = 1;+ r.last = true;+ row1.append (r); + + // row 2+ len = row1.length () / 5;+ if (len == 0) len++;+ for (int i = 0; i < len; i++) {+ r = new RowItem ();+ r.next_row = row1.nth (i * 5);+ r.item = r.next_row.data.item; + r.level = 2;+ row2.append (r);+ }+ r = new RowItem ();+ r.item = data.last ();+ r.level = 2;+ r.next_row = row1.last (); + r.last = true;+ row2.append (r);+ + // row 3+ len = row2.length () / 5;+ if (len == 0) len++;+ for (int i = 0; i < len; i++) {+ r = new RowItem ();+ r.next_row = row2.nth (i * 5);+ r.item = r.next_row.data.item; + r.level = 3;+ row3.append (r);+ }+ r = new RowItem ();+ r.item = data.last ();+ r.level = 3;+ r.next_row = row2.last ();+ r.last = true; + row3.append (r);+ }++ unowned List<Item>? find_next_index (string n) {+ unowned List<Item>? next = find_next_via_index (n);+ return next;+ }++ unowned List<Item>? find_index (string n) {+ unowned List<Item>? next = find_next_via_index (n);+ + if (next == null) {+ return null;+ }+ + if (compare (n, ((!)next).data) == 0) {+ return next;+ }+ + if (((!)next) != data.first () && compare (n, ((!)next).prev.data) == 0) {+ return ((!)next).prev; // fetch previous since we are searching for the next one+ }+ + return null; + }+ + unowned List<Item>? find_next_via_index (string n) {+ unowned List<Item> i = data.first ();+ unowned List<RowItem> r;+ int cmp;+ + if (data.length () == 0) {+ return null;+ }++ r = row3.first ();+ int ti = 0;+ while (true) {+ cmp = compare (n, r.data.item.data);+ + ti++;+ + if (cmp == 1) {+ break;+ }+ + if (cmp == 0) {+ return r.data.item;+ }+ + if (r != row3.last ()) {+ r = r.next;+ } else {+ return null;+ }+ } + r = r.data.next_row;++ while (true) {+ cmp = compare (n, r.data.item.data);+ if (cmp == -1) {+ break;+ }++ if (cmp == 0) {+ return r.data.item;+ }+ + if (r == row2.first ()) {;+ return r.data.item;+ } else r = r.prev;+ }+ r = r.data.next_row;+ + while (true) {+ cmp = compare (n, r.data.item.data);+ if (cmp == 1) {+ break;+ }+ + if (cmp == 0) {+ return r.data.item;+ }+ + if (r == row1.last ()) return null;+ else r = r.next;+ }+ i = r.data.item;+ + + while (true) {+ cmp = compare (n, i.data);++ if (cmp == -1) {+ // null?+ return i.next;+ }++ if (cmp == 0) {+ return i;+ }+ + if (i == data.first ()) {+ return i;+ } else { + i = i.prev;+ }+ } + }++ public bool validate_index () {+ int cmp;+ Item prev;+ int index;+ + if (data.length () == 0) {+ return true;+ }+ + prev = data.first ().data;+ index = 0;+ foreach (Item i in data) {+ cmp = compare (prev.name, i);+ + if (cmp == 1) {+ continue;+ }+ + if (cmp == 0 && index != 0) {+ warning (@"Value is not unique: $(i.name).");+ return false;+ }++ if (cmp == -1) {+ warning (@"Value is not sorted: $cmp == cmp $(prev.name) $(i.name).");+ return false;+ }+ + index++;+ }+ + return true;+ }++ class Item : GLib.Object {+ public string name;+ public GlyphCollection glyph_collection;+ + public Item (string n, GlyphCollection gc) {+ glyph_collection = gc;+ name = n;+ }+ }++ class RowItem : GLib.Object {+ public unowned List<RowItem> next_row;+ public unowned List<Item> item;+ public int level = 0;+ public bool last = false;+ }+}++}new file mode 100644@@ -0,0 +1,244 @@+/*+ 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;+using Math;++namespace Supplement {++class GridTool : Tool {+ + static List <Line> horizontal;+ static List <Line> vertical;+ + static bool visible = false;+ + public static double size_x;+ public static double size_y;+ + public static List <SpinButton> sizes;+ + public GridTool (string n) {+ base (n, _("Show grid"), 'g', NONE);+ + horizontal = new List <Line> ();+ vertical = new List <Line> ();+ + sizes = new List <SpinButton> ();+ + size_x = 2;+ size_y = 2;+ + updata_lines ();+ + select_action.connect((self) => {+ visible = !visible;+ updata_lines ();+ MainWindow.get_glyph_canvas ().redraw ();+ + self.set_selected (visible);+ + Toolbox tb = MainWindow.get_toolbox ();+ Tool t = tb.get_tool ("help_lines");+ + if (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) {+ size_x = w;+ size_y = w;+ + updata_lines ();+ }+ + private static void updata_lines () {+ Glyph g = MainWindow.get_current_glyph ();+ double step = size_y * Glyph.SCALE;+ double i;+ int max_lines = 400;+ 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");++ while (vertical.length () > 0) vertical.delete_link (vertical.first ());+ while (horizontal.length () > 0) horizontal.delete_link (horizontal.first ());+ + 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 (0.2, 0.6, 0.2, 0.2);+ horizontal.append (l);+ + if (++n >= max_lines) {+ break;+ }+ }+ + n = 0;+ for (i = baseline.get_pos () - step; i >= top_margin.get_pos () - 7 * step; i -= step) {+ t = new Line ("grid", i, Line.HOROIZONTAL);+ t.set_moveable (false);+ t.set_color (0.2, 0.6, 0.2, 0.2);+ vertical.append (t); ++ if (++n >= max_lines) {+ break;+ }++ }+ + for (i = baseline.get_pos (); i <= bottom_margin.get_pos () + 7 * step; i += step) {+ u = new Line ("grid", i, Line.HOROIZONTAL);+ u.set_moveable (false);+ u.set_color (0.2, 0.6, 0.2, 0.2);+ vertical.append (u); ++ if (++n >= max_lines) {+ break;+ }++ } + }+ + /** Sets x and y the point closest on grid. */+ public static void tie (ref double x, ref double y) {+ tie_point (ref x, ref y, false);+ }++ public static void tie_coordinate (ref double x, ref double y) {+ tie_point (ref x, ref y, true);+ }+ + private static void tie_point (ref double x, ref double y, bool coordinate)+ requires (horizontal.length () > 0 && vertical.length () > 0)+ {+ double d, m;+ + Glyph g = MainWindow.get_current_glyph ();+ + Line xmin = horizontal.first ().data;+ Line ymin = vertical.first ().data;++ Line xpos;+ Line ypos;+ + if (!coordinate) {+ xpos = new Line ("", 0, Line.VERTICAL);+ ypos = new Line ("", 0, Line.HOROIZONTAL);++ xpos.set_move (true);+ ypos.set_move (true);+ + xpos.move_line_to (x, y, g.allocation);+ ypos.move_line_to (x, y, g.allocation);+ } else {+ xpos = new Line ("", x, Line.VERTICAL);+ ypos = new Line ("", y, Line.HOROIZONTAL);+ }++ m = double.MAX;++ foreach (Line line in vertical) {+ d = Math.fabs (line.get_pos () - ypos.get_pos ());+ + if (d <= m) {+ m = d;+ ymin = line;+ }+ + }++ m = double.MAX;+ foreach (Line line in horizontal) {+ 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 ());+ y = Glyph.reverse_path_coordinate_y (-ymin.get_pos ());+ } else {+ x = xmin.get_pos ();+ y = ymin.get_pos ();+ }+ }++ public static void tie_to_prev (EditPoint ep, double tx, double ty) {+ EditPoint pp;+ double a, b;+ + return_if_fail (ep.prev != null);+ + pp = ep.get_prev ().data;+ + ep.to_coordinate (ref tx, ref ty);+ + a = fabs (tx - pp.x);+ b = fabs (ty - pp.y);+ + if (a > b) {+ ep.y = pp.y;+ } else {+ ep.x = pp.x;+ }+ } ++ public static bool is_visible () {+ return visible;+ }+ + public static unowned List<Line> get_horizontal_lines () {+ return horizontal;+ }+ + public static unowned List<Line> get_vertical_lines () {+ return vertical;+ }++}++}+new file mode 100644@@ -0,0 +1,32 @@+/*+ 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/>.+*/++namespace Supplement {++class IOThread {++ public static void add (ThreadFunc<weak void*> func) {+ try {+ unowned Thread<void*> thread = Thread.create<void*> (func, false);+ } catch (Error e) {+ warning (@"e.message");+ }+ }+ +}++}new file mode 100644@@ -0,0 +1,50 @@+/*+ 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/>.+*/++namespace Supplement {++using Cairo; ++public class Icons {+ + public static ImageSurface? get_icon (string? name) {+ ImageSurface? img = null;+ File f;+ + if (name == null) {+ warning ("Can't find a file for name \"null\".");+ return null;+ }++ f = find_icon ((!) name);+ + if (!f.query_exists ()) {+ warning (@"Can't load icon: $((!)f.get_path ())");+ return null;+ }+ + img = new ImageSurface.from_png ((!)f.get_path ());+ + return img;+ }++ public static File find_icon (string name) {+ return FontDisplay.find_file ("icons", name);+ }+}+ +}new file mode 100644@@ -0,0 +1,36 @@+/*+ 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 Math;++namespace Supplement {++class Intersection {+ public double x;+ public double y;+ public double distance;+ + public EditPoint editpoint_a;+ public EditPoint editpoint_b;+ + public Intersection (double x, double y, double distance) {+ this.x = x;+ this.y = y;+ }+}++}new file mode 100644@@ -0,0 +1,236 @@+/*+ 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 Math;++namespace Supplement {++class IntersectionList {+ public List<Intersection> points = new List<Intersection> ();+ + public void add (Intersection i) {+ double d;+ Intersection l;+ bool close = false;+ + if (points.length () == 0) {+ points.append (i);+ return;+ }+ + if (has_point (i)) {+ return;+ }+ + for (unowned List<Intersection> pl = points.first (); ; pl = pl.next) {+ l = pl.data;+ d = Math.fabs (Math.sqrt (Math.pow (i.x - l.x, 2) + Math.pow (i.y - l.y, 2)));++ if (l.distance >= i.distance) {+ close = true;+ + if (d < 0.3) {+ points.remove_link (points.last ());+ points.append (i);+ return;+ }+ }+ + if (pl == points.last ()) {+ break;+ }+ }+ + if (!close) {+ points.append (i);+ }+ }+ + public void clear () {+ while (points.length () > 0) {+ points.remove_link (points.first ());+ }+ }+ + public void remove_point (EditPoint e) {+ foreach (Intersection n in points) {+ if (n.editpoint_a == e || n.editpoint_b == e) {+ points.remove_all (n);+ }+ } + }+ + public bool has_edit_point (EditPoint e) {+ foreach (Intersection n in points) {+ if (n.editpoint_a == e || n.editpoint_b == e) {+ return true;+ }+ }+ + return false;+ }+ + bool has_point (Intersection i) {+ return has_point_at (i.x, i.y);+ }++ bool has_point_at (double x, double y) {+ foreach (Intersection n in points) {+ if (x == n.x && y == n.y) {+ return true;+ }+ }+ + return false;+ }+ + public int get_point_index (EditPoint e) {+ int i = 0;+ foreach (Intersection n in points) {+ if (e.x == n.x && e.y == n.y) {+ return i;+ }+ i++;+ }+ return -1;+ }+ + public Intersection? get_next_intersection (EditPoint e) {+ bool found = false;+ foreach (Intersection n in points) {+ if (found) {+ return n;+ }+ + if (n.editpoint_a == e || n.editpoint_b == e) {+ found = true;+ }+ }+ return null;+ }+ + public Intersection? get_intersection (EditPoint e) {+ Intersection? p = null;+ foreach (Intersection n in points) {+ if (n.editpoint_a == e || n.editpoint_b == e) {+ assert (p == null);+ p = n;+ }+ }+ return p;+ }+ + public void append (IntersectionList i) {+ foreach (Intersection inter in i.points) {+ points.append (inter);+ }+ }++ public static IntersectionList create_intersection_list (Path p1, Path p2) {+ IntersectionList il = new IntersectionList ();+ + unowned List<EditPoint> a_start, a_stop;+ unowned List<EditPoint> b_start, b_stop;+ + if (p1 == p2) {+ return il;+ }+ + // find crossing paths+ a_start = p1.points.first ();+ for (int i = 0; i < p1.points.length (); i++) {+ + if (a_start.data.next == null) {+ a_stop = p1.points.first ();+ } else {+ a_stop = a_start.data.get_next ();+ }+ + b_start = p2.points.first ();+ for (int j = 0; j < p2.points.length (); j++) {+ + if (b_start.data.next == null) {+ b_stop = p2.points.first ();+ } else {+ b_stop = b_start.data.get_next ();+ }++ il.append (find_intersections (a_start.data, a_stop.data, b_start.data, b_stop.data));++ b_start = b_stop;+ }+ + a_start = a_stop;+ }+ + return il;+ }++ static IntersectionList find_intersections (EditPoint a0, EditPoint a1, EditPoint b0, EditPoint b1) {+ IntersectionList il = new IntersectionList ();+ + double mind = double.MAX; ;+ + Path.all_of (a0, a1, (ax, ay, at) => {+ if (at == 0 || at == 1) {+ return true;+ }+ + Path.all_of (b0, b1, (bx, by, bt) => {+ double d = Math.fabs (Math.sqrt (Math.pow (ax - bx, 2) + Math.pow (ay - by, 2)));+ + if (d < 0.2) {+ il.add (new Intersection (ax, ay, d));+ }+ + if (d < mind) mind = d;+ return true;+ });+ + return true;+ });++ return il;+ }+ + /** return true if ranges does overlap */+ bool has_intersection (double a0, double a1, double b0, double b1) {+ if (a0 <= b0 <= a1 || a1 <= b0 <= a0) {+ if (a0 <= b1 <= a1 || a1 <= b1 <= a0) {+ return true;+ }+ + if (b0 <= a1 <= b1 || b1 <= a1 <= b0) {+ return true;+ }+ }+ + if (b0 <= a0 <= b1 || b1 <= a0 <= b0) {+ if (a0 <= a0 <= a1 || a1 <= a0 <= a0) {+ return true;+ }+ + if (b0 <= a0 <= b1 || b1 <= a0 <= b0) {+ return true;+ }+ }+ + return false;+ }+}++}new file mode 100644@@ -0,0 +1,33 @@+/*+ 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;+using Xml;++namespace Supplement {++public class Kerning : GLib.Object {+ public string glyph_right;+ public double val;+ + public Kerning (string right, double v) {+ glyph_right = right;+ val = v;+ }+}++}new file mode 100644@@ -0,0 +1,366 @@+/*+ 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 Gdk;++namespace Supplement {++// FIXA a lot of these things have been replaced and can safely be removed++public enum Key {+ NONE = 0,+ UP = 65362,+ RIGHT = 65363,+ DOWN = 65364,+ LEFT = 65361,+ PG_UP = 65365,+ PG_DOWN = 65366,+ ENTER = 65293,+ BACK_SPACE = 65288,+ SHIFT_LEFT = 65505,+ SHIFT_RIGHT = 65506,+ CTRL_LEFT = 65507,+ CTRL_RIGHT = 65508,+ CAPS_LOCK = 65509,+ ALT_LEFT = 65513,+ ALT_RIGHT = 65514,+ ALT_GR = 65027,+ LOGO_LEFT = 65515,+ LOGO_RIGHT = 65516,+ CONTEXT_MENU = 65383,+ TAB = 65289,+ DEL = 65535+}++bool is_modifier_key (uint i) {+ return Key.UP == i ||+ Key.RIGHT == i ||+ Key.DOWN == i ||+ Key.LEFT == i ||+ Key.PG_UP == i ||+ Key.PG_DOWN == i ||+ Key.ENTER == i ||+ Key.BACK_SPACE == i ||+ Key.SHIFT_LEFT == i ||+ Key.SHIFT_RIGHT == i ||+ Key.CTRL_LEFT == i ||+ Key.CTRL_RIGHT == i ||+ Key.ALT_LEFT == i ||+ Key.ALT_RIGHT == i ||+ Key.ALT_GR == i || + Key.LOGO_LEFT == i || + Key.LOGO_RIGHT == i || + Key.TAB == i || + Key.CAPS_LOCK == i || + Key.LOGO_RIGHT == i;+}++/** Modifier flags */+public static const uint NONE = 0;+public static const uint CTRL = 1 << 0;+public static const uint ALT = 1 << 2;+public static const uint SHIFT = 1 << 3;++/** A list of all valid key bindings. */+public class BindingList {+ static ShortCut[]? nc = null;+ + public static ShortCut[] get_default_bindings () { + + if (nc == null) {+ + // add all bindings to this list+ ShortCut[] default_bindings = { };++ nc = default_bindings;+ }+ + return nc;+ }+}+++/** Function to be executed for each global key binding. */+public abstract class ShortCut : GLib.Object {+ uint modifier = 0;+ uint key = 0;+ + public abstract void run ();+ + public void set_modifier (uint m) {+ modifier = m;+ }++ public void set_key (uint k) {+ key = k;+ }+ + public uint get_modifier () {+ return modifier;+ }+ + public uint get_key () {+ return key;+ }+ + public abstract uint get_default_modifier ();+ public abstract uint get_default_key ();+ + public abstract unowned string get_description ();+ + public static EqualFunc<ShortCut> equal = (a, b) => {+ if (a.get_key () != b.get_key ()) return false;+ if (a.get_modifier () != b.get_modifier ()) return false;+ if (a.get_description () != b.get_description ()) return false;+ return true;+ };+ +}++public class KeyBindings {+ + bool modifier_ctrl = false;+ bool modifier_alt = false;+ bool modifier_shift = false;+ + public static uint modifier = 0;+ + bool require_modifier = false;+ + /** First uint is modifer flag, second uint is keyval */+ HashTable<uint, HashTable<uint, ShortCut>> short_cuts = new HashTable<uint, HashTable<uint, ShortCut>> (null, null);+ + public static KeyBindings singleton;+ + public KeyBindings () {+ + HashFunc<uint> hash = (v) => {+ return v;+ };+ + HashTable<uint, ShortCut> alt_ctrl_shift = new HashTable<uint, ShortCut> (hash, ShortCut.equal);+ HashTable<uint, ShortCut> alt_shift = new HashTable<uint, ShortCut> (hash, ShortCut.equal);+ HashTable<uint, ShortCut> ctrl_shift = new HashTable<uint, ShortCut> (hash, ShortCut.equal);+ HashTable<uint, ShortCut> alt = new HashTable<uint, ShortCut> (hash, ShortCut.equal);+ HashTable<uint, ShortCut> ctrl = new HashTable<uint, ShortCut> (hash, ShortCut.equal);+ + // Possible modifiers+ short_cuts.insert (ALT|CTRL|SHIFT, alt_ctrl_shift);+ short_cuts.insert (ALT|SHIFT, alt_shift);+ short_cuts.insert (CTRL|SHIFT, ctrl_shift);+ short_cuts.insert (ALT, alt);+ short_cuts.insert (CTRL, ctrl);+ + // Add default bindings+ foreach (var b in BindingList.get_default_bindings ()) {+ add_binding (b);+ }+ + save ();+ load ();+ + singleton = this;+ }+ + public void reset () {+ modifier = NONE;+ modifier_ctrl = false;+ modifier_alt = false;+ modifier_shift = false;+ }+ + public void set_require_modifier (bool m) {+ require_modifier = m;+ }+ + void load () {+ File home = File.new_for_path (Environment.get_home_dir ());+ File settings = home.get_child (".birdfont");+ File bindings = settings.get_child ("keybindings");++ if (!bindings.query_exists ()) {+ return;+ }++ FileStream? bindings_file = FileStream.open ((!) bindings.get_path (), "r");+ + if (bindings_file == null) {+ stderr.printf ("Failed to load keybindings from file %s.\n", (!) bindings.get_path ());+ return;+ }+ + return_if_fail (bindings_file != null);+ + unowned FileStream b = (!) bindings_file;+ + string? l;+ l = b.read_line ();+ while ((l = b.read_line ())!= null) {+ string line;+ + line = (!) l;+ + if (line.get_char (0) == '#') {+ continue;+ }+ + int i = 0;+ int s = 0;+ + i = line.index_of_char(' ', s);+ string mod = line.substring (s, i - s);+ + s = i + 1;+ i = line.index_of_char(' ', s);+ string key = line.substring (s, i - s);+ + s = i + 1;+ i = line.index_of_char('\n', s);+ string description = line.substring (s, i - s);++ update_short_cut (mod, key, description);+ }+ + }+ + void save () {+ try {+ File settings = Supplement.get_settings_directory ();+ File bindings = settings.get_child ("keybindings");+ + if (bindings.query_exists ()) {+ bindings.delete ();+ }++ DataOutputStream os = new DataOutputStream(bindings.create(FileCreateFlags.REPLACE_DESTINATION));+ uint8[] data;+ long written = 0;+ + StringBuilder sb = new StringBuilder ();+ + sb.append_printf ("# Supplement keybindings\n");+ sb.append_printf ("# Version: 1.0\n");+ + short_cuts.foreach ( (k, v) => {+ v.foreach ( (k, s) => {+ sb.append_printf ("%u %u %s\n", s.get_modifier (), s.get_key (), s.get_description ());+ });+ });+ + data = sb.str.data;+ + while (written < data.length) { + written += os.write (data[written:data.length]);+ }+ + } catch (Error e) {+ stderr.printf ("Can not save key bindings. (%s)", e.message); + }+ + }+ + void update_short_cut (string mod, string key, string description) {+ ShortCut? sc = null;+ ShortCut binding;+ short_cuts.foreach ( (k, ht) => {+ ht.foreach ((ki, s) => {+ if (s.get_description () == description) {+ sc = s;+ }+ + // ht.remove (ki); // FIXME! why does it remove all items?+ + });+ });+ + if (unlikely (sc == null)) {+ stderr.printf ("Can not set bindings for \"%s-%s-%s-\"\n", mod, key, description);+ return;+ }+ + binding = (!) sc;++ binding.set_modifier ((uint) uint64.parse (mod));+ binding.set_key ((uint) uint64.parse (key));+ + add_binding (binding);+ }+ + void add_binding (ShortCut binding) {+ uint m = binding.get_modifier ();+ + if (m == 0) {+ m = binding.get_default_modifier ();+ binding.set_modifier (m);+ }++ if (binding.get_key () == 0) {+ binding.set_key (binding.get_default_key ());+ }+ + HashTable<uint, ShortCut>? e = short_cuts.lookup (m);+ + if (e == null) {+ stderr.printf ("Invalid modifier in key binding: expecting ctrl, alt or shift. Flag: (%u) \n", modifier);+ return;+ }+ + var events = (!) e;+ events.insert (binding.get_key (), binding);+ }+ + bool get_short_cut (uint modifier, uint key, out ShortCut? sc) {+ sc = null;+ + foreach (var v in BindingList.get_default_bindings ()) {+ if (v.get_default_modifier () == modifier && v.get_default_key () == key) {+ sc = v;+ return true;+ }+ }+ + return false;+ }++ public static void set_modifier (uint mod) {+ modifier = mod;++ singleton.modifier_ctrl = ((modifier & CTRL) > 0);+ singleton.modifier_alt = ((modifier & ALT) > 0);+ singleton.modifier_shift = ((modifier & SHIFT) > 0);+ }++ public static bool has_alt () {+ return singleton.modifier_alt;+ }+ + public static bool has_shift () {+ return singleton.modifier_shift;+ }+ + public static bool has_ctrl () {+ return singleton.modifier_ctrl;+ }+ + public void key_release (uint keyval) {+ }+ + public void key_press (uint keyval) {+ }+}++}new file mode 100644@@ -0,0 +1,342 @@+/*+ 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 {++/** Help line */+public class Line : GLib.Object {+ public static const bool VERTICAL = true;+ public static const bool HOROIZONTAL = false;+ + string label;+ bool vertical;+ + public double pos;+ + bool active = false;+ bool move = false;+ + public signal void queue_draw_area (int x, int y, int w, int h);+ public signal void position_updated (double pos);+ + public unowned List<Line>? list_item = null;+ + double r;+ double g;+ double b;+ double a;+ + bool visible = true;+ bool moveable = true;+ + public Line (string label = "No label set", double position = 10, bool vertical = false) {+ this.label = label;+ this.vertical = vertical;+ this.pos = position;+ + r = 0.7;+ g = 0.7;+ b = 0.8;+ a = 1;+ }+ + public Line copy () {+ Line l = new Line (label, pos, vertical);+ + l.r = r;+ l.g = g;+ l.b = b;+ l.a = a;+ + l.visible = visible;+ + return l;+ }+ + public void set_visible (bool v) {+ visible = v;+ }+ ++ public void set_moveable (bool m) {+ moveable = m;+ }+ + public void set_color (double r, double g, double b, double a) {+ this.r = r;+ this.g = g;+ this.b = b;+ this.a = a;+ }+ + // Valabug, it should be: public “void set_list_item (unowned List<Line> ll) {”+ // Keeping list_item public for now+ public void set_list_item (void* ll) {+ // list_item = ll;+ }+ + public bool is_moving () {+ return move;+ }+ + public bool set_move (bool moving) {+ bool r = move;+ move = moving;+ return (r == moving);+ }+ + public bool button_press () {+ Glyph g;+ if (get_active ()) {+ move = true;+ + g = MainWindow.get_current_glyph ();+ g.store_undo_state ();+ + return true;+ }+ + return false;+ }++ public void redraw_line () {+ double p;+ Glyph g = MainWindow.get_current_glyph ();+ + if (vertical) {+ p = g.reverse_path_coordinate_x (pos);+ queue_draw_area ((int)p - 10, 0, 200, g.allocation.height);+ } else {+ p = g.reverse_path_coordinate_y (pos);+ queue_draw_area (0, (int)p - 10, g.allocation.width, 300);+ }+ } + + public void move_line_to (double x, double y, Allocation allocation) {+ set_move (true);+ event_move_to (x, y, x, y, allocation);+ }+ + public void event_move_to (double x, double y, double ax, double ay, Allocation allocation) {+ double p, c;+ bool a = false;+ Glyph g = MainWindow.get_current_glyph ();+ double ivz = 1/g.view_zoom;+ double margin = 10;++ if (!moveable) return;++ if (move) {+ double np = pos;+ + redraw_line (); // clear old position+ + if (is_vertical ()) {+ pos = Glyph.path_coordinate_x (x);+ redraw_line (); // draw at new position+ } else {+ pos = -Glyph.path_coordinate_y (y);+ redraw_line ();+ }++ if (Math.fabs (np - pos) > 10) {+ queue_draw_area (0, 0, g.allocation.width, g.allocation.height);+ }++ position_updated (pos); // sinal update++ Supplement.get_current_font ().touch ();+ + swap_lines ();+ }++ if (is_vertical ()) {+ if (ay > g.allocation.height - 10) {+ + p = get_coordinate ();++ c = ax * ivz + g.view_offset_x;+ a = (p - margin * ivz <= c <= p + margin * ivz);+ }+ + if (a != get_active ()) {+ redraw_line ();+ }++ set_active (a);+ + } else {+ if (ax > g.allocation.width - 10) {+ p = get_coordinate ();+ c = ay * ivz + g.view_offset_y;+ a = (p - margin * ivz <= c <= p + margin * ivz);+ }+ + if (a != get_active ()) {+ redraw_line ();+ }+ + set_active (a);+ }+ }+ + private void swap_lines () {+ double ivz = 1 / MainWindow.get_current_glyph ().view_zoom;+ + if (!moveable) return;+ + // switch to correct line if this line passes the next one in the sorted list+ if (list_item != null) {+ unowned List<Line> ll = (!) list_item;+ + if (ll != ll.first ()) {+ if (ll.prev.data.pos > pos) {+ ll.prev.data.set_move (true);+ ll.prev.data.set_visible (true);+ set_move (false);+ pos = ll.prev.data.pos;+ ll.prev.data.pos += 1 * ivz;+ }+ }+ + if (ll != ll.last ()) {+ if (ll.next.data.pos < pos) {+ ll.next.data.set_move (true);+ ll.next.data.set_visible (true);+ pos = ll.next.data.pos;+ ll.next.data.pos -= 1 * ivz;+ set_move (false);+ }+ }+ + }+ + }+ + public bool get_active () {+ return active;+ }+ + protected void set_active (bool active) {+ this.active = active;+ }+ + public string get_label () {+ return label;+ }+ + public bool is_vertical () {+ return vertical;+ }++ public double get_coordinate () {+ Glyph g = MainWindow.get_current_glyph ();+ double t = (is_vertical ()) ? (g.allocation.width / 2.0) : (g.allocation.height / 2.0);+ return pos + t;+ }+ + public double get_pos () {+ return pos;+ }+ + private double get_handle_size () {+ Glyph g = MainWindow.get_current_glyph ();+ double ivz = 1/g.view_zoom;+ return (get_active ()) ? (10 * ivz) : (5 * ivz);+ }+ + public void draw (Context cr, Allocation allocation) {+ Glyph g = MainWindow.get_current_glyph ();+ + double p, h, w;+ double ivz = 1/g.view_zoom;++ double size = get_handle_size ();+ + if (!visible) return;+ + cr.save ();+ cr.set_line_width (ivz);+ + if (active) cr.set_source_rgba (0, 0, 0.3, 1);+ else cr.set_source_rgba (r, this.g, b, a);+ + // Line+ if (is_vertical ()) {+ p = get_coordinate ();+ + h = g.allocation.height * ivz + g.view_offset_y;+ + cr.move_to (p, g.view_offset_y);+ cr.line_to (p, h);+ cr.stroke ();++ cr.scale (1, 1);++ if (moveable) {+ cr.new_path ();+ cr.move_to (p - size, h); + cr.line_to (p, h - size); + cr.line_to (p + size, h);+ cr.close_path();+ cr.fill ();+ }+ + } else {+ p = get_coordinate ();+ + w = g.allocation.width * ivz + g.view_offset_x;+ + cr.move_to (g.view_offset_x, p);+ cr.line_to (w, p); + cr.stroke ();+ + if (moveable) {+ cr.new_path ();+ cr.move_to (w, p - size); + cr.line_to (w - size, p); + cr.line_to (w, p + size);+ cr.close_path();+ cr.fill ();+ }+ }+ + // Label+ if (get_active ()) { + if (is_vertical ()) {+ h = g.allocation.height * ivz + g.view_offset_y;+ cr.move_to (p + 8*ivz, h - 30*ivz);+ } else {+ w = g.allocation.width * ivz + g.view_offset_x;+ cr.move_to (w - 70*ivz, p + 15*ivz);+ }++ cr.set_font_size (12 * ivz);+ cr.select_font_face ("Cantarell", FontSlant.NORMAL, FontWeight.BOLD);+ + cr.show_text (get_label ());+ cr.stroke ();+ }+ + cr.restore ();+ }++}++}new file mode 100644@@ -0,0 +1,158 @@+/* + 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/>. +*/ + +namespace Supplement { + +public class MainWindow { + + public static Toolbox tools; + public static GlyphCanvas glyph_canvas; + public static MainWindow singleton; + public static KeyBindings key_bindings; + public static MenuTab menu_tab; + public static TooltipArea tool_tip; + public static OverView over_view; + public static TabBar tabs; + public static NativeWindow native_window; + + public MainWindow () { + singleton = this; + + key_bindings = new KeyBindings (); + glyph_canvas = new GlyphCanvas (); + tools = new Toolbox (glyph_canvas); + tabs = new TabBar (); + menu_tab = new MenuTab (); + tool_tip = new TooltipArea (); + over_view = new OverView(); + } + + public void set_native (NativeWindow nw) { + native_window = nw; + } + + internal static NativeWindow get_native () { + return native_window; + } + + internal static MainWindow get_current_window () { + return singleton; + } + + public static FontDisplay get_current_display () { + return get_glyph_canvas ().get_current_display (); + } + + public static GlyphCanvas get_glyph_canvas () { + return glyph_canvas; + } + + internal static Glyph get_current_glyph () { + if (unlikely (is_null (Supplement.current_glyph))) { + warning ("No default glyph have been set yet.\n"); + return new Glyph ("no_glyph_created"); + } + + return Supplement.current_glyph; + } + + public static Toolbox get_toolbox () { + return get_singleton ().tools; + } + + internal static Tool get_tool (string n) { + return get_singleton ().tools.get_tool (n); + } + + public static TabBar get_tab_bar () { + return get_singleton ().tabs; + } + + internal static Tab get_current_tab () { + return get_singleton ().tabs.get_selected_tab (); + } + + internal static TooltipArea get_tool_tip () { + return tool_tip; + } + + internal static bool select_tab (Tab t) { + return get_singleton ().tabs.selected_open_tab (t); + } + + internal static OverView get_overview () { + OverView over_view; + + foreach (var t in tabs.tabs) { + if (t.get_display () is OverView) { + return (OverView) t.get_display (); + } + } + + over_view = new OverView(); + tabs.add_unique_tab (over_view, 100, false); + + return over_view; + } + + internal static MenuTab get_content_display () { + if (unlikely ((MenuTab?) get_singleton ().menu_tab == null)) { + warning ("MenuTab not instantiated."); + } + + return get_singleton ().menu_tab; + } + + public static MainWindow get_singleton () { + return singleton; + } + + public static string? file_chooser (string title) { + return get_singleton ().native_window.file_chooser (title); + } + + public void set_title (string title) { + // FIXA: + // native_window.set_title (title); + } + + /** Reaload all paths and help lines from disk. */ + internal static void clear_glyph_cache () { + Glyph g; + foreach (Tab t in get_tab_bar ().tabs) { + if (t.get_display () is Glyph) { + g = (Glyph) t.get_display (); + g.add_help_lines (); + } + } + + get_glyph_canvas ().redraw (); + } + + internal static void close_all_tabs () { + uint i = 0; + uint len = get_tab_bar ().get_length (); + + while (i < len) { + if (!get_tab_bar ().close_tab ((int) i)) { + i++; + } + } + } +} + +} new file mode 100644@@ -0,0 +1,62 @@+/*+ 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 {++public class MenuAction : GLib.Object {+ public string label;+ public DropMenu.Selected action;+ public DropMenu? parent = null;+ public int index = -1;+ bool selected = false;+ + public MenuAction (string label) {+ this.label = label;+ }+ + public void set_selected (bool s) {+ selected = s;+ }+ + public virtual void draw (double x, double y, Context cr) {+ if (selected) {+ cr.save ();+ cr.set_line_join (LineJoin.ROUND);+ cr.set_line_width (12);+ cr.set_source_rgba (102/255.0, 120/255.0, 149/255.0, 1);+ cr.rectangle (x - 2, y - 9, 88, 8);+ cr.fill_preserve ();+ cr.stroke ();+ cr.restore (); + }+ + cr.save ();+ cr.set_source_rgba (1, 1, 1, 1);+ + cr.set_font_size (12);+ cr.select_font_face ("Cantarell", FontSlant.NORMAL, FontWeight.NORMAL);+ + cr.move_to (x, y);++ cr.show_text (label);+ cr.restore ();+ }+}++}new file mode 100644@@ -0,0 +1,339 @@+/*+ 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 WebKit;++namespace Supplement {++public class MenuTab : FontDisplay { + + List<Font> recent_fonts = new List<Font> ();++ public MenuTab () {+ // html callbacks:+ add_html_callback ("export_name", (val) => {+ Font f = Supplement.get_current_font ();+ f.set_name (val);+ f.touch ();+ });++ add_html_callback ("export", (val) => {+ ExportTool.export_all ();+ });++ add_html_callback ("preview", (val) => {+ preview ();+ }); ++ add_html_callback ("load", (val) => {+ load_font (val);+ }); ++ add_html_callback ("glyph_sequence", (val) => {+ Preferences.set ("glyph_sequence", val);+ });++ add_html_callback ("help", (val) => {+ MainWindow.get_tool_tip ().show_text (val);+ });+ }+ + public override string get_name () {+ return "Menu";+ }+ + public override bool is_html_canvas () {+ return true;+ }++ public override string get_html () {+ Font f = Supplement.get_current_font ();+ StringBuilder c = new StringBuilder ();+ string fn;+ + propagate_recent_files ();+ + c.append (+"""+<html>+<head>+ <script type="text/javascript" src="supplement.js"></script>+ <style type="text/css">@import url("style.css");</style>+ <script type="text/javascript">+ document.onkeyup = update_text_fields; + </script>+</head>+<body>+ <div class="inner_format_box">+ <div class="content">+ <div class="heading"><h2>""" + _("Preferences") + """</h2></div>+ <form>+""");+ +c.append ("""+ <h3>""" + _("Glyph sequence") + """</h3>+ <input class="text" type="text" id="glyph_sequence" value=""" + "\"" + Preferences.get ("glyph_sequence") + "\"" + """ onchange="update_text_fields ();"/><br />++ <h3>""" + _("Name") + """</h3>+ <input class="text" type="text" id="fontname" value=""" + "\"" + f.get_name () + "\"" + """ onchange="update_text_fields ();"/><br />+ + <input class="button" type="button" value=""" + "\"" + _("Export") + "\"" + """ id="export_button" onclick="call ('export:fonts');" onmouseover="call ('help:(Ctrl+e) """ + _("Export SVG, TTF & EOT fonts") + """');"/>+ <input class="button" type="button" value=""" + "\"" + _("Preview") + "\"" + """ id="preview_button" onclick="call ('preview:fonts');" onmouseover="call ('help:(Ctrl+p) """ + _("Export SVG font and view the result") + """');"/><br />+""");+ +c.append ("""++ </form> ++ </div>+ </div>+ + <img src="birdfont_logo.png" alt="" style="float:right;margin: 50px 0 0 0;">+ + <br class="clearBoth" />+ + <div class="recent_list">+""");++if (recent_fonts.length () > 0) {+ c.append ("""<div class="heading"><h2>""" + _("Recent files") + """</h2></div>""");+ c.append ("\n");+}++foreach (Font font in recent_fonts) {+ fn = (!) font.font_file;+ fn = fn.substring (fn.replace ("\\", "/").last_index_of ("/") + 1); + + c.append ("""<div class="recent_font" """ + "onclick=\"call ('load:" + ((!) font.font_file).replace ("\\", "\\\\") + "');\">");++ c.append ("<div class=\"one_line\">");+ c.append (fn);+ c.append ("</div>");++ c.append ("<img src=\"");+ c.append (path_to_uri ((!) Supplement.get_thumbnail_directory ().get_path ()));+ c.append ("/");+ c.append (fn);+ c.append (@".png?$(Random.next_int ())\" alt=\"\">");+ + c.append ("<br /><br />");+ c.append ("</div>\n");+}++c.append ("</div>\n");++c.append ("""+</body>+</html>+""");++ return c.str;+ }+ + void translations () {+ // xgettext needs these lines in order to extract strings properly+ // TODO: update xgettext to handle vala strings well.+ _("Preferences");+ _("Export SVG, TTF & EOT fonts");+ _("Name");+ _("Glyph sequence");+ _("Export SVG font and view the result");+ _("Export SVG font and view the result");+ }++ public void propagate_recent_files () {+ Font font;++ while (recent_fonts.length () != 0) {+ recent_fonts.delete_link (recent_fonts.first ());+ }+ + foreach (var f in Preferences.get_recent_files ()) {+ if (f == "") continue;+ + File file = File.new_for_path (f);++ font = new Font ();+ + font.set_font_file (f);+ + if (file.query_exists ()) { + recent_fonts.append (font);+ }+ }+ + recent_fonts.reverse ();+ }+ + public void load_font (string fn) {+ Font font = Supplement.get_current_font ();+ + SaveDialog save = new SaveDialog ();+ save.finished.connect (() => {+ Font f;+ bool loaded;+ + f = Supplement.get_current_font ();+ f.delete_backup ();+ + MainWindow.clear_glyph_cache ();+ MainWindow.close_all_tabs ();+ + loaded = f.load (fn);+ + if (!unlikely (loaded)) {+ warning (@"Failed to load fond $fn");+ return;+ }+ + MainWindow.get_singleton ().set_title (f.get_name ());+ + MainWindow.get_toolbox ().remove_all_grid_buttons ();+ foreach (string v in f.grid_width) {+ MainWindow.get_toolbox ().parse_grid (v);+ }+ + MainWindow.get_toolbox ().background_scale.set_value (f.background_scale);+ + select_overview ();+ });+ + MainWindow.get_tab_bar ().add_unique_tab (save, 80);+ + if (!font.is_modified ()) {+ save.finished ();+ }+ }+ + private static void select_overview () {+ Toolbox tb = MainWindow.get_toolbox ();+ + if (Supplement.get_current_font ().is_empty ()) {+ tb.select_tool_by_name ("custom_character_set");+ } else {+ tb.select_tool_by_name ("available_characters"); + }+ }++ public static bool save_as () {+ string? fn = null;+ string f;+ bool saved = false;+ Font font = Supplement.get_current_font ();+ + fn = MainWindow.file_chooser (_("Save"));+ + if (fn != null) {+ f = (!) fn;+ + if (!f.has_suffix (".ffi")) {+ f += ".ffi";+ }+ + font.font_file = f;+ save ();+ saved = true;+ }++ return saved;+ }++ public static bool save () {+ Font f = Supplement.get_current_font ();+ string fn;+ bool saved = false;+ + fn = (!) f.font_file;+ + if (f.font_file != null && fn.has_suffix (".ffi")) {+ f.background_scale = MainWindow.get_toolbox ().background_scale.get_display_value ();+ + while (f.grid_width.length () > 0) {+ f.grid_width.remove_link (f.grid_width.first ());+ }+ + foreach (SpinButton s in GridTool.sizes) {+ f.grid_width.append (s.get_display_value ());+ }+ + f.save (fn);+ saved = true;+ } else {+ saved = save_as ();+ }+ + return saved;+ }+ + public static void new_file () {+ Font font = Supplement.get_current_font ();+ SaveDialog save = new SaveDialog ();+ save.finished.connect (() => {+ Font f = Supplement.get_current_font ();+ f.delete_backup ();+ + Supplement.new_font ();+ MainWindow.close_all_tabs ();+ + MainWindow.get_toolbox ().remove_all_grid_buttons ();+ MainWindow.get_toolbox ().add_new_grid ();+ MainWindow.get_toolbox ().add_new_grid ();+ + select_overview ();+ });+ + MainWindow.get_tab_bar ().add_unique_tab (save, 80);+ + if (!font.is_modified ()) {+ save.finished ();+ }+ }+ + public static void load () {+ SaveDialog save = new SaveDialog ();+ save.finished.connect (() => {+ load_new_font ();+ });+ MainWindow.get_tab_bar ().add_unique_tab (save, 80);+ }++ private static void load_new_font () {+ string? fn = MainWindow.file_chooser ("load");+ Font f = Supplement.get_current_font ();+ + if (fn != null) {+ f.delete_backup ();+ + MainWindow.clear_glyph_cache ();+ MainWindow.close_all_tabs ();+ f.load ((!)fn);+ + MainWindow.get_singleton ().set_title (f.get_name ());+ select_overview (); + }+ }+ + public static void show_kerning_context () {+ MainWindow.get_tab_bar ().add_unique_tab (new ContextDisplay (), 85, false);+ }+ + public static void preview () {+ TabBar t = MainWindow.get_tab_bar ();+ t.add_unique_tab (new Preview (), 80); + }+}+}new file mode 100644@@ -0,0 +1,271 @@+/*+ 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/>.+*/++namespace Supplement {++class MergeTool : Tool {++ public MergeTool (string n) {+ base (n, _("Merge paths"));+ + select_action.connect((self) => {+ Glyph g = MainWindow.get_current_glyph ();+ g.merge_all ();+ });+ }++ public override bool test () {+ test_merge_second_triangle ();+ test_merge_simple_path_box_and_triangle ();+ test_merge_simple_path_box ();+ test_merge_odd_paths ();+ return true;+ }+ + private void test_merge_simple_path_box () {+ 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 ();++ int x_offset = 10;+ int y_offset = 350;+ + // draw it+ 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); + pen_tool.test_click_action (3, 0, 0);++ pen_tool.test_press_action (1, 1, 1);+ pen_tool.test_move_action (15 + x_offset, 15 + y_offset);+ pen_tool.test_release_action (1, 15 + x_offset, 15 + y_offset);+ + pen_tool.test_click_action (1, 25 + x_offset, 15 + y_offset);+ pen_tool.test_click_action (1, 25 + x_offset, 25 + y_offset);+ pen_tool.test_click_action (1, 15 + x_offset, 25 + y_offset); + pen_tool.test_click_action (3, 0, 0);+ + // merge it+ this.test_select_action ();+ + // test result+ Path merged_outline = new Path ();+ add_point_on_path (merged_outline, 10 + x_offset, 10 + y_offset);+ add_point_on_path (merged_outline, 20 + x_offset, 10 + y_offset);+ add_point_on_path (merged_outline, 20 + x_offset, 15 + y_offset);+ add_point_on_path (merged_outline, 25 + x_offset, 15 + y_offset);+ add_point_on_path (merged_outline, 25 + x_offset, 25 + y_offset);+ add_point_on_path (merged_outline, 15 + x_offset, 25 + y_offset);+ add_point_on_path (merged_outline, 15 + x_offset, 20 + y_offset);+ add_point_on_path (merged_outline, 10 + x_offset, 20 + y_offset);+ merged_outline.close ();+ + // select path+ pen_tool.test_click_action (3, 12 + x_offset, 12 + y_offset);+ + Glyph g = MainWindow.get_current_glyph ();+ Path? l = g.get_active_path ();+ + if (l == null) {+ critical ("No path found in merge test, it did not merge correctly.");+ return;+ }+ + Path last = (!) l;+ bool merged_path_looks_good = false;+ merged_path_looks_good = last.test_is_outline (merged_outline);+ + if (!merged_path_looks_good) critical ("Failed to merge path correctly.");+ }++ private void test_merge_simple_path_box_and_triangle () {+ 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 ();++ int x_offset = 40;+ int y_offset = 350;+ + // draw it+ pen_tool.test_click_action (1, 0 + x_offset, -50 + y_offset);+ pen_tool.test_click_action (1, 25 + x_offset, -50 + y_offset);+ pen_tool.test_click_action (1, 25 + x_offset, 0 + y_offset);+ pen_tool.test_click_action (1, 0 + x_offset, 0 + y_offset); + pen_tool.test_click_action (3, 0, 0);+ + pen_tool.test_click_action (1, 0 + x_offset, 0 + y_offset);+ pen_tool.test_click_action (1, 50 + x_offset, -50 + y_offset);+ pen_tool.test_click_action (1, 50 + x_offset, 0 + y_offset); + pen_tool.test_click_action (3, 0, 0);++ // merge it+ this.test_select_action ();+ + // test result+ Path merged_outline = new Path ();+ add_point_on_path (merged_outline, 0 + x_offset, -50 + y_offset);+ add_point_on_path (merged_outline, 25 + x_offset, -50 + y_offset);+ add_point_on_path (merged_outline, -50 + x_offset, -50 + y_offset);+ add_point_on_path (merged_outline, -50 + x_offset, 0 + y_offset);+ add_point_on_path (merged_outline, 0 + x_offset, 0 + y_offset);+ merged_outline.close ();++ // select path+ pen_tool.test_click_action (3, 21 + x_offset, -11 + y_offset);+ + Glyph g = MainWindow.get_current_glyph ();+ Path? l = g.get_active_path ();+ + if (l == null) {+ critical ("No path found in test_merge_simple_path_box_and_triangle, it did not merge correctly.");+ return;+ }+ + Path last = (!) l;+ bool merged_path_looks_good = false;+ merged_path_looks_good = last.test_is_outline (merged_outline);+ + if (!merged_path_looks_good) critical ("Failed to merge path correctly.");+ }++ private void test_merge_second_triangle () {+ 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 ();++ int x_offset = 100;+ int y_offset = 350;+ + // draw it+ pen_tool.test_click_action (1, 25 + x_offset, 0 + y_offset);+ pen_tool.test_click_action (1, 25 + x_offset, -50 + y_offset);+ pen_tool.test_click_action (1, 0 + x_offset, -50 + y_offset);+ pen_tool.test_click_action (1, 0 + x_offset, 0 + y_offset); ++ pen_tool.test_click_action (3, 0, 0);+ + pen_tool.test_click_action (1, 40 + x_offset, -50 + y_offset);+ pen_tool.test_click_action (1, 10 + x_offset, -20 + y_offset);+ pen_tool.test_click_action (1, 25 + x_offset, -20 + y_offset); + pen_tool.test_click_action (3, 0, 0);+ + // merge it+ this.test_select_action ();+ + // test result+ Path merged_outline = new Path ();+ add_point_on_path (merged_outline, 0 + x_offset, -50 + y_offset);+ add_point_on_path (merged_outline, 25 + x_offset, -50 + y_offset);+ add_point_on_path (merged_outline, -50 + x_offset, -50 + y_offset);+ add_point_on_path (merged_outline, -50 + x_offset, 0 + y_offset);+ add_point_on_path (merged_outline, 0 + x_offset, 0 + y_offset);+ merged_outline.close ();++ // select path+ pen_tool.test_click_action (3, 21 + x_offset, -11 + y_offset);+ + Glyph g = MainWindow.get_current_glyph ();+ Path? l = g.get_active_path ();+ + if (l == null) {+ critical ("No path found in triangle_right, it did not merge correctly.");+ return;+ }+ + Path last = (!) l;+ bool merged_path_looks_good = false;+ merged_path_looks_good = last.test_is_outline (merged_outline);+ + if (!merged_path_looks_good) critical ("Failed to merge path correctly.");+ }+ + private void add_point_on_path (Path p, int x, int y) {+ Glyph g = MainWindow.get_current_glyph ();+ p.add (g.path_coordinate_x (x), g.path_coordinate_y (y));+ }+ + + private void test_merge_odd_paths () {+ 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+ int x_offset = 100;+ int y_offset = 100;+ + // rectangle+ pen_tool.test_click_action (1, 100 + x_offset, 100 + y_offset);+ pen_tool.test_click_action (1, 170 + x_offset, 100 + y_offset);+ pen_tool.test_click_action (1, 170 + x_offset, 120 + y_offset);+ pen_tool.test_click_action (1, 100 + x_offset, 120 + y_offset); + pen_tool.test_click_action (3, 0, 0); // close+ + // triangle+ pen_tool.test_click_action (1, 100 + x_offset, 110 + y_offset);+ pen_tool.test_click_action (1, 180 + x_offset, 130 + y_offset);+ pen_tool.test_click_action (1, -10 + x_offset, 140 + y_offset);+ pen_tool.test_click_action (3, 0, 0);++ // several triangles+ pen_tool.test_click_action (1, 198, 379); + pen_tool.test_click_action (1, 274, 328); + pen_tool.test_click_action (1, 203, 286); + pen_tool.test_click_action (3, 230, 333);++ pen_tool.test_click_action (1, 233, 429); + pen_tool.test_click_action (1, 293, 382); + pen_tool.test_click_action (1, 222, 322); + pen_tool.test_click_action (3, 225, 406);++ pen_tool.test_click_action (1, 164, 316); + pen_tool.test_click_action (1, 262, 289); + pen_tool.test_click_action (1, 203, 260); + pen_tool.test_click_action (3, 203, 260); ++ this.yield ();+ + test_select_action ();+ } +}++}new file mode 100644@@ -0,0 +1,187 @@+/*+ 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 Math;+using Cairo;++namespace Supplement {++class MoveTool : Tool {++ bool move_path = false;+ double last_x = 0;+ double last_y = 0;++ bool resize_path = false;+ Path? resized_path = null;+ double last_resize_y;+ + ImageSurface? resize_handle;+ + public MoveTool (string n) {+ base (n, _("Move paths"), 'm', CTRL);+ + resize_handle = Icons.get_icon ("resize_handle.png");+ + select_action.connect((self) => {+ });++ deselect_action.connect((self) => {+ Glyph glyph = MainWindow.get_current_glyph ();+ glyph.clear_active_paths ();+ });+ + press_action.connect((self, b, x, y) => {+ Glyph glyph = MainWindow.get_current_glyph ();+ + glyph.store_undo_state ();+ + foreach (Path p in glyph.active_paths) {+ if (is_over_resize_handle (p, x, y)) {+ resize_path = true;+ resized_path = p;+ last_resize_y = y;+ return;+ }+ }+ + if (resized_path != null) {+ if (is_over_resize_handle ((!) resized_path, x, y)) {+ resize_path = true;+ last_resize_y = y;+ return; + }+ }+ + if (!glyph.is_over_selected_path (x, y)) {+ if (!glyph.select_path (x, y)) {+ glyph.clear_active_paths ();+ }+ }+ + move_path = true;+ + last_x = x;+ last_y = y;+ });++ release_action.connect((self, b, x, y) => {+ Glyph glyph = MainWindow.get_current_glyph ();+ + move_path = false;+ resize_path = false;+ + if (GridTool.is_visible ()) {+ foreach (Path p in glyph.active_paths) {+ tie_path_to_grid (p, x, y);+ }+ }+ });+ + move_action.connect ((self, x, y) => {+ Glyph glyph = MainWindow.get_current_glyph ();+ double dx = last_x - x;+ double dy = last_y - y; + double p = PenTool.precision;+ Path rp;+ double h, ratio;+ + if (move_path) {+ foreach (Path path in glyph.active_paths) {+ path.move (glyph.ivz () * -dx * p, glyph.ivz () * dy * p);+ }+ }+ + if (resize_path) {+ return_if_fail (!is_null (resized_path));+ rp = (!) resized_path;+ h = rp.xmax - rp.xmin;+ + ratio = 1;+ ratio -= 0.7 * p * (Glyph.path_coordinate_y (last_resize_y) - Glyph.path_coordinate_y (y)) / h;+ + rp.resize (ratio);+ last_resize_y = y;+ }++ last_x = x;+ last_y = y;++ MainWindow.get_glyph_canvas ().redraw ();+ });+ + key_press_action.connect ((self, keyval) => {+ Glyph g = MainWindow.get_current_glyph ();+ + if (keyval == Key.DEL) {+ foreach (Path p in g.active_paths) {+ g.path_list.remove (p);+ g.update_view ();+ }+ }+ });+ + draw_action.connect ((self, cr, glyph) => {+ Glyph g = MainWindow.get_current_glyph ();+ ImageSurface img = (!) resize_handle;+ + foreach (Path p in g.active_paths) {+ cr.set_source_surface (img, Glyph.reverse_path_coordinate_x (p.xmax) - 10, Glyph.reverse_path_coordinate_y (p.ymax) - 10);+ cr.paint (); + }+ + if (resized_path != null) {+ cr.set_source_surface (img, Glyph.reverse_path_coordinate_x (((!) resized_path).xmax) - 10, Glyph.reverse_path_coordinate_y (((!)resized_path).ymax) - 10);+ cr.paint ();+ }+ });+ }++ bool is_over_resize_handle (Path p, double x, double y) {+ double handle_x = Math.fabs (Glyph.reverse_path_coordinate_x (p.xmax)); + double handle_y = Math.fabs (Glyph.reverse_path_coordinate_y (p.ymax));+ return fabs (handle_x - x + 10) < 20 && fabs (handle_y - y + 10) < 20;+ }++ void tie_path_to_grid (Path p, double x, double y) {+ double sx, sy, qx, qy; + + // tie to grid+ sx = p.xmax;+ sy = p.ymax;+ qx = p.xmin;+ qy = p.ymin;+ + GridTool.tie_coordinate (ref sx, ref sy);+ GridTool.tie_coordinate (ref qx, ref qy);+ + if (Math.fabs (qy - p.ymin) < Math.fabs (sy - p.ymax)) {+ p.move (0, qy - p.ymin);+ } else {+ p.move (0, sy - p.ymax);+ }++ if (Math.fabs (qx - p.xmin) < Math.fabs (sx - p.xmax)) {+ p.move (qx - p.xmin, 0);+ } else {+ p.move (sx - p.xmax, 0);+ } + }++}++}new file mode 100644@@ -0,0 +1,35 @@+/*+ 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/>.+*/++namespace Supplement {++public interface NativeWindow : GLib.Object {+ public abstract string? file_chooser (string title);+ public abstract string? set_title (string title);++ public abstract void toggle_expanded_margin_bottom ();+ public abstract void toggle_expanded_margin_right ();+ + public abstract void update_window_size ();++ protected void webkit_callback (string s) {+ FontDisplay fd = MainWindow.get_current_display ();+ fd.process_property (s);+ }+}++}new file mode 100644@@ -0,0 +1,136 @@+/*+ 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/>.+*/++namespace Supplement {++[SimpleType]+[CCode (has_type_id = false)] // Vala bug+internal struct Fixed : uint32 {++ public void @set (uint16 upper, uint16 lower) {+ uint32* p = &this;+ *p = (upper << 16) + lower;+ }++ public bool @equals (uint16 upper, uint16 lower) {+ uint32 t = (upper << 16) + lower;+ return (t == this); + }++ public string get_string () {+ uint a = this >> 16;+ return @"$(a).$(this - (a << 16))";+ }+}++[SimpleType]+[CCode (has_type_id = false)] // same as above+internal struct F2Dot14 : uint32 {+}++errordomain BadFormat {+ PARSE+}++class OpenFontFormatReader : Object {+ + public DirectoryTable directory_table;+ + OtfInputStream dis;+ File file;+ + public OpenFontFormatReader () {+ directory_table = new DirectoryTable ();+ }+ + public void close () {+ dis.close ();+ }+ + public Glyph? read_glyph (string name) {+ try {+ return directory_table.glyf_table.read_glyph (name);+ } catch (GLib.Error e) {+ warning (e.message);+ }+ + return null;+ }+ + public void parse_index (string file_name) throws Error {+ file = File.new_for_path (file_name);+ if (!file.query_exists ()) {+ throw new FileError.EXIST(@"OpenFontFormatReader: file does not exist. $((!) file.get_path ())");+ }+ + dis = new OtfInputStream (file.read ());+ + parse_index_tables ();+ }+ + void parse_index_tables () throws Error {+ OffsetTable offset_table;+ FontData fd = new FontData ();+ + FileInfo file_info = file.query_info ("*", FileQueryInfoFlags.NONE);+ uint32 file_size = (uint32) file_info.get_size ();++ try {+ fd.write_table (dis, 0, file_size);+ } catch (GLib.Error e) {+ warning (@"Failed to read font data. $(e.message)");+ }++ offset_table = new OffsetTable (directory_table);+ offset_table.parse (fd);+ + directory_table = new DirectoryTable ();+ directory_table.set_offset_table (offset_table);+ directory_table.parse (fd, file, this);+ }++ public void set_limits () {+ Font f = OpenFontFormatWriter.font;+ + if (is_null (f)) {+ f = Supplement.get_current_font ();+ }+ + f.top_position = -get_ascender ();+ f.top_limit = f.top_position - 5;+ f.xheight_position = f.top_position - 5;+ f.bottom_position = -get_descender ();+ f.bottom_limit = f.bottom_position + 5; + }++ public double get_ascender () {+ return directory_table.hhea_table.get_ascender ();+ }+ + public double get_descender () {+ return directory_table.hhea_table.get_descender ();+ }++ public unowned List<string> get_all_names () {+ return directory_table.post_table.get_all_names ();+ }+ + public uint32 get_head_checksum () {+ return directory_table.head_table.get_adjusted_checksum ();+ }+}+}new file mode 100644@@ -0,0 +1,4875 @@+/* + 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 Math; + +namespace Supplement { + +const double UNITS = 10; + +class OpenFontFormatWriter : Object { + + DataOutputStream os; + DirectoryTable directory_table; + + public static Font font; + + public OpenFontFormatWriter () { + directory_table = new DirectoryTable (); + } + + public static Font get_current_font () { + return font; + } + + public void open (File file) throws Error { + assert (!is_null (file)); + + if (file.query_exists ()) { + warning ("File exists in export."); + throw new FileError.EXIST("OpenFontFormatWriter: file exists."); + } + + os = new DataOutputStream(file.create (FileCreateFlags.REPLACE_DESTINATION)); + } + + public void write_ttf_font (Font font) throws Error { + long dl; + uint8* data; + unowned List<Table> tables; + FontData fd; + uint l; + + this.font = font; + + directory_table.process (); + tables = directory_table.get_tables (); + + dl = directory_table.get_font_file_size (); + + if (dl == 0) { + warning ("font is of zero size."); + return; + } + + foreach (Table t in tables) { + fd = t.get_font_data (); + data = fd.table_data; + l = fd.length_with_padding (); + + for (int j = 0; j < l; j++) { + os.put_byte (data[j]); + } + } + } + + public void close () throws Error { + os.close (); + } +} + +/** Reader for otf data types. */ +class OtfInputStream : Object { + + public FileInputStream fin; + public DataInputStream din; + + public OtfInputStream (FileInputStream i) throws Error { + din = new DataInputStream (i); + fin = i; + } + + public void seek (int64 pos) throws Error + requires (fin.can_seek ()) { + int64 p = fin.tell (); + fin.seek (pos - p, SeekType.CUR); + } + + public uint8 read_byte () throws Error { + return din.read_byte (); + } + + public void close () { + try { + fin.close (); + din.close (); + } catch (GLib.IOError e) { + warning (e.message); + } + } +} + +class FontData : Object { + + // Read pointer + uint rp = 0; + + // Write pointer + uint wp = 0; + + // length without padding + uint32 len = 0; + uint32 padding = 0; + + uint32 capacity; + + public uint8* table_data = null; + + public FontData (uint32 size = 1024) { + capacity = size; + table_data = malloc (size); + } + + ~FontData () { + if (table_data != null) { + delete table_data; + table_data = null; + } + } + + public void write_at (uint pos, uint8 new_data) throws GLib.Error + requires (pos <= capacity) + { + if (unlikely (pos >= len)) { + warning ("end of table reached"); + assert (false); + } + + table_data[pos]= new_data; + } + + public void write_table_data (FontData fd, uint32 offset, uint32 length) { + fd.seek (offset); + for (uint32 i = 0; i < length; i++) { + add (table_data [rp++]); + } + fd.seek (0); + } + + public void write_table (OtfInputStream dis, uint32 offset, uint32 length) throws Error { + uint32 l = length + (length % 4); // padding after end of table + uint8 b; + + if (length >= l) { + expand (l); + } + + if (table_data == null) { + warning ("Failed to allocate memory for ttf data."); + return; + } + + seek (0); + dis.seek (offset); + + wp = 0; + while (wp < l) { + b = dis.read_byte (); + add (b); + } + + rp = 0; + } + + public uint length_with_padding () { + return len; + } + + public uint length () { + return len - padding; + } + + public void pad () { + while (len % 4 != 0) { + add (0); + padding++; + } + } + + /** Add additional checksum data to this checksum. */ + public void continous_check_sum (ref uint32 current_check_sum) { + uint trp = rp; + uint l; + + if (length_with_padding () % 4 != 0) { + warning ("Table is not padded to correct size."); + } + + seek (0); + + l = (length () % 4 > 0) ? length () / 4 + 1 : length () / 4; + + for (uint32 i = 0; i < l; i++) { + current_check_sum += read_uint32 (); + } + + rp = trp; + + return; + } + + public uint32 check_sum () { + uint32 sum = 0; + continous_check_sum (ref sum); + return sum; + } + + public void seek_end () { + seek (len); + } + + public void seek (uint i) { + rp = i; + wp = i; + } + + public uint8 read () { + if (unlikely (rp >= len)) { + warning ("end of table reached"); + return 0; + } + + return table_data [rp++]; + } + + public Fixed read_fixed () { + return (Fixed) read_uint32 (); + } + + public uint32 read_ulong () { + return read_uint32 (); + } + + public uint16 read_ushort () { + uint16 f; + f = read () << 8; + f += read (); + return f; + } + + public int16 read_int16 () { + int16 f; + f = read () << 8; + f += read (); + return f; + } + + public int16 read_short () throws Error { + return read_int16 (); + } + + public uint32 read_uint32 () { + uint32 f; + f = read () << 8 * 3; + f += read () << 8 * 2; + f += read () << 8 * 1; + f += read () << 8 * 0; + return f; + } + + public uint64 read_uint64 () { + uint64 f; + f = read () << 8 * 7; + f += read () << 8 * 6; + f += read () << 8 * 5; + f += read () << 8 * 4; + f += read () << 8 * 3; + f += read () << 8 * 2; + f += read () << 8 * 1; + f += read () << 8 * 0; + return f; + } + + public F2Dot14 read_f2dot14 () throws Error { + F2Dot14 f = (F2Dot14) read_int16 (); + return f; + } + + public uint64 read_udate () throws Error { + return read_uint64 (); + } + + public uint8 read_byte () throws Error { + return read (); + } + + public char read_char () throws Error { + return (char) read_byte (); + } + + public void add_udate (int64 d) throws Error { + add_64 (d); + } + + public void add_fixed (Fixed f) throws Error { + add_u32 (f); + } + + public void add_fword (int16 word) { + add_16 (word); + } + + public void add_f2dot14 (F2Dot14 f) throws Error { + add_u32 (f); + } + + public void add_short (int16 d) throws Error { + add_16 (d); + } + + public void add_ushort (uint16 d) throws Error { + add_u16 (d); + } + + public void add_ulong (uint32 d) throws Error { + add_u32 (d); + } + + public void add_byte (uint8 b) throws Error { + add (b); + } + + private void expand (uint extra_bytes = 1024) { + capacity += extra_bytes; + table_data = (uint8*) try_realloc (table_data, capacity); + + if (table_data == null) { + warning ("Out of memory."); + capacity = 0; + } + } + + public void add (uint8 d) { + if (unlikely (len == capacity)) { + expand (); + } + + table_data[wp] = d; + + if (wp == len) { + len++; + } + + wp++; + } + + public void add_littleendian_u16 (uint32 i) { + add ((int8) (i & 0x00FF)); + add ((int8) ((i & 0xFF00) >> 8)); + } + + public void add_u16 (uint16 d) { + uint16 n = d >> 8; + add ((uint8)n); + add ((uint8)(d - (n << 8))); + } + + public void add_16 (int16 i) { + uint8 s = (uint8) (i >> 8); + + add ((uint8) s); + add ((uint8) (i - (s << 8))); + } + + public void add_littleendian_u32 (uint32 i) { + add ((int8) (i & 0x000000FF)); + add ((int8) ((i & 0x0000FF00) >> 8)); + add ((int8) ((i & 0x00FF0000) >> 16)); + add ((int8) ((i & 0xFF000000) >> 24)); + } + + public void add_u32 (uint32 i) { + uint32 s = (uint16) (i >> 16); + + add_u16 ((uint16) s); + add_u16 ((uint16) (i - (s << 16))); + } + + public void add_32 (int32 i) { + int32 s = (int16) (i >> 16); + + add_16 ((int16) s); + add_16 ((int16) (i - (s << 16))); + } + + public void add_u64(uint64 i) { + uint64 s = (uint32) (i >> 32); + + add_u32 ((uint32) s); + add_u32 ((uint32)(i - (s << 32))); + } + + public void add_64(int64 i) { + int64 s = (int32) (i >> 32); + + add_u32 ((int32) s); + add_u32 ((int32)(i - (s << 32))); + } + + public void add_str_littleendian_utf16 (string s) { + add_str_utf16 (s, true); + } + + public void add_str_utf16 (string s, bool little_endian = false) { + int index = 0; + unichar c; + uint8 c0; + uint8 c1; + int l = 0; + + while (s.get_next_char (ref index, out c)) { + c0 = (uint8) (c >> 8); + c1 = (uint8) (c - (c0 << 8)); + + if (little_endian) { + add (c1); + add (c0); + } else { + add (c0); + add (c1); + } + + l += 2; + } + + assert (l == 2 * s.char_count ()); + } + + public void add_str (string s) { + uint8[] data = s.data; + for (int n = 0; n < data.length; n++) { + add (data[n]); + } + } + + public void add_tag (string s) + requires (s.length == 4 && s.data.length == 4) { + add_str (s); + } +} + +class Coordinate { + /** TTF coordinate flags. */ + + public static const uint8 NONE = 0; + public static const uint8 ON_PATH = 1 << 0; + public static const uint8 X_SHORT_VECTOR = 1 << 1; + public static const uint8 Y_SHORT_VECTOR = 1 << 2; + public static const uint8 REPEAT = 1 << 3; + + // same flag or short vector sign flag + public static const uint8 X_IS_SAME = 1 << 4; + public static const uint8 Y_IS_SAME = 1 << 5; + public static const uint8 X_SHORT_VECTOR_POSITIVE = 1 << 4; + public static const uint8 Y_SHORT_VECTOR_POSITIVE = 1 << 5; + +} + +class Table : Object { + + public string id = "NO_ID"; + + public uint32 checksum = 0; + public uint32 offset = 0; + public uint32 length = 0; + + protected FontData? font_data = null; + + public virtual string get_id () { + return id; + } + + public FontData get_font_data () { + if (font_data == null) { + warning (@"No font data for $(id)."); + font_data = new FontData (); + } + + return (!) font_data; + } + + public bool has_data () { + return length > 0; + } + + public virtual void parse (FontData dis) throws GLib.Error { + warning (@"Parse is not implemented for $(id)."); + } + + /** Validate table checksum. */ + public bool validate (FontData dis) { + bool valid; + + if (length == 0) { + stderr.printf (@"Table $id is of zero length.\n"); + valid = false; + } else { + valid = Table.validate_table (dis, checksum, offset, length, id); + } + + if (!valid) { + stderr.printf (@"Table $id is invalid.\n"); + } + + return valid; + } + + public static bool validate_table (FontData dis, uint32 checksum, uint32 offset, uint32 length, string name) { + uint32 ch = calculate_checksum (dis, offset, length, name); + bool c; + + c = (ch == checksum); + + if (!c) { + stderr.printf(@"Checksum does not match data for $(name).\n"); + stderr.printf(@"name: $name, checksum: $checksum, offset: $offset, length: $length\n"); + stderr.printf(@"calculated checksum $(ch)\n"); + } + + return c; + } + + public static uint32 calculate_checksum (FontData dis, uint32 offset, uint32 length, string name) { + uint32 checksum = 0; + uint32 l; + + dis.seek (offset); + + l = (length % 4 > 0) ? length / 4 + 1 : length / 4; + + for (uint32 i = 0; i < l; i++) { + checksum += dis.read_ulong (); + } + + return checksum; + } + + public static uint16 max_pow_2_less_than_i (uint16 ind) { + uint16 last = 0; + uint16 i = 1; + + while ((i <<= 1) < ind) { + last = i; + } + + return last; + } + + public static uint16 max_log_2_less_than_i (uint16 ind) { + return (uint16) (Math.log (ind) / Math.log (2)); + } +} + +class LocaTable : Table { + + uint32* glyph_offsets = null; + public uint32 size = 0; + + public LocaTable () { + id = "loca"; + } + + ~LocaTable () { + if (glyph_offsets != null) delete glyph_offsets; + } + + public uint32 get_offset (uint32 i) { + return_if_fail (glyph_offsets != null); + + if (size == 0) { + warning ("No glyphs in loca table"); + } + + if (!(0 <= i < size + 1)) { + warning (@"No offset for glyph $i. Requires (0 <= $i < $(size + 1)"); + } + + return glyph_offsets [i]; + } + + /** Returns true if glyph at index i is empty and have no body to parse. */ + public bool is_empty (uint32 i) { + return_if_fail (glyph_offsets != null); + + if (size == 0) { + warning ("No glyphs in loca table"); + } + + if (!(0 <= i < size + 1)) { + warning (@"No offset for glyph $i. Requires (0 <= $i < $(size + 1)"); + } + + return glyph_offsets[i] == glyph_offsets[i + 1]; + } + + public new void parse (FontData dis, HeadTable head_table, MaxpTable maxp_table) throws GLib.Error { + size = maxp_table.num_glyphs; + glyph_offsets = new uint32[size + 1]; + + dis.seek (offset); + + printd (@"size: $size\n"); + printd (@"length: $length\n"); + printd (@"length/4-1: $(length / 4 - 1)\n"); + printd (@"length/2-1: $(length / 2 - 1)\n"); + printd (@"head_table.loca_offset_size: $(head_table.loca_offset_size)\n"); + + switch (head_table.loca_offset_size) { + case 0: + for (long i = 0; i < size + 1; i++) { + glyph_offsets[i] = 2 * dis.read_ushort (); + + if (0 < i < size && glyph_offsets[i - 1] > glyph_offsets[i]) { + warning (@"Invalid loca table, it must be sorted. ($(glyph_offsets[i - 1]) > $(glyph_offsets[i]))"); + } + } + break; + + case 1: + for (long i = 0; i < size + 1; i++) { + glyph_offsets[i] = dis.read_ulong (); + + if (0 < i < size && glyph_offsets[i - 1] > glyph_offsets[i]) { + warning (@"Invalid loca table, it must be sorted. ($(glyph_offsets[i - 1]) > $(glyph_offsets[i]))"); + } + } + + break; + + default: + warning ("unknown size for offset in loca table"); + break; + } + } + + public void print_offsets () { + for (int i = 0; i < size; i++) { + print (@"get_offset ($i): $(get_offset (i))\n"); + } + } + + public void process (GlyfTable glyf_table, HeadTable head_table) { + FontData fd = new FontData (); + uint32 last = 0; + uint32 prev = 0; + int i = 0; + + foreach (uint32 o in glyf_table.location_offsets) { + if (i != 0 && (o - prev) % 4 != 0) { + warning (@"glyph length is not a multiple of four in gid $i"); + } + + if (o % 4 != 0) { + warning ("glyph is not on a four byte boundry"); + assert_not_reached (); + } + + prev = o; + i++; + } + + if (head_table.loca_offset_size == 0) { + foreach (uint32 o in glyf_table.location_offsets) { + fd.add_u16 ((uint16) (o / 2)); + + if (o < last) { + warning (@"Loca table must be sorted. ($o < $last)"); + } + + last = o; + } + + } else if (head_table.loca_offset_size == 1) { + foreach (uint32 o in glyf_table.location_offsets) { + fd.add_u32 (o); + + if (o < last) { + warning (@"Loca table must be sorted. ($o < $last)"); + } + + last = o; + } + + } else { + warn_if_reached (); + } + + if (!(glyf_table.location_offsets.length () == glyf_table.glyphs.length () + 1)) { + warning (@"(glyf_table.location_offsets.length () == glyf_table.glyphs.length () + 1) ($(glyf_table.location_offsets.length ()) == $(glyf_table.glyphs.length () + 1))"); + } + + fd.pad (); + font_data = fd; + } +} + +class GlyfTable : Table { + + // Flags for composite glyph + static const uint16 BOTH_ARE_WORDS = 1 << 0; + static const uint16 BOTH_ARE_XY_VALUES = 1 << 1; + static const uint16 ROUND_TO_GRID = 1 << 2; + static const uint16 SCALE = 1 << 3; + static const uint16 RESERVED = 1 << 4; + static const uint16 MORE_COMPONENTS = 1 << 5; + static const uint16 SCALE_X_Y = 1 << 6; + static const uint16 SCALE_WITH_ROTATTION = 1 << 7; + static const uint16 INSTRUCTIONS = 1 << 8; + + public int16 xmin = int16.MAX; + public int16 ymin = int16.MAX; + public int16 xmax = int16.MIN; + public int16 ymax = int16.MIN; + + public FontData dis; + public HeadTable head_table; + public HmtxTable hmtx_table; + public LocaTable loca_table; + public CmapTable cmap_table; // hack, hack, hack. cmap and post is null when inistialized and set in parse method + public PostTable post_table; + public KernTable kern_table; + + public List<uint32> location_offsets; + + // list of glyphs sorted in the order we expect to find them in a + // ttf font. notdef is the firs glyph followed by null and nonmarkingreturn. + // after that will all assigned glyphs appear in sorted order, all + // remaining unassigned glyphs live in the last part of the file. + public List<Glyph> glyphs; + + uint16 max_points = 0; + uint16 max_contours = 0; + + double total_width = 0; + int non_zero_glyphs = 0; + + public GlyfTable (LocaTable l) { + id = "glyf"; + loca_table = l; + location_offsets = new List<uint32> (); + glyphs = new List<Glyph> (); + } + + public int get_gid (string name) { + int i = 0; + foreach (Glyph g in glyphs) { + if (g.name == name) { + return i; + } + + i++; + } + + return -1; + } + + public int16 get_average_width () { + return (int16) (total_width / non_zero_glyphs); + } + + public uint16 get_max_contours () { + return max_contours; + } + + public uint16 get_max_points () { + return max_points; + } + + public uint16 get_first_char () { + return 32; // space + } + + public uint16 get_last_char () { + unowned List<Glyph> gl = glyphs.last (); + Glyph g = (!) gl.data; + + while (gl != glyphs.first ()) { + g = (!) gl.data; + + if (!g.is_unassigned ()) { + break; + } + + gl = gl.prev; + } + + return (uint16)g.unichar_code; + } + + public Glyph? read_glyph (string name) throws GLib.Error { + Glyph? glyph; + Glyph g; + int i; + KernList kl; + string right; + double units_per_em; + double kern_val; + + i = post_table.get_gid (name); + + if (i == -1) { + return null; + } + + glyph = parse_index (i, dis, loca_table, hmtx_table, head_table, post_table); + + if (glyph != null) { + printd (@"kern: "); + units_per_em = head_table.get_units_per_em (); + kl = kern_table.get_all_pairs (i); + foreach (Kern k in kl.kernings) { + g = (!) glyph; + right = post_table.get_name (k.right); + kern_val = k.kerning * 1000.0 / units_per_em; + g.add_kerning (right, kern_val); + } + } + + return glyph; + } + + public new void parse (FontData dis, CmapTable cmap_table, LocaTable loca, HmtxTable hmtx_table, HeadTable head_table, PostTable post_table, KernTable kern_table) throws GLib.Error { + this.cmap_table = cmap_table; + this.post_table = post_table; + this.loca_table = loca; + this.hmtx_table = hmtx_table; + this.head_table = head_table; + this.kern_table = kern_table; + this.dis = dis; + } + + Glyph parse_index (int index, FontData dis, LocaTable loca, HmtxTable hmtx_table, HeadTable head_table, PostTable post_table) throws GLib.Error { + Glyph glyph = new Glyph (""); + double xmin, xmax; + double units_per_em = head_table.get_units_per_em (); + unichar character = 0; + string name; + uint32 end; + + character = cmap_table.get_char (index); + name = post_table.get_name (index); + + if (name == "") { + StringBuilder name_c = new StringBuilder (); + name_c.append_unichar (character); + name = name_c.str; + } + + printd (@"name: $(name)\n"); + + if (!loca.is_empty (index)) { + glyph = parse_next_glyf (dis, character, index, out xmin, out xmax, units_per_em); + + glyph.left_limit = xmin - hmtx_table.get_lsb (index); + glyph.left_limit = 0; + glyph.right_limit = glyph.left_limit + hmtx_table.get_advance (index); + } else { + // add empty glyph + glyph = new Glyph (name, character); + glyph.left_limit = -hmtx_table.get_lsb (index); + glyph.right_limit = hmtx_table.get_advance (index) - hmtx_table.get_lsb (index); + } + + glyph.name = name; + + if (character == 0) { + glyph.set_unassigned (true); + } + + if (character == 0 && name != "") { + stderr.printf (@"Got null character\n"); + stderr.printf (@"gid: $index\n"); + stderr.printf (@"char: $((uint) character)\n"); + stderr.printf (@"name: $(name)\n"); + } + + return glyph; + } + + Glyph parse_next_composite_glyf (FontData dis, unichar character, int pgid) throws Error { + uint16 component_flags = 0; + uint16 glyph_index; + int16 arg1 = 0; + int16 arg2 = 0; + uint16 arg1and2; + F2Dot14 scale; + + F2Dot14 scalex; + F2Dot14 scaley; + + F2Dot14 scale01; + F2Dot14 scale10; + + Glyph glyph, linked_glyph; + List<int> x = new List<int> (); + List<int> y = new List<int> (); + List<int> gid = new List<int> (); + + double xmin, xmax; + double units_per_em = head_table.get_units_per_em (); + + int glid; + + StringBuilder name = new StringBuilder (); + name.append_unichar (character); + + glyph = new Glyph (name.str, character); + + do { + component_flags = dis.read_ushort (); + glyph_index = dis.read_ushort (); + + if ((component_flags & BOTH_ARE_WORDS) > 0) { + arg1 = dis.read_short (); + arg2 = dis.read_short (); + } else if ((component_flags & BOTH_ARE_XY_VALUES) > 0) { + arg1 = dis.read_byte (); + arg2 = dis.read_byte (); + } + + gid.append (glyph_index); + x.append (arg1); + y.append (arg2); + + // if ((component_flags & RESERVED) > 0) + + if ((component_flags & SCALE) > 0) { + scale = dis.read_f2dot14 (); + } else if ((component_flags & SCALE_X_Y) > 0) { + scalex = dis.read_f2dot14 (); + scaley = dis.read_f2dot14 (); + } else if ((component_flags & SCALE_WITH_ROTATTION) > 0) { + scalex = dis.read_f2dot14 (); + scale01 = dis.read_f2dot14 (); + scale10 = dis.read_f2dot14 (); + scaley = dis.read_f2dot14 (); + } + + } while ((component_flags & MORE_COMPONENTS) > 0); + + + for (int i = 0; i < gid.length (); i++) { + // compensate xmax ymax with coordinate + glid = gid.nth (i).data; + + if (glid == pgid) { + warning ("Cannot link a glyph to it self."); + continue; + } + + linked_glyph = parse_next_glyf (dis, character, glid, out xmin, out xmax, units_per_em); + } + + + /* + if ((component_flags & INSTRUCTIONS) > 0) { + num_instructions = dis.read_ushort (); + + for (int i = 0; i < num_instructions; i++) { + dis.read_byte (); + } + } + */ + + return glyph; + } + + Glyph parse_next_glyf (FontData dis, unichar character, int gid, out double xmin, out double xmax, double units_per_em) throws Error { + + uint16* end_points = null; + uint8* instructions = null; + uint8* flags = null; + int16* xcoordinates = null; + int16* ycoordinates = null; + + int npoints = 0; + + int16 ncontours; + int16 ixmin; // set boundries + int16 iymin; + int16 ixmax; + int16 iymax; + uint16 ninstructions; + + int16 rxmin = int16.MAX; // real xmin + int16 rymin = int16.MAX;; + int16 rxmax = int16.MIN; + int16 rymax = int16.MIN; + + int nflags; + + Error? error = null; + + uint start, end, len; + + StringBuilder name = new StringBuilder (); + name.append_unichar (character); + + start = loca_table.get_offset (gid); + end = loca_table.get_offset (gid + 1); + len = start - end; + + dis.seek (offset + start); + + ncontours = dis.read_short (); + + return_val_if_fail (start < end, new Glyph ("")); + + if (ncontours == 0) { + warning (@"Got zero contours in glyph $(name.str)."); + + // should skip body + } + + if (ncontours == -1) { + return parse_next_composite_glyf (dis, character, gid); + } + + return_val_if_fail (ncontours < len, new Glyph ("")); + + if (ncontours < -1) { + warning (@"Got $ncontours contours in glyf table."); + error = new BadFormat.PARSE ("Invalid glyf"); + throw error; + } + + ixmin = dis.read_short (); + iymin = dis.read_short (); + ixmax = dis.read_short (); + iymax = dis.read_short (); + + end_points = new uint16[ncontours + 1]; + for (int i = 0; i < ncontours; i++) { + end_points[i] = dis.read_ushort (); // FIXA: mind shot vector is negative + + if (i > 0 && end_points[i] < end_points[i -1]) { + warning (@"Next endpoint has bad value in $(name.str). (end_points[i] > end_points[i -1]) ($(end_points[i]) > $(end_points[i -1])) i: $i ncontours: $ncontours"); + } + } + + if (ncontours > 0) { + npoints = end_points[ncontours - 1] + 1; + } else { + npoints = 0; + } + + return_val_if_fail (npoints < len, new Glyph.no_lines ("")); + + ninstructions = dis.read_ushort (); + + return_val_if_fail (ninstructions < len, new Glyph.no_lines ("")); + + instructions = new uint8[ninstructions + 1]; + uint8 repeat; + for (int i = 0; i < ninstructions; i++) { + instructions[i] = dis.read_byte (); + } + + nflags = 0; + flags = new uint8[npoints + 1]; + for (int i = 0; i < npoints; i++) { + flags[i] = dis.read_byte (); + + if ((flags[i] & Coordinate.REPEAT) > 0) { + repeat = dis.read_byte (); + + if (i + repeat >= npoints) { + error = new BadFormat.PARSE ("Too many flags in glyf in glyph $(name.str). (i >= ninstructions)."); + break; + } + + for (int j = 0; j < repeat; j++) { + flags[j + i + 1] = flags[i]; + } + + nflags += repeat; + i += repeat; + } + + nflags++; + } + + if (nflags != npoints) { + warning (@"(nflags != npoints) ($nflags != $npoints) in $(name.str)"); + error = new BadFormat.PARSE (@"Wrong number of flags in glyph $(name.str). (nflags != npoints) ($nflags != $npoints)"); + } + + warn_if_fail (nflags == npoints); + + printd (@"npoints: $npoints\n"); + printd (@"ncontours: $ncontours\n"); + printd (@"ninstructions: $ninstructions\n"); + printd (@"nflags: $nflags\n"); + + int16 last = 0; + xcoordinates = new int16[npoints + 1]; + for (int i = 0; i < npoints; i++) { + if ((flags[i] & Coordinate.X_SHORT_VECTOR) > 0) { + if ((flags[i] & Coordinate.X_SHORT_VECTOR_POSITIVE) > 0) { + xcoordinates[i] = last + dis.read_byte (); + } else { + xcoordinates[i] = last - dis.read_byte (); + } + } else { + if ((flags[i] & Coordinate.X_IS_SAME) > 0) { + xcoordinates[i] = last; + } else { + xcoordinates[i] = last + dis.read_short (); + } + } + + last = xcoordinates[i]; + + if (last > rxmax) rxmax = last; + if (last < rxmin) rxmin = last; + + if (!(ixmin <= last <= ixmax)) { + stderr.printf (@"x is out of bounds in glyph $(name.str). ($ixmin <= $last <= $ixmax) char $((uint)character)\n"); + } + + if (!(head_table.xmin <= last <= head_table.xmax)) { + stderr.printf (@"x is outside of of font bounding box in glyph $(name.str). ($(head_table.xmin) <= $last <= $(head_table.xmax)) char $((uint)character)\n"); + } + } + + last = 0; + ycoordinates = new int16[npoints + 1]; + for (int i = 0; i < npoints; i++) { + if ((flags[i] & Coordinate.Y_SHORT_VECTOR) > 0) { + if ((flags[i] & Coordinate.Y_SHORT_VECTOR_POSITIVE) > 0) { + ycoordinates[i] = last + dis.read_byte (); + } else { + ycoordinates[i] = last - dis.read_byte (); + } + } else { + if ((flags[i] & Coordinate.Y_IS_SAME) > 0) { + ycoordinates[i] = last; + } else { + ycoordinates[i] = last + dis.read_short (); + } + } + + last = ycoordinates[i]; + + if (last > rymax) rymax = last; + if (last < rymin) rymin = last; + + if (!(iymin <= last <= iymax)) { + stderr.printf (@"y is out of bounds in glyph $(name.str). ($iymin <= $last <= $iymax) char $((uint)character)\n"); + } + + if (!(head_table.ymin <= last <= head_table.ymax)) { + stderr.printf (@"y is outside of of font bounding box in glyph $(name.str). ($(head_table.ymin) <= $last <= $(head_table.ymax)) char $((uint)character)\n"); + } + } + + if (rymin != iymin || rxmin != ixmin || rxmax != ixmax || rymax != iymax) { + warning (@"Warning real boundry for glyph does not match boundry set in glyph header for glyph $(name.str)."); + stderr.printf (@"ymin: $rymin header: $iymin\n"); + stderr.printf (@"xmin: $rxmin header: $ixmin\n"); + stderr.printf (@"ymax: $rymax header: $iymax\n"); + stderr.printf (@"xmax: $rxmax header: $ixmax\n"); + } + + int j = 0; + int first_point; + int last_point = 0; + Glyph glyph; + double x, y; + + glyph = new Glyph (name.str, character); + + xmin = ixmin * 1000.0 / units_per_em; + xmax = ixmax * 1000.0 / units_per_em; + + for (int i = 0; i < ncontours; i++) { + x = 0; + y = 0; + + Path path = new Path (); + EditPoint edit_point = new EditPoint (); + bool prev_is_curve = false; + + first_point = j; + last_point = end_points[i]; + for (; j <= end_points[i]; j++) { + + if (j >= npoints) { + warning (@"j >= npoints in glyph $(name.str). (j: $j, end_points[i]: $(end_points[i]), npoints: $npoints)"); + break; + } + + x = xcoordinates[j] * 1000.0 / units_per_em; // in proportion to em width + y = ycoordinates[j] * 1000.0 / units_per_em; + + if ((flags[j] & Coordinate.ON_PATH) > 0) { + // Point + edit_point = new EditPoint (); + edit_point.set_position (x, y); + path.add_point (edit_point); + + if (prev_is_curve) { + edit_point.get_left_handle ().set_point_type (PointType.NONE); + edit_point.get_left_handle ().length = 0; + } else { + edit_point.recalculate_linear_handles (); + } + + prev_is_curve = false; + } else { + + if (prev_is_curve) { + x = x - (x - edit_point.right_handle.x ()) / 2; + y = y - (y - edit_point.right_handle.y ()) / 2; + + edit_point = new EditPoint (); + edit_point.set_position (x, y); + path.add_point (edit_point); + } + + x = xcoordinates[j] * 1000.0 / units_per_em; // in proportion to em width + y = ycoordinates[j] * 1000.0 / units_per_em; + + edit_point.get_left_handle ().set_point_type (PointType.NONE); + edit_point.get_left_handle ().length = 0; + + edit_point.type = PointType.CURVE; + edit_point.get_right_handle ().set_point_type (PointType.CURVE); + edit_point.get_right_handle ().move_to_coordinate (x, y); + + prev_is_curve = true; + } + } + + // last to first point + if (prev_is_curve) { + x = xcoordinates[first_point] * 1000.0 / units_per_em; // in proportion to em width + y = ycoordinates[first_point] * 1000.0 / units_per_em; + + x = x - (x - edit_point.right_handle.x ()) / 2; + y = y - (y - edit_point.right_handle.y ()) / 2; + + edit_point = new EditPoint (); + edit_point.set_position (x, y); + path.add_point (edit_point); + + x = xcoordinates[first_point] * 1000.0 / units_per_em; // in proportion to em width + y = ycoordinates[first_point] * 1000.0 / units_per_em; + + edit_point.get_left_handle ().set_point_type (PointType.CURVE); + edit_point.get_left_handle ().length = 0; + + edit_point.type = PointType.CURVE; + edit_point.get_right_handle ().set_point_type (PointType.CURVE); + edit_point.get_right_handle ().move_to_coordinate (x, y); + } + + // curve last to first + x = xcoordinates[first_point] * 1000.0 / units_per_em; // in proportion to em width + y = ycoordinates[first_point] * 1000.0 / units_per_em; + edit_point.type = PointType.CURVE; + edit_point.get_right_handle ().set_point_type (PointType.CURVE); + edit_point.get_right_handle ().move_to_coordinate (x, y); + + path.close (); + + glyph.add_path (path); + } + + // glyphs with no bounding boxes + if (ixmax <= ixmin) { + warning (@"Bounding box is bad. (xmax == xmin) ($xmax == $xmin)"); + + if (glyph.path_list.length () > 0) { + + Path ps = ((!) glyph.path_list.first ()).data; + + ps.update_region_boundries (); + xmin = ps.xmin; + xmax = ps.xmax; + + foreach (Path p in glyph.path_list) { + p.update_region_boundries (); + + if (p.xmin < xmin) { + xmin = p.xmin; + } + + if (p.xmax > xmax) { + xmax = p.xmax; + } + } + + } + } + + if (end_points != null) delete end_points; + if (instructions != null) delete instructions; + if (flags != null) delete flags; + if (xcoordinates != null) delete xcoordinates; + if (ycoordinates != null) delete ycoordinates; + + if (error != null) { + warning ("failed to parse glyph"); + throw (!) error; + } + + 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; + + double x, y; + + Font font = OpenFontFormatWriter.get_current_font (); + 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); + + 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 (10); + fd.add_16 (20); + fd.add_16 (30); + fd.add_16 (40); + + // end points + end_point = 0; + last_end_point = 0; + foreach (Path p in g.path_list) { + p = p.get_quadratic_points (); + foreach (EditPoint e in p.points) { + end_point++; + + if (e.get_right_handle ().type == PointType.CURVE) { + end_point++; + } + } + fd.add_u16 (end_point - 1); + + 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; + } + + fd.add_u16 (0); // instruction length + + uint glyph_header = 12 + ncontours * 2; + + printd (@"\next glyf: $(g.name)\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++; + + if (e.get_right_handle ().type == PointType.CURVE) { + fd.add_byte (Coordinate.NONE); + 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.CURVE) { + x = rint (e.get_right_handle ().x () * UNITS - prev - g.left_limit * UNITS); + + fd.add_16 ((int16) x); + coordinate_x.append ((int16) x); + + prev = rint (e.get_right_handle ().x () * UNITS - g.left_limit * 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.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); + } + } + } + + 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; + } + + 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; + } + + 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; + + // 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"); + } + + // necessary in order to have glyphs sorted according to ttf specification + public void create_glyph_table () { + Glyph? gl; + Glyph g; + Font font = OpenFontFormatWriter.get_current_font (); + uint32 indice; + List<Glyph> unassigned_glyphs; + + // add notdef. character at index zero + other special chars first + glyphs.append (font.get_not_def_character ()); + glyphs.append (font.get_null_character ()); + glyphs.append (font.get_nonmarking_return ()); + glyphs.append (font.get_space ()); + + unassigned_glyphs = new List<Glyph> (); + + // add glyphs, first all assigned then the unassigned ones + for (indice = 0; (gl = font.get_glyph_indice (indice)) != null; indice++) { + g = (!) gl; + + if (g.unichar_code <= 27) { // skip control characters + continue; + } + + if (g.unichar_code == 32) { // skip space + continue; + } + + if (g.name == ".notdef") { + continue; + } + + g.remove_empty_paths (); + + if (!g.is_unassigned ()) { + glyphs.append (g); + } else { + unassigned_glyphs.append (g); + } + } + + foreach (Glyph ug in unassigned_glyphs) { + // glyphs.append (ug); + } + + } + + public void process () throws GLib.Error { + FontData fd = new FontData (); + + create_glyph_table (); + uint last_len = 0; + foreach (Glyph g in glyphs) { + // set values for loca table + assert (fd.length () % 4 == 0); + location_offsets.append (fd.length ()); + process_glyph (g, fd); + + printd (@"glyf length: $(fd.length () - last_len)\n"); + printd (@"loca fd.length (): $(fd.length ())\n"); + + last_len = fd.length (); + } + + location_offsets.append (fd.length ()); // last entry in loca table is special + + // every glyph is padded, no padding to be done here + assert (fd.length () % 4 == 0); + + font_data = fd; + } +} + +class CmapSubtable : Table { + + // Override these methods in subtables for each format. + + /** Obtain length of subtable in bytes. */ + public virtual uint get_length () { + warning ("Invalid CmapSubtable"); + return 0; + } + + /** Get char code for a glyph id. */ + public virtual unichar get_char (uint32 i) { + warning ("Invalid CmapSubtable"); + return 0; + } + + public void print_cmap () { + StringBuilder s; + unichar c; + for (uint32 i = 0; i < get_length (); i++) { + s = new StringBuilder (); + c = get_char (i); + s.append_unichar (c); + printd (@"Char: $(s.str) val ($((uint32)c))\tindice: $(i)\n"); + } + } +} + +/** Format 4 cmap subtable */ +class CmapSubtableWindowsUnicode : CmapSubtable { + uint16 format = 0; + HashTable <uint64?, unichar> table = new HashTable <uint64?, unichar> (int64_hash, int_equal); + + public CmapSubtableWindowsUnicode () { + } + + ~CmapSubtableWindowsUnicode () { + + } + + public override uint get_length () { + return table.size (); + } + + public override unichar get_char (uint32 indice) { + int64? c = table.lookup (indice); + + if (c == 0 && indice == 0) { + return 0; + } + + if (c == 0) { + while (table.lookup (--indice) == 0) { + if (indice == 0) { + return 0; + } + } + + warning (@"There is no char for glyph number $indice in cmap table. table.size: $(table.size ()))"); + return 0; + } + + return (unichar) c; + } + + public override void parse (FontData dis) throws GLib.Error { + dis.seek (offset); + + format = dis.read_ushort (); + + switch (format) { + case 4: + parse_format4 (dis); + break; + + default: + stderr.printf (@"CmapSubtable is in format $format, it is not supportet (yet).\n"); + break; + } + } + + public void parse_format4 (FontData dis) throws GLib.Error { + uint16 lang; + uint16 seg_count_x2; + uint16 seg_count; + uint16 search_range; + uint16 entry_selector; + uint16 range_shift; + + uint16* end_char = null; + uint16* start_char = null; + int16* id_delta = null; + uint16* id_range_offset = null; + uint16* glyph_id_array = null; + + uint32 gid_len; + + length = dis.read_ushort (); + lang = dis.read_ushort (); + seg_count_x2 = dis.read_ushort (); + search_range = dis.read_ushort (); + entry_selector = dis.read_ushort (); + range_shift = dis.read_ushort (); + + return_if_fail (seg_count_x2 % 2 == 0); + + seg_count = seg_count_x2 / 2; + + end_char = new uint16[seg_count]; + for (int i = 0; i < seg_count; i++) { + end_char[i] = dis.read_ushort (); + } + + if (end_char[seg_count - 1] != 0xFFFF) { + warning ("end_char is $(end_char[seg_count - 1]), expecting 0xFFFF."); + } + + dis.read_ushort (); // Reserved + + start_char = new uint16[seg_count]; + for (int i = 0; i < seg_count; i++) { + start_char[i] = dis.read_ushort (); + } + + id_delta = new int16[seg_count]; + for (int i = 0; i < seg_count; i++) { + id_delta[i] = dis.read_short (); + } + + id_range_offset = new uint16[seg_count]; + for (int i = 0; i < seg_count; i++) { + id_range_offset[i] = dis.read_ushort (); + } + + if (length == 0) { + warning ("cmap subtable version 4 has length 0."); + return; + } + + gid_len = (length - 16 - 8 * seg_count) / 2; + glyph_id_array = new uint16[gid_len]; + for (int i = 0; i < gid_len; i++) { + glyph_id_array[i] = dis.read_ushort (); + } + + // map all values in a hashtable + int indice = 0; + unichar character = 0; + uint32 id; + for (uint16 i = 0; i < seg_count && start_char[i] != 0xFFFF; i++) { + + // print_range (start_char[i], end_char[i], id_delta[i], id_range_offset[i]); + + uint16 j = 0; + do { + character = start_char[i] + j; + indice = start_char[i] + id_delta[i] + j; + + if (id_range_offset[i] == 0) { + table.insert (indice, character); + } else { + // the awkward indexing trick: + id = id_range_offset[i] / 2 + j + i - seg_count; + + if (!(0 <= id < gid_len)) { + warning (@"(0 <= id < gid_len) (0 <= $id < $gid_len)"); + break; + } + + indice = glyph_id_array [id] + id_delta[i]; + + StringBuilder s = new StringBuilder (); + s.append_unichar (character); + + table.insert (indice, character); + } + + j++; + } while (character != end_char[i]); + + } + + if (end_char != null) delete end_char; + if (start_char != null) delete start_char; + if (id_delta != null) delete id_delta; + if (id_range_offset != null) delete id_range_offset; + if (glyph_id_array != null) delete glyph_id_array; + + // it has a character for every glyph indice + // assert (validate_subtable ()); + } + + void print_range (unichar start_char, unichar end_char, uint16 delta_offset, uint16 range_offset) { + StringBuilder s = new StringBuilder (); + StringBuilder e = new StringBuilder (); + + s.append_unichar (start_char); + e.append_unichar (end_char); + + print (@"New range $(s.str) - $(e.str) delta: $delta_offset, range: $range_offset\n"); + } + + public void process (FontData fd, GlyfTable glyf_table) throws GLib.Error { + GlyphRange glyph_range = new GlyphRange (); + unowned List<UniRange> ranges; + + uint16 seg_count_2; + uint16 seg_count; + uint16 search_range; + uint16 entry_selector; + uint16 range_shift; + + uint16 gid_length = 0; + + uint32 indice; + uint32 first_assigned = 1; + + foreach (Glyph g in glyf_table.glyphs) { + if (!g.is_unassigned ()) { + glyph_range.add_single (g.unichar_code); + } + } + + // glyph_range.print_all (); + + ranges = glyph_range.get_ranges (); + seg_count = (uint16) ranges.length () + 1; + seg_count_2 = seg_count * 2; + search_range = 2 * largest_pow2 (seg_count); + entry_selector = largest_pow2_exponent (seg_count); + range_shift = seg_count_2 - search_range; + + // format + fd.add_ushort (4); + + // length of subtable + fd.add_ushort (16 + 8 * seg_count + gid_length); + + // language + fd.add_ushort (0); + + fd.add_ushort (seg_count_2); + fd.add_ushort (search_range); + fd.add_ushort (entry_selector); + fd.add_ushort (range_shift); + + // end codes + indice = first_assigned; + foreach (UniRange u in ranges) { + if (u.stop >= 0xFFFF) { + warning ("Not implemented yet."); + } + + fd.add_ushort ((uint16) u.stop); + indice += u.length (); + } + fd.add_ushort (0xFFFF); + + fd.add_ushort (0); // Reserved + + // start codes + indice = first_assigned; // since first glyph are notdef, null and nonmarkingreturn + foreach (UniRange u in ranges) { + if (u.start >= 0xFFFF) { + warning ("Not implemented yet."); + } + + fd.add_ushort ((uint16) u.start); + indice += u.length (); + } + fd.add_ushort (0xFFFF); + + // delta + indice = first_assigned; + foreach (UniRange u in ranges) { + + if ((u.start - indice) > 0xFFFF && u.start > indice) { + warning ("Need range offset."); + } + + fd.add_ushort ((uint16) (indice - u.start)); + indice += u.length (); + } + fd.add_ushort (1); + + // range offset + foreach (UniRange u in ranges) { + if (u.stop <= 0xFFFF) { + fd.add_ushort (0); + } else { + warning ("Not implemented yet."); + } + } + fd.add_ushort (0); + + // Fixa: implement rest of type 4 (mind gid_length in length field) + } + + public bool validate_subtable () { + uint32 length = get_length (); + unichar c; + unichar prev; + uint32 i = 0; + uint32 err = 0; + StringBuilder s; + + c = get_char (i); + if (c != 0) { + s = new StringBuilder (); + s.append_unichar (c); + warning ("nodef. is mapped to $(s.str)"); + } + + i++; + + while (i < length) { + if (c == 0) { + s = new StringBuilder (); + s.append_unichar (c); + warning (@"No entry in cmap for index $i. Last avalable char is $(s.str) Got $(s.str), ($((uint32)c))"); + err++; + return false; + } else { + prev = c; + } + + c = get_char (i); + i++; + } + + if (err > 0) { + stderr.printf (@"$err glyphs without mapping to a charactercode were found in this font.\n"); + } + + return true; + } +} + +class CmapTable : Table { + + GlyfTable glyf_table; + List<CmapSubtable> subtables; + + public CmapTable(GlyfTable gt) { + glyf_table = gt; + subtables = new List<CmapSubtable> (); + id = "cmap"; + } + + public uint32 get_length () { + return get_prefered_table ().get_length (); + } + + public unichar get_char (uint32 i) { + return get_prefered_table ().get_char (i) ; + } + + CmapSubtable get_prefered_table () { + if (subtables.length () == 0) { + warning ("No cmap table has been parsed."); + return new CmapSubtable (); + } + + return subtables.first ().data; + } + + public override string get_id () { + return "cmap"; + } + + public override void parse (FontData dis) throws GLib.Error { + uint16 version; + uint16 nsubtables; + + uint16 platform; + uint16 encoding; + uint32 sub_offset; + + CmapSubtable subtable; + + return_if_fail (offset > 0 && length > 0); + + dis.seek (offset); + + version = dis.read_ushort (); + nsubtables = dis.read_ushort (); + + printd (@"cmap version: $version\n"); + printd (@"cmap subtables: $nsubtables\n"); + + if (version != 0) { + warning (@"Bad version for cmap table: $version expecting 0. Number of subtables: $nsubtables"); + return; + } + + for (uint i = 0; i < nsubtables; i++) { + platform = dis.read_ushort (); + encoding = dis.read_ushort (); + sub_offset = dis.read_ulong (); + + if (platform == 3 && encoding == 1) { + printd (@"Parsing Unicode BMP (UCS-2) Platform: $platform Encoding: $encoding\n"); + subtable = new CmapSubtableWindowsUnicode (); + subtable.offset = offset + sub_offset; + subtables.append (subtable); + } else { + stderr.printf (@"Unknown cmap format. Platform: $platform Encoding: $encoding.\n"); + } + + if (encoding == 3) { + stderr.printf ("Font contains a cmap table with the obsolete encoding 3.\n"); + } + } + + if (subtables.length () == 0) { + warning ("No suitable cmap subtable found."); + } + + foreach (CmapSubtable t in subtables) { + t.parse (dis); + t.print_cmap (); + } + + } + + /** Character to glyph mapping */ + public void process (GlyfTable glyf_table) throws GLib.Error { + FontData fd = new FontData (); + CmapSubtableWindowsUnicode cmap = new CmapSubtableWindowsUnicode (); + uint16 n_encoding_tables; + uint32 subtable_offset = 0; + + n_encoding_tables = 1; + + fd.add_u16 (0); // table version + fd.add_u16 (n_encoding_tables); + + fd.add_u16 (3); // platform + fd.add_u16 (1); // encoding (Format Unicode UCS-4) + + subtable_offset = fd.length () + 4; + printd (@"subtable_offset: $(subtable_offset)\n"); + + fd.add_ulong (subtable_offset); + cmap.process (fd, glyf_table); + + // padding + fd.pad (); + + this.font_data = fd; + } +} + +class HeadTable : Table { + + public int16 xmin = int16.MIN; + public int16 ymin = int16.MIN; + public int16 xmax = int16.MAX; + public int16 ymax = int16.MAX; + + uint32 adjusted_checksum = 0; + + uint16 mac_style; + uint16 lowest_PPEM; + int16 font_direction_hint; + + public int16 loca_offset_size = 0; // 0 for int16 1 for int32 + int16 glyph_data_format; + + Fixed version; + Fixed font_revision; + + uint32 magic_number; + + uint16 flags; + + uint64 created; + uint64 modified; + + uint16 units_per_em = 2048; + + const uint8 BASELINE_AT_ZERO = 1 << 0; + const uint8 LSB_AT_ZERO = 1 << 1; + + GlyfTable glyf_table; + + public HeadTable (GlyfTable gt) { + glyf_table = gt; + id = "head"; + } + + public uint32 get_adjusted_checksum () { + return adjusted_checksum; + } + + public double get_units_per_em () { + return units_per_em * 10; // Fixa: we can refactor this number + } + + public override void parse (FontData dis) throws GLib.Error { + return_if_fail (offset > 0 && length > 0); + + dis.seek (offset); + + font_data = new FontData (); + + version = dis.read_fixed (); + + if (!version.equals (1, 0)) { + warning (@"Expecting head version 1.0 got $(version.get_string ())\n"); + } + + font_revision = dis.read_fixed (); + adjusted_checksum = dis.read_ulong (); + magic_number = dis.read_ulong (); + + if (magic_number != 0x5F0F3CF5) { + warning (@"Magic number is invalid. Got $(magic_number)."); + return; + } + + flags = dis.read_ushort (); + + if ((flags & BASELINE_AT_ZERO) > 0) { + printd ("Flag BASELINE_AT_ZERO has been set.\n"); + } + + if ((flags & LSB_AT_ZERO) > 0) { + printd ("Flags LSB_AT_ZERO has been set.\n"); + } + + units_per_em = dis.read_ushort (); + + created = dis.read_udate (); + modified = dis.read_udate (); + + xmin = dis.read_short (); + ymin = dis.read_short (); + + xmax = dis.read_short (); + ymax = dis.read_short (); + + printd (@"font boundries:\n"); + printd (@"xmin: $xmin\n"); + printd (@"ymin: $ymin\n"); + printd (@"xmax: $xmax\n"); + printd (@"ymax: $ymax\n"); + + mac_style = dis.read_ushort (); + lowest_PPEM = dis.read_ushort (); + font_direction_hint = dis.read_short (); + + loca_offset_size = dis.read_short (); + glyph_data_format = dis.read_short (); + + if (glyph_data_format != 0) { + warning (@"Unknown glyph data format. Expecting 0 got $glyph_data_format."); + } + + // print_values (); + // Some deprecated values follow here ... + } + + void print_values () { + print (@"Version: $(version.get_string ())\n"); + print (@"flags: $flags\n"); + print (@"font_revision: $(font_revision.get_string ())\n"); + print (@"flags: $flags\n"); + print (@"Units per em: $units_per_em\n"); + print (@"lowest_PPEM: $lowest_PPEM\n"); + print (@"font_direction_hint: $font_direction_hint\n"); + print (@"loca_offset_size: $loca_offset_size\n"); + print (@"glyph_data_format: $glyph_data_format\n"); + } + + public uint32 get_font_checksum () { + return adjusted_checksum; + } + + public void set_check_sum_adjustment (uint32 csa) { + this.adjusted_checksum = csa; + } + + public uint32 get_checksum_position () { + return 8; + } + + public void process () throws GLib.Error { + FontData font_data = new FontData (); + Fixed version = 1 << 16; + Fixed font_revision = 1 << 16; + + font_data.add_fixed (version); + font_data.add_fixed (font_revision); + + // Zero on the first run and updated by directory tables checksum calculation + // for the entire font. + font_data.add_u32 (adjusted_checksum); + + font_data.add_u32 (0x5F0F3CF5); // magic number + + //font_data.add_u16 (BASELINE_AT_ZERO | LSB_AT_ZERO); + font_data.add_u16 (0); // flags + + font_data.add_u16 (1000); // units per em (should be a power of two for ttf fonts) + + font_data.add_64 (0); // creation time since 1904-01-01 + font_data.add_64 (0); // modified time since 1904-01-01 + + xmin = glyf_table.xmin; + ymin = glyf_table.ymin; + xmax = glyf_table.xmax; + ymax = glyf_table.ymax; + + printd (@"font boundries:\n"); + printd (@"xmin: $xmin\n"); + printd (@"ymin: $ymin\n"); + printd (@"xmax: $xmax\n"); + printd (@"ymax: $ymax\n"); + + font_data.add_short (xmin); + font_data.add_short (ymin); + font_data.add_short (xmax); + font_data.add_short (ymax); + + font_data.add_u16 (0); // mac style + font_data.add_u16 (2); // smallest recommended size in pixels, ppem + font_data.add_16 (2); // deprecated direction hint + font_data.add_16 (loca_offset_size); // long offset + font_data.add_16 (0); // Use current glyph data format + + font_data.pad (); + + this.font_data = font_data; + } +} + +class HheaTable : Table { + + Fixed version; + int16 ascender; + int16 descender; + int16 linegap; + uint16 max_advance; + int16 min_lsb; + int16 min_rsb; + int16 xmax_extent; + int16 carret_slope; + int16 carret_slope_run; + int16 carret_offset; + + int16 metric_format; + public int16 num_horizontal_metrics; + + GlyfTable glyf_table; + HeadTable head_table; + HmtxTable hmtx_table; + + public HheaTable (GlyfTable g, HeadTable h, HmtxTable hm) { + glyf_table = g; + head_table = h; + hmtx_table = hm; + id = "hhea"; + } + + public double get_ascender () { + return ascender * 1000 / head_table.get_units_per_em (); + } + + public double get_descender () { + return descender * 1000 / head_table.get_units_per_em (); + } + + public override void parse (FontData dis) throws GLib.Error { + dis.seek (offset); + + version = dis.read_fixed (); + + if (!version.equals (1, 0)) { + warning (@"wrong version in hhea table $(version.get_string ())"); + } + + ascender = dis.read_short (); + descender = dis.read_short (); + linegap = dis.read_short (); + max_advance = dis.read_ushort (); + min_lsb = dis.read_short (); + min_rsb = dis.read_short (); + xmax_extent = dis.read_short (); + carret_slope = dis.read_short (); + carret_slope_run = dis.read_short (); + carret_offset = dis.read_short (); + + // reserved x 4 + dis.read_short (); + dis.read_short (); + dis.read_short (); + dis.read_short (); + + metric_format = dis.read_short (); + num_horizontal_metrics = dis.read_short (); + } + + public void process () throws GLib.Error { + Font font = OpenFontFormatWriter.get_current_font (); + FontData fd = new FontData (); + Fixed version = 1 << 16; + + fd.add_fixed (version); // table version + + fd.add_16 ((int16) (-1 * (font.top_position - font.base_line) * UNITS)); // Ascender + fd.add_16 ((int16) (-1 * font.bottom_position * UNITS)); // Descender + fd.add_16 (0); // LineGap + + fd.add_u16 (hmtx_table.max_advance); // maximum advance width value in 'hmtx' table. + + fd.add_16 (hmtx_table.min_lsb); // min left side bearing + fd.add_16 (hmtx_table.min_rsb); // min right side bearing + fd.add_16 (hmtx_table.max_extent); // x max extent Max(lsb + (xMax - xMin)) + + fd.add_16 (1); // caretSlopeRise + fd.add_16 (0); // caretSlopeRun + fd.add_16 (0); // caretOffset + + // reserved + fd.add_16 (0); + fd.add_16 (0); + fd.add_16 (0); + fd.add_16 (0); + + fd.add_16 (0); // metricDataFormat 0 for current format. + + fd.add_u16 ((uint16) glyf_table.glyphs.length()); // numberOfHMetrics Number of hMetric entries in 'hmtx' table + + // padding + fd.pad (); + this.font_data = fd; + } +} + +class HmtxTable : Table { + + uint32 nmetrics; + uint32 nmonospaced; + + uint16* advance_width = null; + uint16* left_side_bearing = null; + uint16* left_side_bearing_monospaced = null; + + public int16 max_advance = 0; + public int16 max_extent = 0; + public int16 min_lsb = 0; + public int16 min_rsb = 0; + + HeadTable head_table; + GlyfTable glyf_table; + + public HmtxTable (HeadTable h, GlyfTable gt) { + head_table = h; + glyf_table = gt; + id = "hmtx"; + } + + ~HmtxTable () { + if (advance_width != null) delete advance_width; + if (left_side_bearing != null) delete left_side_bearing; + } + + public double get_advance (uint32 i) { + if (i >= nmetrics) { + warning (@"i >= nmetrics $i >= $nmetrics"); + return 0; + } + + return_val_if_fail (advance_width != null, 0.0); + + return advance_width[i] * 1000 / head_table.get_units_per_em (); + } + + /** Get left side bearing relative to xmin. */ + public double get_lsb (uint32 i) { + return_val_if_fail (i < nmetrics, 0.0); + return_val_if_fail (left_side_bearing != null, 0.0); + + return left_side_bearing[i] * 1000 / head_table.get_units_per_em (); + } + + /** Get left side bearing relative to xmin for monospaces fonts. */ + public double get_lsb_mono (uint32 i) + requires (i < nmonospaced && left_side_bearing_monospaced != null) { + return left_side_bearing_monospaced[i] * 1000 / head_table.get_units_per_em (); + } + + public void print_all () { + return_if_fail (advance_width != null); + return_if_fail (left_side_bearing != null); + + for (int i = 0; i < nmetrics; i++) { + print (@"get_lsb ($i): $(get_lsb(i))\n"); + print (@"get_advance ($i): $(get_advance(i))\n"); + } + + if (left_side_bearing_monospaced != null) { + for (int i = 0; i < nmonospaced; i++) { + print (@"get_lsb_mono ($i): $(get_lsb_mono(i))\n"); + } + } + + } + + public new void parse (FontData dis, HheaTable hhea_table, LocaTable loca_table) throws GLib.Error { + nmetrics = hhea_table.num_horizontal_metrics; + nmonospaced = loca_table.size - nmetrics; + + dis.seek (offset); + + if (nmetrics > loca_table.size) { + warning (@"(nmetrics > loca_table.size) ($nmetrics > $(loca_table.size))"); + return; + } + + printd (@"nmetrics: $nmetrics\n"); + printd (@"loca_table.size: $(loca_table.size)\n"); + + advance_width = new uint16[nmetrics]; + left_side_bearing = new uint16[nmetrics]; + left_side_bearing_monospaced = new uint16[nmonospaced]; + + for (int i = 0; i < nmetrics; i++) { + advance_width[i] = dis.read_ushort (); + left_side_bearing[i] = dis.read_short (); + } + + for (int i = 0; i < nmonospaced; i++) { + left_side_bearing_monospaced[i] = dis.read_short (); + } + } + + public void process () { + FontData fd = new FontData (); + + int16 advance; + int16 extent; + int16 rsb; + int16 lsb; + + double xmin; + double ymin; + double xmax; + double ymax; + + // advance and lsb + foreach (Glyph g in glyf_table.glyphs) { + g.boundries (out xmin, out ymin, out xmax, out ymax); + lsb = (int16) (-1 * (g.left_limit - xmin) * UNITS); + advance = (int16) ((g.right_limit - g.left_limit) * UNITS); + extent = (int16) (lsb + (xmax - xmin) * UNITS); + rsb = (int16) (advance - extent); + + fd.add_u16 (advance); + fd.add_16 (lsb); + + if (advance > max_advance) { + max_advance = advance; + } + + if (extent > max_extent) { + max_extent = extent; + } + + if (rsb < min_rsb) { + min_rsb = rsb; + } + + if (lsb < min_lsb) { + min_lsb = lsb; + } + } + + // monospaced lsb ... + + font_data = fd; + + warn_if_fail (max_advance != 0); + } +} + + +class MaxpTable : Table { + + GlyfTable glyf_table; + + public uint16 num_glyphs = 0; + + public MaxpTable (GlyfTable g) { + glyf_table = g; + id = "maxp"; + } + + public override void parse (FontData dis) + requires (offset > 0 && length > 0) { + Fixed format; + + dis.seek (offset); + + format = dis.read_fixed (); + printd (@"Maxp version: $(format.get_string ())\n"); + + num_glyphs = dis.read_ushort (); + + if (format == 0x00005000) { + return; + } + + // Format 1.0 continues here + } + + public void process () { + FontData fd = new FontData(); + + // Version 0.5 for fonts with cff data and 1.0 for ttf + fd.add_u32 (0x00010000); + + if (glyf_table.glyphs.length () == 0) { + warning ("Zero glyphs in maxp table."); + } + + fd.add_u16 ((uint16) glyf_table.glyphs.length ()); // numGlyphs in the font + + fd.add_u16 (glyf_table.get_max_points ()); // max points + fd.add_u16 (glyf_table.get_max_contours ()); // max contours + fd.add_u16 (0); // max composite points + fd.add_u16 (0); // max composite contours + fd.add_u16 (1); // max zones + fd.add_u16 (0); // twilight points + fd.add_u16 (0); // max storage + fd.add_u16 (0); // max function defs + fd.add_u16 (0); // max instruction defs + fd.add_u16 (0); // max stack elements + fd.add_u16 (0); // max size of instructions + fd.add_u16 (0); // max component elements + fd.add_u16 (0); // component depth + + fd.pad (); + + this.font_data = fd; + } +} + +class OffsetTable : Table { + DirectoryTable directory_table; + + public uint16 num_tables = 0; + uint16 search_range = 0; + uint16 entry_selector = 0; + uint16 range_shift = 0; + + public OffsetTable (DirectoryTable t) { + id = "Offset table"; + directory_table = t; + } + + public override void parse (FontData dis) throws Error { + Fixed version; + + dis.seek (offset); + + version = dis.read_fixed (); + num_tables = dis.read_ushort (); + search_range = dis.read_ushort (); + entry_selector = dis.read_ushort (); + range_shift = dis.read_ushort (); + + printd (@"Font file version $(version.get_string ())\n"); + printd (@"Number of tables $num_tables\n"); + } + + public void process () throws GLib.Error { + FontData fd = new FontData (); + Fixed version = 0x00010000; // sfnt version 1.0 for TTF CFF else use OTTO + + + num_tables = (uint16) directory_table.get_tables ().length () - 2; // number of tables, skip DirectoryTable and OffsetTable + + search_range = max_pow_2_less_than_i (num_tables) * 16; + entry_selector = max_log_2_less_than_i (num_tables); + range_shift = 16 * num_tables - search_range; + + fd.add_fixed (version); + fd.add_u16 (num_tables); + fd.add_u16 (search_range); + fd.add_u16 (entry_selector); + fd.add_u16 (range_shift); + + // skip padding for offset table + + this.font_data = fd; + } +} + +class NameTable : Table { + + public static const uint16 COPYRIGHT_NOTICE = 0; + public static const uint16 FONT_NAME = 1; + public static const uint16 SUBFAMILY_NAME = 2; + public static const uint16 UNIQUE_IDENTIFIER = 3; + public static const uint16 FULL_FONT_NAME = 4; // name + subfamily + public static const uint16 VERSION = 5; + public static const uint16 DESCRIPTION = 10; + public static const uint16 PREFERED_FAMILY = 16; + public static const uint16 PREFERED_SUB_FAMILY = 17; + + List<uint16> identifiers; + List<string> text; + + public NameTable () { + id = "name"; + text = new List<string> (); + identifiers = new List<uint16> (); + } + + public string get_name (uint16 identifier) { + int i = 0; + + foreach (uint16 n in identifiers) { + if (n == identifier) { + return text.nth (i).data; + } + i++; + } + + return ""; + } + + public void print_all () { + stdout.printf (@"$(text.length ()) name table texts:\n"); + foreach (string s in text) { + stdout.printf (s); + stdout.printf ("\n"); + } + } + + public override void parse (FontData dis) throws Error { + uint16 format; + + dis.seek (offset); + + format = dis.read_ushort (); + + switch (format) { + case 0: + parse_format0 (dis); + break; + + case 1: + warning ("name table format 1 is not implemented yet"); + break; + + default: + warning (@"unknown format $format in name table"); + break; + } + } + + public void parse_format0 (FontData dis) throws Error { + uint16 count; + uint16 storage_offset; + + List<uint16> strlen = new List<uint16> (); + List<uint16> off = new List<uint16> (); + List<uint16> name_id = new List<uint16> (); + List<uint16> encoding_id = new List<uint16> (); + List<uint16> platform = new List<uint16> (); + List<uint16> lang = new List<uint16> (); + + count = dis.read_ushort (); + storage_offset = dis.read_ushort (); + + for (int i = 0; i < count; i++) { + platform.append (dis.read_ushort ()); + encoding_id.append (dis.read_ushort ()); + lang.append (dis.read_ushort ()); + name_id.append (dis.read_ushort ()); + strlen.append (dis.read_ushort ()); + off.append (dis.read_ushort ()); + + identifiers.append (name_id.last ().data); + } + + int plat; + StringBuilder str; + for (int i = 0; i < count; i++) { + plat = platform.nth (i).data; + dis.seek (offset + storage_offset + off.nth (i).data); + str = new StringBuilder (); + + switch (plat) { + case 1: + for (int j = 0; j < strlen.nth (i).data; j++) { + char c = dis.read_char (); + str.append_c (c); + } + break; + + case 3: + for (int j = 0; j < strlen.nth (i).data; j += 2) { + unichar c; + char c0 = dis.read_char (); + char c1 = dis.read_char (); + + c = c0 << 8; + c += c1; + + str.append_unichar (c); + } + break; + + default: + break; + } + + text.append (str.str); + printd (@"Name id: $(name_id.nth (i).data) platform: $(platform.nth (i).data) enc: $(encoding_id.nth (i).data) lang: $(lang.nth (i).data) len: $(strlen.nth (i).data) str: \"$(str.str)\"\n"); + } + } + + public void process () throws GLib.Error { + FontData fd = new FontData (); + Font font = OpenFontFormatWriter.get_current_font (); + uint16 len = 0; + string t; + uint16 p; + uint16 l; + uint16 num_records; + + List<uint16> type = new List<uint16> (); + List<string> text = new List<string> (); + + text.append ("Copyright"); + type.append (COPYRIGHT_NOTICE); + + text.append (font.get_name ()); + type.append (FONT_NAME); + + text.append ("Regular"); + type.append (SUBFAMILY_NAME); + + text.append (font.get_name ()); // TODO: validate + type.append (UNIQUE_IDENTIFIER); + + text.append (font.get_name ()); + type.append (FULL_FONT_NAME); + + // This does for some reason cause an internal error in ms fontvalidatior utility. + // Head table can't parse integer from string. + text.append ("Version 1.0"); + type.append (VERSION); + + text.append (""); + type.append (DESCRIPTION); + + text.append (font.get_name ()); + type.append (PREFERED_FAMILY); + + text.append ("Regular"); + type.append (PREFERED_SUB_FAMILY); + + num_records = (uint16) text.length (); + + fd.add_ushort (0); // format 1 + fd.add_ushort (2 * num_records); // nplatforms * nrecords + fd.add_ushort (6 + 12 * 2 * num_records); // string storage offset + + for (int i = 0; i < num_records; i++) { + t = (!) text.nth (i).data; + p = (!) type.nth (i).data; + l = (uint16) t.length; + + fd.add_ushort (1); // platform + fd.add_ushort (0); // encoding id + fd.add_ushort (0); // language + fd.add_ushort (p); // name id + fd.add_ushort (l); // strlen + fd.add_ushort (len); // offset from begining of string storage + len += l; + } + + for (int i = 0; i < num_records; i++) { + t = (!) text.nth (i).data; + p = (!) type.nth (i).data; + l = (uint16) (2 * t.char_count ()); + + fd.add_ushort (3); // platform + fd.add_ushort (1); // encoding id + fd.add_ushort (0x0409); // language + fd.add_ushort (p); // name id + fd.add_ushort (l); // strlen + fd.add_ushort (len); // offset from begining of string storage + len += l; + } + + // platform 1 + foreach (string s in text) { + fd.add_str (s); + } + + // platform 3 + foreach (string s in text) { + fd.add_str_utf16 (s); + } + + fd.pad (); + + this.font_data = fd; + } +} + +class Os2Table : Table { + + public Os2Table () { + id = "OS/2"; + } + + public override void parse (FontData dis) throws Error { + + } + + public void process (GlyfTable glyf_table) { + FontData fd = new FontData (); + Font font = OpenFontFormatWriter.get_current_font (); + + int16 ascender; + int16 descender; + + fd.add_u16 (0x0002); // USHORT Version 0x0000, 0x0001, 0x0002, 0x0003, 0x0004 + + fd.add_16 (glyf_table.get_average_width ()); // SHORT xAvgCharWidth + + fd.add_u16 (400); // USHORT usWeightClass (400 is normal) + fd.add_u16 (5); // USHORT usWidthClass (5 is normal) + fd.add_u16 (0); // USHORT fsType + + fd.add_16 (40); // SHORT ySubscriptXSize + fd.add_16 (40); // SHORT ySubscriptYSize + fd.add_16 (40); // SHORT ySubscriptXOffset + fd.add_16 (40); // SHORT ySubscriptYOffset + fd.add_16 (40); // SHORT ySuperscriptXSize + fd.add_16 (40); // SHORT ySuperscriptYSize + fd.add_16 (40); // SHORT ySuperscriptXOffset + fd.add_16 (40); // SHORT ySuperscriptYOffset + fd.add_16 (40); // SHORT yStrikeoutSize + fd.add_16 (200); // SHORT yStrikeoutPosition + fd.add_16 (40); // SHORT sFamilyClass + + // PANOSE + fd.add (0); + fd.add (0); + fd.add (0); + fd.add (0); + fd.add (0); + fd.add (0); + fd.add (0); + fd.add (0); + fd.add (0); + fd.add (0); + + // FIXA: + 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 + fd.add_u32 (0); // ULONG ulUnicodeRange4 Bits 96-127 + + fd.add_tag ("----"); // VendID + + fd.add_u16 (0); // USHORT fsSelection + + fd.add_u16 (glyf_table.get_first_char ()); // USHORT usFirstCharIndex + fd.add_u16 (glyf_table.get_last_char ()); // USHORT usLastCharIndex + + ascender = (int16) (glyf_table.xmax + font.base_line * UNITS); + descender = (int16) (-glyf_table.xmin + font.base_line * UNITS); + + fd.add_16 (ascender); // SHORT sTypoAscender + fd.add_16 (descender); // SHORT sTypoDescender + fd.add_16 (3); // SHORT sTypoLineGap + + fd.add_u16 (ascender); // USHORT usWinAscent + fd.add_u16 (descender); // USHORT usWinDescent + + // FIXA: + fd.add_u32 (0); // ULONG ulCodePageRange1 Bits 0-31 + fd.add_u32 (0); // ULONG ulCodePageRange2 Bits 32-63 + + fd.add_16 (ascender); // SHORT sxHeight version 0x0002 and later + fd.add_16 (ascender); // SHORT sCapHeight version 0x0002 and later + + fd.add_16 (0); // USHORT usDefaultChar version 0x0002 and later + fd.add_16 (0x0020); // USHORT usBreakChar version 0x0002 and later, also known as space + + // FIXA: calculate these values + fd.add_16 (1); // USHORT usMaxContext version 0x0002 and later + + // padding + fd.pad (); + + this.font_data = fd; + } + +} + +class PostTable : Table { + + GlyfTable glyf_table; + + List<uint16> index = new List<uint16> (); + List<string> names = new List<string> (); + + List<string> available_names = new List<string> (); + + public PostTable (GlyfTable g) { + id = "post"; + glyf_table = g; + } + + public unowned List<string> get_all_names () { + return available_names; + } + + public int get_gid (string name) { // fixa do fast lookup + int i = 0; + int j = 0; + foreach (string n in names) { + if (n == name) { + j = 0; + foreach (uint16 k in index) { + if (k == i) { + return j; + } + j++; + } + + return i; + } + i++; + } + + // warn_if_reached (); + return -1; + } + + public string get_name (int gid) { + int k; + + if (!(0 <= gid < index.length ())) { + warning ("gid is out of range."); + return ""; + } + + k = (!) index.nth (gid).data; + + if (gid != 0 && k == 0) { + warning (@"Glyph $gid is assigned to name .notdef, only gid 0 can be .notdef character."); + return ""; + } + + if (!(0 <= k < names.length ())) { + warning ("k is out of range."); + return ""; + } + + return (!) names.nth (k).data; + } + + public override void parse (FontData dis) throws Error { + dis.seek (offset); + + Fixed format = dis.read_fixed (); + Fixed italic = dis.read_fixed (); + + int16 underlie_pos = dis.read_short (); + int16 underlie_thickness = dis.read_short (); + uint32 is_fixed_pitch = dis.read_ulong (); + + uint32 mem_min42 = dis.read_ulong (); + uint32 mem_max42 = dis.read_ulong (); + uint32 mem_min1 = dis.read_ulong (); + uint32 mem_max1 = dis.read_ulong (); + + uint16 nnames = dis.read_ushort (); + + if (format != 0x00020000) { + warning ("Only post tables of version 2 will parset got $(format.get_string ())"); + return; + } + + printd (@"format: $(format.get_string ())\n"); + printd (@"italic: $(italic.get_string ())\n"); + printd (@"underlie_pos: $(underlie_pos)\n"); + printd (@"underlie_thickness: $(underlie_thickness)\n"); + printd (@"is_fixed_pitch: $(is_fixed_pitch)\n"); + printd (@"mem_min42: $(mem_min42)\n"); + printd (@"mem_max42: $(mem_max42)\n"); + printd (@"mem_min1: $(mem_min1)\n"); + printd (@"mem_max1: $(mem_max1)\n"); + printd (@"\n"); + + printd (@"Num names: $(nnames)\n"); + + uint16 k; + int non_standard_names = 0; + for (uint16 i = 0; i < nnames; i++) { + k = dis.read_ushort (); + index.append (k); + + if (k >= 258) { + non_standard_names++; + } + } + + add_standard_names (); + + // read non standard names + for (int i = 0; i < non_standard_names; i++) { + uint8 len = dis.read_byte (); + StringBuilder name = new StringBuilder (); + + for (int j = 0; j < len; j++) { + name.append_c (dis.read_char ()); + } + + names.append (name.str); + } + + populate_available (); + } + + void populate_available () { + for (int i = 0; i < index.length (); i++) { + available_names.append (get_name (i)); + } + } + + public void print_all () { + print (@"PostScript glyph mapping:\n"); + for (int i = 0; i < index.length (); i++) { + print (@"gid $i -> $(get_name (i))\n"); + } + } + + public void print_all_names () { + print (@"Post table names:\n"); + foreach (var n in names) { + print (@"$n\n"); + } + } + + // the Macintosh standard order + private void add_standard_names () { + names.append (".notdef"); + names.append (".null"); + names.append ("nonmarkingreturn"); + names.append ("space"); + names.append ("exclam"); + names.append ("quotedbl"); + names.append ("numbersign"); + names.append ("dollar"); + names.append ("percent"); + names.append ("ampersand"); + names.append ("quotesingle"); + names.append ("parenleft"); + names.append ("parenright"); + names.append ("asterisk"); + names.append ("plus"); + names.append ("comma"); + names.append ("hyphen"); + names.append ("period"); + names.append ("slash"); + names.append ("zero"); + names.append ("one"); + names.append ("two"); + names.append ("three"); + names.append ("four"); + names.append ("five"); + names.append ("six"); + names.append ("seven"); + names.append ("eight"); + names.append ("nine"); + names.append ("colon"); + names.append ("semicolon"); + names.append ("less"); + names.append ("equal"); + names.append ("greater"); + names.append ("question"); + names.append ("at"); + names.append ("A"); + names.append ("B"); + names.append ("C"); + names.append ("D"); + names.append ("E"); + names.append ("F"); + names.append ("G"); + names.append ("H"); + names.append ("I"); + names.append ("J"); + names.append ("K"); + names.append ("L"); + names.append ("M"); + names.append ("N"); + names.append ("O"); + names.append ("P"); + names.append ("Q"); + names.append ("R"); + names.append ("S"); + names.append ("T"); + names.append ("U"); + names.append ("V"); + names.append ("W"); + names.append ("X"); + names.append ("Y"); + names.append ("Z"); + names.append ("bracketleft"); + names.append ("backslash"); + names.append ("bracketright"); + names.append ("asciicircum"); + names.append ("underscore"); + names.append ("grave"); + names.append ("a"); + names.append ("b"); + names.append ("c"); + names.append ("d"); + names.append ("e"); + names.append ("f"); + names.append ("g"); + names.append ("h"); + names.append ("i"); + names.append ("j"); + names.append ("k"); + names.append ("l"); + names.append ("m"); + names.append ("n"); + names.append ("o"); + names.append ("p"); + names.append ("q"); + names.append ("r"); + names.append ("s"); + names.append ("t"); + names.append ("u"); + names.append ("v"); + names.append ("w"); + names.append ("x"); + names.append ("y"); + names.append ("z"); + names.append ("braceleft"); + names.append ("bar"); + names.append ("braceright"); + names.append ("asciitilde"); + names.append ("Adieresis"); + names.append ("Aring"); + names.append ("Ccedilla"); + names.append ("Eacute"); + names.append ("Ntilde"); + names.append ("Odieresis"); + names.append ("Udieresis"); + names.append ("aacute"); + names.append ("agrave"); + names.append ("acircumflex"); + names.append ("adieresis"); + names.append ("atilde"); + names.append ("aring"); + names.append ("ccedilla"); + names.append ("eacute"); + names.append ("egrave"); + names.append ("ecircumflex"); + names.append ("edieresis"); + names.append ("iacute"); + names.append ("igrave"); + names.append ("icircumflex"); + names.append ("idieresis"); + names.append ("ntilde"); + names.append ("oacute"); + names.append ("ograve"); + names.append ("ocircumflex"); + names.append ("odieresis"); + names.append ("otilde"); + names.append ("uacute"); + names.append ("ugrave"); + names.append ("ucircumflex"); + names.append ("udieresis"); + names.append ("dagger"); + names.append ("degree"); + names.append ("cent"); + names.append ("sterling"); + names.append ("section"); + names.append ("bullet"); + names.append ("paragraph"); + names.append ("germandbls"); + names.append ("registered"); + names.append ("copyright"); + names.append ("trademark"); + names.append ("acute"); + names.append ("dieresis"); + names.append ("notequal"); + names.append ("AE"); + names.append ("Oslash"); + names.append ("infinity"); + names.append ("plusminus"); + names.append ("lessequal"); + names.append ("greaterequal"); + names.append ("yen"); + names.append ("mu"); + names.append ("partialdiff"); + names.append ("summation"); + names.append ("product"); + names.append ("pi"); + names.append ("integral"); + names.append ("ordfeminine"); + names.append ("ordmasculine"); + names.append ("Omega"); + names.append ("ae"); + names.append ("oslash"); + names.append ("questiondown"); + names.append ("exclamdown"); + names.append ("logicalnot"); + names.append ("radical"); + names.append ("florin"); + names.append ("approxequal"); + names.append ("Delta"); + names.append ("guillemotleft"); + names.append ("guillemotright"); + names.append ("ellipsis"); + names.append ("nonbreakingspace"); + names.append ("Agrave"); + names.append ("Atilde"); + names.append ("Otilde"); + names.append ("OE"); + names.append ("oe"); + names.append ("endash"); + names.append ("emdash"); + names.append ("quotedblleft"); + names.append ("quotedblright"); + names.append ("quoteleft"); + names.append ("quoteright"); + names.append ("divide"); + names.append ("lozenge"); + names.append ("ydieresis"); + names.append ("Ydieresis"); + names.append ("fraction"); + names.append ("currency"); + names.append ("guilsinglleft"); + names.append ("guilsinglright"); + names.append ("fi"); + names.append ("fl"); + names.append ("daggerdbl"); + names.append ("periodcentered"); + names.append ("quotesinglbase"); + names.append ("quotedblbase"); + names.append ("perthousand"); + names.append ("Acircumflex"); + names.append ("Ecircumflex"); + names.append ("Aacute"); + names.append ("Edieresis"); + names.append ("Egrave"); + names.append ("Iacute"); + names.append ("Icircumflex"); + names.append ("Idieresis"); + names.append ("Igrave"); + names.append ("Oacute"); + names.append ("Ocircumflex"); + names.append ("apple"); + names.append ("Ograve"); + names.append ("Uacute"); + names.append ("Ucircumflex"); + names.append ("Ugrave"); + names.append ("dotlessi"); + names.append ("circumflex"); + names.append ("tilde"); + names.append ("macron"); + names.append ("breve"); + names.append ("dotaccent"); + names.append ("ring"); + names.append ("cedilla"); + names.append ("hungarumlaut"); + names.append ("ogonek"); + names.append ("caron"); + names.append ("Lslash"); + names.append ("lslash"); + names.append ("Scaron"); + names.append ("scaron"); + names.append ("Zcaron"); + names.append ("zcaron"); + names.append ("brokenbar"); + names.append ("Eth"); + names.append ("eth"); + names.append ("Yacute"); + names.append ("yacute"); + names.append ("Thorn"); + names.append ("thorn"); + names.append ("minus"); + names.append ("multiply"); + names.append ("onesuperior"); + names.append ("twosuperior"); + names.append ("threesuperior"); + names.append ("onehalf"); + names.append ("onequarter"); + names.append ("threequarters"); + names.append ("franc"); + names.append ("Gbreve"); + names.append ("gbreve"); + names.append ("Idotaccent"); + names.append ("Scedilla"); + names.append ("scedilla"); + names.append ("Cacute"); + names.append ("cacute"); + names.append ("Ccaron"); + names.append ("ccaron"); + names.append ("dcroat"); + } + + // mapping with char code to standard order + int get_standard_index (unichar c) { + switch (c) { + // entry 0 is the .notdef + + case '\0': + return 1; + + case '\r': + return 2; + + case ' ': // space + return 3; + + case '!': + return 4; + + case '"': + return 5; + + case '#': + return 6; + + case '$': + return 7; + + case '%': + return 8; + + case '&': + return 9; + + case '\'': + return 10; + + case '(': + return 11; + + case ')': + return 12; + + case '*': + return 13; + + case '+': + return 14; + + case ',': + return 15; + + case '-': + return 16; + + case '.': + return 17; + + case '/': + return 18; + + case '0': + return 19; + + case '1': + return 20; + + case '2': + return 21; + + case '3': + return 22; + + case '4': + return 23; + + case '5': + return 24; + + case '6': + return 25; + + case '7': + return 26; + + case '8': + return 27; + + case '9': + return 28; + + case ':': + return 29; + + case ';': + return 30; + + case '<': + return 31; + + case '=': + return 32; + + case '>': + return 33; + + case '?': + return 34; + + case '@': + return 35; + + case 'A': + return 36; + + case 'B': + return 37; + + case 'C': + return 38; + + case 'D': + return 39; + + case 'E': + return 40; + + case 'F': + return 41; + + case 'G': + return 42; + + case 'H': + return 43; + + case 'I': + return 44; + + case 'J': + return 45; + + case 'K': + return 46; + + case 'L': + return 47; + + case 'M': + return 48; + + case 'N': + return 49; + + case 'O': + return 50; + + case 'P': + return 51; + + case 'Q': + return 52; + + case 'R': + return 53; + + case 'S': + return 54; + + case 'T': + return 55; + + case 'U': + return 56; + + case 'V': + return 57; + + case 'W': + return 58; + + case 'X': + return 59; + + case 'Y': + return 60; + + case 'Z': + return 61; + + case '[': + return 62; + + case '\\': + return 63; + + case ']': + return 64; + + case '^': + return 65; + + case '_': + return 66; + + case '`': + return 67; + + case 'a': + return 68; + + case 'b': + return 69; + + case 'c': + return 70; + + case 'd': + return 71; + + case 'e': + return 72; + + case 'f': + return 73; + + case 'g': + return 74; + + case 'h': + return 75; + + case 'i': + return 76; + + case 'j': + return 77; + + case 'k': + return 78; + + case 'l': + return 79; + + case 'm': + return 80; + + case 'n': + return 81; + + case 'o': + return 82; + + case 'p': + return 83; + + case 'q': + return 84; + + case 'r': + return 85; + + case 's': + return 86; + + case 't': + return 87; + + case 'u': + return 88; + + case 'v': + return 89; + + case 'w': + return 90; + + case 'x': + return 91; + + case 'y': + return 92; + + case 'z': + return 93; + + case '{': + return 94; + + case '|': + return 95; + + case '}': + return 96; + + case '~': + return 97; + + case 'Ä': + return 98; + + case 'Å': + return 99; + + case 'Ç': + return 100; + + case 'É': + return 101; + + case 'Ñ': + return 102; + + case 'Ö': + return 103; + + case 'Ü': + return 104; + + case 'á': + return 105; + + case 'à': + return 106; + + case 'â': + return 107; + + case 'ä': + return 108; + + case 'ã': + return 109; + + case 'å': + return 110; + + case 'ç': + return 111; + + case 'é': + return 112; + + case 'è': + return 113; + + case 'ê': + return 114; + + case 'ë': + return 115; + + case 'í': + return 116; + + case 'ì': + return 117; + + case 'î': + return 118; + + case 'ï': + return 119; + + case 'ñ': + return 120; + + case 'ó': + return 121; + + case 'ò': + return 122; + + case 'ô': + return 123; + + case 'ö': + return 124; + + case 'õ': + return 125; + + case 'ú': + return 126; + + case 'ù': + return 127; + + case 'û': + return 128; + + case 'ü': + return 129; + + case '†': + return 130; + + case '°': + return 131; + + case '¢': + return 132; + + case '£': + return 133; + + case '§': + return 134; + + case '•': + return 135; + + case '¶': + return 136; + + case 'ß': + return 137; + + case '®': + return 138; + + case '©': + return 139; + + case '™': + return 140; + + case '´': + return 141; + + case '¨': + return 142; + + case '≠': + return 143; + + case 'Æ': + return 144; + + case 'Ø': + return 145; + + case '∞': + return 146; + + case '±': + return 147; + + case '≤': + return 148; + + case '≥': + return 149; + + case '¥': + return 150; + + case 'µ': + return 151; + + case '∂': + return 152; + + case '∑': + return 153; + + case '∏': + return 154; + + case 'π': + return 155; + + case '∫': + return 156; + + case 'ª': + return 157; + + case 'º': + return 158; + + case 'Ω': + return 159; + + case 'æ': + return 160; + + case 'ø': + return 161; + + case '¿': + return 162; + + case '¡': + return 163; + + case '¬': + return 164; + + case '√': + return 165; + + case 'ƒ': + return 166; + + case '≈': + return 167; + + case '∆': + return 168; + + case '«': + return 169; + + case '»': + return 170; + + case '…': + return 171; + + case ' ': // non breaking space + return 172; + + case 'À': + return 173; + + case 'Ã': + return 174; + + case 'Õ': + return 175; + + case 'Œ': + return 176; + + case 'œ': + return 177; + + case '–': + return 178; + + case '—': + return 179; + + case '“': + return 180; + + case '”': + return 181; + + case '‘': + return 182; + + case '’': + return 183; + + case '÷': + return 184; + + case '◊': + return 185; + + case 'ÿ': + return 186; + + case 'Ÿ': + return 187; + + case '⁄': + return 188; + + case '¤': + return 189; + + case '‹': + return 190; + + case '›': + return 191; + + case 'fi': + return 192; + + case 'fl': + return 193; + + case '‡': + return 194; + + case '·': + return 195; + + case '‚': + return 196; + + case '„': + return 197; + + case '‰': + return 198; + + case 'Â': + return 199; + + case 'Ê': + return 200; + + case 'Á': + return 201; + + case 'Ë': + return 202; + + case 'È': + return 203; + + case 'Í': + return 204; + + case 'Î': + return 205; + + case 'Ï': + return 206; + + case 'Ì': + return 207; + + case 'Ó': + return 208; + + case 'Ô': + return 209; + + // Machintosh apple goes here + // return 210; + + case 'Ò': + return 211; + + case 'Ú': + return 212; + + case 'Û': + return 213; + + case 'Ù': + return 214; + + case 'ı': + return 215; + + case 'ˆ': + return 216; + + case '˜': + return 217; + + case '¯': + return 218; + + case '˘': + return 219; + + case '˙': + return 220; + + case '˚': + return 221; + + case '¸': + return 222; + + case '˝': + return 223; + + case '˛': + return 224; + + case 'ˇ': + return 225; + + case 'Ł': + return 226; + + case 'ł': + return 227; + + case 'Š': + return 228; + + case 'š': + return 229; + + case 'Ž': + return 230; + + case 'ž': + return 231; + + case '¦': + return 232; + + case 'Ð': + return 233; + + case 'ð': + return 234; + + case 'Ý': + return 235; + + case 'ý': + return 236; + + case 'Þ': + return 237; + + case 'þ': + return 238; + + case '−': + return 239; + + case '×': + return 240; + + case '¹': + return 241; + + case '²': + return 242; + + case '³': + return 243; + + case '½': + return 244; + + case '¼': + return 245; + + case '¾': + return 246; + + case '₣': + return 247; + + case 'Ğ': + return 248; + + case 'ğ': + return 249; + + case 'İ': + return 250; + + case 'Ş': + return 251; + + case 'ş': + return 252; + + case 'Ć': + return 253; + + case 'ć': + return 254; + + case 'Č': + return 255; + + case 'č': + return 256; + + case 'đ': + return 257; + } + + return 0; + } + + public void process () throws Error { + FontData fd = new FontData (); + string n; + + fd.add_fixed (0x00020000); // Version + fd.add_fixed (0x00000000); // italicAngle + + fd.add_short (-2); // underlinePosition + fd.add_short (1); // underlineThickness + + fd.add_ulong (0); // non zero for monospaced font + + // mem boundries may be omitted + fd.add_ulong (0); // min mem for type 42 + fd.add_ulong (0); // max mem for type 42 + + fd.add_ulong (0); // min mem for Type1 + fd.add_ulong (0); // max mem for Type1 + + fd.add_ushort ((uint16) glyf_table.glyphs.length ()); + + // this part of the spec is so weird + + fd.add_ushort ((uint16) 0); // first index is .notdef + index.append (0); + + assert (names.length () == 0); + add_standard_names (); + + int index; + Glyph g; + for (int i = 1; i < glyf_table.glyphs.length (); i++) { + g = (!) glyf_table.glyphs.nth (i).data; + index = get_standard_index (g.unichar_code); + + if (index != 0) { + fd.add_ushort ((uint16) index); // use standard name + } else { + printd ("Adding non standard postscript name $(g.get_name ())\n"); + + index = (int) names.length (); // use font specific name + fd.add_ushort ((uint16) index); + names.append (g.get_name ()); + } + + this.index.append ((uint16) index); + } + + for (int i = 258; i < names.length (); i++) { + n = (!) names.nth (i).data; + + if (n.length > 0xFF) { + warning ("too long name for glyph $n"); + } + + fd.add ((uint8) n.length); // length of string + fd.add_str (n); + } + + fd.pad (); + + this.font_data = fd; + } + +} + +class GaspTable : Table { + + public GaspTable () { + id = "gasp"; + } + + public override void parse (FontData dis) throws Error { + } + + public void process () throws GLib.Error { + FontData fd = new FontData (); + + fd.add_ushort (0); + fd.add_ushort (0); + + fd.pad (); + + this.font_data = fd; + } + +} + +class GdefTable : Table { + + public GdefTable () { + id = "GDEF"; + } + + public override void parse (FontData dis) throws Error { + } + + public void process () throws GLib.Error { + FontData fd = new FontData (); + + fd.add_ulong (0x00010002); + fd.add_ushort (0); // class def + fd.add_ushort (0); // attach list + fd.add_ushort (0); // ligature carret + fd.add_ushort (0); // mark attach + fd.add_ushort (0); // mark glyf + fd.add_ushort (0); // mark glyf set def + + fd.pad (); + + this.font_data = fd; + } + +} + +class CvtTable : Table { + + public CvtTable () { + id = "cvt "; + } + + public override void parse (FontData dis) throws Error { + } + + public void process () throws GLib.Error { + FontData fd = new FontData (); + + fd.add_ushort (0); + fd.pad (); + + this.font_data = fd; + } + +} + +class Kern : GLib.Object { + public uint16 left; + public uint16 right; + public int16 kerning; + + public Kern (uint16 l, uint16 r, int16 k) { + left = l; + right = r; + kerning = k; + } +} + +class KernList : GLib.Object { + public List<Kern> kernings; + + public KernList () { + kernings = new List<Kern> (); + } +} + +class KernTable : Table { + + public static const uint16 HORIZONTAL = 1; + public static const uint16 MINIMUM = 1 << 1; + public static const uint16 CROSS_STREAM = 1 << 2; + public static const uint16 OVERRIDE = 1 << 3; + public static const uint16 FORMAT = 1 << 8; + + GlyfTable glyf_table; + + List<Kern> kernings = new List<Kern> (); + + public int kerning_pairs = 0; + + public KernTable (GlyfTable gt) { + glyf_table = gt; + id = "kern"; + } + + public KernList get_all_pairs (int gid) { + KernList kl = new KernList (); + + foreach (Kern k in kernings) { + if (k.left == gid) { + kl.kernings.append (k); + } + } + + return kl; + } + + public override void parse (FontData dis) throws GLib.Error { + uint16 version; + uint16 sub_tables; + + uint16 subtable_version; + uint16 subtable_length; + uint16 subtable_flags; + + uint16 search_range; + uint16 entry_selector; + uint16 range_shift; + + uint16 n_pairs; + + dis.seek (offset); + + version = dis.read_ushort (); + warn_if_fail (version == 0); + sub_tables = dis.read_ushort (); + + for (uint16 i = 0; i < sub_tables; i++) { + subtable_version = dis.read_ushort (); + subtable_length = dis.read_ushort (); + subtable_flags = dis.read_ushort (); + + n_pairs = dis.read_ushort (); + search_range = dis.read_ushort (); + entry_selector = dis.read_ushort (); + range_shift = dis.read_ushort (); + + // TODO: check more flags + if ((subtable_flags & HORIZONTAL) > 0 && (subtable_flags & CROSS_STREAM) == 0 && (subtable_flags & MINIMUM) == 0) { + parse_pairs (dis, n_pairs); + } + } + } + + public void parse_pairs (FontData dis, uint16 n_pairs) throws Error { + uint16 left; + uint16 right; + int16 kerning; + + for (int i = 0; i < n_pairs; i++) { + left = dis.read_ushort (); + right = dis.read_ushort (); + kerning = dis.read_short (); + + kernings.append (new Kern (left, right, kerning)); + } + } + + public void process () throws GLib.Error { + FontData fd = new FontData (); + uint16 n_pairs = 0; + + uint16 gid_left; + int gid_right; + + uint16 range_shift = 0; + uint16 entry_selector = 0; + uint16 search_range = 0; + + fd.add_ushort (0); // version + fd.add_ushort (1); // n subtables + + fd.add_ushort (0); // subtable version + + foreach (Glyph g in glyf_table.glyphs) { + foreach (Kerning k in g.kerning) { + n_pairs++; + } + } + + this.kerning_pairs = n_pairs; + + fd.add_ushort (6 * n_pairs + 14); // subtable length + fd.add_ushort (HORIZONTAL); // subtable flags + + fd.add_ushort (n_pairs); + + search_range = 6 * largest_pow2 (n_pairs); + entry_selector = largest_pow2_exponent (n_pairs); + range_shift = 6 * n_pairs - search_range; + + fd.add_ushort (search_range); + fd.add_ushort (entry_selector); + fd.add_ushort (range_shift); + + gid_left = 0; + foreach (Glyph g in glyf_table.glyphs) { + + foreach (Kerning k in g.kerning) { + gid_right = glyf_table.get_gid (k.glyph_right); + + if (gid_right == -1) { + warning ("right glyph not found in kerning table"); + } + + fd.add_ushort (gid_left); + fd.add_ushort ((uint16)gid_right); + fd.add_short ((int16)k.val); + } + + gid_left++; + } + + fd.pad (); + this.font_data = fd; + } + +} + +/** Table with list of tables sorted by table tag. */ +class DirectoryTable : Table { + + public CmapTable cmap_table; + public CvtTable cvt_table; + public GaspTable gasp_table; + public GdefTable gdef_table; + public GlyfTable glyf_table; + public HeadTable head_table; + public HheaTable hhea_table; + public HmtxTable hmtx_table; + public KernTable kern_table; + public MaxpTable maxp_table; + public NameTable name_table; + public Os2Table os_2_table; + public PostTable post_table; + public LocaTable loca_table; + + public OffsetTable offset_table; + + List<Table> tables; + + public DirectoryTable () { + offset_table = new OffsetTable (this); + + loca_table = new LocaTable (); + gasp_table = new GaspTable (); + gdef_table = new GdefTable (); + glyf_table = new GlyfTable (loca_table); + cmap_table = new CmapTable (glyf_table); + cvt_table = new CvtTable (); + head_table = new HeadTable (glyf_table); + hmtx_table = new HmtxTable (head_table, glyf_table); + hhea_table = new HheaTable (glyf_table, head_table, hmtx_table); + kern_table = new KernTable (glyf_table); + maxp_table = new MaxpTable (glyf_table); + name_table = new NameTable (); + os_2_table = new Os2Table (); + post_table = new PostTable (glyf_table); + + id = "Directory table"; + } + + public void process () throws GLib.Error { + // generate font data + glyf_table.process (); + gasp_table.process (); + gdef_table.process (); + cmap_table.process (glyf_table); + cvt_table.process (); + hmtx_table.process (); + hhea_table.process (); + maxp_table.process (); + name_table.process (); + os_2_table.process (glyf_table); + head_table.process (); + loca_table.process (glyf_table, head_table); + post_table.process (); + kern_table.process (); + + offset_table.process (); + process_directory (); // this table + } + + public unowned List<Table> get_tables () { + if (tables.length () == 0) { + tables.append (offset_table); + tables.append (this); + + // tables.append (gdef_table); // invalid table + tables.append (os_2_table); + tables.append (cmap_table); + // tables.append (cvt_table); + // tables.append (gasp_table); + tables.append (glyf_table); + tables.append (head_table); + tables.append (hhea_table); + tables.append (hmtx_table); + + if (kern_table.kerning_pairs > 0) { + tables.append (kern_table); + } + + tables.append (loca_table); + tables.append (maxp_table); + tables.append (name_table); + tables.append (post_table); + } + + return tables; + } + + public void set_offset_table (OffsetTable ot) { + offset_table = ot; + } + + public new void parse (FontData dis, File file, OpenFontFormatReader reader_callback) throws Error { + StringBuilder tag = new StringBuilder (); + uint32 checksum; + uint32 offset; + uint32 length; + + return_if_fail (offset_table.num_tables > 0); + + for (uint i = 0; i < offset_table.num_tables; i++) { + tag.erase (); + + tag.append_unichar ((unichar) dis.read_byte ()); + tag.append_unichar ((unichar) dis.read_byte ()); + tag.append_unichar ((unichar) dis.read_byte ()); + tag.append_unichar ((unichar) dis.read_byte ()); + + checksum = dis.read_ulong (); + offset = dis.read_ulong (); + length = dis.read_ulong (); + + printd (@"$(tag.str) \toffset: $offset \tlength: $length \tchecksum: $checksum.\n"); + + if (tag.str == "cvt") { + cvt_table.id = tag.str; + cvt_table.checksum = checksum; + cvt_table.offset = offset; + cvt_table.length = length; + } else if (tag.str == "hmtx") { + hmtx_table.id = tag.str; + hmtx_table.checksum = checksum; + hmtx_table.offset = offset; + hmtx_table.length = length; + } else if (tag.str == "hhea") { + hhea_table.id = tag.str; + hhea_table.checksum = checksum; + hhea_table.offset = offset; + hhea_table.length = length; + } else if (tag.str == "loca") { + loca_table.id = tag.str; + loca_table.checksum = checksum; + loca_table.offset = offset; + loca_table.length = length; + } else if (tag.str == "cmap") { + cmap_table.id = tag.str; + cmap_table.checksum = checksum; + cmap_table.offset = offset; + cmap_table.length = length; + } else if (tag.str == "maxp") { + maxp_table.id = tag.str; + maxp_table.checksum = checksum; + maxp_table.offset = offset; + maxp_table.length = length; + } else if (tag.str == "gasp") { + gasp_table.id = tag.str; + gasp_table.checksum = checksum; + gasp_table.offset = offset; + gasp_table.length = length; + } else if (tag.str == "gdef") { + gdef_table.id = tag.str; + gdef_table.checksum = checksum; + gdef_table.offset = offset; + gdef_table.length = length; + } else if (tag.str == "glyf") { + glyf_table.id = tag.str; + glyf_table.checksum = checksum; + glyf_table.offset = offset; + glyf_table.length = length; + } else if (tag.str == "head") { + head_table.id = tag.str; + head_table.checksum = checksum; + head_table.offset = offset; + head_table.length = length; + } else if (tag.str == "name") { + name_table.id = tag.str; + name_table.checksum = checksum; + name_table.offset = offset; + name_table.length = length; + } else if (tag.str == "OS/2") { + os_2_table.id = tag.str; + os_2_table.checksum = checksum; + os_2_table.offset = offset; + os_2_table.length = length; + } else if (tag.str == "post") { + post_table.id = tag.str; + post_table.checksum = checksum; + post_table.offset = offset; + post_table.length = length; + } else if (tag.str == "kern") { + kern_table.id = tag.str; + kern_table.checksum = checksum; + kern_table.offset = offset; + kern_table.length = length; + } + } + + head_table.parse (dis); + + if (!validate_tables (dis, file)) { + warning ("Missing required table or bad checksum."); + } + + hhea_table.parse (dis); + reader_callback.set_limits (); + + name_table.parse (dis); + post_table.parse (dis); + os_2_table.parse (dis); + maxp_table.parse (dis); + loca_table.parse (dis, head_table, maxp_table); + hmtx_table.parse (dis, hhea_table, loca_table); + cmap_table.parse (dis); + + if (kern_table.has_data ()) { + kern_table.parse (dis); + } + + glyf_table.parse (dis, cmap_table, loca_table, hmtx_table, head_table, post_table, kern_table); + + if (kern_table.has_data ()) { + gasp_table.parse (dis); + } + + if (kern_table.has_data ()) { + cvt_table.parse (dis); + } + } + + public bool validate_tables (FontData dis, File file) { + bool valid = true; + + try { + dis.seek (0); + + if (!validate_checksum_for_entire_font (dis, file)) { + warning ("file has invalid checksum"); + } else { + printd ("Font file has valid checksum.\n"); + } + + // Skip validation of head table for now it should be realy simple but seems to + // be broken in some funny way. + + if (!glyf_table.validate (dis)) { + warning ("glyf_table has invalid checksum"); + valid = false; + } + + if (!maxp_table.validate (dis)) { + warning ("maxp_table has is invalid checksum"); + valid = false; + } + + if (!loca_table.validate (dis)) { + warning ("loca_table has invalid checksum"); + valid = false; + } + + if (!cmap_table.validate (dis)) { + warning ("cmap_table has invalid checksum"); + valid = false; + } + + if (!hhea_table.validate (dis)) { + warning ("hhea_table has invalid checksum"); + valid = false; + } + + if (!hmtx_table.validate (dis)) { + warning ("hmtx_table has invalid checksum"); + valid = false; + } + + if (!name_table.validate (dis)) { + warning ("name_table has invalid checksum"); + valid = false; + } + + if (!os_2_table.validate (dis)) { + warning ("os_2_table has invalid checksum"); + valid = false; + } + + if (!post_table.validate (dis)) { + warning ("post_table has invalid checksum"); + valid = false; + } + + if (kern_table.has_data () && !kern_table.validate (dis)) { + warning ("kern_table has invalid checksum"); + valid = false; + } + + } catch (GLib.Error e) { + warning (e.message); + valid = false; + } + + return valid; + } + + bool validate_checksum_for_entire_font (FontData dis, File f) throws GLib.Error { + uint p = head_table.offset + head_table.get_checksum_position (); + uint32 checksum_font, checksum_head; + + checksum_head = head_table.get_font_checksum (); + + dis.seek (0); + + // zero out checksum entry in head table before validating it + dis.write_at (p + 0, 0); + dis.write_at (p + 1, 0); + dis.write_at (p + 2, 0); + dis.write_at (p + 3, 0); + + checksum_font = (uint32) (0xB1B0AFBA - dis.check_sum ()); + + if (checksum_font != checksum_head) { + warning (@"Fontfile checksum in head table does not match calculated checksum. checksum_font: $checksum_font checksum_head: $checksum_head"); + return false; + } + + return true; + } + + public long get_font_file_size () { + long length = 0; + + foreach (Table t in tables) { + length += t.get_font_data ().length_with_padding (); + } + + return length; + } + + public void process_directory () throws GLib.Error { + create_directory (); // create directory without offsets to calculate length of offset table and checksum for entre file + create_directory (); // generate a valid directory + } + + // Check sum adjustment for the entire font + public uint32 get_font_file_checksum () { + uint32 check_sum = 0; + foreach (Table t in tables) { + t.get_font_data ().continous_check_sum (ref check_sum); + } + return check_sum; + } + + public void create_directory () throws GLib.Error { + FontData fd; + + uint32 table_offset = 0; + uint32 table_length = 0; + + uint32 check_sum = 0; + + fd = new FontData (); + + return_val_if_fail (offset_table.num_tables > 0, fd); + + table_offset += offset_table.get_font_data ().length_with_padding (); + + if (this.font_data != null) { + table_offset += this.get_font_data ().length_with_padding (); + } + + head_table.set_check_sum_adjustment (0); // Set this to zero, calculate checksums and update the value + head_table.process (); + + // write the directory + foreach (Table t in tables) { + + if (t is DirectoryTable || t is OffsetTable) { + continue; + } + + printd (@"c $(t.id) offset: $(table_offset) len with pad $(t.get_font_data ().length_with_padding ())\n"); + + table_length = t.get_font_data ().length (); // without padding + + fd.add_tag (t.get_id ()); // name of table + fd.add_u32 (t.get_font_data ().check_sum ()); + fd.add_u32 (table_offset); + fd.add_u32 (table_length); + + table_offset += t.get_font_data ().length_with_padding (); + } + + // padding + fd.pad (); + + this.font_data = fd; + + check_sum = get_font_file_checksum (); + head_table.set_check_sum_adjustment ((uint32)(0xB1B0AFBA - check_sum)); + head_table.process (); // update the value + } + +} + +/** Largest power of two less than max. */ +internal static uint16 largest_pow2 (uint16 max) { + uint16 x = 1; + uint16 l = 0; + + while (x <= max) { + l = x; + x = x << 1; + } + + return l; +} + +internal static uint16 largest_pow2_exponent (uint16 max) { + uint16 exp = 0; + uint16 l = 0; + uint16 x = 0; + + while (x <= max) { + l = exp; + exp++; + x = 1 << exp; + } + + return l; +} + +void printd (string s) { + // print (s); +} + +} new file mode 100644@@ -0,0 +1,1069 @@+/*+ 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 {++/** A display with all glyphs present in this font. */+public class OverView : FontDisplay {+ Allocation allocation;+ + int rows = 0;+ int items_per_row = 0;+ uint32 first_character = 0;+ int64 first_visible = 0;+ uint32 selected = 0;+ + double view_offset_y = 0;+ double view_offset_x = 0;+ + int nail_height = 0;+ int nail_width = 0;++ double nail_zoom_height = 0;+ double nail_zoom_width = 0;+ + public signal void open_glyph_signal (string c);++ GlyphRange glyph_range;++ List<GlyphCollection> visible_characters = new List<GlyphCollection> ();++ List<unichar> zoom_list = new List<unichar> ();+ int zoom_list_index = 0;++ List<GlyphCollection> deleted_glyphs = new List<GlyphCollection> ();++ Scrollbar scrollbar;+ + bool all_avail = true;+ + public OverView (GlyphRange? range = null) {+ GlyphRange gr;+ + if (range == null) {+ gr = new GlyphRange ();+ set_glyph_range (gr);+ }+ + reset_zoom ();++ this.open_glyph_signal.connect ((t, s) => {+ TabBar tabs = MainWindow.get_tab_bar ();+ Toolbox tools = MainWindow.get_toolbox ();+ bool selected = tabs.select_char (s);+ unichar new_char = s.get_char (0);+ Font f = Supplement.get_current_font ();+ + if (!selected) {+ GlyphCollection? fg = f.get_glyph_collection (s);+ Glyph g = (fg == null) ? new Glyph (s, new_char) : ((!) fg).get_current ();+ ZoomTool z = (ZoomTool) tools.get_tool ("zoom_tool");+ + stdout.printf (@"Open '%s' %u (%s)\n", s, new_char, Font.to_hex (new_char));+ + z.store_current_view ();+ f.add_glyph (g);+ tabs.add_tab (g);+ + MainWindow.get_glyph_canvas ().set_current_glyph (g);+ + g.close_path ();+ g.default_zoom ();+ z.store_current_view ();+ }+ });+ + scrollbar = new Scrollbar ();+ scrollbar.signal_scroll.connect ((delta, delta_last, absolute) => {+ int64 adjustement = (int64) (-delta_last * get_height ());+ int64 pixels = (int64) (-5.0 * delta_last * nail_height);+ + if (absolute <= 0.002) {+ first_character = 0;+ first_visible = 0;+ selected = 0;+ scroll_top ();+ } else if (absolute >= 1) {+ scroll_bottom ();+ } else {+ scroll_to (absolute);+ } + + redraw_area (0, 0, allocation.width, allocation.height);+ });+ + update_scrollbar ();+ }+ + void scroll_bottom () {+ Font f;+ + if (all_avail) {+ f = Supplement.get_current_font ();+ scroll_to_position (f.length () - items_per_row * (rows - 1));+ } else {+ scroll_to_position (glyph_range.length () - items_per_row * (rows - 1));+ }+ }+ + public double get_height () {+ double l;+ Font f;+ + if (rows == 0) {+ return 0;+ }+ + if (all_avail) {+ f = Supplement.get_current_font ();+ l = f.length ();+ } else {+ l = glyph_range.length ();+ }+ + return 2.0 * nail_height * (l / rows);+ }++ public bool selected_char_is_visible () {+ return first_visible <= selected <= first_visible + items_per_row * rows;+ }++ public override void scroll_wheel_up (double x, double y) {+ key_up ();+ close_menus ();+ redraw_area (0, 0, allocation.width, allocation.height);+ }+ + public override void scroll_wheel_down (double x, double y) {+ key_down ();+ close_menus ();+ redraw_area (0, 0, allocation.width, allocation.height);+ }+ + public override void selected_canvas () {+ redraw_area (0, 0, allocation.width, allocation.height);+ KeyBindings.singleton.set_require_modifier (true);+ }+ + public override void zoom_min () {+ int minw = 26 * 2;+ int minh = 46 * 2;+ + if (zoom_value_in_range (minw, minh)) {+ nail_width = minw;+ nail_height = minh;+ + nail_zoom_height = minw; + nail_zoom_width = minh; + + adjust_scroll ();+ redraw_area (0, 0, allocation.width, allocation.height);+ } else {+ warn_if_reached ();+ } + }+ + public override void reset_zoom () {+ nail_height = 210;+ nail_width = 150;+ + nail_zoom_height = nail_height; + nail_zoom_width = nail_width;+ + adjust_scroll ();+ redraw_area (0, 0, allocation.width, allocation.height);+ }++ public override void zoom_max () {+ if (zoom_value_in_range (allocation.width, allocation.height)) {+ nail_width = allocation.width;+ nail_height = allocation.height;+ + nail_zoom_height = allocation.height; + nail_zoom_width = allocation.width; + + adjust_scroll ();+ redraw_area (0, 0, allocation.width, allocation.height);+ } else {+ warn_if_reached ();+ }+ }+ + public override void zoom_in () {+ double w = (1.1 * nail_width);+ double h = (1.1 * nail_height);+ int nw = (int) w;+ int nh = (int) h;+ + if (zoom_value_in_range (nw, nh)) {+ nail_width = nw;+ nail_height = nh;+ + nail_zoom_height = h; + nail_zoom_width = w;+ + adjust_scroll ();+ redraw_area (0, 0, allocation.width, allocation.height);+ }+ }+ + public override void zoom_out () {+ double w = (nail_width / 1.1);+ double h = (nail_height / 1.1);+ int nw = (int) w;+ int nh = (int) h;+ + if (zoom_value_in_range (nw, nh)) {+ nail_width = nw;+ nail_height = nh;++ nail_zoom_height = h; + nail_zoom_width = w;+ + adjust_scroll (); + redraw_area (0, 0, allocation.width, allocation.height);+ }+ }+ + private bool zoom_value_in_range (int w, int h) {+ return (25 < w <= allocation.width && 45 < h <= allocation.height);+ }++ public override void store_current_view () {+ if (zoom_list_index + 1 < zoom_list.length ()) {+ unowned List<unichar> n = zoom_list.nth (zoom_list_index);+ while (n != zoom_list.last ()) zoom_list.delete_link (zoom_list.last ());+ }+ + zoom_list.append (selected);+ zoom_list_index = (int) zoom_list.length () - 1;+ + if (zoom_list.length () > 50) zoom_list.delete_link (zoom_list.first ());+ }+ + public override void restore_last_view () + requires (zoom_list.length () > 0)+ { + if (zoom_list_index - 1 < 0 || zoom_list.length () == 0)+ return;+ + zoom_list_index--;+ }++ public override void next_view () + requires (zoom_list.length () > 0)+ {+ if (zoom_list_index + 1 >= zoom_list.length ())+ return;+ + zoom_list_index++;+ }++ + public override string get_name () {+ return "Overview";+ }+ + public void display_all_available_glyphs () {+ all_avail = true;+ + first_character = 0;+ first_visible = 0;+ selected = 0;+ }+ + private unowned List<GlyphCollection> get_visible_glyphs () {+ return visible_characters;+ }+ + public void draw_caption (int row, double width, Context cr, double y, uint64 index_begin) {+ string character_string;+ double left_margin, x, caption_y; // for glyph caption+ uint64 index = index_begin;+ int i = row * items_per_row;+ Font f = Supplement.get_current_font ();+ + cr.save ();+ cr.set_line_width (1);+ + x = 0;+ + cr.set_font_size (14);++ left_margin = 0;+ + for (int j = 0; j < items_per_row; j++) {+ + if (all_avail) {+ if (! (0 <= index < f.length ())) {+ break;+ }+ + character_string = ((!) f.get_glyph_indice ((uint32) index)).get_name ();+ } else {+ if (!(0 <= index < glyph_range.get_length ())) {+ break;+ }+ + character_string = glyph_range.get_char ((uint32) index);+ }+ + cr.save ();+ draw_thumbnail (cr, character_string, x, y);+ cr.restore ();++ cr.save ();+ + if (index == selected) {+ cr.set_source_rgba (142/255.0, 158/255.0, 190/255.0, 1);+ } else {+ cr.set_source_rgba (164/255.0, 185/255.0, 215/255.0, 1);+ }+ + cr.set_line_join (LineJoin.ROUND);+ cr.set_line_width(12);+ + cr.rectangle (x, y + nail_height - 20, nail_width - 30, 10); + cr.stroke ();+ cr.restore ();+ + draw_menu (cr, character_string, x, y);+ + caption_y = y + 10 + nail_height - 20;+ + cr.move_to (x, caption_y);+ cr.show_text (character_string);+ + x += nail_width;+ index++;+ i++;+ }+ + cr.stroke();+ }+ + private void draw_menu (Context cr, string name, double x, double y) {+ GlyphCollection? gl = Supplement.get_current_font ().get_glyph_collection (name);+ GlyphCollection g;+ DropMenu menu;+ + if (gl == null) {+ return;+ }+ + g = (!) gl;+ + menu = g.get_version_list ();+ menu.set_position (x + nail_width - 40, y + nail_height - 21);+ menu.draw_icon (cr);+ menu.draw_menu (cr);+ + visible_characters.append (g);+ }+ + private bool draw_thumbnail (Context cr, string name, double x, double y) {+ Glyph? gl = Supplement.get_current_font ().get_glyph (name);+ Glyph g;+ + double gx, gy;+ double x1, x2, y1, y2;+ double scale = nail_zoom_width / 150.0;+ double w, h;++ w = nail_width - 19;+ h = nail_height - 22;+ + scale = nail_zoom_height / (210.0 - 15.0);+ + if (gl == null) {+ return false;+ }+ + g = (!) gl;+ g.boundries (out x1, out y1, out x2, out y2);++ gx = 0;+ gy = h / scale - 40;+ + Surface s = new Surface.similar (cr.get_target (), Content.COLOR_ALPHA, (int) w, (int) h);+ Context c = new Context (s);+/*+ c.set_source_rgb (200/255.0, 200/255.0, 229/255.0);+ c.rectangle (0, 0, w, h); + c.fill ();+*/+ c.scale (scale, scale); + Svg.draw_svg_path (c, g.get_svg_data (), gx, gy, 1.0);+ + cr.save ();+ cr.set_source_surface (s, x - 6, y);+ cr.paint ();+ cr.restore ();++ return true;+ }++ // 150 * 190 px+ public override void draw (Allocation allocation, Context cr) {+ double width;+ double y;+ uint64 t;+ int i;+ int n_items;+ + while (visible_characters.length () > 9) {+ visible_characters.remove_link (visible_characters.first ());+ }++ this.allocation = allocation;+ + cr.save ();+ cr.set_source_rgba (1, 1, 1, 1);+ cr.rectangle (0, 0, allocation.width, allocation.height + nail_height);+ cr.fill ();+ cr.restore ();+ + if (Supplement.experimental) {+ scrollbar.draw (cr, allocation);+ }+ + n_items = ((allocation.width - 40) / nail_width);+ rows = (allocation.height / nail_height);++ if (items_per_row != n_items) { + if (items_per_row == 0) {+ items_per_row = n_items;+ first_visible = first_character;+ + key_down ();+ key_up ();+ } else {+ items_per_row = n_items;+ first_visible = first_character;+ }+ }++ width = nail_width * items_per_row;+ view_offset_x = (allocation.width - width) / 2.0;+ + cr.translate(view_offset_x, view_offset_y);+ + y = 0;+ t = first_visible;+ i = 0;+ for (int j = 0; j < rows + 1; j++) {+ draw_caption (i, width, cr, y, t);++ t += items_per_row;+ y += nail_height;+ i++;+ }+ + draw_menus (allocation, cr);+ }++ private void draw_menus (Allocation allocation, Context cr) {+ }++ private void close_menus () {+ }+ + public void scroll_rows (int row_adjustment) {+ for (int i = 0; i < row_adjustment; i++) {+ scroll (-nail_height);+ }+ + for (int i = 0; i > row_adjustment; i--) {+ scroll (nail_height);+ }+ }+ + public void scroll_adjustment (double pixel_adjustment) {+ double l;+ Font f;+ + if (all_avail) {+ f = Supplement.get_current_font ();+ l = f.length ();+ } else {+ l = glyph_range.length ();+ }+ + if (first_visible <= 0) {+ return;+ }++ if (first_visible + rows * items_per_row >= l) {+ return;+ }+ + scroll ((int64) pixel_adjustment);+ }+ + void default_position () {+ scroll_top ();+ scroll_rows (1);+ }+ + void scroll_to_position (int64 r) {+ int64 l;+ Font f;+ + if (all_avail) {+ f = Supplement.get_current_font ();+ l = f.length ();+ } else {+ l = glyph_range.length ();+ }+ + // bottom+ if (r > l - items_per_row * rows) {+ return;+ }+ + if (r < 0) {+ scroll_top ();+ return;+ }+ + default_position ();++ first_visible = r;+ selected = (uint32) r;+ + adjust_scroll ();+ }+ + public void scroll_to (double percent) {+ int64 r;+ double l;+ Font f;+ + if (all_avail) {+ f = Supplement.get_current_font ();+ l = f.length ();+ } else {+ l = glyph_range.length ();+ }+ + l /= rows;+ r = (int64) (percent * l * items_per_row);+ scroll_to_position (r);+ }+ + private void scroll (int64 pixel_adjustment) {++ if (first_visible < 0 && pixel_adjustment < 0) {+ scroll_top ();+ return;+ }+ + view_offset_y += pixel_adjustment;+ + if (view_offset_y >= 0) {+ while (view_offset_y > nail_height) { + view_offset_y -= nail_height;+ first_visible -= items_per_row;+ }++ first_visible -= items_per_row;+ view_offset_y -= nail_height;+ } else if (view_offset_y < -nail_height) {+ view_offset_y = 0;+ first_visible += items_per_row;+ }+ + }+ + public void scroll_top () {+ first_visible = first_character;+ view_offset_y = 0;+ }+ + private int selected_offset_y (unichar item) {+ int64 di;+ int64 a, b;+ if (unlikely (items_per_row == 0)) return 0;+ + a = (int64) item;+ b = (int64) first_visible;+ di = (a - b);+ + return (int) (view_offset_y + ((di / items_per_row) * nail_height));+ }++ /** Make selected item fully visible if it partly off screen. */+ private void adjust_scroll () {+ int off = selected_offset_y (selected);++ if (off < 0) {+ close_menus ();+ scroll (-1 * off);+ } else if (off > allocation.height - nail_height) {+ scroll ((allocation.height - nail_height) - off);+ }+ }++ public void key_down () {+ double l;+ Font f;+ + if (all_avail) {+ f = Supplement.get_current_font ();+ l = f.length ();+ } else {+ l = glyph_range.length ();+ }+ + update_scrollbar ();+ + if (l == 0) return;+ + if (at_bottom ()) {+ int len = (int) l;+ int s = (int) selected;+ + if (len - items_per_row >= s) {+ selected += items_per_row; + } else if (s < len) {+ selected++;+ }+ + if (selected >= len) {+ selected = len - 1;+ }+ + adjust_scroll ();+ + return;+ }+ + selected += items_per_row;+ + if (selected > rows * items_per_row + first_visible) {+ scroll (-nail_height);+ }+ + adjust_scroll ();+ }++ public void key_up () {+ update_scrollbar ();++ if (selected < first_character) { + warn_if_reached ();+ }+ + if (selected < first_character + items_per_row) {+ scroll_top ();+ return;+ }+ + if (selected - items_per_row >= first_character) {+ selected -= items_per_row;+ if (selected < first_visible) {+ scroll (-nail_height);+ }+ } + + if (first_visible < first_character) {+ selected = first_character;+ }+ + adjust_scroll ();++ if (first_visible < first_character) {+ scroll_top ();+ }+ } + + public void open_current_glyph () {+ key_press (Key.ENTER);+ }++ public void select_next_glyph () {+ key_press (Key.RIGHT);+ }++ public void key_right () { + uint len;+ Font f;+ + if (all_avail) {+ f = Supplement.get_current_font ();+ len = f.length ();+ } else {+ len = glyph_range.length ();+ }+ + if (len == 0) return;+ + selected++;+ + if (selected >= len) {+ selected = len - 1;+ }++ if (selected_offset_y (selected) > allocation.height - nail_height) {+ selected -= items_per_row;+ key_down ();+ }+ }+ + public void key_left () {+ if (selected > first_character) {+ selected--; + if (selected_offset_y (selected) < 0) {+ selected -= items_per_row;+ key_up ();+ }+ }+ }+ + public string get_selected_char () {+ Font f;+ Glyph? g;+ + if (all_avail) {+ f = Supplement.get_current_font ();+ g = f.get_glyph_indice (selected);+ return_val_if_fail (g != null, "".dup ());+ return ((!) g).get_name ();+ }+ + return glyph_range.get_char (selected);+ }+ + public override void key_press (uint keyval) {+ redraw_area (0, 0, allocation.width, allocation.height);+ close_menus ();++ if (KeyBindings.modifier == CTRL) {+ return;+ }++ switch (keyval) {+ case Key.ENTER:+ open_glyph_signal (get_selected_char ());+ return;+ + case Key.UP:+ key_up ();+ return;+ + case Key.RIGHT:+ key_right ();+ return;+ + case Key.LEFT:+ key_left ();+ return;+ + case Key.DOWN:+ key_down ();+ return;+ + case Key.PG_UP:+ for (int i = 0; i++ < rows; i++) {+ key_up ();+ }+ return;+ + case Key.PG_DOWN:+ for (int i = 0; i++ < rows; i++) {+ key_down ();+ }+ return;+ + case Key.DEL:+ delete_selected_glyph ();+ return;+ }++ scroll_to_char (keyval);+ }++ public void delete_selected_glyph () {+ delete_glyph (glyph_range.get_char (selected));+ }+ + public void delete_glyph (string g) {+ GlyphCollection? gc;+ Font f = Supplement.get_current_font ();+ + gc = f.get_glyph_collection (g);++ if (gc != null) {+ deleted_glyphs.append ((!) gc);+ }++ f.delete_glyph (g);+ MainWindow.get_tab_bar ().close_by_name (g);+ set_glyph_range (glyph_range);+ f.touch ();+ }+ + public override void undo () {+ Font f = Supplement.get_current_font ();+ + if (deleted_glyphs.length () == 0) {+ return;+ }+ + f.add_glyph_collection (deleted_glyphs.last ().data);+ deleted_glyphs.remove_link (deleted_glyphs.last ());+ }+ + public void scroll_to_char (unichar c) {+ GlyphRange gr = glyph_range;+ GlyphRange full = new GlyphRange ();+ + if (is_modifier_key (c)) {+ return;+ }+ + full.use_full_unicode_range ();+ + set_glyph_range (full);+ selected = c;+ set_glyph_range (gr);+ }+ + public override void motion_notify (double x, double y) {+ if (x > allocation.x - 10) {+ scrollbar.motion_notify ((int) x, (int) y);+ }+ }+ + public override void button_release (int button, double ex, double ey) {+ if (ex > allocation.x - 10) {+ scrollbar.button_release (button, ex, ey);+ }+ }+ + private void selection_click (uint button, double ex, double ey) {+ double x = view_offset_x;+ double y = view_offset_y;++ unichar current = selected;++ // click in margin+ int n_items = (allocation.width - 40 / nail_width);+ double m = (allocation.width - 40 - (n_items * nail_width)) / 2.0;+ + bool menu_action = false;+ + Font f;+ uint len;++ redraw_area (0, 0, allocation.width, allocation.height);++ if (all_avail) {+ f = Supplement.get_current_font ();+ len = f.length ();+ } else {+ len = glyph_range.length ();+ }+ + foreach (GlyphCollection g in get_visible_glyphs ()) {+ bool a;++ a = g.get_version_list ().menu_item_action (ex - view_offset_x, ey - view_offset_y); // select one item on the menu+ + if (a) {+ return;+ }+ + a = g.get_version_list ().menu_icon_action (ex - view_offset_x, ey - view_offset_y); // open menu+ + if (a) {+ menu_action = true;+ }+ }+ + if (ex < m) {+ ex = m + 1;+ }+ + if (ex > allocation.width - m) {+ ex = allocation.width - m - 1;+ }+ + if (ey < 5) {+ ey = 5;+ }+ + if (ey > allocation.height - 5) {+ ey = allocation.height - 5;+ }+ + // empty set+ if (len == 0) return;++ // scroll to the selected glyph+ selected = (uint32) first_visible;++ while (y < ey < allocation.height) {+ selected += items_per_row;+ y += nail_height;+ }+ selected -= items_per_row;++ while (x < ex < allocation.width) {+ selected++;+ x += nail_width;+ }+ + selected--;+ + if (at_bottom () && selected >= len) {+ selected = len - 1;+ }+ + adjust_scroll ();+ + if (current == selected && !menu_action) {+ open_glyph_signal (get_selected_char ());+ }+ + adjust_scroll ();+ adjust_scroll ();+ }+ + public override void button_press (uint button, double x, double y) {+ if (x > allocation.width - 10) {+ if (Supplement.experimental) {+ scrollbar.button_press (button, x, y);+ }+ } else {+ selection_click (button, x, y);+ }+ }++ /** Returns true if overview shows the last character. */+ private bool at_bottom () {+ Font f;+ double t = rows * items_per_row + items_per_row + first_visible;+ + if (all_avail) {+ f = Supplement.get_current_font ();+ return t >= f.length ();+ }+ + return t >= glyph_range.length ();+ }++ public void set_glyph_range (GlyphRange range) {+ GlyphRange? current = glyph_range;+ string c = "";+ bool done = false;+ uint32 i;+ uint32 len = 0;+ + if (current != null) {+ c = glyph_range.get_char (selected);+ }+ + all_avail = false;+ + glyph_range = range;++ // Todo: optimized search when full range is selected.+ + // scroll to the selected character+ if (c == "") {+ c = "a";+ }+ + if (items_per_row == 0) {+ return;+ }+ + scroll_top ();++ // Skip scroll if this is a unassigned character+ if (c.char_count () > 1) {+ return;+ }++ while (true) {+ for (i = (uint32) first_visible; i < first_visible + items_per_row * (rows + 1); i++) {+ if (range.get_char (i) == c) {+ selected = i;+ done = true;+ break;+ }+ }+ + if (done) break;+ if (at_bottom ()) break;++ scroll_rows (rows);+ }++ if (unlikely (!done)) {+ MainWindow.get_toolbox ().select_tool_by_name ("custom_character_set");+ warning (@"No glyph \"$c\"\n");+ return;+ }+ + adjust_scroll ();+ + if (at_bottom ()) {+ while (c != glyph_range.get_char (selected) && glyph_range.get_length () >= items_per_row) {+ key_right (); + if (selected >= glyph_range.get_length () - items_per_row) {+ break;+ }+ }++ len = glyph_range.get_length ();+ for (i = selected; i < len; i++) {+ if (c == glyph_range.get_char (i)) {+ break;+ }+ }++ if (i == len) {+ selected = first_character;+ scroll_top ();+ }+ }++ update_scrollbar ();+ redraw_area (0, 0, allocation.width, allocation.height);+ }++ public void update_scrollbar () {+ double len;+ Font f;+ + if (all_avail) {+ f = Supplement.get_current_font ();+ len = f.length ();+ } else {+ len = glyph_range.get_length ();+ }+ + scrollbar.set_handle_size (items_per_row * rows / len);+ scrollbar.set_handle_position (first_visible / len);+ }+}++}new file mode 100644@@ -0,0 +1,1879 @@+/*+ 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;+using Math;++namespace Supplement {++public enum Direction {+ CLOCKWISE,+ COUNTER_CLOCKWISE+}++public class Path {+ + public List<EditPoint> points = new List<EditPoint> ();++ EditPoint? last_point = null;+ EditPoint? second_last_point = null;++ /** Region boundries */+ public double xmax = double.MIN;+ public double xmin = double.MAX;+ public double ymax = double.MIN;+ public double ymin = double.MAX;++ bool edit = true;+ bool open = true;+ bool direction_is_set = false;++ bool no_derived_direction = false;+ bool clockwise_direction = true;++ public delegate bool RasterIterator (double x, double y, double step); // iterate over each pixel at a given zoom level+ + bool selected = false;+ + private static ImageSurface? edit_point_image = null;+ private static ImageSurface? active_edit_point_image = null;+ + private static ImageSurface? edit_point_handle_image = null;+ private static ImageSurface? active_edit_point_handle_image = null;++ private static ImageSurface? selected_edit_point_image = null;+ private static ImageSurface? active_selected_edit_point_image = null;+ + Path quadratic_path; // quadratic points for ttf export+ + public Path () {+ if (edit_point_image == null) {+ edit_point_image = Icons.get_icon ("edit_point.png");+ active_edit_point_image = Icons.get_icon ("active_edit_point.png");+ edit_point_handle_image = Icons.get_icon ("edit_point_handle.png");+ active_edit_point_handle_image = Icons.get_icon ("active_edit_point_handle.png");+ selected_edit_point_image = Icons.get_icon ("selected_edit_point.png");+ active_selected_edit_point_image = Icons.get_icon ("active_selected_edit_point.png");+ }+ }++ public bool has_direction () {+ return direction_is_set;+ }++ public bool empty () {+ return points.length () == 0;+ }++ public void draw_outline (Context cr, Allocation allocation, double view_zoom) {+ unowned List<EditPoint> ep = points;+ + unowned EditPoint? n = null;+ unowned EditPoint en;+ unowned EditPoint em;+ + if (ep.length () < 2) {+ return;+ }+ + cr.new_path ();+ + // draw lines+ foreach (EditPoint e in ep) {+ if (n != null) {+ en = (!) n;+ draw_next (e, en, cr);+ }+ + n = e;+ }++ // close path+ if (!is_open () && n != null) {+ en = (!) n;+ em = ep.first ().data;+ + draw_next (em, en, cr);+ }++ cr.stroke ();+ }+ + public void draw_edit_points (Context cr, Allocation allocation, double view_zoom) {+ unowned List<EditPoint> ep = points;+ + if (is_editable ()) {+ // control points for curvature+ foreach (EditPoint e in ep) {+ if (e.selected || e.selected_handle > 0)+ draw_edit_point_handles (e, cr);+ }+ + // control points+ foreach (EditPoint e in ep) {+ draw_edit_point (e, cr);+ }+ }+ }++ public void fill_path (Context cr, Allocation allocation, double view_zoom) {+ unowned List<EditPoint> ep = points;+ + unowned EditPoint? n = null;+ unowned EditPoint en;+ unowned EditPoint em;+ + cr.new_path ();+ + // draw lines+ foreach (EditPoint e in ep) {+ if (n != null) {+ en = (!) n;+ draw_next (e, en, cr);+ }+ + n = e;+ }+ + // close path+ if (!is_open () && ep.length () >= 2 && n != null) {+ en = (!) n;+ em = ep.first ().data;+ draw_next (em, en, cr);+ }++ // fill path+ cr.close_path ();+ + if (is_clockwise ()) {+ cr.set_source_rgba (80/255.0, 95/255.0, 137/255.0, 0.5);+ } else {+ cr.set_source_rgba (144/255.0, 145/255.0, 236/255.0, 0.5);+ }+ + cr.fill ();+ }++ private void draw_next (EditPoint e, EditPoint en, Context cr) {+ draw_curve (e, 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;+ + get_bezier_points (e, en, out xa, out ya, out xb, out yb, out xc, out yc, out xd, out yd);++ cr.set_source_rgba (0, 0, 0, alpha);+ cr.set_line_width (1 / g.view_zoom);+ + cr.line_to (xa, ya); // this point makes sense only if it is the first or last position, the other points are meaning less don't export them++ cr.curve_to (xb, yb, xc, yc, xd, yd);+ }+ + /** Curve relative to window center. */+ public static void get_bezier_points (EditPoint e, EditPoint en, out double xa, out double ya, out double xb, out double yb, out double xc, out double yc, out double xd, out double yd) {+ Glyph g = MainWindow.get_current_glyph ();+ + double center_x, center_y;+ + center_x = g.allocation.width / 2.0;+ center_y = g.allocation.height / 2.0;+ + xa = center_x + en.x;+ ya = center_y - en.y;++ xb = center_x + en.get_right_handle ().x ();+ yb = center_y - en.get_right_handle ().y ();+ + xc = center_x + e.get_left_handle ().x ();+ yc = center_y - e.get_left_handle ().y ();+ + xd = center_x + e.x;+ yd = center_y - e.y; + }++ /** Curve absolute glyph data. */+ public static void get_abs_bezier_points (EditPoint e, EditPoint en, out double xa, out double ya, out double xb, out double yb, out double xc, out double yc, out double xd, out double yd) {+ xa = + en.x;+ ya = - en.y;++ xb = + en.get_right_handle ().x ();+ yb = - en.get_right_handle ().y ();+ + xc = + e.get_left_handle ().x ();+ yc = - e.get_left_handle ().y ();+ + xd = + e.x;+ yd = - e.y; + }+ + /** Line points relative to center. */+ public static void get_line_points (EditPoint e, EditPoint en, out double xa, out double ya, out double xb, out double yb) {+ double xc = Glyph.xc ();+ double yc = Glyph.yc ();+ + xa = xc + en.x;+ ya = yc - en.y;+ + xb = xc + e.x;+ yb = yc - e.y;+ }+ + public void draw_line (EditPoint e, EditPoint en, Context cr, double alpha = 1) { + Glyph g = MainWindow.get_current_glyph ();+ double ax, ay, bx, by;++ get_line_points (e, en, out ax, out ay, out bx, out by);+ + cr.set_source_rgba (0/255.0, 0/255.0, 0/255.0, 1);+ cr.set_line_width (1.7 * (1/g.view_zoom));++ cr.line_to (ax, ay);+ cr.line_to (bx, by);+ + cr.stroke ();+ }+ + public void draw_edit_point (EditPoint e, Context cr) {+ draw_edit_point_center (e, cr);+ }+ + public void draw_edit_point_handles (EditPoint e, Context cr)+ requires (active_edit_point_handle_image != null && edit_point_handle_image != null)+ {+ ImageSurface img_right, img_left;++ EditPoint handle_right = e.get_right_handle ().get_point ();+ EditPoint handle_left = e.get_left_handle ().get_point ();++ cr.stroke ();+ + img_right = (e.get_right_handle ().active) ? (!) active_edit_point_handle_image : (!) edit_point_handle_image;+ img_left = (e.get_left_handle ().active) ? (!) active_edit_point_handle_image : (!) edit_point_handle_image;+ + if (!(is_open () && e == points.last ().data)) {+ draw_line (handle_right, e, cr, 0.15);+ draw_img_center (cr, img_right, e.get_right_handle ().x (), e.get_right_handle ().y ());+ }+ + if (!(is_open () && e == points.first ().data)) {+ draw_line (handle_left, e, cr, 0.15);+ draw_img_center (cr, img_left, e.get_left_handle ().x (), e.get_left_handle ().y ());+ }+ }++ public static void draw_edit_point_center (EditPoint e, Context cr) + requires (active_edit_point_image != null && edit_point_image != null)+ { + ImageSurface img;+ + if (e.is_selected ()) {+ img = (e.active) ? (!) active_selected_edit_point_image : (!) selected_edit_point_image;+ } else {+ img = (e.active) ? (!) active_edit_point_image : (!) edit_point_image;+ }+ + draw_img_center (cr, img, e.x, e.y);+ }+ + public static void draw_img_center (Context cr, ImageSurface img, double x, double y) {+ Glyph g = MainWindow.get_current_glyph ();+ + double ivz = 1 / g.view_zoom;++ double xc = g.allocation.width / 2.0;+ double yc = g.allocation.height / 2.0;++ cr.save ();++ cr.scale (ivz, ivz);+ + x = xc + x - (img.get_width () / 2.0) * ivz; + y = yc - y - (img.get_height () / 2.0) * ivz;+ + cr.set_source_surface (img, x * g.view_zoom, y * g.view_zoom);+ cr.paint ();+ + cr.restore (); + }+ + /** Returns true if there is an edit point at (p.x, p.y). */+ private bool has_edit_point (EditPoint p) {+ foreach (var t in points) {+ if (p.x == t.x && p.y == t.y) {+ return true;+ }+ }+ return false;+ }++ private EditPoint? get_edit_point_at (double x, double y) {+ foreach (var p in points) {+ if (p.x == x && p.y == y) return p;+ }+ + return null;+ }+ + private void remove_all_edit_points () {+ while (points.length () > 0) {+ points.remove_link (points.first ());+ }+ }+ + /** Replace edit points in this path with @param new_path.points. */+ public void replace_path (Path new_path) {+ while (points.length () > 0) {+ points.remove_link (points.first ());+ }+ + foreach (var np in new_path.points) {+ add (np.x, np.y);+ }+ + close ();+ }+ + /** Returns true if this path can be merged with @param union */ + private bool is_valid_union (Path union) {+ if (union == this) {+ warning ("Path can't union with it self.");+ return false;+ }+ + if (union.points.length () <= 2 || points.length () <= 2) {+ return false;+ }++ if (!has_overlapping_boundry (union)) {+ return false;+ }++ return true;+ }+ + /** Set direction for this path to clockwise for outline and + * counter clockwise for inline paths.+ */+ public bool force_direction (Direction direction) {+ bool c = (direction == Direction.CLOCKWISE);+ direction_is_set = true;+ + if (c != is_clockwise ()) {+ this.reverse ();+ }+ + if (unlikely (is_clockwise () != c)) {+ warning ("Failed to set direction for path in force_direction.");+ return true;+ }+ + return false;+ }++ /** Switch direction from clockwise path to counter clockwise path or vise versa. */+ public void reverse () {+ bool direction = is_clockwise ();+ + if (no_derived_direction) {+ clockwise_direction = !clockwise_direction;+ }+ + reverse_points ();+ + if (unlikely (direction == is_clockwise ())) {+ stderr.printf ("Error: Direction did not change after reversing path.\n");+ stderr.printf (@"Length: $(points.length ())\n");+ stderr.printf (@"No particular direction can be derived: $no_derived_direction \n");+ warning ("Path.reverse () failed.\n");+ }+ }++ private void reverse_points () {+ points.reverse ();+ + EditPointHandle t;+ foreach (var e in points) {+ t = e.right_handle;+ e.right_handle = e.left_handle;+ e.left_handle = t;+ }+ + create_list ();+ }++ public void print_all_points () {+ int i = 0;+ foreach (var p in points) {+ ++i;+ string t = (p.type == PointType.END) ? " endpoint" : "";+ stdout.printf (@"Point $i at ($(p.x), $(p.y)) $t \n");+ }+ }+ + private bool validate_list () {+ unowned List<EditPoint> ep = points.first ();+ + if (points.length () < 3) {+ warning ("points.length () < 3");+ }+ + if (!is_open ()) {+ for (int i = 0; i < points.length (); i++) {+ if (ep.data.next == null) {+ return false;+ }++ if (ep.data.prev == null) {+ return false;+ }+ + ep = (!) ep.data.next;+ }+ }++ if (is_open ()) {+ for (int i = 1; i < points.length () - 1; i++) {+ if (ep.data.next == null) {+ return false;+ }++ if (ep.data.prev == null) {+ return false;+ }+ + ep = (!) ep.data.next;+ }+ + if (ep.data.next != null) {+ return false;+ }+ }+ + return true;+ }+ + private double clockwise_sum () {+ return_if_fail (points.length () >= 3);+ + double sum = 0;+ EditPoint prev = points.last ().data;+ foreach (EditPoint e in points) {+ sum += (e.x - prev.x) * (e.y + prev.y);+ prev = e;+ }+ + return sum;+ }+ + public bool is_clockwise () {+ double s;+ + if (unlikely (points.length () <= 2)) {+ no_derived_direction = true;+ return clockwise_direction;+ }+ + s = clockwise_sum ();+ + if (s == 0) {+ no_derived_direction = true;+ return clockwise_direction; + }+ + return s > 0;+ }+ + public bool is_editable () {+ return edit;+ }++ /** Show control points on outline path. */+ public void set_editable (bool e) {+ edit = e;+ }+ + public bool is_open () {+ return open;+ }+ + private bool direction_x (EditPoint start, EditPoint stop) {+ return (start.x < stop.x);+ }++ private bool direction_y (EditPoint start, EditPoint stop) {+ return (start.y < stop.y);+ }+ + public void thinner (double ratio) + requires (points.length () > 2)+ { + update_region_boundries ();+ + double w = Math.fabs(xmax - xmin);+ double h = Math.fabs(ymax - ymin);+ double cx = w / 2 + xmin; + double cy = h / 2 + ymin;+ + ratio = 1 - ratio;++ unowned List<EditPoint> i = points.first ();+ unowned List<EditPoint> prev = points.last ();+ + bool dirx = true;+ bool ldirx = direction_x (prev.data, i.data);+ bool ndirx;++ bool diry = true;+ bool ldiry = direction_y (prev.data, i.data);+ bool ndiry;+ + while (true) {+ ndirx = direction_x (prev.data, i.data);+ + if (ndirx != ldirx) {+ dirx = !dirx;+ }+ + ldirx = ndirx;++ ndiry = direction_y (prev.data, i.data);+ + if (ndiry != ldiry) {+ diry = !diry;+ }+ + ldiry = ndiry;+ + EditPoint p = i.data;+ + if (dirx) {+ if (p.x < cx)+ p.x += (cx - p.x) * ratio;+ else+ p.x -= (p.x - cx) * ratio;+ } else {+ if (p.x < cx)+ p.x += (cx - p.x) * ratio * 2;+ else+ p.x -= (p.x - cx) * ratio * 2;+ }++ // err borde vara -y+ if (p.y < cy && diry || p.y > cy && ! diry)+ p.y += (cy - p.y) * ratio;+ else+ p.y -= (p.y - cy) * ratio; + + if (i == points.last ()) {+ break;+ }+ + prev = i;+ i = i.next;+ }+ }+ + public void resize (double ratio) {+ double h = ymax - ymin;+ double px, py, nx, ny;++ px = xmin;+ py = ymin;+ + if (ratio < 1 && h < 2) {+ return;+ }+ + foreach (var p in points) {+ p.x *= ratio;+ p.y *= ratio;+ p.right_handle.length *= ratio;+ p.left_handle.length *= ratio;+ }+ + xmin *= ratio; + xmax *= ratio;+ ymin *= ratio;+ ymax *= ratio;++ nx = xmin;+ ny = ymin;+ + move (px - nx, py - ny);+ }+ + public Path copy () {+ Path new_path = new Path ();+ + foreach (var ep in points) {+ new_path.add_point (ep.copy ());+ }+ + new_path.edit = edit;+ new_path.open = open;+ + new_path.create_list ();+ + return new_path;+ } + + public bool is_over (double x, double y) {+ Glyph g = MainWindow.get_current_glyph ();+ + x = x * Glyph.ivz () + g.view_offset_x - Glyph.xc ();+ y = y * Glyph.ivz () + g.view_offset_y - Glyph.yc ();++ y *= -1;+ + return is_over_coordinate (x, y);+ }+ + public bool is_over_coordinate (double x, double y) {+ return is_over_coordinate_var (x, y, 0.1);+ }+ + public static double distance (double ax, double bx, double ay, double by) {+ return Math.fabs (Math.sqrt (Math.pow (ax - bx, 2) + Math.pow (ay - by, 2)));+ }+ + /** Estimate length of path. */+ private double get_length () {+ double len = 0;+ + return_if_fail (points.length () > 2);+ + EditPoint prev = points.last ().data;+ foreach (EditPoint p in points) {+ len += get_length_from (prev, p);+ prev = p;+ }+ + return len;+ }+ + private static double get_length_from (EditPoint a, EditPoint b) {+ double x, y;+ + x = Math.fabs (a.x - a.get_right_handle ().x ());+ x += Math.fabs (a.get_right_handle ().x () - b.get_left_handle ().x ());+ x += Math.fabs (b.get_left_handle ().x () - b.x);++ y = Math.fabs (a.y - a.get_right_handle ().y ());+ y += Math.fabs (a.get_right_handle ().y () - b.get_left_handle ().y ());+ y += Math.fabs (b.get_left_handle ().y () - b.y);+ + return Math.fabs (Math.sqrt (x * x + y * y));+ } + + /** Variable precision */+ public bool is_over_coordinate_var (double x, double y, double tolerance) {+ List<EditPoint> ycoordinates = new List<EditPoint> ();+ double last = 0;+ bool on_edge = false;+ double last_x = 0;++ if (points.length () < 3) {+ return false;+ }+ + if (!is_over_boundry_precision (x, y, tolerance)) {+ return false;+ }++ foreach (EditPoint e in points) {+ if (distance (e.x, x, e.y, y) < tolerance) {+ return true;+ }+ }++ all_of_path ((cx, cy, ct) => {+ double distance = Math.fabs (Math.sqrt (Math.pow (cx - x, 2) + Math.pow (cy - y, 2)));+ + if (distance < tolerance) {+ on_edge = true;+ return false;+ }+ + if (Math.fabs (cx - x) < tolerance && Math.fabs (last - cy) > 2 * tolerance) {+ ycoordinates.append (new EditPoint (cx, cy));+ last = cy;+ }+ + last_x = cx;+ return true;+ });++ if (on_edge) {+ return true;+ }++ if (ycoordinates.length () == 0) {+ warning ("No ycoordinates is empty");+ return true;+ }++ ycoordinates.sort ((a, b) => {+ return (a.y < b.y) ? 1 : -1;+ });+ + for (int i = 0; i < ycoordinates.length () - 1; i++) {+ if (Math.fabs (ycoordinates.nth (i).data.y - ycoordinates.nth (i + 1).data.y) < 4 * tolerance) {+ ycoordinates.remove_link (ycoordinates.nth (i));+ }+ }+ + if (unlikely (ycoordinates.length () % 2 != 0)) {+ warning (@"not an even number of coordinates ($(ycoordinates.length ()))");+ stderr.printf (@"(ymin <= y <= ymax) && (xmin <= x <= xmax);\n");+ stderr.printf (@"($ymin <= $y <= $ymax) && ($xmin <= $x <= $xmax);\n");+ + stderr.printf (@"tolerance: $(tolerance)\n");+ + stderr.printf ("ycoordinates:\n");+ foreach (EditPoint e in ycoordinates) {+ stderr.printf (@"$(e.y)\n");+ }++ ycoordinates.append (ycoordinates.last ().data.copy ());+ + return true;+ }+ + for (unowned List<EditPoint> e = ycoordinates.first (); true; e = e.next) {+ return_val_if_fail (!is_null (e), true);+ + if (y <= e.data.y + tolerance) {+ return_val_if_fail (!is_null (e.next), true);+ + e = e.next;++ if (y >= e.data.y - tolerance) {+ return true;+ }+ }+ + if (e == ycoordinates.last ()) {+ break;+ }+ }++ return false;+ }+ + public bool is_over_boundry_precision (double x, double y, double p) {+ if (unlikely (ymin == double.MAX || ymin == 10000)) {+ warning (@"no bounding box. length ($(points.length ()))");+ update_region_boundries ();+ }+ + return (ymin - p <= y <= ymax + p) && (xmin - p <= x <= xmax + p);+ }+ + public bool is_over_boundry (double x, double y) {+ if (unlikely (ymin == double.MAX)) {+ warning ("bounding box is not calculated, run update_region_boundries first.");+ }++ return (ymin <= y <= ymax) && (xmin <= x <= xmax);+ }++ public bool has_overlapping_boundry (Path p) {+ return !(xmax <= p.xmin || ymax <= p.ymin) || (xmin >= p.xmax || ymin >= p.ymax);+ }+ + public void add (double x, double y) {+ add_after (x, y, points.last ());+ }++ public void add_point (EditPoint p) {+ add_point_after (p, points.last ());+ }++ /** Insert a new point after @param previous_point and return a reference + * to the new item in list.+ */+ public unowned List<EditPoint> add_after (double x, double y, List<EditPoint>? previous_point) {+ EditPoint p = new EditPoint (x, y, PointType.LINE); + return add_point_after (p, previous_point);+ }+ + public unowned List<EditPoint> add_point_after (EditPoint p, List<EditPoint>? previous_point) {+ unowned List<EditPoint> np;+ int prev_index;++ if (points.length () > 0 && previous_point != null && ((!)previous_point).data.type == PointType.END) {+ points.delete_link ((!) previous_point);+ }++ if (unlikely (previous_point == null && points.length () != 0)) warning ("previous_point == null");++ if (points.length () == 0) {+ points.append (p);+ np = points.last ();+ p.prev = points.last ();+ p.next = points.last ();+ } else {+ p.prev = (!) previous_point;+ p.next = ((!) previous_point).next;++ points.insert_before (((!) previous_point).next, p);+ + prev_index = points.position ((!) previous_point);+ np = points.nth (prev_index + 1);+ }+ + second_last_point = last_point;+ last_point = p;+ + PenTool.set_default_handle_positions ();+ + return np;+ }++ public void close () {+ open = false;+ edit = false;++ create_list ();+ + if (points.length () > 2) {+ points.first ().data.recalculate_linear_handles ();+ points.last ().data.recalculate_linear_handles ();+ }+ }+ + public void reopen () {+ open = true;+ edit = true;+ }+ + public EditPoint? get_last_point () {+ return last_point;+ }++ public EditPoint? get_second_last_point () {+ return second_last_point;+ }+ + /** Move path. */+ public void move (double delta_x, double delta_y) {+ foreach (var ep in points) {+ ep.x += delta_x;+ ep.y += delta_y; // kanske -y+ }+ + update_region_boundries ();+ }++ public void update_region_boundries () {+ if (points.length () == 0) {+ xmax = 0;+ xmin = 0;+ ymax = 0;+ ymin = 0; + }++ // inside and outside in vala lambda functions reveals a tricky problem+ // (look at c code). that's the reason for the !new_val expression++ xmax = -10000;+ xmin = 10000;+ ymax = -10000;+ ymin = 10000;++ double txmax = -10000;+ double txmin = 10000;+ double tymax = -10000;+ double tymin = 10000;++ bool new_val = false;+ + all_of_path ((cx, cy) => { + if (!new_val) {+ txmax = cx;+ txmin = cx;+ tymax = cy;+ tymin = cy;+ new_val = true;+ }+ + if (cx < txmin) {+ txmin = cx;+ }++ if (cx > txmax) {+ txmax = cx;+ }+ + if (cy < tymin) {+ tymin = cy;+ }++ if (cy > tymax) {+ tymax = cy;+ }++ return true;+ });+ + xmax = txmax;+ xmin = txmin;+ ymax = tymax;+ ymin = tymin;++ if (unlikely (!new_val)) {+ // only one point+ } else if (unlikely (!got_region_boundries ())) {+ warning (@"No new region boundries.\nPoints.length: $(points.length ())");+ print_boundries ();+ }+ }+ + /** Test if @param path is a valid outline for this object. */ + public bool test_is_outline (Path path) {+ assert (false);+ return this.test_is_outline_of_path (path) && path.test_is_outline_of_path (this);+ }+ + private bool test_is_outline_of_path (Path outline)+ requires (outline.points.length () >= 2 || points.length () >= 2)+ { + // rather slow use it for testing, only+ unowned List<EditPoint> i = outline.points.first ();+ unowned List<EditPoint> prev = i.last ();++ double tolerance = 1;+ bool g = false;+ + EditPoint ep = new EditPoint (0, 0);+ double min = double.MAX;+ + while (true) {+ min = 10000;+ + all_of (prev.data, i.data, (cx, cy) => {+ get_closest_point_on_path (ep, cx, cy);++ double n = pow (ep.x - cx, 2) + pow (cy - ep.y, 2);+ + if (n < min) min = n;+ + if (n < tolerance) {+ g = true;+ return false;+ }++ return true;+ });+ + if (!g) {+ critical (@"this path does not seem to be the outline. (min $min)");+ }+ + g = false;+ + if (i == i.last ()) {+ break;+ }+ + i = i.next;+ }+ + return true;+ }++ /** Convert quadratic bezier points to cubic representation of the glyph+ * suitable for ttf-export.+ */ + public Path get_quadratic_points () {+ EditPoint middle = new EditPoint ();+ unowned List<EditPoint> e;+ EditPointHandle eh;+ + quadratic_path = copy ();+ quadratic_path.close ();+ + if (quadratic_path.points.length () < 2) {+ return quadratic_path;+ }++ // split all curves in as many regions as we need+ split_cubic_in_parts (quadratic_path); ++ // estimate quatdratic form+ middle.prev = quadratic_path.points.last ();+ middle.next = quadratic_path.points.first ();++ convert_to_quadratic (quadratic_path, middle);+ + e = (!) quadratic_path.points.first ();+ while (e != quadratic_path.points.last ().prev) {+ middle.prev = e;+ middle.next = e.next;+ + convert_to_quadratic (quadratic_path, middle); + + e = (!) e.next;+ }+ + eh = ((!)quadratic_path.points.last ().prev).data.get_left_handle ();+ eh.set_point_type (PointType.NONE);+ eh.length = 0;++ eh = ((!)quadratic_path.points.last ().prev).data.get_right_handle ();+ eh.set_point_type (PointType.CURVE);+ eh.length *= 1.6;++ return quadratic_path;+ }++ void convert_to_quadratic (Path cubic_path, EditPoint middle) {+ double curve_x, curve_y; + EditPointHandle eh;+ + EditPoint start = middle.get_prev ().data;+ EditPoint stop = middle.get_next ().data;++ estimate_quadratic (start, stop, out curve_x, out curve_y);+ + eh = start.get_left_handle ();+ eh.set_point_type (PointType.NONE);+ eh.length = 0;+ + eh = start.get_right_handle ();+ eh.set_point_type (PointType.CURVE);+ eh.move_to_coordinate (curve_x, curve_y);+ }++ public void split_cubic_in_parts (Path cubic_path) {+ int j = 0;+ while (split_all_cubic_in_half (cubic_path)) {+ j++;+ + if (j > 5) {+ warning ("too many iterations in split path");+ break;+ }+ } + }++ public bool split_all_cubic_in_half (Path cubic_path) {+ EditPoint middle = new EditPoint ();+ unowned List<EditPoint> e;+ bool need_split = false;+ uint len = cubic_path.points.length ();+ + if (len < 2) {+ return false;+ }++ middle.prev = cubic_path.points.last ();+ middle.next = cubic_path.points.first ();+ + if (split_cubic_in_half (cubic_path, middle)) {+ need_split = true;+ }+ + e = (!) cubic_path.points.first ();+ for (uint i = 0; i < len - 1; i++) {+ middle.prev = e;+ middle.next = e.next;+ + middle.get_prev ().data.recalculate_linear_handles ();+ middle.get_next ().data.recalculate_linear_handles ();+ + if (split_cubic_in_half (cubic_path, middle)) {+ e = (!) e.next.next;+ need_split = true;+ } else {+ e = (!) e.next;+ }+ }+ + return need_split; + }++ public void estimate_quadratic (EditPoint start, EditPoint stop, out double curve_x, out double curve_y) {+ curve_x = -0.25 * start.x + 0.75 * stop.get_left_handle ().x () + 0.75 * start.get_right_handle ().x () - 0.25 * stop.x;+ curve_y = -0.25 * start.y + 0.75 * stop.get_left_handle ().y () + 0.75 * start.get_right_handle ().y () - 0.25 * stop.y; + }++ bool split_cubic_in_half (Path cubic_path, EditPoint middle) {+ double curve_x, curve_y, cx, cy, qx, qy, nx, ny, distance; + + EditPoint start = middle.get_prev ().data;+ EditPoint stop = middle.get_next ().data;+ + estimate_quadratic (start, stop, out curve_x, out curve_y);+ + qx = quadratic_bezier_path (0.75, start.x, curve_x, stop.x);+ qy = quadratic_bezier_path (0.75, start.y, curve_y, stop.y);++ cx = bezier_path (0.75, start.x, start.get_right_handle ().x (), stop.get_left_handle ().x (), stop.x);+ cy = bezier_path (0.75, start.y, start.get_right_handle ().y (), stop.get_left_handle ().y (), stop.y);++ distance = Math.sqrt (Math.pow (cx - qx, 2) + Math.pow (cy - qy, 2));++ nx = bezier_path (0.5, start.x, start.get_right_handle ().x (), stop.get_left_handle ().x (), stop.x);+ ny = bezier_path (0.5, start.y, start.get_right_handle ().y (), stop.get_left_handle ().y (), stop.y);++ if (Math.fabs (distance) > 0.01) {+ EditPoint new_edit_point = new EditPoint (nx, ny, PointType.CURVE);++ new_edit_point.next = middle.get_next ();+ new_edit_point.prev = middle.get_prev ();+ cubic_path.insert_new_point_on_path (new_edit_point);+ cubic_path.create_list ();++ return true; + }+ + return false;+ }++ double distance_to_path (EditPoint start, EditPoint stop, double x, double y) {+ double min = double.MAX;+ + all_of (start, stop, (cx, cy, t) => {+ double n = pow (x - cx, 2) + pow (y - cy, 2);+ + if (n < min) {+ min = n;+ }+ + return true;+ });+ + return min;+ }++ public void insert_new_point_on_path (EditPoint? epp) {+ EditPoint start, stop;+ double x0, x1, y0, y1;+ double px, py;+ + double position, t, d, min;+ double steps = 500;+ + EditPoint ep;++ if (epp == null) {+ return;+ }++ ep = (!) epp;++ start = ep.get_prev ().data;+ stop = ep.get_next ().data;++ add_point_after (ep, ep.get_prev ());++ min = double.MAX;++ position = 0.5;++ for (int i = 0; i < steps; i++) {+ t = i / steps;+ + px = bezier_path (t, start.x, start.get_right_handle ().x (), stop.get_left_handle ().x (), stop.x);+ py = bezier_path (t, start.y, start.get_right_handle ().y (), stop.get_left_handle ().y (), stop.y);+ + d = Math.sqrt (Math.pow (ep.x - px, 2) + Math.pow (ep.y - py, 2));+ + if (d < min) {+ min = d;+ position = t;+ }+ }+ + bezier_vector (position, start.x, start.get_right_handle ().x (), stop.get_left_handle ().x (), stop.x, out x0, out x1);+ bezier_vector (position, start.y, start.get_right_handle ().y (), stop.get_left_handle ().y (), stop.y, out y0, out y1);++ ep.get_left_handle ().set_point_type (PointType.CURVE);+ ep.get_left_handle ().move_to_coordinate (x0, y0);+ ep.get_left_handle ().parent = ep;+ + ep.get_right_handle ().set_point_type (PointType.CURVE);+ ep.get_right_handle ().move_to_coordinate (x1, y1);+ ep.get_right_handle ().parent = ep;++ stop.get_left_handle ().length *= 1 - position;+ start.get_right_handle ().length *= position;+ }+ + /** Get a point on the this path closest to x and y coordinates. */+ public void get_closest_point_on_path (EditPoint edit_point, double x, double y) {+ return_if_fail (points.length () != 0);+ + double min = double.MAX;+ double n = 0;+ bool g = false;+ + double ox = 0;+ double oy = 0;+ + double handle_x0, handle_x1;+ double handle_y0, handle_y1;+ + unowned List<EditPoint> i = points.first ();+ unowned List<EditPoint> prev = i.next;++ bool done = false;+ bool exit = false;+ + unowned List<EditPoint>? previous_point = null;+ unowned List<EditPoint>? next_point = null;++ EditPoint previous;+ EditPoint next;+ double step = 0;++ if (points.length () == 1) {+ edit_point.x = i.data.x;+ edit_point.y = i.data.y;+ + edit_point.prev = i;+ edit_point.next = i;+ + exit = true;+ + return;+ }+ + if (points.length () != 1) {+ edit_point.x = i.data.x;+ edit_point.y = i.data.y;+ }+ + while (!exit) {+ + if (i == points.last ()) {+ done = true;+ }+ + if (!done) {+ i = i.next;+ prev = i.prev;+ } else if (done && !is_open ()) {+ i = points.first ();+ prev = points.last ();+ exit = true;+ } else {+ break;+ }+ + all_of (prev.data, i.data, (cx, cy, t) => {+ n = pow (x - cx, 2) + pow (y - cy, 2);+ + if (n < min) {+ min = n;+ + ox = cx;+ oy = cy;+ + previous_point = prev;+ next_point = i;+ + step = t;+ + g = true;+ }+ + return true;+ });+ }++ if (previous_point == null) {+ warning (@"previous_point == null, points.length (): $(points.length ())");+ return;+ }+ + if (next_point == null) {+ warning ("next_point != null");+ return;+ }++ previous = ((!) previous_point).data;+ next = ((!) next_point).data;++ bezier_vector (step, previous.x, previous.get_right_handle ().x (), next.get_left_handle ().x (), next.x, out handle_x0, out handle_x1);+ bezier_vector (step, previous.y, previous.get_right_handle ().y (), next.get_left_handle ().y (), next.y, out handle_y0, out handle_y1);++ // position + edit_point.set_position (ox, oy);+ edit_point.prev = previous_point;+ edit_point.next = next_point;++ // curve (angle)+ edit_point.get_right_handle ().move_to_coordinate (handle_x0, handle_y0);+ edit_point.get_left_handle ().move_to_coordinate (handle_x1, handle_y1);+ // FIXA: maybe just edit_point.set_tie_handle (true);++ if (unlikely (!g)) {+ warning (@"Error: Got no coordinates for point on path. Num points $(points.length ())\n");+ }+ }++ public static void all_of (EditPoint start, EditPoint stop, RasterIterator iter, int steps = -1) {+ if (steps == -1) {+ steps = (int) (10 * get_length_from (start, stop));+ }+ + all_of_curve (start.x, start.y, start.get_right_handle ().x (), start.get_right_handle ().y (), stop.get_left_handle ().x (), stop.get_left_handle ().y (), stop.x, stop.y, iter, steps);+ }++ private static void all_of_curve (double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3, RasterIterator iter, double steps = 400) {+ double px = x1;+ double py = y1;+ + double t;+ + for (int i = 0; i < steps; i++) {+ t = i / steps;+ + px = bezier_path (t, x0, x1, x2, x3);+ py = bezier_path (t, y0, y1, y2, y3);+ + if (!iter (px, py, t)) {+ return;+ }+ + } + }+ + void point_on_bezier_path (double t, EditPoint start, EditPoint stop, out double x, out double y) { + double x0 = start.x;+ double y0 = start.y; + double x1 = start.get_right_handle ().x ();+ double y1 = start.get_right_handle ().y ();+ double x2 = stop.get_left_handle ().x ();+ double y2 = stop.get_left_handle ().y (); + double x3 = stop.x;+ double y3 = stop.y;++ x = bezier_path (t, x0, x1, x2, x3);+ y = bezier_path (t, y0, y1, y2, y3);+ }++ private void all_of_path (RasterIterator iter) {+ unowned List<EditPoint> i, next;+ + if (points.length () < 2) {+ return;+ }++ i = points.first ();+ next = i.next;++ while (i != points.last ()) {+ all_of (i.data, next.data, iter);+ i = i.next;+ next = i.next;+ }+ + if (!is_open ()) {+ all_of (points.last ().data, points.first ().data, iter);+ }+ }++ public static double bezier_path (double step, double p0, double p1, double p2, double p3) {+ double q0, q1, q2;+ double r0, r1;++ q0 = step * (p1 - p0) + p0;+ q1 = step * (p2 - p1) + p1;+ q2 = step * (p3 - p2) + p2;++ r0 = step * (q1 - q0) + q0;+ r1 = step * (q2 - q1) + q1;++ return step * (r1 - r0) + r0;+ }++ public static void bezier_vector (double step, double p0, double p1, double p2, double p3, out double a0, out double a1) {+ double q0, q1, q2;++ q0 = step * (p1 - p0) + p0;+ q1 = step * (p2 - p1) + p1;+ q2 = step * (p3 - p2) + p2;++ a0 = step * (q1 - q0) + q0;+ a1 = step * (q2 - q1) + q1;+ }++ public static double quadratic_bezier_path (double step, double p0, double p1, double p2) {+ double q0 = step * (p1 - p0) + p0;+ double q1 = step * (p2 - p1) + p1;+ + return step * (q1 - q0) + q0;+ }+ + public void plot (Context cr, Allocation allocation, double view_zoom) {+ double px = 0, py = 0;+ double xc = allocation.width / 2.0;+ double yc = allocation.height / 2.0;++ cr.save ();+ + all_of_path ((x, y) => {+ //cr.set_source_rgba (0.3, 0.3, 0.3, 1);+ cr.move_to (px + xc, -py + yc);+ cr.line_to (x + xc, -y + yc);+ + px = x;+ py = y;+ + return true;+ });++ cr.stroke ();+ cr.restore ();+ }+ + public void print_boundries () {+ stderr.printf (@"xmax $xmax \n");+ stderr.printf (@"xmin $xmin \n");+ stderr.printf (@"ymax $ymax \n");+ stderr.printf (@"ymin $ymin \n"); + }+ + public bool got_region_boundries () {+ return !(xmax == -10000 || xmin == 10000 || ymax == -10000 || ymin == 10000);+ }+ + public void create_list () {+ unowned List<EditPoint> ep = points.first ();+ unowned List<EditPoint> prev = ep;+ + if (points.length () == 0) {+ return;+ }+ + if (points.length () == 1) {+ ep.data.next = null;+ ep.data.prev = null;+ return;+ }+ + ep.data.next = ep.next;+ ep.data.prev = points.last ();+ prev = ep;+ + assert (ep.data.next != null);+ assert (ep.data.prev != null);+ + while (ep != ep.last ()) {+ ep.data.next = ep.next;+ ep.data.prev = prev;+ prev = ep;+ + assert (ep.data.next != null);+ assert (ep.data.prev != null);+ + ep = ep.next;+ }+ + if (is_open ()) {+ ep.data.next = points.first ();+ } else {+ ep.data.next = points.first ();+ assert (ep.data.next != null);+ }+ + ep.data.prev = prev;+ assert (ep.data.prev != null);+ }++ public void delete_edit_point (EditPoint ep) + requires (points.length () > 0)+ {+ unowned List<EditPoint>? pl = null;+ EditPoint p;+ + if (ep.prev != null) {+ set_new_start (ep);+ }+ + p = points.first ().data;+ + for (uint i = 0; i < points.length (); i++) {+ p = points.nth (i).data;+ + if (p == ep) {+ p.prev = null;+ p.next = null;+ + pl = points.nth (i);+ break;+ }+ }++ if (pl != null) {+ points.delete_link ((!) pl);+ reopen ();+ MainWindow.get_current_glyph ().add_active_path (this);+ create_list ();+ }+ }+ + public void set_new_start (EditPoint ep) {+ List<EditPoint> list = new List<EditPoint> ();+ uint len = points.length ();+ unowned List<EditPoint> iter = points.first ();+ unowned List<EditPoint>? ni = null;+ bool found = false;+ + foreach (EditPoint it in points) {+ if (it == ep) {+ found = true;+ break;+ }+ + iter = iter.next;+ ni = (!) iter;+ }+ + if (unlikely (!found)) {+ warning ("Could not find edit point.");+ }+ + if (ni == null) {+ return; + }+ + iter = (!) ni;+ + for (uint i = 0; i < len; i++) {+ list.append (iter.data);+ + if (iter == iter.last ())+ iter = iter.first ();+ else+ iter = iter.next;+ + }+ + while (points.length () > 0) {+ points.remove_link (points.first ());+ }+ + foreach (EditPoint p in list) {+ points.append (p);+ }+ + }+ + public static PathList merge (Path p0, Path p1) {+ bool done;+ PathList path_list;+ + done = try_merge (p0.copy(), p1.copy(), out path_list);+ + if (!done) {+ warning ("failed to merge paths");+ }+ + return path_list;+ }+ + private static bool try_merge (Path p0, Path p1, out PathList path_list) {+ EditPoint e;+ IntersectionList il;+ int i;+ Path np;+ + unowned List<Path> pi;+ + il = IntersectionList.create_intersection_list (p0, p1);+ + path_list = new PathList ();+ + if (p0 == p1) {+ return false;+ }+ + // add editpoints on intersections + p0.update_region_boundries ();+ p1.update_region_boundries ();+ foreach (Intersection inter in il.points) {+ e = new EditPoint ();+ p0.get_closest_point_on_path (e, inter.x, inter.y);+ inter.editpoint_a = e;+ + e = inter.editpoint_a;+ if (!p0.has_edit_point (e)) {+ p0.insert_new_point_on_path (e);+ }++ e = new EditPoint ();+ p1.get_closest_point_on_path (e, inter.x, inter.y);+ inter.editpoint_b = e;+ + inter.editpoint_b.x = inter.editpoint_a.x;+ inter.editpoint_b.y = inter.editpoint_a.y;+ + e = inter.editpoint_b;+ if (!p1.has_edit_point (e)) {+ p1.insert_new_point_on_path (e);+ }+ }+ + if (il.points.length () < 2) {+ return false;+ }+ + //path_list.paths.append (p0);+ //path_list.paths.append (p1);+ //return false;+ + // get all parts+ foreach (Intersection inter in il.points) {+ p0.set_new_start (inter.editpoint_a);+ get_merge_part (il, p0, p1, out np);+ path_list.paths.append (np);+ }+ + foreach (Path pp in path_list.paths) {+ pp.update_region_boundries ();+ }++ // remove duplicate paths+ for (i = 0; i < path_list.paths.length (); i++) {+ pi = path_list.paths.nth (i);++ if (is_duplicated (path_list, pi.data)) {+ path_list.paths.remove_link (pi);+ --i; + }+ }+ + // remove paths contained in other paths+ for (i = 0; i < path_list.paths.length (); i++) {+ pi = path_list.paths.nth (i);+ + if (pi.data.is_clockwise () && is_clasped (path_list, pi.data)) {+ path_list.paths.remove_link (pi);+ i--;+ }+ }+ + return true;+ }+ + private static bool is_duplicated (PathList pl, Path p) {+ bool duplicate = false;+ foreach (Path pd in pl.paths) {+ if (pd == p) {+ continue;+ }+ + if (is_duplicated_path (p, pd)) {+ duplicate = true;+ }+ }+ + return duplicate;+ }+ + private static bool is_duplicated_path (Path p0, Path p1) {+ bool eq;+ + assert (p1 != p0); + + foreach (EditPoint ep in p0.points) {+ eq = false;+ + foreach (EditPoint e in p1.points) {+ eq = (Math.fabs (ep.x - e.x) < 0.04 && Math.fabs (ep.y - e.y) < 0.04);+ + if (eq) {+ break;+ }+ }+ + if (eq) {+ continue;+ } else {+ return false;+ }+ }+ + return true;+ }+ + private static bool is_clasped (PathList pl, Path p) {+ foreach (Path o in pl.paths) {+ if (o == p) {+ continue;+ }+ + if (is_clasped_path (o, p)) {+ return true;+ }+ }+ + return false;+ }++ private static bool is_clasped_path (Path outside, Path inside) {+ bool i = true;+ foreach (EditPoint e in inside.points) {+ if (!outside.is_over_coordinate_var (e.x, e.y, 0.5)) { // high tolerance since point may be off curve in both paths+ i = false;+ break;+ }+ }+ return i;+ }+ + private static bool get_merge_part (IntersectionList il, Path p0, Path p1, out Path new_path) {+ EditPoint ex;+ EditPoint ix;+ EditPoint en;+ uint offset_i = 0;+ uint offset_j;+ uint len_i;+ int i, j;+ uint len_j;+ Path np = new Path ();+ Intersection s = new Intersection (0, 0, 1);+ + ex = p0.points.last ().data;+ ix = p0.points.last ().data;+ len_i = p0.points.length ();+ + for (i = 0; i < p0.points.length (); i++) {+ ex = p0.points.nth ((i + offset_i) % len_i).data;++ if (ex == p0.points.first ().data && i != 0) { + s = (!) il.get_intersection (ex); + break;+ }++ // add new point for path a+ if (np.has_edit_point (ex)) {+ // SPLIT+ warning ("Merged path need split");+ np.close ();+ new_path = np;+ + return false;+ } else {+ en = ex.copy ();+ np.add_point (en);+ en.recalculate_linear_handles ();+ }+ + // swap paths+ if (il.has_edit_point (ex)) {+ s = (!) il.get_intersection (ex);+ + en.type = PointType.CURVE;+ en.right_handle.type = PointType.CURVE;+ en.right_handle.angle = s.editpoint_b.right_handle.angle;+ en.right_handle.length = s.editpoint_b.right_handle.length;+ + // read until we find ex+ for (j = 0; j < p1.points.length (); j++) {+ ix = p1.points.nth (j).data;+ + if (ix == s.editpoint_b) {+ break;+ }+ }+ + offset_j = j + 1;+ len_j = p1.points.length ();+ for (j = 0; j < p1.points.length (); j++) {+ + ix = p1.points.nth ((j + offset_j) % len_j).data;+ + // add+ if (np.has_edit_point (ix)) {+ // SPLIT+ warning ("Merged path need split");+ np.close ();+ new_path = np;+ + break;+ } else {+ en = ix.copy ();+ np.add_point (en);+ ix.recalculate_linear_handles ();+ }+ + if (il.has_edit_point (ix)) {+ s = (!) il.get_intersection (ix);+ break;+ }+ }++ en.type = PointType.CURVE;+ en.right_handle.type = PointType.CURVE;+ en.right_handle.angle = s.editpoint_a.right_handle.angle;+ en.right_handle.length = s.editpoint_a.right_handle.length;+ + if (j == p0.points.length ()) {+ np.close ();+ new_path = np;+ return true;+ }++ // skip to next intersection+ int k;+ for (k = 0; k < p0.points.length (); k++) {+ ix = p0.points.nth (k).data; ++ if (ix == s.editpoint_a) {+ break;+ }+ }+ + if (k == p0.points.length ()) {+ new_path = np;+ return true;+ }+ + offset_i = 0;+ i = k;+ }+ }++ new_path = np;+ + return true; + }++}++public class PathList : GLib.Object {+ public List<Path> paths = new List<Path> ();+ + public PathList () {+ }+ + public void clear () {+ while (paths.length () > 0) {+ paths.remove_link (paths.first ());+ }+ }+}++}new file mode 100644@@ -0,0 +1,1001 @@+/*+ 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/>.+*/++namespace Supplement {++class PenTool : Tool {++ private static const double CONTACT_SURFACE = 50;++ public static bool move_selected = false;+ public static bool begin_new_point_on_path = false;+ public static bool move_point_on_path = false;++ public static bool edit_active_corner = false;+ + public static List<EditPoint> selected_points = new List<EditPoint> (); ++ public static EditPointHandle active_handle = new EditPointHandle.empty ();+ public static EditPointHandle selected_handle = new EditPointHandle.empty ();+ + public static EditPoint? active_edit_point = new EditPoint ();++ /** Move handle vertical or horizontal. */+ public static bool tie_x_or_y_coordinates = false;++ private static bool move_selected_handle = false;++ private static double last_point_x = 0;+ private static double last_point_y = 0;++ public static double precision = 1;+ + public PenTool (string name) {+ base (name, _("Right click to add new points, left click to move points") + " " + _("and double click to add new point on path."), ',', CTRL);+ + select_action.connect ((self) => {+ });++ deselect_action.connect ((self) => {+ Glyph glyph = MainWindow.get_current_glyph ();+ + CutTool.force_direction ();+ glyph.close_path ();+ + move_point_on_path = false;+ begin_new_point_on_path = false;+ });+ + press_action.connect ((self, b, x, y) => {+ last_point_x = x;+ last_point_y = y;++ press (b, x, y, false);+ });+ + double_click_action.connect ((self, b, x, y) => {+ last_point_x = x;+ last_point_y = y;++ press (b, x, y, true);+ });++ release_action.connect ((self, b, ix, iy) => {+ double x = ix;+ double y = iy;+ Glyph g = MainWindow.get_current_glyph ();+ EditPoint selected_corner;+ + if (move_selected && GridTool.is_visible () && selected_points.length () > 0) {+ selected_corner = selected_points.last ().data;+ GridTool.tie (ref x, ref y);+ g.move_selected_edit_point (selected_corner, x, y);+ }++ move_selected = false;+ move_selected_handle = false;+ edit_active_corner = false;+ + active_handle = new EditPointHandle.empty ();+ + move (x, y);+ });++ move_action.connect ((self, x, y) => {+ move (x, y);+ });+ + key_press_action.connect ((self, keyval) => {+ Glyph g = MainWindow.get_current_glyph ();+ + if (keyval == Key.DEL) {+ foreach (EditPoint p in selected_points) {+ g.delete_edit_point (p);+ }+ + g.update_view ();+ }+ });+ + key_release_action.connect ((self, keyval) => {+ });+ }+ + public double get_precision () {+ return precision;+ }+ + public void set_precision (double p) {+ precision = p;+ MainWindow.get_toolbox ().precision.set_value_round (p, false, false);+ }+ + public void move (double x, double y) {+ Glyph glyph = MainWindow.get_current_glyph ();+ + control_point_event (x, y);+ curve_active_corner_event (x, y);+ set_default_handle_positions ();++ // show new point on path+ if (glyph.new_point_on_path != null) {+ move_current_point_on_path (x, y);+ }+ + // move curve handles+ if (move_selected_handle) {+ if (GridTool.is_visible ()) {+ GridTool.tie (ref x, ref y);+ selected_handle.set_point_type (PointType.CURVE);+ selected_handle.move_to (x, y);+ } else {+ selected_handle.set_point_type (PointType.CURVE);+ selected_handle.move_delta ((x - last_point_x) * precision, (y - last_point_y) * precision);+ }+ + selected_handle.parent.recalculate_linear_handles ();+ + // Fixa: redraw line only+ glyph.redraw_area (0, 0, glyph.allocation.width, glyph.allocation.height);+ + last_point_x = x;+ last_point_y = y;+ + return;+ }+ + // move edit point+ if (move_selected) {+ foreach (EditPoint p in selected_points) {+ glyph.move_selected_edit_point_delta (p, (x - last_point_x) * precision, (y - last_point_y) * precision);+ + if (tie_x_or_y_coordinates) {+ GridTool.tie_to_prev (p, x, y);+ }+ + p.recalculate_linear_handles ();+ }+ }+ + last_point_x = x;+ last_point_y = y;+ }+ + public void press (int button, int x, int y, bool double_click) {+ Glyph? g = MainWindow.get_current_glyph ();+ Glyph glyph = (!) g;+ + return_if_fail (g != null);++ if (button == 2) {+ if (glyph.is_open ()) {+ CutTool.force_direction ();+ glyph.close_path ();+ } else {+ glyph.open_path ();+ }+ + return;+ }++ if (double_click) {+ glyph.insert_new_point_on_path (x, y);+ return;+ }++ if (is_new_point_from_path_selected ()) {+ new_point_on_path_at (x, y);+ return; + }++ // add new point on path + if (is_new_point_from_path_selected ()) {+ move_selected = true;+ move_point_on_path = true;+ glyph.new_point_on_path = null;+ return;+ }+ + // add new point+ if (button == 3) {+ remove_all_selected_points ();+ new_point_action (x, y);+ glyph.store_undo_state ();+ return;+ }+ + control_point_event (x, y);+ curve_corner_event (x, y);+ + if (!move_selected_handle) {+ select_active_point (x, y);+ }++ glyph.store_undo_state ();+ }+ + public void select_active_point (double x, double y) {+ Glyph? g = MainWindow.get_current_glyph ();+ Glyph glyph = (!) g;+ + if (KeyBindings.modifier != SHIFT) {+ if (active_edit_point != null) {+ ((!)active_edit_point).set_active (false);+ }++ remove_all_selected_points ();+ }+ + move_selected = true;+ move_point_on_path = true;++ if (active_edit_point != null) {+ ((!)active_edit_point).set_selected (true);+ }++ if (!is_over_handle (x, y)) {+ edit_active_corner = true;+ set_default_handle_positions ();+ + if (active_edit_point != null) {+ add_selected_point ((!) active_edit_point);+ }+ }+ + // continue adding points from the selected one+ foreach (Path p in glyph.active_paths) {+ if (p.is_open () && p.points.length () > 0 && active_edit_point == p.points.first ().data) {+ p.reverse ();+ }+ }+ }+ + public void move_current_point_on_path (double x, double y) {+ Glyph g = MainWindow.get_current_glyph ();++ EditPoint e;+ + double rax, ray;+ double pax, pay;++ double distance, min;+ + return_if_fail (g.new_point_on_path != null);+ + if (GridTool.is_visible ()) {+ GridTool.tie (ref x, ref y);+ }++ min = double.MAX;++ foreach (Path p in g.path_list) {+ if (p.points.length () < 2) {+ continue;+ }+ + e = new EditPoint ();+ + pax = x * Glyph.ivz () + g.view_offset_x - Glyph.xc ();+ pay = y * Glyph.ivz () + g.view_offset_y - Glyph.yc ();++ pay *= -1;++ p.get_closest_point_on_path (e, pax, pay);+ + distance = Math.sqrt (Math.pow (Math.fabs (pax - e.x), 2) + Math.pow (Math.fabs (pay - e.y), 2));+ + if (distance < min) {+ min = distance;+ + g.new_point_on_path = e;+ + rax = (pax + g.view_offset_x - Glyph.xc ()) / Glyph.ivz ();+ ray = (pax + g.view_offset_x - Glyph.xc ()) / Glyph.ivz ();+ + g.redraw_area (rax - 5, ray - 5, 10, 10); + }+ }+ }++ public void set_new_point_on_path (Path ap, int x, int y) {+ Glyph g = MainWindow.get_current_glyph ();+ + return_if_fail (ap.is_editable ());+ + g.clear_active_paths ();+ g.add_active_path (ap);++ if (g.new_point_on_path == null) {+ g.new_point_on_path = new EditPoint (0, 0, PointType.FLOATING);+ }+ + move_current_point_on_path (x, y);+ }+ + public static void set_active_edit_point (EditPoint? e) {+ Glyph g = MainWindow.get_current_glyph ();+ foreach (var p in g.path_list) {+ foreach (var ep in p.points) {+ ep.set_active (false);+ }+ }+ + active_edit_point = e;+ + if (e != null) {+ ((!)e).set_active (true);+ }++ g.redraw_area (0, 0, g.allocation.width, g.allocation.height);+ }++ EditPoint? get_closest_point (double ex, double ey, out Path? path) {+ double x = Glyph.path_coordinate_x (ex);+ double y = Glyph.path_coordinate_y (ey);+ double d = double.MAX;+ double nd;+ EditPoint? ep = null;+ Glyph g = MainWindow.get_current_glyph ();+ + path = null;+ + foreach (Path current_path in g.path_list) {+ foreach (EditPoint e in current_path.points) {+ nd = e.get_distance (x, y);+ + if (nd < d) {+ d = nd;+ ep = e;+ path = current_path;+ }+ } + }+ + return ep;+ }++ public double get_distance_to_closest_edit_point (double event_x, double event_y) {+ Path? p;+ EditPoint e;+ EditPoint? ep = get_closest_point (event_x, event_y, out p);++ double x = Glyph.path_coordinate_x (event_x);+ double y = Glyph.path_coordinate_y (event_y);+ + if (ep == null) {+ return double.MAX;+ }+ + e = (!) ep;+ + return e.get_distance (x, y);+ }++ public void control_point_event (double event_x, double event_y) {+ Path? p;+ EditPoint? ep = get_closest_point (event_x, event_y, out p);+ Glyph g = MainWindow.get_current_glyph ();+ double x = Glyph.path_coordinate_x (event_x);+ double y = Glyph.path_coordinate_y (event_y);+ double distance;+ EditPoint e;+ + set_active_edit_point (null);+ + if (ep == null) {+ return; + }+ + e = (!) ep;+ distance = e.get_distance (x, y) * g.view_zoom;++ if (distance < CONTACT_SURFACE) {+ set_active_edit_point (ep);+ g.add_active_path (p);+ }+ }++ public static bool insert_new_point_on_path_selected () {+ Tool t = MainWindow.get_toolbox ().get_tool ("insert_point_on_path");+ return t.is_selected ();+ }++ public static bool is_new_point_from_path_selected () {+ if (!Supplement.experimental) {+ return false;+ }+ + Tool t = MainWindow.get_toolbox ().get_tool ("new_point_on_path");+ return t.is_selected ();+ }++ public static bool is_erase_selected () {+ Tool t = MainWindow.get_toolbox ().get_tool ("erase_tool");+ return t.is_selected ();+ }+ + public void new_point_action (int x, int y) {+ Glyph glyph;+ EditPoint new_point;+ glyph = MainWindow.get_current_glyph ();+ glyph.open_path ();+ + new_point = glyph.add_new_edit_point (x, y);+ new_point.set_selected (true);+ + if (KeyBindings.modifier != SHIFT) {+ remove_all_selected_points ();+ }+ + add_selected_point (new_point);+ + move_selected = true;+ }++ void set_linear_handles_for_last_point () {+ Glyph glyph;+ glyph = MainWindow.get_current_glyph ();+ return_if_fail (glyph.active_paths.length () > 0);+ return_if_fail (glyph.active_paths.last ().data.points.length () > 0);+ glyph.active_paths.last ().data.points.last ().data.right_handle.parent.recalculate_linear_handles ();+ }++ public static void set_default_handle_positions () {+ Glyph g = MainWindow.get_current_glyph ();+ foreach (var p in g.path_list) {+ if (p.is_editable ()) {+ p.create_list ();+ set_default_handle_positions_on_path (p);+ }+ }+ }++ static void set_default_handle_positions_on_path (Path path) {+ foreach (EditPoint e in path.points) {+ e.recalculate_linear_handles ();+ }+ }++ bool new_point_on_path_at (int x, int y) {+ EditPoint ep;+ Glyph glyph = MainWindow.get_current_glyph ();+ + if (is_new_point_from_path_selected ()) {+ + if (glyph.new_point_on_path != null) {+ ep = (!)glyph.new_point_on_path; + + begin_new_point_on_path = false;+ + glyph.add_new_edit_point (glyph.reverse_path_coordinate_x (ep.x), glyph.reverse_path_coordinate_x (ep.y));+ glyph.new_point_on_path = null;+ + MainWindow.get_toolbox ().select_tool_by_name ("new_point");++ return false;+ }+ + start_from_new_point_on_path (x, y);+ begin_new_point_on_path = true;+ + return false;+ } else {+ begin_new_point_on_path = false;+ }+ + return true;+ }++ void start_from_new_point_on_path (double x, double y) { + Glyph g = MainWindow.get_current_glyph ();+ Path? p = null;+ Path pn;+ + p = g.get_closeset_path (x, y);++ pn = (!)p;++ if (pn.points.length () >= 2) {+ pn.set_editable (true);+ set_new_point_on_path (pn, 0, 0);+ g.open_path ();+ }+ + begin_new_point_on_path = false;+ }+ + public void begin_from_new_point_on_path () {+ begin_new_point_on_path = true;+ }+ + private bool is_over_handle (double event_x, double event_y) {+ double x = Glyph.path_coordinate_x (event_x);+ double y = Glyph.path_coordinate_y (event_y);+ Glyph g = MainWindow.get_current_glyph (); + double d_point = g.view_zoom * get_distance_to_closest_edit_point (event_x, event_y);+ + double dl, dr;+ + foreach (EditPoint selected_corner in selected_points) {+ dl = g.view_zoom * selected_corner.get_left_handle ().get_point ().get_distance (x, y);+ dr = g.view_zoom * selected_corner.get_right_handle ().get_point ().get_distance (x, y);+ + if (dl < CONTACT_SURFACE && dl < d_point) {+ return true;+ }++ if (dr < CONTACT_SURFACE && dr < d_point) {+ return true;+ }+ }+ + return false;+ }++ EditPointHandle get_closest_handle (double event_x, double event_y) {+ EditPointHandle left, right;+ double x = Glyph.path_coordinate_x (event_x);+ double y = Glyph.path_coordinate_y (event_y); + EditPointHandle eh = new EditPointHandle.empty();++ double d = double.MAX;+ double dn;+ + foreach (EditPoint selected_corner in selected_points) {+ left = selected_corner.get_left_handle ();+ right = selected_corner.get_right_handle ();++ dn = left.get_point ().get_distance (x, y);+ + if (dn < d) {+ eh = left;+ d = dn;+ }++ dn = right.get_point ().get_distance (x, y);+ + if (dn < d) {+ eh = right;+ d = dn;+ }+ }+ + return eh;+ }++ private void curve_active_corner_event (double event_x, double event_y) {+ EditPointHandle eh;+ + active_handle.active = false;+ + if (!is_over_handle (event_x, event_y)) {+ return;+ } + + eh = get_closest_handle (event_x, event_y);+ eh.active = true;+ active_handle = eh;+ }++ private void curve_corner_event (double event_x, double event_y) {+ MainWindow.get_current_glyph ().open_path ();++ if (!is_over_handle (event_x, event_y)) {+ return;+ }++ move_selected_handle = true;+ selected_handle = get_closest_handle (event_x, event_y);+ }++ void add_selected_point (EditPoint p) {+ foreach (EditPoint ep in selected_points) {+ if (p == ep) {+ return;+ }+ }+ + selected_points.append (p);+ }+ + void remove_all_selected_points () {+ foreach (EditPoint e in selected_points) {+ e.set_active (false);+ e.set_selected (false);+ }+ + while (selected_points.length () > 0) {+ selected_points.remove_link (selected_points.first ());+ }+ }+ + /** Draw a test glyph. */+ public override bool test () {+ test_select_action ();+ + test_open_next_glyph ();+ + // paint+ test_click_action (1, 30, 30); + test_click_action (1, 60, 30);+ test_click_action (1, 60, 60);+ test_click_action (1, 30, 60);+ + // close+ test_click_action (3, 0, 0);++ // reopen+ test_click_action (3, 35, 35);+ + // move around+ test_move_action (100, 200);+ test_move_action (20, 300);+ test_move_action (0, 0);+ + // add to path+ test_move_action (70, 50);+ + test_click_action (1, 70, 50);+ test_click_action (1, 70, 50);+ test_click_action (1, 70, 100);+ test_click_action (1, 50, 100); + test_click_action (1, 50, 50);+ + // close+ test_click_action (3, 0, 0);+ + // merge it+ + + this.yield ();+ + return true;+ }++ public void test_active_edit_point () {+ Glyph g;+ EditPoint epa, epb;+ + // paint+ test_select_action ();+ test_open_next_glyph ();++ g = MainWindow.get_current_glyph ();++ test_click_action (1, 130, 130); // open path+ test_click_action (1, 130, 130); // add point+ epa = g.get_last_edit_point ();+ + test_click_action (1, 160, 130);+ test_click_action (1, 160, 160);+ epb = g.get_last_edit_point ();+ + test_click_action (1, 130, 160);+ + // validate active point+ test_move_action (130, 130);+ warn_if_fail (active_edit_point == epa);+ + test_move_action (161, 161); // close but not on+ warn_if_fail (active_edit_point == epb);+ + warn_if_fail (epa != epb);+ + // TODO: Test move handle here.+ }++ /** Test path coordinates and reverse path coordinates. */+ public void test_coordinates () {+ int x, y, xc, yc;+ double px, py, mx, my;+ string n;+ + Tool.test_open_next_glyph ();+ Glyph g = MainWindow.get_current_glyph ();+ + xc = (int) (g.allocation.width / 2.0);+ yc = (int) (g.allocation.height / 2.0);++ g.default_zoom ();+ + x = 10;+ y = 15;+ + px = g.path_coordinate_x (x);+ py = g.path_coordinate_y (y);++ mx = x * g.ivz () - g.xc () + g.view_offset_x;+ my = g.yc () - y * g.ivz () - g.view_offset_y;+ + if (mx != px || my != py) {+ warning (@"bad coordinate $mx != $px || $my != $py");+ }+ + test_reverse_coordinate (x, y, px, py, "ten fifteen");+ test_click_action (1, x, y);+ + // offset no zoom+ n = "Offset no zoom";+ g.reset_zoom ();+ + px = g.path_coordinate_x (x);+ py = g.path_coordinate_y (y);+ + test_reverse_coordinate (x, y, px, py, n);+ test_click_action (1, x, y);+ + // close path+ test_click_action (3, x, y);+ }+ + private void test_coordinate (double x, double y, double px, double py, string n) {+ if (!(px - 0.5 <= x <= px + 0.5|| py - 0.5 <= y <= py + 0.5)) {+ warning (@"Expecting (x == px || y == py) got ($x == $px || $y == $py) in \"$n\"\n");+ }+ }+ + private void test_reverse_coordinate (int x, int y, double px, double py, string n) {+ Glyph g = MainWindow.get_current_glyph ();+ if (x != g.reverse_path_coordinate_x (px) || g.reverse_path_coordinate_y (py) != y) {+ warning (@"Reverse coordinates does not match current point for test case \"$n\".\n $x != $(g.reverse_path_coordinate_x (px)) || $(g.reverse_path_coordinate_y (py)) != $y (x != g.reverse_path_coordinate_x (px) || g.reverse_path_coordinate_y (py) != y)");+ }+ }++ private void test_last_is_counter_clockwise (string name) {+ bool d = ((!)MainWindow.get_current_glyph ().get_last_path ()).is_clockwise ();+ + if (d) {+ critical (@"\nPath $name is clockwise, in test_last_is_counter_clockwise");+ } + }+ + private void test_last_is_clockwise (string name) {+ bool d = ((!)MainWindow.get_current_glyph ().get_last_path ()).is_clockwise ();+ + if (!d) {+ critical (@"\nPath $name is counter clockwise, in test_last_is_clockwise");+ }++ }+ + private bool test_reverse_last (string name) + requires (MainWindow.get_current_glyph ().get_last_path () != null)+ {+ Glyph g = MainWindow.get_current_glyph ();+ Path p = (!) g.get_last_path ();+ bool direction = p.is_clockwise ();++ p.reverse ();+ + if (direction == p.is_clockwise ()) {+ critical (@"Direction did not change after reverseing path \"$name\"\n");+ stderr.printf (@"Path length: $(p.points.length ()) \n");+ return false;+ }++ this.yield ();+ return true;+ }+ + + class Point {+ + public int x;+ public int y;+ + public Point (int x, int y) {+ this.x = x;+ this.y = y;+ }+ }+ + private Point p (int x, int y) {+ return new Point (x, y);+ }+ + private void test_triangle (Point a, Point b, Point c, string name = "") {+ 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 ();+ + pen_tool.test_click_action (1, 0, 0); // open path+ + pen_tool.test_click_action (1, a.x, a.y);+ pen_tool.test_click_action (1, b.x, b.y);+ pen_tool.test_click_action (1, c.x, c.y);+ pen_tool.test_click_action (3, 0, 0);+ + test_reverse_last (@"Triangle reverse \"$name\" ($(a.x), $(a.y)), ($(b.x), $(b.y)), ($(c.x), $(c.y)) failed.");+ + this.yield ();+ }+ + private void test_various_triangles () {+ test_triangle (p (287, 261), p (155, 81), p (200, 104), "First");+ test_triangle (p (65, 100), p (168, 100), p (196, 177), "Second");+ test_triangle (p (132, 68), p (195, 283), p (195, 222), "Third");+ test_triangle (p (144, 267), p (147, 27), p (296, 267), "Fourth");+ }+ + public bool test_reverse_path () {+ // 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+ int x_offset = 10;+ int y_offset = 10;+ + test_open_next_glyph ();+ test_various_triangles ();+ + test_open_next_glyph ();+ // draw clockwise and check direction ++ y_offset += 160;+ pen_tool.test_click_action (1, 10 + x_offset, 20 + y_offset);+ pen_tool.test_click_action (1, 17 + x_offset, 17 + y_offset);+ pen_tool.test_click_action (1, 20 + x_offset, 0 + y_offset);+ pen_tool.test_click_action (3, 0, 0);+ test_last_is_clockwise ("Clockwise triangle 1.2");++ // draw paths clockwise / counter clockwise and reverse them+ + pen_tool.test_click_action (1, 115, 137);+ pen_tool.test_click_action (1, 89, 74);+ pen_tool.test_click_action (1, 188, 232);+ pen_tool.test_click_action (3, 0, 0);+ test_reverse_last ("Triangle 0");++ // draw incomplete paths+ y_offset += 20;+ pen_tool.test_click_action (1, 10 + x_offset, 20 + y_offset);+ pen_tool.test_click_action (3, 0, 0);+ test_reverse_last ("Point");++ y_offset += 20;+ pen_tool.test_click_action (1, 10 + x_offset, 20 + y_offset);+ pen_tool.test_click_action (1, 10 + x_offset, 20 + y_offset);+ pen_tool.test_click_action (3, 0, 0);+ test_reverse_last ("Double point");++ y_offset += 20;+ pen_tool.test_click_action (1, 10 + x_offset, 30 + y_offset);+ pen_tool.test_click_action (1, 10 + x_offset, 20 + y_offset);+ pen_tool.test_click_action (3, 0, 0);+ test_reverse_last ("Vertical line");++ y_offset += 20;+ pen_tool.test_click_action (1, 30 + x_offset, 20 + y_offset);+ pen_tool.test_click_action (1, 10 + x_offset, 20 + y_offset);+ pen_tool.test_click_action (3, 0, 0);+ test_reverse_last ("Horisontal line");++ // triangle 1+ y_offset += 20;+ pen_tool.test_click_action (1, 10 + x_offset, -10 + y_offset);+ pen_tool.test_click_action (1, 20 + x_offset, 20 + y_offset);+ pen_tool.test_click_action (1, 30 + x_offset, 0 + y_offset);+ pen_tool.test_click_action (3, 0, 0);+ test_reverse_last ("Triangle reverse 1");++ // box+ y_offset += 20;+ pen_tool.test_click_action (1, 100 + x_offset, 150 + y_offset);+ pen_tool.test_click_action (1, 150 + x_offset, 150 + y_offset);+ pen_tool.test_click_action (1, 150 + x_offset, 100 + y_offset);+ pen_tool.test_click_action (1, 100 + x_offset, 100 + y_offset); + pen_tool.test_click_action (3, 0, 0); // close+ test_reverse_last ("Box 1");+ + return true;+ }+ + private Tool select_pen () {+ Tool pen_tool = MainWindow.get_toolbox ().get_tool ("pen_tool");+ pen_tool.test_select_action ();+ return pen_tool;+ }+ + public bool test_delete_points () {+ PenTool pen;+ Tool delete_tool = MainWindow.get_toolbox ().get_tool ("erase_tool");+ + test_open_next_glyph ();+ + pen = (PenTool) select_pen ();+ pen.test_click_action (1, 0, 0); // open path+ + // draw a line with ten points+ for (int i = 1; i <= 10; i++) {+ pen.test_click_action (1, 20*i, 20);+ } + + // TODO: it would be nice to test if points were created here+ + // delete points+ delete_tool.test_select_action ();+ for (int i = 1; i <= 10; i++) {+ pen.move (20*i, 20);+ pen.test_click_action (1, 20*i, 20);+ }+ + return true;+ }+ + public bool test_reverse_random_triangles () {+ Tool pen;+ + int ax, bx, cx;+ int ay, by, cy;++ bool r = true;++ test_open_next_glyph ();+ pen = select_pen ();++ for (int i = 0; i < 30; i++) {+ this.yield ();+ + ax = Random.int_range (0, 300);+ bx = Random.int_range (0, 300);+ cx = Random.int_range (0, 300);++ ay = Random.int_range (0, 300);+ by = Random.int_range (0, 300);+ cy = Random.int_range (0, 300);++ pen.test_click_action (1, ax, ay);+ pen.test_click_action (1, bx, by);+ pen.test_click_action (1, cx, cy);+ pen.test_click_action (3, 0, 0);+ + r = test_reverse_last (@"Random triangle № $(i + 1) ($ax, $ay), ($bx, $by), ($cx, $cy)");+ if (!r) {+ test_open_next_glyph ();+ pen = select_pen ();++ pen.test_click_action (1, ax, ay);+ pen.test_click_action (1, bx, by);+ pen.test_click_action (1, cx, cy);+ + return false;+ }+ + test_open_next_glyph ();+ }+ + if (r) test_open_next_glyph ();+ + return true;+ }+}++}new file mode 100644@@ -0,0 +1,175 @@+/*+ 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/>.+*/+namespace Supplement {++public class Preferences {+ + static HashTable<string, string> data;++ public Preferences () {+ data = new HashTable<string, string> (str_hash, str_equal);+ }++ public static void set_last_file (string fn) {+ set ("last_file", fn);+ }++ public static string @get (string k) {+ string? s;+ + lock (data) {+ s = data.lookup (k);+ }+ + return (s != null) ? (!) s : "";+ }++ public static void @set (string k, string v) {+ lock (data) {+ data.replace (k, v);+ save (); // Fixa: save in separate io thread instead.+ }+ }++ public static string[] get_recent_files () {+ string recent = get ("recent_files");+ string[] files = recent.split ("\t");+ + for (uint i = 0; i < files.length; i++) {+ files[i] = files[i].replace ("\\t", "\t");+ }+ + return files;+ }++ public static void add_recent_files (string file) {+ string escaped_string = file.replace ("\t", "\\t");+ StringBuilder recent = new StringBuilder ();++ foreach (string f in get_recent_files ()) {+ if (f != file) {+ recent.append (f.replace ("\t", "\\t"));+ recent.append ("\t");+ }+ }++ recent.append (escaped_string);++ set ("recent_files", @"$(recent.str)");+ }++ public static int get_window_width() {+ string wp = get ("window_width");+ int w = int.parse (wp);+ return (w == 0) ? 860 : w;+ }++ public static int get_window_height() {+ int h = int.parse (get ("window_height"));+ return (h == 0) ? 500 : h;+ }+ + public static void load () {+ File app_dir = Supplement.get_settings_directory ();+ File settings = app_dir.get_child ("settings");++ data = new HashTable<string, string> (str_hash, str_equal);++ if (!settings.query_exists ()) {+ stderr.printf ("No settings file.\n");+ return;+ }++ FileStream? settings_file = FileStream.open ((!) settings.get_path (), "r");+ + if (settings_file == null) {+ stderr.printf ("Failed to load settings from file %s.\n", (!) settings.get_path ());+ return;+ }+ + return_if_fail (settings_file != null);+ + unowned FileStream b = (!) settings_file;+ + string? l;+ l = b.read_line ();+ while ((l = b.read_line ())!= null) {+ string line;+ + line = (!) l;+ + if (line.get_char (0) == '#') {+ continue;+ }+ + int i = 0;+ int s = 0;+ + i = line.index_of_char(' ', s);+ string key = line.substring (s, i - s);++ s = i + 1;+ i = line.index_of_char('"', s);+ s = i + 1;+ i = line.index_of_char('"', s);+ string val = line.substring (s, i - s);+ + // (key, val, description);+ + data.insert (key, val);+ }+ }+ + public static void save () {+ try {+ File app_dir = Supplement.get_settings_directory ();+ File settings = app_dir.get_child ("settings");++ return_if_fail (app_dir.query_exists ());+ + if (settings.query_exists ()) {+ settings.delete ();+ }++ DataOutputStream os = new DataOutputStream(settings.create(FileCreateFlags.REPLACE_DESTINATION));+ uint8[] d;+ long written = 0;+ + StringBuilder sb = new StringBuilder ();+ + sb.append ("# Supplement settings\n");+ sb.append ("# Version: 1.0\n");+ + foreach (var k in data.get_keys ()) {+ sb.append (k);+ sb.append (" \"");+ sb.append (data.lookup (k));+ sb.append ("\"\n");+ }+ + d = sb.str.data;+ + while (written < d.length) { + written += os.write (d[written:d.length]);+ }+ } catch (Error e) {+ stderr.printf ("Can not save key settings. (%s)", e.message); + } + }+}++}new file mode 100644@@ -0,0 +1,55 @@+/*+ 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 WebKit;++namespace Supplement {++public class Preview : FontDisplay { + + public Preview () {+ }+ + public override string get_name () {+ return "Preview";+ }+ + public override bool is_html_canvas () {+ return true;+ }++ public override void selected_canvas () {+ }++ public File get_html_file () {+ Font font = Supplement.get_current_font ();+ string path = @"$(font.get_name ()).html";+ File dir = font.get_folder ();+ File file = dir.get_child (path);+ + if (!file.query_exists ()) {+ ExportTool.generate_html_document ((!)file.get_path (), font); + }+ + return file; + }++ public override string get_uri () {+ return path_to_uri ((!) get_html_file ().get_path ());+ }+}+}new file mode 100644@@ -0,0 +1,249 @@+/*+ 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);+ }+ +}++}new file mode 100644@@ -0,0 +1,98 @@+/*+ 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 {++public class SaveDialog : FontDisplay {++ public signal void finished ();++ public SaveDialog () {+ add_html_callback ("save_dialog", (val) => {+ if (val == "save") {+ if (MenuTab.save ()) {+ finished ();+ }+ }+ + if (val == "save_as") {+ if (MenuTab.save_as ()) {+ finished ();+ }+ }+ + if (val == "discard") {+ // discard it+ finished ();+ }+ + if (val == "cancel") {+ MainWindow.get_tab_bar ().close_display (this);+ } + + });+ }++ public override string get_name () {+ return "Save?";+ }++ public override bool is_html_canvas () {+ return true;+ }++ public override string get_html () {+ Font f = Supplement.get_current_font ();+ string fn = f.get_file_name ();+ + if (fn == "") {+ fn = f.get_name () + ".ffi";+ }+ + return """+<html>+<head>+ <script type="text/javascript" src="supplement.js"></script>+ <style type="text/css">@import url("style.css");</style>+</head>+<body>+ + <div style="width:300px; margin: 50px auto 0 auto;">+ <div class="heading"><h2>""" + _("Save?") + """</h2></div>+ + <p>""" + fn + """</p>+ + <form>+ <input class="button" type="button" value=""" + "\"" + _("Save") + "\"" + """ onclick="call ('save_dialog:save');"/>+ <input class="button" type="button" value=""" + "\"" + _("Save as") + "\"" + """ onclick="call ('save_dialog:save_as');"/>+ <input class="button" type="button" value=""" + "\"" + _("Discard") + "\"" + """ onclick="call ('save_dialog:discard');"/>+ <input class="button" type="button" value=""" + "\"" + _("Cancel") + "\"" + """ onclick="call ('save_dialog:cancel');"/>+ <br />+ </form>+ </div>+</body>+</html>""";++ // FIXME:+ // tell xgettext to extract this:+ _("Save?");+ }+}+ +}new file mode 100644@@ -0,0 +1,141 @@+/*+ 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 Scrollbar : GLib.Object {+ Allocation alloc;++ double pos = 0;+ double height = 0.1;+ + double y = 0;+ double h = 0;+ + double y_begin = 0;+ double y_last = 0;+ bool move = false;+ bool visible = true;++ public signal void signal_scroll (double delta, double delta_last, double absolute);++ public Scrollbar () {+ }+ + public void set_handle_size (double s) {+ height = (s < 0.01) ? 0.01 : s;+ + if (height > 1) {+ height = 1;+ }+ + h = height * alloc.height;+ }++ public void set_handle_position (double p) {+ pos = (p > 0) ? p : 0;+ y = pos * alloc.height;+ }+ + public void button_press (uint button, double x, double y) {+ move = true;+ y_begin = y;+ y_last = y;+ }+ + public void button_release (int button, double ex, double ey) {+ move = false;+ }+ + public void motion_notify (double ex, double ey) {+ double d = 0;+ double dl = 0;+ double m;+ + h = height * alloc.height;+ m = alloc.height;+ + if (move && 0 < ey < m) {+ d = ey - y_begin;+ dl = ey - y_last;+ y += dl;+ y_last = ey;+ }++ if (y < 0) {+ y = 0;+ }++ if (y > alloc.height - h) {+ y = alloc.height - h;+ d = 0;+ dl = 0;+ }+ + if (move) {+ pos = y / alloc.height;+ }+ + if (move) {+ signal_scroll (d / (m - h), dl / (m -h), y / (m - h));+ pos = y / m;+ }++ }+ + public void draw (Context cr, Allocation alloc) {+ double ax, ay, aw, ah;++ this.alloc = alloc;++ ax = alloc.width - 10;+ ay = 0;+ aw = alloc.width;+ ah = alloc.height;+ + if (!visible) {+ cr.save ();+ cr.rectangle (ax, ay, aw, ah);+ cr.set_line_width (0);+ cr.set_source_rgba (194/255.0, 194/255.0, 194/255.0, 1);+ cr.fill_preserve ();+ cr.stroke ();+ cr.restore ();+ return;+ }+ + cr.save ();+ cr.rectangle (ax, ay, aw, ah);+ cr.set_line_width (0);+ cr.set_source_rgba (183/255.0, 200/255.0, 223/255.0, 1);+ cr.fill_preserve ();+ cr.stroke ();+ cr.restore (); + + cr.save ();+ cr.rectangle (ax, pos * alloc.height, alloc.width, ah * height);+ cr.set_line_width (0);+ cr.set_source_rgba (133/255.0, 143/255.0, 174/255.0, 1);+ cr.fill_preserve ();+ cr.stroke ();+ cr.restore (); + }+}++}new file mode 100644@@ -0,0 +1,113 @@+/*+ 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/>.+*/++namespace Supplement {++// TODO contrast++class ShrinkTool : Tool {++ public ShrinkTool (string n) {+ base (n, "shrink path");++ select_action.connect((self) => {+ Glyph g = MainWindow.get_current_glyph ();+ + foreach (Path p in g.path_list) {+ shrink_copy (p, 0.25);+ }+ + MainWindow.get_glyph_canvas ().redraw ();+ });++ select_action.connect((self) => {+ });+ + press_action.connect((self, b, x, y) => {+ });++ move_action.connect((self, x, y) => {+ });+ }+ + public static int path_index (Path p) {+ Glyph g = MainWindow.get_current_glyph ();+ int i = 0;+ + foreach (Path pt in g.path_list) {+ if (p == pt) {+ return i;+ }+ i++;+ }+ + return -1;+ }+ + public static void shrink_copy (Path px, double step) { + Path p = px.copy ();+ shrink (px, step);+ p.set_editable (true);+ } + + public static void shrink (Path p, double step) { + if (p.points.length () < 3) return;++ p.create_list ();+ + EditPoint ep;++ p.create_list ();++ foreach (EditPoint pep in p.points) {+ unowned List<EditPoint>? lep = pep.get_next ();+ + if (unlikely (lep == null)) {+ warning ("Bad list.");+ stderr.printf (@"No links in path $(path_index (p)). Length: $(p.points.length ())\n");+ p.print_boundries ();+ continue;+ }+ + ep = ((!)lep).data;+ move_point (p, pep, ep, step);+ }+ + move_point (p, p.points.last ().data, p.points.first ().data, step);+ + p.set_editable (true);+ }+ + static void move_point (Path p, EditPoint pep, EditPoint ep, double step) { + double istep = 1 - step;+ + double mx = ep.get_right_handle ().x () / 2 + ep.get_left_handle ().x () / 2;+ double my = ep.get_right_handle ().y () / 2 + ep.get_left_handle ().y () / 2;+ + ep.x = mx * step + ep.x * istep;+ ep.y = my * step + ep.y * istep;++ }+ + public override bool test () {+ test_select_action ();+ return true;+ }++}+ +}new file mode 100644@@ -0,0 +1,228 @@+/*+ 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 {++public class SpinButton : Tool {++ public signal void new_value_action (SpinButton selected);+ + public int8 deka = 2;+ public int8 deci = 0;+ public int8 centi = 0;+ public int8 milli = 0;+ + bool value_from_motion = false;+ double begin_x = 0;+ double begin_y = 0;+ double begin_value = 0;+ + double max = 9.999;+ double min = 0;+ + public SpinButton (string? name = null, string tip = "", unichar key = '\0', uint modifier_flag = 0) {+ base (null , tip, key, modifier_flag);+ + if (name != null) {+ base.name = (!) name;+ }+ + set_icon ("spin_button");+ + panel_press_action.connect ((selected, button, tx, ty) => {+ double py = Math.fabs (y - ty);+ int n;+ + if (is_selected ()) {+ n = (button == 3) ? 10 : 1;+ + for (int i = 0; i < n; i++) {+ if (py < 28) increase ();+ if (py > 35) decrease ();+ }+ }+ + value_from_motion = true;+ begin_x = tx;+ begin_y = ty;+ + begin_value = double.parse (get_display_value ());+ });++ panel_move_action.connect ((selected, button, tx, ty) => {+ double d;+ if (value_from_motion) {+ d = (begin_y - ty) / 200;+ d = (d < 0) ? -Math.pow (d, 2) : Math.pow (d, 2);+ set_value_round (begin_value + d);+ }+ });++ panel_release_action.connect ((selected, button, tx, ty) => {+ value_from_motion = false;+ });+ }+ + public void set_max (double max) {+ this.max = max;+ }++ public void set_min (double min) {+ this.min = min;+ }+ + public void increase () { + if (deka == 9 && deci == 9 && centi == 9 && milli == 9) {+ return;+ }+ + milli++;++ if (milli == 10) {+ centi++;+ milli = 0;+ }+ + if (centi == 10) {+ deci++;+ centi = 0;+ }++ if (deci == 10) {+ deka++;+ deci = 0;+ }+ + if (get_value () > max) {+ set_value_round (max, false);+ }++ new_value_action (this);+ }++ public void decrease () {+ if (deka == 0 && deci == 0 && centi == 0) {+ return;+ }+ + milli--;+ + if (milli == -1) {+ centi--;+ milli = 9;+ }+ + if (centi == -1) {+ deci--;+ centi = 9;+ }++ if (deci == -1) {+ deka--;+ deci = 9;+ }+ + if (get_value () < 0.05) { // lower limit+ set_value_round (0.05);+ }++ if (get_value () < min) {+ set_value_round (min, false);+ }+ + new_value_action (this);+ }++ public void set_value (string new_value, bool check_boundries = true, bool emit_signal = true) {+ string v = new_value;+ + while (!(v.char_count () >= 5)) {+ if (v.index_of (".") == -1) {+ v += ".";+ } else {+ v += "0";+ }+ + return;+ }+ + deka = (int8) int.parse (v.substring (0, 1));+ deci = (int8) int.parse (v.substring (2, 1));+ centi = (int8) int.parse (v.substring (3, 1));+ milli = (int8) int.parse (v.substring (4, 1));+ + if (emit_signal) {+ new_value_action (this);+ }+ + if (check_boundries && get_value () > max) {+ set_value_round (max, false);+ }++ if (check_boundries && get_value () < min) {+ set_value_round (min, false);+ }+ }++ public void set_value_round (double v, bool check_boundries = true, bool emit_signal = true) {+ int8 m;+ + v += 0.005;+ + if (v > 10) v = 9.999;+ if (v < 0.001) v = 0.001;+ + m = milli; // ignore milli value+ set_value (@"$v", check_boundries, emit_signal);+ milli = m;+ }+ + public double get_value () {+ return deka + (deci / 10.0) + (centi / 100.0) + (milli / 1000.0);+ }++ public string get_display_value () {+ return @"$deka.$deci$centi$milli";+ }++ public override void draw (Context cr) {+ double xt = x + 3 + w;+ double yt = y + h + 17;++ double text_x = -4.5;+ double text_y = 11;++ base.draw (cr);+ + cr.save ();+ + cr.set_source_rgba (99/255.0, 99/255.0, 99/255.0, 1);+ + cr.set_font_size (10);+ cr.select_font_face ("Cantarell", FontSlant.NORMAL, FontWeight.NORMAL);++ cr.move_to (xt + text_x, yt + text_y);+ + cr.show_text (get_display_value ());+ + cr.restore ();+ }+}++}new file mode 100644@@ -0,0 +1,223 @@+/* + 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 Supplement; +using Birdfont; + +const string GETTEXT_PACKAGE = "birdfont"; + +namespace Supplement { + +public class Supplement { + public static Argument args; + internal static bool experimental = false; + internal static bool show_coordinates = false; + internal static bool fatal_wanings = false; + public static bool win32 = false; + internal static string exec_path = ""; + + internal static Font current_font; + public static Glyph current_glyph; + + public void init (string[] arg) { + int err_arg; + int i; + File font_file; + IdleSource idle; + string exec_path; + + stdout.printf ("birdfont version %s\n", VERSION); + stdout.printf ("built on %s\n", BUILD_TIMESTAMP); + + init_gettext (); + + args = new Argument.command_line (arg); + + if (args.has_argument ("--help")) { + args.print_help (); + Process.exit (0); + } + + err_arg = args.validate (); + if (err_arg != 0) { + stdout.printf (@"Unknown parameter $(arg [err_arg])\n\n"); + args.print_help (); + Process.exit (0); + } + + current_font = new Font (); + current_glyph = new Glyph (""); + + experimental = args.has_argument ("--test"); + show_coordinates = args.has_argument ("--show-coordinates"); + fatal_wanings = args.has_argument ("--fatal-warning"); + win32 = (arg[0].index_of (".exe") > -1) || arg[0] == "wine"; + exec_path = ""; + + if (win32) { + // wine hack to get "." folder in win32 environment + i = arg[0].last_index_of ("\\"); + + if (i != -1) { + exec_path = arg[0]; + exec_path = exec_path.substring (0, i); + exec_path = wine_to_unix_path (exec_path); + } + } else { + exec_path = "./"; + } + + Preferences preferences = new Preferences (); + preferences.load (); + + if (args.get_file () != "") { + font_file = File.new_for_path (args.get_file ()); + + if (!font_file.query_exists ()) { + stderr.printf (@"File $(args.get_file ()) not found."); + Process.exit (-1); + } + } + + if (fatal_wanings) { + LogLevelFlags levels = LogLevelFlags.LEVEL_ERROR | LogLevelFlags.LEVEL_CRITICAL | LogLevelFlags.LEVEL_WARNING; + Log.set_handler (null, levels, fatal_warning); + } + + preferences.set_last_file (get_current_font ().get_path ()); + } + + static void init_gettext () { + File f; + + Intl.setlocale (LocaleCategory.MESSAGES, ""); + Intl.bind_textdomain_codeset (GETTEXT_PACKAGE, "utf-8"); + + f = File.new_for_path ("./build/locale/sv/LC_MESSAGES/birdfont.mo"); + if (f.query_exists ()) { + Intl.bindtextdomain (GETTEXT_PACKAGE, "./build/locale"); + return; + } + + f = File.new_for_path ("./locale/sv/LC_MESSAGES/birdfont.mo"); + if (f.query_exists ()) { + Intl.bindtextdomain (GETTEXT_PACKAGE, "./locale"); + return; + } + + f = File.new_for_path ("../locale/sv/LC_MESSAGES/birdfont.mo"); + if (f.query_exists ()) { + Intl.bindtextdomain (GETTEXT_PACKAGE, "../locale"); + return; + } + + f = File.new_for_path ("/usr/local/share/locale/sv/LC_MESSAGES/birdfont.mo"); + if (f.query_exists ()) { + Intl.bindtextdomain (GETTEXT_PACKAGE, "/usr/local/share/locale"); + return; + } + + f = File.new_for_path ("/usr/share/locale/sv/LC_MESSAGES/birdfont.mo"); + if (f.query_exists ()) { + Intl.bindtextdomain (GETTEXT_PACKAGE, "/usr/share/locale"); + return; + } + + warning ("translations not found"); + } + + public static Font get_current_font () { + return current_font; + } + + internal static void fatal_warning (string? log_domain, LogLevelFlags log_levels, string message) { + bool fatal = true; + + if (log_domain != null) { + stderr.printf ("%s: \n", (!) log_domain); + } + + stderr.printf ("\n%s\n\n", message); + assert (!fatal); + } + + internal static void new_font () { + current_font = new Font (); + } + + public static File get_preview_directory () { + File settings = get_settings_directory (); + File backup = settings.get_child ("preview"); + + if (!backup.query_exists ()) { + DirUtils.create ((!) backup.get_path (), 0xFFFFFF); + } + + return backup; + } + + internal static File get_thumbnail_directory () { + File thumbnails = get_settings_directory ().get_child ("thumbnails"); + + if (!thumbnails.query_exists ()) { + DirUtils.create ((!) thumbnails.get_path (), 0xFFFFFF); + } + + return thumbnails; + } + + internal static File get_settings_directory () { + File home = File.new_for_path (Environment.get_home_dir ()); + File settings = home.get_child (".birdfont"); + + if (!settings.query_exists ()) { + DirUtils.create ((!) settings.get_path (), 0xFFFFFF); + } + + return settings; + } + + internal static File get_backup_directory () { + File settings = get_settings_directory (); + File backup = settings.get_child ("backup"); + + if (!backup.query_exists ()) { + DirUtils.create ((!) backup.get_path (), 0xFFFFFF); + } + + return backup; + } + + internal static bool has_argument (string param) { + if (is_null (args)) { + warning ("args is null"); + return false; + } + + return args.has_argument (param); + } + + internal static string? get_argument (string param) { + return args.get_argument (param); + } +} + +internal bool is_null (void* n) { + return n == null; +} + +} new file mode 100644@@ -0,0 +1,345 @@+/*+ 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 Svg {++ /** Export to svg glyph data. */+ public static string to_svg_glyph (Glyph g, double scale = 1) { + StringBuilder svg = new StringBuilder ();++ foreach (Path pl in g.path_list) {+ write_path_as_glyph (pl, svg, g, scale);+ }+ + return svg.str;+ }++ /** Export to regular svg path data. */+ public static string to_svg_path (Path pl, Glyph g, double scale = 1) { + StringBuilder svg = new StringBuilder ();+ pl.create_list ();+ write_path (pl, svg, g, false, scale);+ return svg.str;+ }++ private static void write_path_as_glyph (Path pl, StringBuilder svg, Glyph g, double scale = 1) {+ write_path (pl, svg, g, true, scale);+ }++ private static void write_path (Path pl, StringBuilder svg, Glyph g, bool do_glyph, double scale = 1) {+ int i = 0;+ EditPoint? n = null;+ EditPoint m;+ + if (pl.points.length () <= 2) {+ return;+ }+ + pl.create_list ();+ + foreach (var e in pl.points) {+ if (i == 0) {+ add_abs_start (e, svg, g, do_glyph, scale);+ i++;+ n = e;+ continue;+ }+ + m = (!) n;++ add_abs_next (m, e, svg, g, do_glyph, scale);+ + n = e;+ i++;+ }++ m = pl.points.first ().data;+ + add_abs_next ((!) n, m, svg, g, do_glyph, scale);++ close_path (svg);+ }++ private static void add_abs_next (EditPoint start, EditPoint end, StringBuilder svg, Glyph g, bool do_glyph, double scale = 1) {+ if (start.right_handle.type == PointType.LINE && end.left_handle.type == PointType.LINE) {+ add_abs_line_to (end, start, svg, g, do_glyph, scale);+ } else if (end.get_left_handle ().type == PointType.NONE) {+ add_quadratic_abs_path (end, start, svg, g, do_glyph, scale);+ } else {+ add_cubic_abs_path (end, start, svg, g, do_glyph, scale);+ }+ }++ private static void add_abs_start (EditPoint ep, StringBuilder svg, Glyph g, bool do_glyph, double scale = 1) { + double left = g.left_limit;+ double baseline = Supplement.get_current_font ().base_line;+ + svg.append_printf ("M");++ svg.append_printf ("%s ", round ((ep.x - left) * scale));+ svg.append_printf ("%s ", round ((ep.y + baseline) * scale));+ }+ + private static void close_path (StringBuilder svg) {+ svg.append ("z");+ } + + private static void add_abs_line_to (EditPoint start, EditPoint stop, StringBuilder svg, Glyph g, bool to_glyph, double scale = 1) {+ double baseline = Supplement.get_current_font ().base_line;+ double left = g.left_limit;+ + double xa, ya, xb, yb;+ + Path.get_line_points (start, stop, out xa, out ya, out xb, out yb);++ double height = Supplement.get_current_font ().get_height (); // no probably not++ double center_x = Glyph.xc ();+ double center_y = Glyph.yc ();+ + if (!to_glyph) {+ svg.append ("L");+ + svg.append_printf ("%s ", round ((xa - center_x - left) * scale));+ svg.append_printf ("%s ", round ((ya - center_y - baseline + height) * scale)); ++ svg.append ("L");+ + svg.append_printf ("%s ", round ((xb - center_x - left) * scale));+ svg.append_printf ("%s ", round ((yb - center_y - baseline + height) * scale)); ++ } else {+ svg.append ("L");+ + svg.append_printf ("%s ", round ((xa - center_x - left) * scale));+ svg.append_printf ("%s ", round ((-ya + center_y + baseline) * scale));++ svg.append ("L");+ + svg.append_printf ("%s ", round ((xb - center_x - left) * scale));+ svg.append_printf ("%s ", round ((-yb + center_y + baseline) * scale)); + }+ }+ + private static void add_quadratic_abs_path (EditPoint start, EditPoint end, StringBuilder svg, Glyph g, bool to_glyph, double scale = 1) {+ double left = g.left_limit;+ double baseline = Supplement.get_current_font ().base_line;+ double height = Supplement.get_current_font ().get_height (); // no probably not+ + double xa, ya, xb, yb, xc, yc, xd, yd;+ + Path.get_bezier_points (start, end, out xa, out ya, out xb, out yb, out xc, out yc, out xd, out yd);++ double center_x = Glyph.xc ();+ double center_y = Glyph.yc ();+ + // cubic path+ if (!to_glyph) {+ svg.append_printf ("Q");++ svg.append_printf ("%s ", round ((xb - center_x - left) * scale));+ svg.append_printf ("%s ", round ((yb - center_y - baseline + height) * scale));+ + svg.append_printf ("%s ", round ((xc - center_x - left) * scale));+ svg.append_printf ("%s ", round ((yc - center_y - baseline + height) * scale));++ } else { + svg.append_printf ("Q");++ svg.append_printf ("%s ", round ((xb - center_x - left) * scale));+ svg.append_printf ("%s ", round ((-yb + center_y + baseline) * scale));+ + svg.append_printf ("%s ", round ((xc - center_x - left) * scale));+ svg.append_printf ("%s ", round ((-yc + center_y + baseline) * scale)); + }+ }+ + private static void add_cubic_abs_path (EditPoint start, EditPoint end, StringBuilder svg, Glyph g, bool to_glyph, double scale = 1) {+ double left = g.left_limit;+ double baseline = Supplement.get_current_font ().base_line;+ double height = Supplement.get_current_font ().get_height (); // no probably not+ + double xa, ya, xb, yb, xc, yc, xd, yd;+ + Path.get_bezier_points (start, end, out xa, out ya, out xb, out yb, out xc, out yc, out xd, out yd);++ double center_x = Glyph.xc ();+ double center_y = Glyph.yc ();+ + // cubic path+ if (!to_glyph) {+ svg.append_printf ("C");++ svg.append_printf ("%s ", round ((xb - center_x - left) * scale));+ svg.append_printf ("%s ", round ((yb - center_y - baseline + height) * scale));+ + svg.append_printf ("%s ", round ((xc - center_x - left) * scale));+ svg.append_printf ("%s ", round ((yc - center_y - baseline + height) * scale));+ + svg.append_printf ("%s ", round ((xd - center_x - left) * scale));+ svg.append_printf ("%s ", round ((yd - center_y - baseline + height) * scale)); ++ } else { + svg.append_printf ("C");++ svg.append_printf ("%s ", round ((xb - center_x - left) * scale));+ svg.append_printf ("%s ", round ((-yb + center_y + baseline) * scale));+ + svg.append_printf ("%s ", round ((xc - center_x - left) * scale));+ svg.append_printf ("%s ", round ((-yc + center_y + baseline) * scale)); + + svg.append_printf ("%s ", round ((xd - center_x - left) * scale));+ svg.append_printf ("%s ", round ((-yd + center_y + baseline) * scale)); + }+ }+ + /** Draw path from svg font data. */+ public static void draw_svg_path (Context cr, string svg, double x, double y, double scale = 1) {+ double x1, x2, x3;+ double y1, y2, y3;++ string[] d = svg.split (" ");+ + x /= scale;+ y /= scale;++ if (d.length == 0) {+ return;+ }+ + cr.save ();++ cr.set_line_width (1);+ cr.set_source_rgba (0, 0, 0, 1);+ + if (svg == "") {+ return;+ }+ + for (int i = 0; i < d.length; i++) {+ + // trim off leading white space + while (d[i].index_of (" ") == 0) { + d[i] = d[i].substring (1);+ }+ + if (d[i].index_of ("L") == 0) {+ x1 = double.parse (d[i].substring (1)) + x;+ y1 = -double.parse (d[i+1]) + y;+ + x1 *= scale;+ y1 *= scale;+ + cr.line_to (x1, y1);+ continue;+ }++ if (d[i].index_of ("Q") == 0) {+ x1 = double.parse (d[i].substring (1)) + x;+ y1 = -double.parse (d[i+1]) + y;++ x2 = double.parse (d[i+2]) + x;+ y2 = -double.parse (d[i+3]) + y;++ x1 *= scale;+ y1 *= scale;++ x2 *= scale;+ y2 *= scale;+ + cr.curve_to (x1, y1, x2, y2, x2, y2);+ continue;+ }+ + if (d[i].index_of ("C") == 0) {+ x1 = double.parse (d[i].substring (1)) + x;+ y1 = -double.parse (d[i+1]) + y;++ x2 = double.parse (d[i+2]) + x;+ y2 = -double.parse (d[i+3]) + y;++ x3 = double.parse (d[i+4]) + x;+ y3 = -double.parse (d[i+5]) + y;++ x1 *= scale;+ y1 *= scale;++ x2 *= scale;+ y2 *= scale;+ + x3 *= scale;+ y3 *= scale;+ + cr.curve_to (x1, y1, x2, y2, x3, y3);+ continue;+ }++ if (d[i].index_of ("M") == 0) {+ x1 = double.parse (d[i].substring (1)) + x;+ y1 = -double.parse (d[i+1]) + y;+ + x1 *= scale;+ y1 *= scale;+ + cr.move_to (x1, y1);+ continue;+ }+ + if (d[i].index_of ("zM") == 0) {+ cr.close_path ();+ + x1 = double.parse (d[i].substring (2)) + x;+ y1 = -double.parse (d[i+1]) + y;+ + x1 *= scale;+ y1 *= scale;+ + cr.move_to (x1, y1);+ continue;+ }++ if (d[i].index_of ("z") == 0) {+ cr.close_path ();+ continue;+ }+ + }+ + cr.fill ();+ cr.restore ();+ }++}++internal static string round (double p) {+ string v = p.to_string ();+ char[] c = new char [501];+ + v = p.format (c, "%3.15f");+ + if (v.index_of ("e") != -1) { + return "0.0";+ }+ + return v;+}+ +}new file mode 100644@@ -0,0 +1,132 @@+/*+ 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/>.+*/++namespace Supplement {++class SvgFontFormatWriter : Object {++ DataOutputStream os;++ public SvgFontFormatWriter () {+ }++ public void open (File file) throws Error {+ if (file.query_exists ()) {+ throw new FileError.EXIST ("SvgFontFormatWriter: file exists.");+ }+ + os = new DataOutputStream (file.create(FileCreateFlags.REPLACE_DESTINATION));+ }++ public void close () throws Error {+ os.close ();+ }+ + public void write_font_file (Font font) throws Error {+ string font_name = font.get_name ();+ + int units_per_em = 100;+ + int ascent = 80;+ int descent = -20;+ + StringBuilder b;+ + Glyph? g;+ Glyph glyph;+ unichar indice = 0;+ + string uni;+ + put ("""<?xml version="1.0" standalone="no"?>""");+ put ("""<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >""");+ put ("""<svg xmlns="http://www.w3.org/2000/svg">""");++ // (metadata goes here)++ put ("<defs>");++ put (@"<font id=\"$font_name\" horiz-adv-x=\"250\" >");+ put (@"<font-face units-per-em=\"$units_per_em\" ascent=\"$ascent\" descent=\"$descent\" />");++ // (missing-glyph goes here)++ while (true) {+ g = font.get_glyph_indice (indice++);+ + if (g == null) {+ break;+ }+ + glyph = (!) g;+ + b = new StringBuilder ();+ b.append_unichar (glyph.get_unichar ());++ if (glyph.get_unichar () >= ' ' && b.str.validate ()) {+ if (b.str == "\"" || b.str == "&" || b.str == "<") {+ uni = Font.to_hex_code (glyph.get_unichar ());+ put (@"<glyph unicode=\"&#x$(uni);\" horiz-adv-x=\"$(glyph.get_width ())\" d=\"$(glyph.get_svg_data ())\" />"); + } else {+ put (@"<glyph unicode=\"$(b.str)\" horiz-adv-x=\"$(glyph.get_width ())\" d=\"$(glyph.get_svg_data ())\" />");+ }+ }+ }++ while (true) {+ g = font.get_glyph_indice (indice++);+ + if (g == null) {+ break;+ }+ + glyph = (!) g;+ + foreach (Kerning k in glyph.kerning) {+ string l, r;+ Font f = Supplement.get_current_font ();+ Glyph? gr = f.get_glyph (k.glyph_right);+ Glyph glyph_right;+ + if (gr == null) {+ warning ("kerning glyph that does not exist.");+ }+ + glyph_right = (!) gr;+ + l = Font.to_hex_code (glyph.unichar_code);+ r = Font.to_hex_code (glyph_right.unichar_code);+ + os.put_string (@"<hkern u1=\"&#x$l;\" u2=\"&#x$r;\" k=\"$(k.val)\"/>\n");+ }+ } ++ put ("</font>");+ put ("</defs>");+ put ("</svg>");+ }++ /** Write a new line */+ private void put (string line) throws Error {+ os.put_string (line);+ os.put_string ("\n");+ }++}+++}new file mode 100644@@ -0,0 +1,49 @@+/*+ 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/>.+*/++namespace Supplement {++public class Tab : GLib.Object {++ bool always_open;+ double width; + FontDisplay display;++ public Tab (FontDisplay glyph, double tab_width, bool always_open) {+ width = tab_width;+ this.display = glyph;+ this.always_open = always_open;+ }++ public bool has_close_button () {+ return !always_open;+ }++ public FontDisplay get_display () {+ return display;+ }+ + public string get_label () {+ return display.get_name ();+ }+ + public double get_width () {+ return width;+ }+}++}new file mode 100644@@ -0,0 +1,583 @@+/*+ 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 {++public class TabBar : GLib.Object {+ + int width = 0;+ int height = 0;+ + public List<Tab> tabs = new List<Tab> ();++ static const int NO_TAB = -1;+ static const int NEXT_TAB = -2;+ static const int PREVIOUS_TAB = -3;++ int first_tab = 0;+ int selected = 0;+ int over = NO_TAB;+ int over_close = NO_TAB;+ + public signal void signal_tab_selected (Tab selected_tab);++ Tab? previous_tab = null;+ Tab? current_tab = null;++ ImageSurface? tab1_left = null;+ ImageSurface? tab1_right = null;++ ImageSurface? tab2_left = null;+ ImageSurface? tab2_right = null;++ ImageSurface? tab3_left = null;+ ImageSurface? tab3_right = null;+ + ImageSurface? bar_background = null;+ ImageSurface next_tab;+ ImageSurface to_previous_tab;+ + public TabBar () {+ tab1_left = Icons.get_icon ("tab1_left.png");+ tab1_right = Icons.get_icon ("tab1_right.png");++ tab2_left = Icons.get_icon ("tab2_left.png");+ tab2_right = Icons.get_icon ("tab2_right.png");++ tab3_left = Icons.get_icon ("tab3_left.png");+ tab3_right = Icons.get_icon ("tab3_right.png");+ + bar_background = Icons.get_icon ("tabbar_background.png");+ + next_tab = (!) Icons.get_icon ("next_tab.png");+ to_previous_tab = (!) Icons.get_icon ("previous_tab.png");+ + return_if_fail (!is_null (next_tab));+ }+ + public void motion (double x, double y) {+ TooltipArea status = MainWindow.get_tool_tip ();+ is_over_close (x, y, out over, out over_close);+ }+ + private bool is_over_next (double x) {+ return x > width - 19 && has_scroll ();+ }++ private bool is_over_previous (double x) {+ return x < 19 && has_scroll ();+ }++ private void is_over_close (double x, double y, out int over, out int over_close) {+ int i = 0;+ double offset = 19;+ + if (x < 19 && has_scroll ()) {+ over_close = NO_TAB;+ over = PREVIOUS_TAB;+ return;+ }+ + foreach (Tab t in tabs) {+ + if (i < first_tab) {+ i++;+ continue;+ }+ + if (offset + t.get_width () + 3 > width && has_scroll ()) {+ over_close = NO_TAB;+ over = NEXT_TAB;+ return;+ }+ + if (offset < x < offset + t.get_width ()) {+ over = i;+ + if (8 < y < 20 && x > offset + t.get_width () - 16) {+ over_close = i;+ } else {+ over_close = NO_TAB;+ }+ + return;+ }+ + offset += t.get_width () + 3;+ i++;+ }++ over_close = NO_TAB; + over = NO_TAB;+ } + + + /** Select tab for a glyph by charcode or name.+ * @return true if the tab was found+ */+ public bool select_char (string s) {+ int i = 0;+ foreach (Tab t in tabs) {+ if (t.get_label () == s) {+ select_tab (i);+ return true;+ }+ i++;+ }+ + return false;+ }++ public bool select_tab_name (string s) {+ return select_char (s);+ }++ public void select_overview () {+ select_tab_name ("Overview");+ }++ private void select_previous_tab () {+ Tab t;+ bool open;+ + if (previous_tab == null) {+ return;+ }+ + t = (!) previous_tab;+ open = selected_open_tab (t);+ + if (!open) {+ select_tab ((int) tabs.length () - 1);+ }+ }+ + public void close_display (FontDisplay f) {+ int i = -1;+ foreach (var t in tabs) {+ ++i;+ + if (t.get_display () == f) {+ close_tab (i) ;+ return;+ }+ }+ + return_if_fail (i != -1);+ } ++ public bool close_tab (int index, bool background_tab = false) { + unowned List<Tab?>? lt;+ Tab t;+ + if (!(0 <= index < tabs.length ())) {+ return false;+ }+ + lt = tabs.nth(index);+ + if (lt == null || ((!) lt).data == null) {+ return false;+ }++ if (first_tab > 0) {+ first_tab--;+ }++ t = (!) ((!) lt).data;++ if (t.has_close_button ()) {+ tabs.delete_link (tabs.nth(index));+ + if (!background_tab) {+ select_previous_tab ();+ }+ + return true;+ }+ + select_tab (index);+ return false;+ }+ + public bool close_by_name (string name, bool background_tab = false) {+ int i = 0;+ + foreach (var t in tabs) {+ if (t.get_label () == name) {+ return close_tab (i, background_tab);+ }+ + i++;+ }+ + return false;+ }+ + public void close_background_tab_by_name (string name) {+ close_by_name (name, true);+ }+ + /** Select a tab and return true if it is open. */+ public bool selected_open_tab (Tab t) {+ int i = 0;+ foreach (var n in tabs) {+ if (n == t) {+ select_tab (i);+ return true;+ }+ + i++;+ }+ + return false;+ }++ public Tab? get_tab (string name) {+ foreach (var n in tabs) {+ if (n.get_label () == name) {+ return n;+ }+ }+ + return null;+ }++ public bool selected_open_tab_by_name (string t) {+ int i = 0;+ foreach (var n in tabs) {+ if (n.get_label () == t) {+ select_tab (i);+ return true;+ }+ + i++;+ }+ + return false;+ }+ + public Tab get_selected_tab () {+ return tabs.nth (get_selected ()).data;+ }+ + public uint get_length () {+ return tabs.length ();+ }++ public int get_selected () {+ return selected;+ }+ + public void select_tab (int index) {+ Tab t;++ if (index == NEXT_TAB) {+ selected++;+ + if (selected >= tabs.length ()) {+ selected = (int) tabs.length () - 1;+ }+ + scroll_to_tab (selected);+ return;+ }+ + if (index == PREVIOUS_TAB) {++ if (selected > 0) {+ selected--;+ }+ + scroll_to_tab (selected);+ return;+ }+ + if (!(0 <= index < tabs.length ())) {+ return;+ }++ selected = index;+ + unowned List<Tab?>? lt = tabs.nth(index);+ + return_if_fail(lt != null);+ t = (!) ((!) lt).data;+ + previous_tab = current_tab;+ current_tab = t;++ scroll_to_tab (selected);+ }+ + private bool has_scroll () {+ int i = 0;+ double offset = 19;+ + if (first_tab > 0) {+ return true;+ }+ + foreach (Tab t in tabs) { + if (i < first_tab) {+ i++;+ continue;+ }+ + if (offset + t.get_width () + 3 > width - 19) {+ return true;+ }++ offset += t.get_width () + 3;+ i++;+ }+ + return false; + }+ + private void signal_selected (int index) {+ unowned List<Tab?>? lt;+ Tab t;+ + lt = tabs.nth(index);+ return_if_fail(lt != null); + t = (!) ((!) lt).data;+ + signal_tab_selected (t); + }+ + private void scroll_to_tab (int index) {+ double offset = 19;+ int i = 0;+ + if (index < first_tab) {+ first_tab = index;+ signal_selected (index);+ return;+ }+ + foreach (Tab t in tabs) {+ + if (i < first_tab) {+ i++;+ continue;+ }+ + // out of view+ if (offset + t.get_width () + 3 > width - 19) {+ first_tab++;+ scroll_to_tab (index);+ return;+ }++ // in view+ if (i == index) {+ signal_selected (index);+ return;+ }++ offset += t.get_width () + 3;+ i++;+ }+ + warning ("");+ }+ + public void select_tab_click (double x, double y, int width, int height) {+ int over, close;+ + this.width = width;+ this.height = height;+ + is_over_close (x, y, out over, out close);++ if (over_close >= 0 && over == selected) {+ close_tab (over_close);+ } else {+ select_tab (over);+ }+ }+ + public void add_tab (FontDisplay display_item, double tab_width = -1, bool always_open = false) {+ int s = (tabs.length () == 0) ? 0 : selected + 1;+ + if (tab_width < 0) {+ //cr.text_extents (display_item.get_name (), out te); // this is not a good estimation, pango might solve it+ //tab_width = te.width + 30;+ + tab_width = 9 * display_item.get_name ().char_count ();+ tab_width += 30;+ }+ + tabs.insert (new Tab (display_item, tab_width, always_open), s);+ select_tab (s);+ }+ + /** Returns true if the new item was added to the bar. */+ public bool add_unique_tab (FontDisplay display_item, double tab_width = -1, bool always_open = false) {+ bool i = select_tab_name (display_item.get_name ());++ if (!i) {+ add_tab (display_item, tab_width, always_open);+ return true;+ }+ + return false;+ }+ + public void draw (Context cr, int width, int height) {+ this.width = width;+ this.height = height;+ + cr.save ();+ cr.rectangle (0, 0, width, height);+ cr.set_line_width (0);+ cr.set_source_rgba (183/255.0, 200/255.0, 223/255.0, 1);+ cr.fill_preserve ();+ cr.stroke ();+ cr.restore ();++ for (int j = 0; j < width; j++) {+ cr.set_source_surface ((!) bar_background, j, 0);+ cr.paint ();+ }++ if (has_scroll ()) {+ // left arrow+ cr.set_source_surface (to_previous_tab, 3, (height - to_previous_tab.get_height ()) / 2.0);+ cr.paint ();++ // right arrow+ cr.set_source_surface (next_tab, width - 19, (height - next_tab.get_height ()) / 2.0);+ cr.paint ();+ }+ + draw_tabs (cr);+ + }+ + // this can be removed when tabs are not referenced by name+ string translate (string s) {+ switch (s) {+ case "Overview":+ return _("Overview");+ break;+ case "Kerning":+ return _("Kerning");+ break;+ case "Menu":+ return _("Menu");+ break;+ default:+ return s;+ break;+ }+ }+ + private void draw_tabs (Context cr) {+ double close_opacity;+ double offset = 19;+ int i = 0;+ + foreach (Tab t in tabs) {+ + if (i < first_tab) {+ i++;+ continue;+ }++ cr.save ();+ cr.translate (offset, 0);+ + if (offset + t.get_width () + next_tab.get_width () + 3 > width) {+ break;+ }+ + // background+ if (i == selected) {+ for (int j = 0; j < t.get_width (); j++) {+ cr.set_source_surface ((!) tab3_right, j, 2);+ cr.paint ();+ } + } else if (i == over) {+ for (int j = 0; j < t.get_width (); j++) {+ cr.set_source_surface ((!) tab2_right, j, 2);+ cr.paint ();+ } + } else {+ for (int j = 0; j < t.get_width (); j++) {+ cr.set_source_surface ((!) tab1_right, j, 2);+ cr.paint ();+ }+ }+ + // close+ if (t.has_close_button ()) {+ cr.set_line_width (1);+ + close_opacity = (over_close == i) ? 1 : 0.2; + cr.set_source_rgba (0, 0, 0, close_opacity);+ + cr.move_to (t.get_width () - 5, 11);+ cr.line_to (t.get_width () - 10, 16);++ cr.move_to (t.get_width () - 10, 11);+ cr.line_to (t.get_width () - 5, 16);+ + cr.stroke (); + }++ cr.set_source_rgba (0, 0, 0, 1);+ cr.set_font_size (14);+ cr.move_to (8, 18);+ cr.show_text (translate (t.get_label ()));+ cr.stroke ();+ + // edges+ if (tab1_left != null && tab1_right != null && tab2_left != null && tab2_right != null && tab3_left != null && tab3_right != null) {+ if (i == selected) {+ cr.set_source_surface ((!) tab3_left, 0, 2);+ cr.paint ();+ + cr.set_source_surface ((!) tab3_right, t.get_width () - 2, 2);+ cr.paint ();+ } else if (i == over) {+ cr.set_source_surface ((!) tab2_left, 0, 2);+ cr.paint ();++ cr.set_source_surface ((!) tab2_right, t.get_width () - 2, 2);+ cr.paint ();+ } else {+ cr.set_source_surface ((!) tab1_left, 0, 2);+ cr.paint ();++ cr.set_source_surface ((!) tab1_right, t.get_width () - 2, 2);+ cr.paint ();+ }+ }++ cr.restore ();+ + offset += t.get_width () + 3;+ i++;+ }+ }+}++}new file mode 100644@@ -0,0 +1,527 @@+/*+ 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/>.+*/++namespace Supplement {++/** All the things we want to test listed is here. */+class TestCases {+ + public List<Test> test_cases;++ public TestCases () {+ add (test_overview, "Overview");+ add (test_data_reader, "Font data reader");+ add (test_argument, "Argument list");+ add (test_glyph_ranges, "Glyph ranges");+ add (test_glyph_table, "Glyph table");+ add (test_active_edit_point, "Active edit point");+ add (test_hex, "Unicode hex values");+ add (test_reverse_path, "Reverse path");+ add (test_reverse_random_paths, "Reverse random paths");+ add (test_coordinates, "Coordinates");+ add (test_drawing, "Pen tool");+ add (test_delete_points, "Delete edit points");+ add (test_save_backup, "Save backup");+ add (test_convert_to_quadratic_bezier_path, "Convert to quadratic path");+ add (test_notdef, "Notdef");+ add (test_merge, "Merge");+ add (test_over_path, "Over path");+ add (test_preview, "Preview");+ add (test_export, "Export");+ add (test_background_coordinates, "Background coordinates");+ }++ public static void test_background_coordinates () {+ GlyphBackgroundImage bg = new GlyphBackgroundImage ("");+ + bg.set_position (100, 100);+ bg.set_img_offset (bg.img_offset_x, bg.img_offset_y);+ warn_if_fail (bg.img_x == 100 && bg.img_y == 100);++ bg.set_img_offset (100, 100);+ bg.set_position (bg.img_x, bg.img_y);+ warn_if_fail (bg.img_offset_x == 100 && bg.img_offset_y == 100);+ }++ public static void test_export () {+ Glyph g;+ Path p = new Path ();+ + Tool.test_open_next_glyph ();+ g = MainWindow.get_current_glyph ();+ + p.add (-10, -10);+ p.add (50, 50);+ p.add (10, -10);+ + p.close ();+ g.add_path (p); ++ MenuTab.preview ();++ // TODO: run this many times on big font+ for (int i = 0; i < 10; i++) {+ ExportTool.export_all ();+ Tool.yield ();+ } + }+++ public static void test_preview () {+ Glyph g;+ Path p = new Path ();+ + Tool.test_open_next_glyph ();+ g = MainWindow.get_current_glyph ();+ + p.add (-10, 10);+ p.add (10, 10);+ p.add (10, -10);+ p.add (-10, -10);+ p.close ();+ g.add_path (p); ++ MenuTab.preview ();++ // TODO: run this many times on big font+ for (int i = 0; i < 100; i++) {+ MainWindow.get_tab_bar ().select_tab_name ("Menu");+ Tool.yield ();+ + MainWindow.get_tab_bar ().select_tab_name ("Preview");+ Tool.yield ();+ }+ + }++ public static void test_over_path () {+ Glyph g;+ Path p = new Path ();+ Tool pen_tool;+ + pen_tool = MainWindow.get_toolbox ().get_tool ("pen_tool");+ pen_tool.test_select_action ();+ Tool.test_open_next_glyph ();+ + g = MainWindow.get_current_glyph ();++ pen_tool.test_click_action (1, 10, 10);+ pen_tool.test_click_action (1, 10, 10);+ pen_tool.test_click_action (1, 100, 10);+ pen_tool.test_click_action (1, 100, 100);+ pen_tool.test_click_action (1, 10, 100);+ pen_tool.test_click_action (3, 0, 0);++ warn_if_fail (g.active_paths.length () == 0);++ g.select_path (50, 50);++ warn_if_fail (g.active_paths.length () == 1);+ + p.add (-10, 10);+ p.add (10, 10);+ p.add (10, -10);+ p.add (-10, -10);+ p.update_region_boundries ();+ g.add_path (p);+ g.close_path ();++ if (!p.is_over_coordinate (0, 0)) {+ warning ("Coordinate 0, 0 is not in path.");+ }+ + if (!p.is_over_coordinate (-10, 10)) {+ warning ("Corner corrdinate -10, 10 is not in path.");+ }+ + warn_if_fail (!p.is_over_coordinate (-20, -20));+ + for (double x = -10; x <= 10; x += 0.1) {+ for (double y = 10; y <= 10; y += 0.1) {+ warn_if_fail (p.is_over_coordinate (x, y));+ } + } + + }++ public static void test_merge () {+ Glyph g;+ Path p = new Path ();+ Path p2 = new Path ();+ + Tool.test_open_next_glyph ();+ g = MainWindow.get_current_glyph ();+ + p.add (-10, 10);+ p.add (10, 10);+ p.add (10, -10);+ p.add (-10, -10);+ p.close ();+ g.add_path (p);++ p2.add (10, 10);+ p2.add (10, -10); + p2.add (20, -10);+ p2.add (20, 10);+ p2.reverse ();+ p2.close ();+ g.add_path (p2);+ + g.add_active_path (p);+ g.add_active_path (p);+ + g.add_active_path (p);+ g.merge_all ();+ }++ public static void test_notdef () {+ Font f = Supplement.get_current_font ();+ Glyph n = f.get_not_def_character ();+ Glyph g;+ Path pn;+ + f.add_glyph (n);+ + Tool.test_open_next_glyph ();+ g = MainWindow.get_current_glyph ();+ foreach (Path p in n.path_list) {+ pn = p.copy ().get_quadratic_points ();+ g.path_list.append (pn);+ pn.move (50, 0);+ g.path_list.append (p.copy ());+ }+ }++ public static void test_convert_to_quadratic_bezier_path () {+ Glyph g;+ Path gqp;+ Path gqp_points;+ + Path p = new Path ();+ Path p1 = new Path ();+ List<Path> qpl = new List<Path> (); + + EditPoint e0, e1, e2, e3;+ + g = MainWindow.get_current_glyph ();+ + // split_all_cubic_in_half+ foreach (Path gp in g.path_list) {+ gqp_points = gp.copy ();+ gqp_points.split_cubic_in_parts (gqp_points);+ gqp_points.move (50, 0);+ qpl.append (gqp_points);+ }+ + // convert to quadratic points+ foreach (Path gp in g.path_list) {+ gqp = gp.get_quadratic_points ();+ gqp.move (100, 0);+ qpl.append (gqp);+ }++ foreach (Path gp in qpl) {+ g.add_path (gp);+ }++ Tool.test_open_next_glyph ();+ + g = MainWindow.get_current_glyph ();+ + p.add (-10, 10);+ p.add (10, 10);+ p.add (10, -10);+ p.add (-10, -10);+ p.close ();+ g.add_path (p);+ g.add_path (p1.get_quadratic_points ());++ e0 = new EditPoint (20, 40);+ e1 = new EditPoint (40, 40);+ e2 = new EditPoint (40, 20);+ e3 = new EditPoint (20, 20);++ p1.add_point (e0);+ p1.add_point (e1);+ p1.add_point (e2);+ p1.add_point (e3);+ p1.close ();++ e0.set_tie_handle (true);+ e1.set_tie_handle (true);+ e2.set_tie_handle (true);+ e3.set_tie_handle (true);++ e0.process_tied_handle ();+ e1.process_tied_handle ();+ e2.process_tied_handle ();+ e3.process_tied_handle ();++ g.add_path (p1);+ g.add_path (p1.get_quadratic_points ());+ }++ public static void test_overview () {+ OverView o = MainWindow.get_overview ();++ warn_if_fail (o.selected_char_is_visible ());+ + for (int i = 0; i < 10; i++) {+ o.key_down ();+ warn_if_fail (o.selected_char_is_visible ());+ }++ for (int i = 0; i < 15; i++) {+ o.key_up ();+ warn_if_fail (o.selected_char_is_visible ());+ }++ for (int i = 0; i < 6; i++) {+ o.key_down ();+ warn_if_fail (o.selected_char_is_visible ());+ }++ for (int i = 0; i < 3; i++) {+ o.key_down ();+ warn_if_fail (o.selected_char_is_visible ());+ }+ + for (int i = 0; i < 2000; i++) {+ o.scroll_adjustment (5);+ }++ for (int i = 0; i < 2000; i++) {+ o.scroll_adjustment (-5);+ }+ }++ public static void test_data_reader () {+ FontData fd = new FontData ();+ uint len;++ try {+ fd.add (7);+ fd.add_ulong (0x5F0F3CF5);+ fd.add_ulong (9);+ + warn_if_fail (fd.table_data[0] == 7);+ warn_if_fail (fd.read () == 7);+ warn_if_fail (fd.read_ulong () == 0x5F0F3CF5);+ warn_if_fail (fd.read_ulong () == 9);+ + fd = new FontData ();+ for (int16 i = 0; i < 2048; i++) {+ fd.add_short (i);+ }+ + fd.seek (2 * 80);+ warn_if_fail (fd.read_short () == 80);+ + fd.seek (100);+ fd.add_short (7);+ fd.seek (100);+ warn_if_fail (fd.read_short () == 7);+ + fd.seek_end ();+ len = fd.length ();+ fd.add (0);+ warn_if_fail (len + 1 == fd.length ());+ } catch (GLib.Error e) {+ warning (e.message);+ }+ }++ public static void test_argument () {+ Argument arg = new Argument ("supplement -t \"Argument list\" --unknown -unknown --help -s");+ + return_if_fail (arg.has_argument ("--test"));+ return_if_fail ((!) arg.get_argument ("--test") == "\"Argument list\"" );+ return_if_fail (arg.has_argument ("--unknown"));+ return_if_fail (arg.has_argument ("--help"));+ return_if_fail (arg.has_argument ("--slow"));+ return_if_fail (arg.validate () != 0);++ arg = new Argument ("supplement --test \"Argument list\"");+ return_if_fail ((!) arg.get_argument ("--test") == "\"Argument list\"" );+ return_if_fail (!arg.has_argument ("--help"));+ return_if_fail (!arg.has_argument ("--slow"));+ return_if_fail (arg.validate () == 0);+ + }++ public static void test_glyph_ranges () {+ GlyphRange gr = new GlyphRange ();+ + gr.add_range ('b', 'c');+ gr.add_single ('d');+ gr.add_range ('e', 'h');+ gr.add_range ('k', 'm');+ gr.add_range ('o', 'u');+ gr.add_range ('a', 'd');+ gr.add_range ('f', 'z');+ gr.add_range ('b', 'd');+ + return_if_fail (gr.length () == 'z' - 'a' + 1);+ return_if_fail (gr.get_ranges ().length () == 1);+ return_if_fail (gr.get_ranges ().first ().data.length () == 'z' - 'a' + 1);+ + for (unichar i = 'a'; i <= 'z'; i++) {+ uint index = i - 'a';+ string c = gr.get_char (index);+ StringBuilder s = new StringBuilder ();+ s.append_unichar (i);+ + if (c != s.str) {+ warning (@"wrong glyph in glyph range got \"$c\" expected \"$(s.str)\" for index $(index).");+ }+ }+ + gr = new GlyphRange ();+ gr.add_single ('a');+ gr.add_range ('c', 'e');+ gr.add_single ('◊');+ return_if_fail (gr.get_char (0) == "a");+ return_if_fail (gr.get_char (1) == "c");+ return_if_fail (gr.get_char (2) == "d");+ return_if_fail (gr.get_char (3) == "e");+ return_if_fail (gr.get_char (4) == "◊");+ }++ public static void test_glyph_table () {+ GlyphTable table = new GlyphTable ();+ List<GlyphCollection> gc = new List<GlyphCollection> ();+ List<unowned GlyphCollection> gc_copy;+ GlyphCollection g;+ + return_if_fail (table.length () == 0);+ return_if_fail (table.get ("Some glyph") == null);+ + // generate test data+ for (uint i = 0; i < 1000; i++) {+ gc.append (new GlyphCollection (new Glyph (@"TEST $i", i + 'a')));+ }++ // insert in random order+ gc_copy = gc.copy ();+ for (uint i = 0; gc_copy.length () > 0; i++) {+ int t = (int) ((gc_copy.length () - 1) * Random.next_double ());+ g = gc_copy.nth (t).data;+ + if (!table.insert (g)) {+ warning (@"Failed to insert $(g.get_name ())");+ return;+ }+ + gc_copy.remove_all (g);+ + if (!table.validate_index ()) {+ table.print_all ();+ warning ("index is invalid");+ return;+ }+ }+ + return_if_fail (table.length () == gc.length ());+ + // validate table+ for (uint i = 0; i > 1000; i++) {+ g = (!) table.get (gc.nth (i).data.get_name ());+ return_if_fail (gc.nth (i).data == g);+ }+ + // search + for (int i = 0; i < 2000; i++) {+ int t = (int) (999 * Random.next_double ());+ if (table.get (@"TEST $t") == null) {+ table.print_all ();+ warning (@"Did't find TEST $t in glyph table.");+ return;+ }+ }++ // remove+ table.remove ("TEST 0");+ table.remove ("TEST 53");+ return_if_fail (table.get ("TEST 0") == null);+ return_if_fail (table.get ("TEST 53") == null);+ + // search + return_if_fail (table.get ("TEST 52") != null);+ return_if_fail (table.get ("TEST 54") != null);+ }++ public static void test_delete_points () {+ PenTool tool = (PenTool) MainWindow.get_toolbox ().get_tool ("pen_tool");+ tool.test_delete_points ();+ }++ public static void test_active_edit_point () {+ PenTool tool = (PenTool) MainWindow.get_toolbox ().get_tool ("pen_tool");+ tool.test_active_edit_point ();+ }+ + public static void test_save_backup () {+ // TODO draw various things and assert that they are restored correctly+ Supplement.get_current_font ().save_backup ();+ Supplement.get_current_font ().restore_backup ();+ }++ public static void test_hex () {+ test_hex_conv ('H', "U+48", 72);+ test_hex_conv ('1', "U+31", 49);+ test_hex_conv ('å', "U+e5", 229);+ test_hex_conv ('◊', "U+25ca", 9674);+ }++ private static void test_hex_conv (unichar h, string sr, int r) {+ string s = Font.to_hex (h);+ unichar t = Font.to_unichar (sr);+ + if (s != sr) warning (@"($s != \"$sr\")");+ if ((int)t != r || t != h) warning (@"$((int)t) != $r || $t != '$h'");+ }++ public static void test_coordinates () {+ PenTool tool = (PenTool) MainWindow.get_toolbox ().get_tool ("pen_tool");+ tool.test_coordinates (); + }++ public static void test_reverse_random_paths () {+ PenTool tool = (PenTool) MainWindow.get_toolbox ().get_tool ("pen_tool");+ tool.test_reverse_random_triangles (); + }++ public static void test_reverse_path () {+ PenTool tool = (PenTool) MainWindow.get_toolbox ().get_tool ("pen_tool");+ tool.test_reverse_path ();+ }++ public static void test_drawing () {+ Tool tool = MainWindow.get_toolbox ().get_tool ("pen_tool");+ tool.test ();+ }+ + private void add (Callback callback, string name) {+ test_cases.append (new Test (callback, name));+ }+ + public unowned List<Test> get_test_functions () {+ return test_cases;+ }+} + +}new file mode 100644@@ -0,0 +1,262 @@+/* + 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/>. +*/ + +namespace Supplement { + +class Test : Object { + public Callback callback; + public string name; + + public Test (Callback callback, string name) { + this.callback = callback; + this.name = name; + } +} + +/** Class for executing tests cases. */ +class TestSupplement : GLib.Object { + + const int NOT_STARTED = 0; + const int RUNNING = 1; + const int PAUSED = 2; + const int DONE = 3; + + static int state = NOT_STARTED; + + TestCases tests; + + unowned List<Test> test_cases; + unowned List<Test?>? current_case = null; + + unowned List<Test> passed; + unowned List<Test> failed; + unowned List<Test> skipped; + + static TestSupplement? singleton = null; + + bool has_failed = false; + bool has_skipped = false; + + static bool slow_test = false; + + string test_cases_to_run; // name of specific test case or all to run all test cases + + public TestSupplement () { + assert (singleton == null); + tests = new TestCases (); + test_cases = tests.get_test_functions (); + current_case = test_cases.first (); + test_cases_to_run = "All"; + + from_command_line (); + } + + public static bool is_slow_test () { + return slow_test; + } + + public static void set_slow_test (bool s) { + slow_test = s; + } + + public static TestSupplement get_singleton () { + if (singleton == null) { + singleton = new TestSupplement (); + } + + return (!) singleton; + } + + private bool has_test_case (string s) { + foreach (var t in test_cases) { + if (t.name == s) return true; + } + + if (s == "" || s == "All") { + print ("No specific tescase given run all test cases.\n"); + return true; + } + + return false; + } + + /** Run only test specified on the command line. */ + private void from_command_line () { + string? stn = Supplement.get_argument ("--test"); + + if (stn != null) { + string st = (!) stn; + + if (!has_test_case (st)) { + stderr.printf (@"Test case \"$st\" does not exist.\n"); + stderr.printf ("\nAvaliable test cases:\n"); + + foreach (var t in test_cases) { + stderr.printf (t.name); + stderr.printf ("\n"); + } + + Process.exit(1); + } + + if (st == "All" || st == "") { + return; + } else { + stderr.printf (@"Run only test case \"$st\" \n"); + } + + test_cases_to_run = st; + } + } + + public static void log (string? log_domain, LogLevelFlags log_levels, string message) { + Test t = (!)((!)get_singleton ().current_case).data; + + if (log_domain != null) { + stderr.printf ("%s: \n", (!) log_domain); + } + + stderr.printf ("Testcase \"%s\" failed because:\n", t.name); + stderr.printf ("%s\n\n", message); + + get_singleton ().has_failed = true; + + assert (!Supplement.fatal_wanings); + } + + public static void @continue () { + int s = AtomicInt.get (ref state); + if (s == DONE) { + singleton = null; + } + + TestSupplement t = get_singleton (); + + LogLevelFlags levels = LogLevelFlags.LEVEL_ERROR | LogLevelFlags.LEVEL_CRITICAL | LogLevelFlags.LEVEL_WARNING; + Log.set_handler (null, levels, log); + + AtomicInt.set (ref state, RUNNING); + t.run_all_tests (); + } + + public static bool is_running () { + int r = AtomicInt.get (ref state); + return (r == RUNNING); + } + + public static void pause () { + AtomicInt.set (ref state, PAUSED); + Log.set_handler (null, LogLevelFlags.LEVEL_MASK, Log.default_handler); + } + + private static void pad (int t) { + for (int i = 0; i < t; i++) stdout.printf (" "); + } + + public void print_result () { + stdout.printf ("\n"); + stdout.printf ("Testcase results:\n"); + + foreach (var t in skipped) { + stdout.printf ("%s", t.name); + pad (40 - t.name.char_count()); + stdout.printf ("Skipped\n"); + } + + if (skipped.length () > 0) { + stdout.printf ("\n"); + } + + foreach (var t in passed) { + stdout.printf ("%s", t.name); + pad (40 - t.name.char_count()); + stdout.printf ("Passed\n"); + } + + foreach (var t in failed) { + stdout.printf ("%s", t.name); + pad (40 - t.name.char_count()); + stdout.printf ("Failed\n"); + } + + stdout.printf ("\n"); + + stdout.printf ("Total %u test cases executed, %u passed and %u failed.\n", (passed.length () + failed.length ()), passed.length (), failed.length ()); + } + + /** Run tests in main loop. */ + public void run_all_tests () { + var idle = new TimeoutSource (20); + + idle.set_callback(() => { + int s = AtomicInt.get (ref state); + + if (s != RUNNING || current_case == null) { + return false; + } + + Test test = (!)((!) current_case).data; + + has_failed = false; + has_skipped = false; + + if (test_cases_to_run != "All" && test_cases_to_run != test.name) { + has_skipped = true; + } else { + test.callback (); + } + + if (has_failed) { + failed.append ((!) test); + + if (Supplement.has_argument ("--exit")) { + print_result (); + Process.exit (1); + } + + } else if (has_skipped) { + skipped.append ((!) test); + } else { + passed.append ((!) test); + } + + if (unlikely (current_case == test_cases.last ())) { + stdout.printf ("Finished running test suite.\n"); + + AtomicInt.set (ref state, DONE); + Log.set_handler (null, LogLevelFlags.LEVEL_MASK, Log.default_handler); + + print_result (); + + if (Supplement.has_argument ("--exit")) { + print_result (); + Process.exit ((failed.length () == 0) ? 0 : 1); + } + return false; + } + + current_case = ((!) current_case).next; + + return true; + }); + + idle.attach (null); + } + +} + +} new file mode 100644@@ -0,0 +1,353 @@+/*+ 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 {++public class Tool : GLib.Object {+ + public double x = 0;+ public double y = 0;+ public double w = 0;+ public double h = 0;++ protected bool active = false;+ protected bool selected = false;+ + double r_default_selected = 131/255.0;+ double g_default_selected = 179/255.0;+ double b_default_selected = 231/255.0;+ double a_default_selected = 1;+ + double r_default = 185/255.0;+ double g_default = 207/255.0;+ double b_default = 231/255.0;+ double a_default = 1;+ + double r;+ double g;+ double b;+ double a;+ + ImageSurface? icon = null;+ + public signal void select_action (Tool selected);+ public signal void deselect_action (Tool selected);+ + public signal void press_action (Tool selected, int button, int x, int y);+ public signal void double_click_action (Tool selected, int button, int x, int y);+ public signal void move_action (Tool selected, int x, int y);+ public signal void release_action (Tool selected, int button, int x, int y);+ + public signal void key_press_action (Tool selected, uint32 keyval);+ public signal void key_release_action (Tool selected, uint32 keyval);+ + public signal void panel_press_action (Tool selected, uint button, double x, double y);+ public signal void panel_release_action (Tool selected, uint button, double x, double y);+ public signal void panel_move_action (Tool selected, double x, double y);+ + public signal void draw_action (Tool selected, Context cr, Glyph glyph);+ + protected string name = "";+ + static int next_id = 1;+ + int id;+ + public bool new_selection = false;+ + bool show_bg = true;+ + string tip = "";++ // keyboard bindings+ public uint modifier_flag;+ public unichar key;+ + private static ImageSurface? selected_button = null;+ private static ImageSurface? active_selected_button = null;+ private static ImageSurface? deselected_button = null;+ private static ImageSurface? active_deselected_button = null;+ + /** Create tool with a certain name and load icon "name".png */+ public Tool (string? name = null, string tip = "", unichar key = '\0', uint modifier_flag = 0) {+ this.tip = tip;+ + if (selected_button == null) {+ selected_button = Icons.get_icon ("tool_button_selected.png");+ active_selected_button = Icons.get_icon ("tool_button_selected_active.png");+ deselected_button = Icons.get_icon ("tool_button_deselected.png");+ active_deselected_button = Icons.get_icon ("tool_button_deselected_active.png");+ }+ + if (name != null) {+ set_icon ((!) name);+ this.name = (!) name;+ }+ + this.key = key;+ this.modifier_flag = modifier_flag;+ + id = next_id;+ next_id++;+ + panel_press_action.connect ((self, button, x, y) => {+ MainWindow.get_tool_tip ().set_text_from_tool ();+ });+ + r = r_default;+ g = g_default;+ b = b_default;+ a = a_default;+ }+ + public void set_icon (string name) {+ StringBuilder n = new StringBuilder ();+ n.append ((!) name);+ n.append (".png");+ icon = Icons.get_icon (n.str);+ }+ + public bool is_active () {+ return active;+ }+ + public void set_show_background (bool bg) {+ show_bg = bg;+ }+ + public int get_id () {+ return id;+ }++ public string get_name () {+ return name;+ }++ public bool is_selected () {+ return selected;+ }+ + public virtual void set_properties (double tx, double ty, double tw, double th) {+ x = tx;+ y = ty;+ w = tw;+ h = th;+ }++ public string get_tip () {+ return tip;+ }++ public bool is_over (double xp, double yp) { + return (x <= xp <= x + w + 12 + 15 && y + 15 <= yp <= y + w + 12 + 30); + }+ + public bool set_selected (bool a) {+ TooltipArea? tpa = null; + tpa = MainWindow.get_tool_tip ();+ + new_selection = true;+ selected = a;+ set_active (a);+ + if (!a) {+ deselect_action (this);+ } else {+ ((!)tpa).update_text ();+ }+ + return true;+ }+ + public bool set_active (bool ac) {+ bool ret = (active != ac);++ if (selected) {+ if (ac) {+ r = r_default_selected;+ g = g_default_selected;+ b = b_default_selected;+ a = a_default_selected * 0.5;+ } else {+ r = r_default_selected;+ g = g_default_selected;+ b = b_default_selected;+ a = a_default_selected;+ } + } else {+ if (ac) {+ r = r_default;+ g = g_default;+ b = b_default;+ a = a_default * 0.2;+ } else {+ r = r_default;+ g = g_default;+ b = b_default;+ a = a_default;+ }+ }++ active = ac;+ return ret;+ }+ + public virtual void draw (Context cr) {+ double xt = x + 3 + w;+ double yt = y + h + 17;++ double bgx, bgy;+ + bgx = xt - 6;+ bgy = yt - 7;++ cr.save ();++ // Button in four states+ if (selected && selected_button != null) {+ cr.set_source_surface ((!) selected_button, bgx, bgy);+ cr.paint ();+ }++ if (selected && active && active_selected_button != null) {+ cr.set_source_surface ((!) active_selected_button, bgx, bgy);+ cr.paint ();+ }++ if (!selected && deselected_button != null) {+ cr.set_source_surface ((!) deselected_button, bgx, bgy);+ cr.paint ();+ }++ if (!selected && active && active_deselected_button != null) {+ cr.set_source_surface ((!) active_deselected_button, bgx, bgy);+ cr.paint ();+ }+ + if (icon != null) {+ ImageSurface i = (!) icon;+ + if (likely (i.status () == Cairo.Status.SUCCESS)) {+ cr.set_source_surface (i, xt + (15 - i.get_width () / 2) - 5.7, yt + (15 - i.get_height ()) / 2.0);+ cr.paint ();+ } else {+ warning (@"Falied to load icon for $name");+ }+ }+ + cr.restore ();+ }++ /** Run a test case for this tool. */+ public virtual bool test () {+ stderr.printf (@"$(get_name ()) does not have a test case, implement one by overriding test method in Tool base class.");+ return false;+ }+ + /** Help function to test button press actions. */+ public void test_click_action (int b, int x, int y) {+ Tool.yield ();+ press_action (this, b, x, y);+ + Tool.yield ();+ release_action (this, b, x, y);+ }++ /** Help function to test select action for this tool. */+ public void test_select_action () {+ Tool.yield ();+ MainWindow.get_toolbox ().select_tool (this);+ }++ public void test_move_action (int x, int y) {+ Tool.yield ();+ move_action (this, x, y);+ }++ public void test_press_action (int b, int x, int y) {+ Tool.yield ();+ press_action (this, b, x, y);+ }++ public void test_release_action (int b, int x, int y) {+ Tool.yield ();+ release_action (this, b, x, y);+ }++ public static void test_open_next_glyph () {+ Tool.yield ();+ OverView o = MainWindow.get_overview ();+ + MainWindow.get_tab_bar ().select_overview ();+ MainWindow.get_toolbox ().select_tool_by_name ("utf_8");+ + o.select_next_glyph ();+ Tool.yield ();+ + o.open_current_glyph ();+ Tool.yield ();+ }+ + /** Run pending events in main loop before continue. */+ public static void @yield () {+ int t = 0;+ var time = new TimeoutSource(500);+ bool timeout;++ if (TestSupplement.is_slow_test ()) {+ timeout = false;+ + time.set_callback(() => {+ timeout = true;+ return false;+ });++ time.attach(null); + } else {+ timeout = true;+ }+ + unowned MainContext c = MainContext.default ();+ bool a = c.acquire ();+ + while (unlikely (!a)) {+ warning ("Failed to acquire main loop.\n");+ return;+ }++ while (c.pending () || TestSupplement.is_slow_test ()) {+ c.iteration (true);+ t++;++ if (!c.pending () && TestSupplement.is_slow_test ()) {+ if (timeout) break;+ }+ + if (unlikely (t > 100000)) {+ assert (false);+ break;+ }+ }+ + c.release ();+ }+ +}++}new file mode 100644@@ -0,0 +1,868 @@+/*+ 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;+using Math;++namespace Supplement {++public class Toolbox : GLib.Object {+ GlyphCanvas glyph_canvas;+ + bool expanded = true;+ + ToolboxExpander toolbox_expander;+ + public List<Expander> expanders;+ + Tool current_tool = new Tool ("no_icon");+ + Expander draw_tools;+ Expander grid_expander;+ + BackgroundTool move_background;+ CutBackgroundTool cut_background;+ + public Tool press_tool = new Tool (null); // activate the pressed button on release+ + public SpinButton background_scale = new SpinButton ();+ public SpinButton precision;+ + public signal void redraw (int x, int y, int w, int h);+ + public int allocation_width = 0;+ public int allocation_height = 0;+ + ImageSurface? toolbox_background = null;+ + public Toolbox (GlyphCanvas main_glyph_canvas) {+ glyph_canvas = main_glyph_canvas;++ toolbox_background = Icons.get_icon ("toolbox_background.png");++ toolbox_expander = new ToolboxExpander ();+ + add_expander (toolbox_expander);+ + draw_tools = new Expander ();+ + Expander path_tool_modifiers = new Expander ();+ Expander draw_tool_modifiers = new Expander ();+ Expander characterset_tools = new Expander ();+ Expander test_tools = new Expander ();+ Expander guideline_tools = new Expander ();+ Expander view_tools = new Expander ();+ Expander grid = new Expander ();+ Expander background_tools = new Expander ();+ Expander trace = new Expander ();+ + grid_expander = grid;++ // Draw tools+ PenTool pen_tool = new PenTool ("pen_tool");+ draw_tools.add_tool (pen_tool);+ + ZoomTool zoom_tool = new ZoomTool ("zoom_tool");+ draw_tools.add_tool (zoom_tool);++ CutTool cut_tool = new CutTool ("cut");+ draw_tools.add_tool (cut_tool); ++ Tool move_tool = new MoveTool ("move");+ draw_tools.add_tool (move_tool);++ Tool shrink_tool = new ShrinkTool ("shrink");+ // Fixa: draw_tools.add_tool (shrink_tool); + + // Draw tool modifiers+ // DELETE tools:+ + Tool new_point = new Tool ("new_point", _("Right click to add new point, left click to move points") + " " + _("and double click to add new point on path"), 'a');+ new_point.select_action.connect ((self) => {+ select_draw_tool ();+ });+ + // draw_tool_modifiers.add_tool (new_point);++ Tool insert_point_on_path = new Tool ("insert_point_on_path", _("Add new point on path"), 'n');+ insert_point_on_path.select_action.connect ((self) => {+ select_draw_tool ();+ pen_tool.begin_from_new_point_on_path ();+ });+ // draw_tool_modifiers.add_tool (insert_point_on_path);+ + if (Supplement.experimental) {+ Tool new_point_on_path = new Tool ("new_point_on_path", _("Begin new line from point on path"), 'n');+ new_point_on_path.select_action.connect ((self) => {+ select_draw_tool ();+ pen_tool.begin_from_new_point_on_path ();+ });+ draw_tool_modifiers.add_tool (new_point_on_path); + }++ Tool tie_editpoint_tool = new Tool ("tie_point", _("Tie curve handles for selected edit point"), 'w');+ tie_editpoint_tool.select_action.connect ((self) => {+ bool tie;+ + select_draw_tool ();+ + foreach (EditPoint ep in pen_tool.selected_points) {+ tie = !ep.tie_handles;++ if (tie) {+ ep.process_tied_handle ();+ }++ ep.set_tie_handle (tie);+ + MainWindow.get_current_glyph ().update_view (); + }+ });+ draw_tool_modifiers.add_tool (tie_editpoint_tool); ++ Tool tie_x_or_y = new Tool ("tie_x_or_y", _("Tie coordinates to previous edit point"), 'q');+ tie_x_or_y.select_action.connect((self) => {+ select_draw_tool ();+ pen_tool.tie_x_or_y_coordinates = !pen_tool.tie_x_or_y_coordinates;+ });+ // Fixa: draw_tool_modifiers.add_tool (tie_x_or_y); ++ Tool erase_tool = new Tool ("erase_tool", _("Delete edit points"));+ erase_tool.select_action.connect((self) => {+ select_draw_tool ();+ });+ // draw_tool_modifiers.add_tool (erase_tool); ++ // adjust precision+ string precision_value = Preferences.get ("precision");+ + precision = new SpinButton ("precision", _("Set precision"));+ + if (precision_value != "") {+ precision.set_value (precision_value);+ } else {+ precision.set_value_round (1);+ }+ + precision.new_value_action.connect ((self) => {+ select_tool (precision);+ + Preferences.set ("precision", self.get_display_value ());+ redraw ((int) precision.x, (int) precision.y, 70, 70);+ });++ precision.select_action.connect((self) => {+ pen_tool.set_precision (((SpinButton)self).get_value ());+ });+ + precision.set_min (0.001);+ precision.set_max (1);+ + draw_tool_modifiers.add_tool (precision);+ + // path tools+ Tool union_paths_tool = new MergeTool ("union_paths");+ path_tool_modifiers.add_tool (union_paths_tool);+ + Tool reverse_path_tool = new Tool ("reverse_path", _("Create counter from outline"), 'r');+ reverse_path_tool.select_action.connect ((self) => {+ Glyph g = MainWindow.get_current_glyph ();+ + foreach (Path p in g.active_paths) {+ p.reverse ();+ }+ + g.redraw_area (0, 0, g.allocation.width, g.allocation.height);+ });+ path_tool_modifiers.add_tool (reverse_path_tool);++ Tool move_layer = new Tool ("move_layer", _("Move to path to the bottom layer"), 'd');+ move_layer.select_action.connect ((self) => {+ Glyph g = MainWindow.get_current_glyph ();++ foreach (Path p in g.active_paths) {+ g.path_list.remove (p);+ g.path_list.prepend (p);+ }+ });+ path_tool_modifiers.add_tool (move_layer);+ + // Character set tools+ Tool full_unicode = new Tool ("utf_8", _("Show full unicode characters set"), 'f', CTRL);+ full_unicode.select_action.connect ((self) => {+ MainWindow.get_tab_bar ().add_unique_tab (new OverView (), 100, false); + OverView o = MainWindow.get_overview ();+ GlyphRange gr = new GlyphRange ();+ gr.use_full_unicode_range ();+ o.set_glyph_range (gr);+ MainWindow.get_tab_bar ().select_tab_name ("Overview");+ });+ characterset_tools.add_tool (full_unicode);++ Tool custom_character_set = new Tool ("custom_character_set", _("Show default characters set"), 'r', CTRL);+ custom_character_set.select_action.connect ((self) => {+ MainWindow.get_tab_bar ().add_unique_tab (new OverView (), 100, false);+ OverView o = MainWindow.get_overview ();+ GlyphRange gr = new GlyphRange ();+ gr.use_default_range ();+ o.set_glyph_range (gr);+ MainWindow.get_tab_bar ().select_tab_name ("Overview");+ });+ characterset_tools.add_tool (custom_character_set);++ Tool avalilable_characters = new Tool ("available_characters", _("Show all characters in font"), 'd', CTRL);+ avalilable_characters.select_action.connect ((self) => {+ MainWindow.get_tab_bar ().add_unique_tab (new OverView (), 100, false);+ OverView o = MainWindow.get_overview ();+ o.display_all_available_glyphs ();+ MainWindow.get_tab_bar ().select_tab_name ("Overview");+ });+ characterset_tools.add_tool (avalilable_characters);++ Tool delete_glyph = new Tool ("delete_selected_glyph", _("Delete selected glyph"));+ delete_glyph.select_action.connect ((self) => {+ OverView o = MainWindow.get_overview ();+ + if (MainWindow.get_current_display () is OverView) {+ o.delete_selected_glyph ();+ }+ + MainWindow.get_tab_bar ().select_tab_name ("Overview");+ });+ characterset_tools.add_tool (delete_glyph);+ + if (Supplement.has_argument ("--test")) {+ Tool test_case = new Tool ("test_case");+ test_case.select_action.connect((self) => {+ if (self.is_selected ()) {+ if (TestSupplement.is_running ()) {+ TestSupplement.pause ();+ } else {+ TestSupplement.continue ();+ }+ }+ });+ test_tools.add_tool (test_case);++ Tool slow_test = new Tool ("slow_test");+ slow_test.select_action.connect((self) => {+ bool s = TestSupplement.is_slow_test ();+ TestSupplement.set_slow_test (!s);+ s = TestSupplement.is_slow_test ();+ self.set_selected (s);+ });+ + test_tools.add_tool (slow_test);+ + // Run from commad line+ string? st = Supplement.get_argument ("--test");+ if (st != null && ((!)st).char_count () > 0) {+ IdleSource idle = new IdleSource ();++ idle.set_callback (() => { + select_tool (test_case);+ return false;+ });+ + idle.attach (null);+ }+ + if (Supplement.has_argument ("--slow")) {+ select_tool (slow_test);+ }+ + }+ + // help lines, grid and other guidlines+ Tool help_lines = new Tool ("help_lines", "Show help lines", 'l');+ help_lines.select_action.connect ((self) => {+ bool h;+ h = glyph_canvas.get_current_glyph ().get_show_help_lines ();+ glyph_canvas.get_current_glyph ().set_show_help_lines (!h);+ self.set_selected (!h);+ glyph_canvas.get_current_glyph ().redraw_help_lines ();+ }); ++ guideline_tools.add_tool (help_lines);++ Tool xheight_help_lines = new Tool ("show_xheight_helplines", _("Show help lines for x-height and baseline"), 'x');+ xheight_help_lines.select_action.connect ((self) => {+ Glyph g = MainWindow.get_current_glyph ();+ bool v = !g.get_xheight_lines_visible ();+ g.set_xheight_lines_visible (v);+ self.set_selected (v);+ MainWindow.get_glyph_canvas ().redraw ();+ + if (v && !help_lines.is_selected ()) {+ select_tool (help_lines);+ }+ + });+ guideline_tools.add_tool (xheight_help_lines);++ Tool background_help_lines = new Tool ("background_help_lines", _("Show help lines at top and bottom margin"), 't');+ background_help_lines.select_action.connect ((self) => {+ Glyph g = MainWindow.get_current_glyph ();+ bool v = !g.get_margin_lines_visible ();+ g.set_margin_lines_visible (v);+ self.set_selected (v);+ MainWindow.get_glyph_canvas ().redraw ();+ + if (v && !help_lines.is_selected ()) {+ select_tool (help_lines);+ }+ });+ guideline_tools.add_tool (background_help_lines);++ Tool new_grid = new GridTool ("new_grid");+ guideline_tools.add_tool (new_grid);++ // Zoom tools + Tool zoom_in = new Tool ("zoom_in", _("Zoom in"), '+', CTRL);+ zoom_in.select_action.connect ((self) => {+ zoom_tool.store_current_view ();+ glyph_canvas.get_current_display ().zoom_in ();+ });+ view_tools.add_tool (zoom_in);++ Tool zoom_out = new Tool ("zoom_out", _("Zoom out"), '-', CTRL);+ zoom_out.select_action.connect ((self) => {+ zoom_tool.store_current_view ();+ glyph_canvas.get_current_display ().zoom_out ();+ });+ view_tools.add_tool (zoom_out);++ Tool reset_zoom = new Tool ("zoom_1_1", _("Zoom to scale 1:1"), '0', CTRL);+ reset_zoom.select_action.connect ((self) => {+ zoom_tool.store_current_view ();+ glyph_canvas.get_current_display ().reset_zoom ();+ glyph_canvas.redraw_area(0, 0, glyph_canvas.allocation.width, glyph_canvas.allocation.height);+ });+ view_tools.add_tool (reset_zoom);++ Tool full_glyph = new Tool ("full_glyph", _("Show full glyph"));+ full_glyph.select_action.connect((self) => {+ zoom_tool.store_current_view ();+ zoom_tool.zoom_full_glyph ();+ });+ view_tools.add_tool (full_glyph);++ Tool zoom_boundries = new Tool ("zoom_boundries", _("Zoom in at region boundries"), 'v');+ zoom_boundries.select_action.connect((self) => {+ zoom_tool.store_current_view ();+ glyph_canvas.get_current_display ().zoom_max ();+ });+ view_tools.add_tool (zoom_boundries);++ Tool zoom_bg = new Tool ("zoom_background_image", _("Zoom in background image"), 'b');+ zoom_bg.select_action.connect((self) => {+ if (MainWindow.get_current_glyph ().get_background_image () != null) {+ zoom_tool.store_current_view (); + glyph_canvas.get_current_display ().reset_zoom ();+ + zoom_tool.zoom_full_background_image ();+ + glyph_canvas.redraw_area(0, 0, glyph_canvas.allocation.width, glyph_canvas.allocation.height);+ }+ });+ view_tools.add_tool (zoom_bg);++ Tool zoom_prev = new Tool ("prev", _("Previous view"), 'j', CTRL);+ zoom_prev.select_action.connect((self) => {+ zoom_tool.previous_view ();+ });+ view_tools.add_tool (zoom_prev);++ Tool zoom_next = new Tool ("next", _("Next view"), 'l', CTRL);+ zoom_next.select_action.connect((self) => {+ zoom_tool.next_view ();+ });+ view_tools.add_tool (zoom_next);+ + // background tools+ background_scale = new SpinButton ("scale_background", _("Set size for background image"));+ background_scale.set_value_round (1);+ + background_scale.new_value_action.connect((self) => {+ background_scale.select_action (self);+ });+ + background_scale.select_action.connect((self) => {+ SpinButton sb = (SpinButton) self;+ Glyph g = MainWindow.get_current_glyph ();+ GlyphBackgroundImage? img = g.get_background_image ();+ double s = sb.get_value ();+ + if (img != null) {+ ((!)img).set_img_scale (s, s);+ }+ + MainWindow.get_glyph_canvas ().redraw ();+ });+ + background_tools.add_tool (background_scale); ++ BackgroundTool move_background = new BackgroundTool ("move_background"); + background_tools.add_tool (move_background);+ this.move_background = move_background;+ + CutBackgroundTool cut_background = new CutBackgroundTool ("cut_background");+ background_tools.add_tool (cut_background);+ this.cut_background = cut_background;+ + Tool show_bg = new Tool ("show_background", _("Show/hide background image"));+ show_bg.select_action.connect ((self) => {+ Glyph g = MainWindow.get_current_glyph ();+ g.set_background_visible (!g.get_background_visible ());+ MainWindow.get_glyph_canvas ().redraw ();+ });+ background_tools.add_tool (show_bg);++ Tool bg_selection = new Tool ("insert_background", _("Insert a new background image"));+ bg_selection.select_action.connect((self) => {+ Glyph? g = null;+ FontDisplay fd = MainWindow.get_current_display ();+ TooltipArea tp = MainWindow.get_tool_tip ();+ tp.show_text (_("Creating thumbnails"));+ + bg_selection.yield ();+ + if (fd is Glyph) {+ g = (Glyph) fd;+ } else {+ return;+ }+ + BackgroundSelection bgs = new BackgroundSelection ();+ MainWindow.get_tab_bar ().add_unique_tab (bgs , 120, false);+ + tp.show_text ("");+ });+ + bg_selection.set_show_background (true);+ background_tools.add_tool (bg_selection);+ + SpinButton background_contrast = new SpinButton ("background_contrast", _("Set contrast for background image"));+ background_contrast.set_value_round (1);++ background_contrast.new_value_action.connect ((self) => {+ background_contrast.select_action (self);+ });+ + background_contrast.select_action.connect ((self) => { + Glyph g = MainWindow.get_current_glyph ();+ GlyphBackgroundImage? bg = g.get_background_image ();+ GlyphBackgroundImage b;+ + if (bg != null) {+ b = (!) bg;+ b.set_contrast (background_contrast.get_value ());+ }+ });+ + // Fixa: background_tools.add_tool (background_contrast);++ SpinButton background_threshold = new SpinButton ("background_threshold", _("Set threshold"));+ background_threshold.set_value_round (1);++ background_threshold.new_value_action.connect ((self) => {+ background_threshold.select_action (self);+ });+ + background_threshold.select_action.connect ((self) => { + Glyph g = MainWindow.get_current_glyph ();+ GlyphBackgroundImage? bg = g.get_background_image ();+ GlyphBackgroundImage b;+ + if (bg != null) {+ b = (!) bg;+ b.set_threshold (background_threshold.get_value ());+ }+ + });+ trace.add_tool (background_threshold);+ + draw_tools.set_open (true);+ draw_tool_modifiers.set_open (true);+ path_tool_modifiers.set_open (true);+ view_tools.set_open (true);+ grid.set_open (true);+ characterset_tools.set_open (true);+ test_tools.set_open (true);+ guideline_tools.set_open (true);+ background_tools.set_open (true);+ trace.set_open (true);+ + add_expander (draw_tools);+ add_expander (draw_tool_modifiers);+ add_expander (path_tool_modifiers); + + add_expander (characterset_tools);+ add_expander (guideline_tools);+ add_expander (grid);+ add_expander (view_tools);+ add_expander (background_tools);+ + // Fixa: add_expander (trace);+ + add_expander (test_tools);+ + draw_tools.set_persistent (true);+ draw_tools.set_unique (true);+ + draw_tool_modifiers.set_persistent (true);+ draw_tool_modifiers.set_unique (true);++ path_tool_modifiers.set_persistent (false);+ path_tool_modifiers.set_unique (false);++ characterset_tools.set_persistent (true);+ characterset_tools.set_unique (true);+ + test_tools.set_persistent (true);+ + guideline_tools.set_persistent (true);+ guideline_tools.set_unique (false);+ + grid.set_persistent (true);+ grid.set_unique (true);+ grid.set_open (false);+ + background_tools.set_persistent (true);+ background_tools.set_unique (true);+ + trace.set_persistent (false);+ trace.set_unique (false);++ update_expanders ();+ reset_active_tool ();++ // Default selection+ var idle = new IdleSource();+ idle.set_callback (() => {+ new_point.set_selected (true);+ pen_tool.set_selected (true);+ select_draw_tool (); + + if (glyph_canvas.get_current_glyph ().get_show_help_lines ()) {+ help_lines.set_selected (true);+ help_lines.set_active (false);+ }++ add_new_grid ();+ add_new_grid ();+ + move (0, 0);+ + return false;+ });+ + idle.attach (null);+ }+ + public void key_press (uint keyval) {+ print ("key_press\n");+ foreach (var exp in MainWindow.get_toolbox ().expanders) {+ foreach (Tool t in exp.tool) {+ t.set_active (false);+ + if (t.key == keyval && t.modifier_flag == NONE) {+ MainWindow.get_toolbox ().select_tool (t);+ }+ }+ }+ }+ + public void press (uint button, double x, double y) {+ foreach (var exp in expanders) {+ foreach (Tool t in exp.tool) {+ if (t.is_over (x, y)) {+ t.panel_press_action (t, button, x, y);+ press_tool = t;+ }+ }+ }+ }+ + public void release (uint button, double x, double y) {+ foreach (var exp in expanders) {+ if (exp.is_over (x, y)) {+ exp.set_open (! exp.is_open ()); + update_expanders (); + redraw ((int) exp.x - 10, (int) exp.y - 10, allocation_width, (int) (allocation_height - exp.y + 10));+ }+ + if (exp.is_open ()) {+ foreach (Tool t in exp.tool) {+ bool active = t.is_over (x, y);+ + if (active) {+ if (press_tool == t) {+ select_tool (t);+ }+ }+ + t.panel_release_action (t, button, x, y);+ }+ }+ }+ }+ + private void select_draw_tool () {+ select_tool_by_name ("pen_tool");+ }+ + public void move (double x, double y) {+ bool update;+ bool a;+ foreach (var exp in expanders) {+ a = exp.is_over (x, y);+ update = exp.set_active (a);+ + if (update) {+ MainWindow.get_toolbox ().redraw ((int) exp.x - 10, (int) exp.y - 10, (int) (exp.x + exp.w + 10), (int) (exp.y + exp.h + 10));+ }+ + if (exp.is_open ()) {+ foreach (Tool t in exp.tool) {+ bool active = t.is_over (x, y);+ TooltipArea? tpa = null;+ + update = t.set_active (active);+ tpa = MainWindow.get_tool_tip ();+ + if (active && tpa != null) {+ ((!)tpa).update_text ();+ }+ + if (update) {+ MainWindow.get_toolbox ().redraw (0, 0, allocation_width, allocation_height);+ }+ + t.panel_move_action (t, x, y);+ }+ }+ }+ }+ + public void remove_all_grid_buttons () {+ while (grid_expander.tool.length () > 0) {+ grid_expander.tool.remove_link (grid_expander.tool.last ());+ }+ + while (GridTool.sizes.length () > 0) {+ GridTool.sizes.remove_link (GridTool.sizes.first ());+ }+ + grid_expander.set_open (true);+ update_expanders ();+ redraw (0, 0, allocation_width, allocation_height); + }+ + public void parse_grid (string spin_button_value) {+ SpinButton sb = add_new_grid ();+ sb.set_value (spin_button_value);+ select_tool (sb);+ }++ public SpinButton add_new_grid () {+ SpinButton grid_width = new SpinButton ("grid_width", _("Set size for grid"));+ + grid_width.new_value_action.connect((self) => {+ grid_width.select_action (grid_width);+ });++ grid_width.select_action.connect((self) => {+ SpinButton sb = (SpinButton) self;+ GridTool.set_grid_width (sb.get_value ());+ MainWindow.get_glyph_canvas ().redraw ();+ });+ + grid_expander.add_tool (grid_width);++ GridTool.sizes.append (grid_width);++ grid_expander.set_open (true);+ update_expanders ();+ + redraw (0, 0, allocation_width, allocation_height);+ + select_tool (grid_width);+ grid_width.set_active (false);+ + return grid_width;+ }++ public void remove_current_grid () {+ Tool grid_width;+ + foreach (Tool t in grid_expander.tool) {+ if (t.is_selected () && t is SpinButton) {+ GridTool.sizes.remove ((SpinButton)t);+ grid_expander.tool.remove (t);+ break;+ }+ }+ + if (grid_expander.tool.length () > 0) {+ grid_width = grid_expander.tool.last ().data;+ select_tool (grid_width);+ grid_width.set_active (false);+ }+ + update_expanders ();+ redraw (0, 0, allocation_width, allocation_height);+ }++ public void reset_active_tool () {+ foreach (var exp in expanders) {+ foreach (Tool t in exp.tool) {+ t.set_active (false);+ }+ }+ }++ public Tool? get_active_tool () {+ foreach (var exp in expanders) {+ foreach (Tool t in exp.tool) {+ if (t.is_active ()) {+ return t;+ }+ }+ }+ + return null;+ }++ public Tool get_current_tool () {+ return current_tool;+ }+ + public void select_tool (Tool tool) {+ foreach (var exp in expanders) {+ + foreach (Tool t in exp.tool) {+ if (tool.get_id () == t.get_id ()) {+ exp.set_open (true);+ + bool update;+ + update = tool.set_selected (true);+ update = tool.set_active (true);+ + tool.select_action (tool); // execute command+ + if (update) { + redraw ((int) exp.x - 10, (int) exp.y - 10, allocation_width, (int) (allocation_height - exp.y + 10));+ }+ + if (exp == draw_tools || t == move_background || t == cut_background) {+ current_tool = tool;+ }+ }+ }+ + }+ }+ + public Tool get_tool (string name) {+ foreach (var e in expanders) {+ foreach (var t in e.tool) {+ if (t.get_name () == name) {+ return t;+ }+ }+ }++ if (name == cut_background.get_name ()) {+ return cut_background;+ }+ + warning ("No tool found for name \"%s\".\n", name);+ + return new Tool ("no_icon");+ }+ + public static void select_tool_by_name (string name) {+ Toolbox b = MainWindow.get_toolbox ();+ b.select_tool (b.get_tool (name));+ }+ + private void update_expanders () {+ Expander? p = null; + Expander pp;+ foreach (var e in expanders) {+ if (p != null) {+ pp = (!) p;+ e.set_offset (pp.y + pp.margin + 12);+ }+ + p = e;+ }+ }+ + private void add_expander (Expander e) {+ expanders.append (e);+ }+ + private void draw_expanders (int w, int h, Context cr) {+ foreach (Expander e in expanders) {+ e.draw (w, h, cr);+ if (e.is_open ()) {+ e.draw_content (w, h, cr);+ }+ }+ }+ + public void draw (int w, int h, Context cr) { + cr.save ();+ + cr.rectangle(0, 0, w, h);+ cr.set_line_width(0);+ cr.set_source_rgba(255/255.0, 255/255.0, 255/255.0, 1);+ cr.fill();++ for (int i = 0; i < w; i++) {+ cr.set_source_surface ((!) toolbox_background, i, 0);+ cr.paint ();+ }++ cr.rectangle(0, 0, 1, h);+ cr.set_line_width(0);+ cr.set_source_rgba(0/255.0, 0/255.0, 0/255.0, 1);+ cr.fill();+ + draw_expanders (w, h, cr);+ + cr.restore ();+ }+}++class ToolboxExpander : Expander {+ + public ToolboxExpander () {+ }+ + public override bool set_open (bool o) {+ bool r = (open != o);+ rotation = (o) ? 0 : Math.PI;+ open = o;+ return r;+ }+}++}new file mode 100644@@ -0,0 +1,122 @@+/*+ 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;+using Gdk;+using Gtk;++namespace Supplement {++public class TooltipArea : GLib.Object {++ string tool_tip;+ public signal void redraw ();++ public TooltipArea () {+ set_text_from_tool ();+ }++ public void update_text () {+ set_text_from_current_tool ();+ redraw ();+ }+ + public void show_text (string text) {+ tool_tip = text;+ redraw ();+ }+ + public void set_text_from_tool () {+ set_text_from_current_tool ();+ redraw ();+ }+ + private void set_text_from_current_tool () {+ Toolbox? tb = MainWindow.get_toolbox ();+ Tool? t;+ Tool tool;+ StringBuilder sb;+ + if (tb == null) {+ return;+ }+ + t = ((!)tb).get_active_tool ();+ + if (t == null) {+ return;+ }+ + tool = (!) t;+ + if (tool.key != '\0') {+ sb = new StringBuilder ();++ sb.append ("(");+ + if (tool.modifier_flag == CTRL) {+ sb.append ("Ctrl+");+ } else if (tool.modifier_flag == SHIFT) {+ sb.append ("Shift+");+ } ++ sb.append_unichar (tool.key);+ sb.append (") ");+ sb.append (tool.get_tip ());+ + show_text (sb.str);+ } else {+ show_text (tool.get_tip ());+ }+ + }+ + public void draw (Context cr, Allocation alloc) {+ cr.save ();+ cr.rectangle (0, 0, alloc.width, alloc.height);+ cr.set_line_width (0);+ cr.set_source_rgba (183/255.0, 200/255.0, 223/255.0, 1);+ cr.fill_preserve ();+ cr.stroke ();+ cr.restore ();++ cr.save ();+ cr.rectangle (0, 0, alloc.width, 1);+ cr.set_line_width (0);+ cr.set_source_rgba (107/255.0, 127/255.0, 168/255.0, 1);+ cr.fill_preserve ();+ cr.stroke ();+ cr.restore ();+ + cr.save ();+ cr.rectangle (0, 1, alloc.width, 1);+ cr.set_line_width (0);+ cr.set_source_rgba (145/255.0, 160/255.0, 190/255.0, 1);+ cr.fill_preserve ();+ cr.stroke ();+ cr.restore ();+ + cr.save ();+ cr.set_font_size (14);+ cr.move_to (5, 15);+ cr.show_text (tool_tip);+ cr.restore ();++ }+}++}new file mode 100644@@ -0,0 +1,49 @@+/*+ 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/>.+*/++namespace Supplement {+ +public class UniRange : GLib.Object {+ + public unichar start;+ public unichar stop;+ + public UniRange (unichar start, unichar stop) {+ this.start = start;+ this.stop = stop;+ }+ + public unichar length () {+ return stop - start + 1;+ }++ public unichar get_char (unichar index) {+ unichar result = start + index;+ + if (unlikely (index < 0)) {+ warning ("Index is negative in UniRange.");+ }+ + if (unlikely (!(start <= result <= stop))) {+ warning ("Index out of range in UniRange (%u <= %u <= %u) (index: %u)\n", start, result, stop, index);+ }+ + return result;+ }+}++}new file mode 100644@@ -0,0 +1,52 @@+/*+ 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/>.+*/++namespace Supplement {++/** User interface action for unit testing and undo command.+ */+interface UserActions {+ public abstract void do_action ();+ public abstract void undo ();+ public abstract void reundo ();+ public abstract void test ();+}++interface UserClickActions {+ public abstract void do_click (int button, int x, int y);+}++class NewPointAction : UserActions, UserClickActions {++ public void do_action () {+ }++ public void undo () {+ }+ + public void reundo () {+ }+ + public void do_click (int button, int x, int y) {+ }+ + public void test () {+ }+ +}++}new file mode 100644@@ -0,0 +1,113 @@+/*+ 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;+using Math;++namespace Supplement {++public class VersionList : DropMenu {+ int versions = 1;+ int current_version = 0;+ public List<Glyph> glyphs = new List<Glyph> ();+ public static bool suppress_signal = false;++ public VersionList (Glyph? g = null) {+ base ("version");+ set_direction (MenuDirection.POP_UP);+ + MenuAction ma = add_item (_("New version"));+ ma.action = (self) => {+ return_if_fail (self.parent != null);+ return_if_fail (glyphs.length () > 0);+ + Supplement.get_current_font ().touch ();+ + add_new_version ();+ current_version = (int) glyphs.length () - 1;+ };+ + if (g != null) {+ add_glyph ((!) g);+ }+ }+ + public static void set_suppress_signal (bool s) {+ suppress_signal = s;+ }+ + public Glyph get_current () {+ if (unlikely (current_version >= glyphs.length ())) {+ warning (@"current_version >= glyphs.length ($current_version >= $(glyphs.length ()))");+ return new Glyph ("");+ }+ + return glyphs.nth (current_version).data;+ }++ public void add_new_version () {+ Glyph g = get_current ();+ Glyph new_version = g.copy ();+ add_glyph (new_version);+ }+ + private void set_selected_item (MenuAction ma) {+ int i = ma.index;+ + return_if_fail (0 <= i < glyphs.length ());++ current_version = i;+ + return_if_fail (ma.parent != null);+ + ((!)ma.parent).deselect_all ();+ ma.set_selected (true); + + if (!is_null (MainWindow.get_tab_bar ())) {+ MainWindow.get_tab_bar ().close_by_name (get_current ().name);+ } + }+ + public void add_glyph (Glyph new_version, bool selected = true) {+ MenuAction ma;+ + versions++;+ glyphs.append (new_version);++ ma = add_item (_("Version") + @" $(versions - 1)");+ ma.index = (int) glyphs.length () - 1;+ + ma.action = (self) => {+ Font font;+ + if (suppress_signal) {+ return;+ }+ + font = Supplement.get_current_font ();+ set_selected_item (self);+ font.touch ();+ };++ if (selected && !suppress_signal) {+ set_selected_item (ma);+ }+ }++}++}new file mode 100644@@ -0,0 +1,157 @@+/*+ 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 Gtk;++namespace Supplement {++class ZoomTool : Tool {++ int zoom_area_begin_x = -1;+ int zoom_area_begin_y = -1;+ + int view_index = 0;+ List<Tab> views = new List<Tab> ();+ + public ZoomTool (string n) {+ base (n, "Zoom");++ select_action.connect((self) => {+ });++ select_action.connect((self) => {+ });+ + press_action.connect((self, b, x, y) => {+ if (b == 1) {+ zoom_area_begin_x = x;+ zoom_area_begin_y = y;+ }+ });++ move_action.connect((self, x, y) => {+ if (zoom_area_begin_x > 0) {+ Glyph g = MainWindow.get_current_glyph ();+ g.show_zoom_area (zoom_area_begin_x, zoom_area_begin_y, x, y);+ }+ });+ + release_action.connect((self, b, x, y) => {+ Glyph g;+ + if (b == 1) {+ store_current_view ();+ + g = MainWindow.get_current_glyph ();+ + if (zoom_area_begin_x == x && zoom_area_begin_y == y) { // zero width center this point but don't zoom in+ g.set_center (x, y);+ } else { + g.set_zoom_from_area ();+ }+ + zoom_area_begin_x = -1;+ zoom_area_begin_y = -1;+ }+ });+ }++ public void zoom_full_background_image () {+ GlyphBackgroundImage bg;+ Glyph g = MainWindow.get_current_glyph ();+ int x, y;+ + if (g.get_background_image () == null) {+ return;+ }+ + bg = (!) g.get_background_image ();+ + x = (int) (bg.img_offset_x);+ y = (int) (bg.img_offset_y);+ + g.set_zoom_area (x, y, (int) (x + bg.size_margin * bg.img_scale_x), (int) (y + bg.size_margin * bg.img_scale_y));+ g.set_zoom_from_area ();+ }++ public void zoom_full_glyph () {+ store_current_view ();+ + MainWindow.get_current_display ().zoom_min ();+ }+ + public override bool test () {+ test_select_action ();+ return true;+ }+ + /** Add an item to zoom view list. */+ public void store_current_view () { + if (views.length () - 1 > view_index) {+ unowned List<Tab> i = views.nth (view_index + 1);+ while (i != i.last ()) {+ i.delete_link (i.next);+ }+ }+ + views.append (MainWindow.get_current_tab ());+ view_index = (int)views.length () - 1;+ MainWindow.get_current_display ().store_current_view ();+ }++ /** Redo last zoom.*/+ public void next_view () {+ if (view_index + 1 >= (int) views.length ()) {+ return;+ }+ + view_index++;+ + MainWindow.select_tab (views.nth (view_index).data);+ MainWindow.get_current_display ().next_view ();+ MainWindow.get_glyph_canvas ().redraw ();+ }+ + /** Undo last zoom. */+ public void previous_view () {+ if (view_index == 0) {+ return;+ }+ + view_index--;+ + MainWindow.select_tab (views.nth (view_index).data);+ MainWindow.get_current_display ().restore_last_view ();+ MainWindow.get_glyph_canvas ().redraw ();+ }+}++class ZoomView : GLib.Object {+ public double x;+ public double y;+ public double zoom;+ public Allocation allocation;+ + public ZoomView (double x, double y, double zoom, Allocation allocation) {+ this.x = x;+ this.y = y;+ this.zoom = zoom;+ this.allocation = allocation;+ }+}+ +}new file mode 100644@@ -0,0 +1,92 @@+BIRDFONT = [+ 'Allocation.vala',+ 'Argument.vala',+ 'Font.vala',+ 'BackgroundTool.vala',+ 'BackgroundSelection.vala',+ 'Birdfont.vala',+ 'EotWriter.vala',+ 'Expander.vala',+ 'ExportTool.vala',+ 'FontDisplay.vala',+ 'CharDatabase.vala',+ 'Config.vala',+ 'Context.vala',+ 'CutBackgroundTool.vala',+ 'CutTool.vala',+ 'ClipTool.vala',+ 'DropMenu.vala',+ 'EditPoint.vala',+ 'EditPointHandle.vala',+ 'Glyph.vala',+ 'GlyphBackgroundImage.vala',+ 'GlyphCanvas.vala',+ 'GlyphCollection.vala',+ 'GlyphRange.vala',+ 'GlyphTable.vala',+ 'GridTool.vala',+ 'Icons.vala',+ 'Kerning.vala', + 'KeyBindings.vala', + 'Line.vala',+ 'MainWindow.vala',+ 'MenuAction.vala',+ 'MenuTab.vala',+ 'MergeTool.vala',+ 'MoveTool.vala',+ 'NativeWindow.vala',+ 'Intersection.vala',+ 'IntersectionList.vala',+ 'IOThread.vala',+ 'OpenFontFormatWriter.vala',+ 'OpenFontFormatReader.vala',+ 'OverView.vala',+ 'Path.vala',+ 'PenTool.vala',+ 'Preview.vala',+ 'ResizeTool.vala',+ 'SaveDialog.vala',+ 'Scrollbar.vala',+ 'ShrinkTool.vala',+ 'SpinButton.vala',+ 'Supplement.vala',+ 'Svg.vala',+ 'SvgFontFormatWriter.vala',+ 'Preferences.vala',+ 'Tab.vala',+ 'TabBar.vala',+ 'TestCases.vala', + 'TestSupplement.vala', + 'Tool.vala',+ 'Toolbox.vala',+ 'TooltipArea.vala',+ 'UniRange.vala',+ 'VersionList.vala',+ 'ZoomTool.vala',+]++PARAM = [+ '-X -D\'GETTEXT_PACKAGE="birdfont"\' '+ '--thread',+ '--disable-warnings',+ '--enable-experimental',+ '--enable-experimental-non-null',+ '--vapidir=./',+]++def build (bld):++ bld.shlib (+ target = 'birdfont',+ param = PARAM,+ packages = 'libxml-2.0',+ uselib = 'CAIRO GLIB GIO PIXBUF XML',+ source = BIRDFONT,+ name = "libbirdfont",+ )++ bld (+ rule = "mv libbirdfont/birdfont.h ./",+ depends = "libbirdfont",+ name = "birdfont_h"+ )deleted file mode 100644@@ -1,27 +0,0 @@-/*- 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/>.-*/--namespace Supplement {--public struct Allocation {- public int width;- public int height;- public int x;- public int y;-}--}deleted file mode 100644@@ -1,241 +0,0 @@-/*- 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/>.-*/--namespace Supplement {- -public class Argument : GLib.Object {- - List<string> args;- - public Argument (string line) {- args = new List<string> ();- set_argument (line);- }- - public Argument.command_line (string[] arg) { - args = new List<string> ();-- foreach (string a in arg) {- args.append (a);- }- }- - /** returns 0 if valid or index the bad parameter. */ - public int validate () {- string prev = "";- int i = 0;- foreach (string a in args) {- - // program name- if (i == 0) {- prev = a;- i++;- continue;- }- - // file name- if (i == 1 && !a.has_prefix ("-")) {- prev = a;- i++;- continue;- }-- // a single character, like -t- if (!a.has_prefix ("--") && a.has_prefix ("-")) {- a = expand_param (a);- }- - // valid parameter- if (a == "--exit" || a == "--slow" || a == "--help" || a == "--test" || a == "--fatal-warning" || a == "--show-coordinates") {- prev = a;- i++;- continue;- } else if (a.has_prefix ("--")) {- return i;- }- - // not argument to parameter- if (!(prev == "--test")) {- return i;- }- - prev = a;- i++;- }- - return 0;- }- - /** Return the font file parameter. */- public string get_file () {- string f = "";- - if (args.length () >= 2) {- f = args.nth (1).data;- }-- if (f.has_prefix ("-")) {- return "";- }- - return f;- }- - public void print_all () {- print (@"$(args.length ()) arguments:\n");- - foreach (string p in args) {- print (@"$p\n");- }- }- - public bool has_argument (string param) {- return (get_argument (param) != null);- }- - /** Get commandline argument. */- public string? get_argument (string param) {- int i = 0;- string? n;- string p;-- if (param.substring (0, 1) != "-") {- warning (@"parameters must begin with \"-\" got $param");- return null;- }-- foreach (string s in args) {-- // this is content not a parameter - if (s.substring (0, 1) != "-") continue;-- // we might need to expand -t to test fo instance- if (s.substring (0, 2) != "--") {- p = expand_param (s);- } else { - p = s;- }- - if (param == p) {- if (i + 2 >= args.length ()) {- return "";- }- - n = args.nth (i + 2).data;- if (n == null) {- return "";- }- - if (args.nth (i + 2).data.substring (0, 1) == "-") {- return "";- }- - return args.nth (i + 2).data;- }- - i++;- }- - return null;- }-- private void print_padded (string cmd, string desc) {- int l = 25 - cmd.char_count ();-- stdout.printf (cmd);- - for (int i = 0; i < l; i++) {- stdout.printf (" ");- }- - stdout.printf (desc);- stdout.printf ("\n");- }-- /** Return full command line parameter for the abbrevation.- * -t becomes --test.- */- private string expand_param (string? param) {- if (param == null) return "";- var p = (!) param;- - if (p.length == 0) return "";- if (p.get_char (0) != '-') return "";- if (p.char_count () != 2) return "";- - switch (p.get_char (1)) {- case 'c':- return "--show-coordinates";- case 'e': - return "--exit";- case 'f': - return "--fatal-warning";- case 'h': - return "--help";- case 's': - return "--slow";- case 't': - return "--test";- }- - return "";- }-- private void set_argument (string arg) {- int i = 0;- int a;- string n;- - if (arg.char_count () <= 1) {- return;- }- - do {- a = arg.index_of (" ", i + 1);- n = arg.substring (i, a - i);- - if (n.index_of ("\"") == 0) {- a = arg.index_of ("\"", i + 1);- n = arg.substring (i, a - i + 1);- }- - args.append (n);- - i += n.char_count () + 1;- } while (i < arg.char_count ());- }-- public void print_help () - requires (args.length () > 0)- {- stdout.printf (_("Usage") + ": ");- stdout.printf (args.nth (0).data);- stdout.printf (" [" + _("FILE") + "] [" + _("OPTION") + " ...]\n");-- print_padded ("-c, --show-coordinates", _("show coordinate in glyph view"));- print_padded ("-e, --exit", _("exit if a test case failes"));- print_padded ("-f, --fatal-warning", _("treat warnings as fatal"));- print_padded ("-h, --help", _("show this message"));- print_padded ("-s, --slow", _("sleep between each command in test suite"));- print_padded ("-t, --test [TEST]", _("run test case"));- - stdout.printf ("\n");- }--}- -}deleted file mode 100644@@ -1,302 +0,0 @@-/*- 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;-using Gdk;--namespace Supplement {--class BackgroundSelection : FontDisplay {-- int active_box = -1;- ImageSurface add_icon;- List <string> background_images = new List <string> ();- Allocation allocation;- Glyph glyph;- - public BackgroundSelection () {- ImageSurface? i;- glyph = MainWindow.get_current_glyph ();- - i = Icons.get_icon ("add_background_image.png");- return_if_fail (i != null);- add_icon = (!) i;- }-- public override string get_name () {- return "Backgrounds";- }-- public override void draw (Allocation allocation, Context cr) { - double x, y, zoom;- int box_index = 0;- Font font;-- this.allocation = allocation;-- // bg color- cr.save ();- cr.set_source_rgba (255/255.0, 255/255.0, 255/255.0, 1);- cr.rectangle (0, 0, allocation.width, allocation.height);- cr.fill ();- cr.restore ();- - draw_box (box_index, 20, 20, cr);- cr.set_source_surface (add_icon, 62, 62);- cr.paint ();- - cr.save ();- x = 160;- y = 20;- zoom = 0.04;- - font = Supplement.get_current_font ();- foreach (string file in background_images) {- box_index++;-- if (x > allocation.width - 140) {- x = 20;- y += 140;- }-- draw_box (box_index, x, y, cr);- draw_thumbnail (file, x, y, cr, box_index);- - x += 140; - }- cr.restore ();- - }- - private ImageSurface create_thumbnail (string file) throws GLib.Error {- Pixbuf pixbuf;- - Font font = Supplement.get_current_font ();- File folder = font.get_backgrounds_folder ();- string fn = Checksum.compute_for_string (ChecksumType.SHA1, file);- File original = File.new_for_path (file);- File thumbnail_path = folder.get_child (@"thumbnail");- File png_image = thumbnail_path.get_child (@"$(fn).png");- - if (!thumbnail_path.query_exists ()) {- DirUtils.create ((!) thumbnail_path.get_path (), 0xFFFFFF);- }- - if (!png_image.query_exists ()) {- pixbuf = new Pixbuf.from_file_at_scale ((!) original.get_path (), 100, 100, true);- if (!pixbuf.save ((!) png_image.get_path (), "png")) {- warning (@"can't save $((!) png_image.get_path ())");- }- }- - return new ImageSurface.from_png ((!) png_image.get_path ());- }- - private bool draw_thumbnail (string file, double x, double y, Context cr, int box_index) {- ImageSurface img;- - try {- img = create_thumbnail (file);- - if (img.status () == Cairo.Status.SUCCESS) {- cr.set_source_surface (img, x + 10, y + 10);- cr.paint ();- }- } catch (GLib.Error e) {- warning (e.message);- return false;- }- - return true; - }- - private void draw_box (int index, double x, double y, Context cr) {- cr.save ();- cr.set_line_join (LineJoin.ROUND);- cr.set_line_width (7);- - if (index == active_box) {- cr.set_source_rgba (203/255.0, 220/255.0, 249/255.0, 1);- } else {- cr.set_source_rgba (183/255.0, 200/255.0, 223/255.0, 1);- }- - cr.rectangle (x, y, 120, 120);- cr.fill_preserve ();- cr.stroke ();- - cr.restore (); - }- - public override void button_release (int button, double ex, double ey) {- motion (ex, ey);- - if (active_box == 0) {- add_image ();- } else {- select_image ();- }- }-- private void add_image () {- string? file;-- file = MainWindow.file_chooser ("Add image");-- if (file != null) {- Supplement.get_current_font ().add_background_image ((!) file);- }- - MainWindow.get_glyph_canvas ().redraw (); - - collect_background_images ();- }-- private void select_image () {- Font font;- string file;- GlyphBackgroundImage bg;- TabBar tb;- Tool zoom_background;-- font = Supplement.get_current_font ();- - if (!(0 <= active_box - 1 < background_images.length ())) {- return;- }- - file = background_images.nth (active_box - 1).data; // Fixa: check bounds- bg = new GlyphBackgroundImage (file);- tb = MainWindow.get_tab_bar ();- - bg.reset_scale (glyph);- - glyph.set_background_visible (true);- - MainWindow.get_current_glyph ().set_background_image (bg);- tb.select_tab_name (MainWindow.get_current_glyph ().get_name ());-- zoom_background = MainWindow.get_tool ("zoom_background_image");- zoom_background.select_action (zoom_background);-- MainWindow.get_toolbox ().select_tool_by_name ("cut_background");- }-- private void add_unique_background (string file) {- foreach (var f in background_images) {- if (f == file) {- return;- }- }- - background_images.prepend (file);- }-- public override void selected_canvas () {- Font font = Supplement.get_current_font ();- File folder = font.get_backgrounds_folder ();- - if (!folder.query_exists ()) {- DirUtils.create ((!) folder.get_path (), 0xFFFFFF);- }- - collect_background_images ();- }-- bool is_image (string file_name) {- return file_name.index_of (".png") != -1 - || file_name.index_of (".jpg") != -1- || file_name.index_of (".jpeg") != -1;- }-- void collect_background_images () {- Font font = Supplement.get_current_font ();- File directory = font.get_folder ();- FileEnumerator enumerator;- FileInfo? file_info;- FileInfo fi;- File img_file;-- while (background_images.length () > 0) {- background_images.delete_link (background_images.first ());- }- - foreach (string file in font.background_images) {- add_unique_background (file);- }- - try {- enumerator = directory.enumerate_children (FILE_ATTRIBUTE_STANDARD_NAME, 0);- - while ((file_info = enumerator.next_file ()) != null) {- fi = (!) file_info;- - if (is_image (fi.get_name ())) {- img_file = directory.get_child (fi.get_name ()); - add_unique_background ((!)img_file.get_path ());- }- - }- } catch (GLib.Error e) {- warning (e.message);- }- }- - public override void key_press (uint e) {- }-- public override void key_release (uint e) {- }- - public override void motion_notify (double x, double y) {- motion (x, y);- }- - public void motion (double x, double y) {- int active;- - active = (int) (x / 140); // column- active += (int) (y / 140) * (int) (allocation.width / 140.0); // row- - if (active != active_box) {- MainWindow.get_glyph_canvas ().redraw ();- active_box = active;- }- }-- public override void button_press (uint button, double ex, double ey) {- motion (ex, ey);- }-- public override void store_current_view () {- }- - public override void restore_last_view () {- }-- public override void next_view () {- }- - public override void scroll_wheel_up (double x, double y) {- }-- public override void scroll_wheel_down (double x, double y) {- }--}--}deleted file mode 100644@@ -1,158 +0,0 @@-/*- 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/>.-*/--namespace Supplement {--class BackgroundTool : Tool {-- double begin_x = 0;- double begin_y = 0;-- public double img_offset_x = 0;- public double img_offset_y = 0;-- public double img_width = 0;- public double img_height = 0;- - public double img_scale_x = 0;-- public static double top_limit;- public static double bottom_limit;- - bool move_bg;- bool resize_bg; // DEL- - public BackgroundTool (string name) {- base (name, _("Move, resize and rotate background image"));- - top_limit = 0;- bottom_limit = 0;-- select_action.connect((self) => {- });-- deselect_action.connect ((self) => {- });- - press_action.connect((self, b, x, y) => {- Glyph g = MainWindow.get_current_glyph ();- GlyphBackgroundImage? bg = g.get_background_image ();- GlyphBackgroundImage background;- - if (bg == null) {- return;- }- - background = (!) bg;- - g.store_undo_state ();- - background.handler_press (x, y);- - begin_x = x;- begin_y = y;- - img_offset_x = background.img_offset_x;- img_offset_y = background.img_offset_y;- - img_scale_x = background.img_scale_x;- - img_width = background.get_img ().get_width () * background.img_scale_x;- img_height = background.get_img ().get_height () * background.img_scale_y;- - move_bg = true;- });-- release_action.connect((self, b, x, y) => {- Glyph g = MainWindow.get_current_glyph ();- GlyphBackgroundImage? bg = g.get_background_image ();- - if (bg == null) {- return;- }- - img_offset_x = ((!)bg).img_offset_x;- img_offset_y = ((!)bg).img_offset_y;- - ((!)bg).handler_release (x, y);- - move_bg = false;- resize_bg = false;- });-- move_action.connect((self, x, y) => { - move (x, y);- });- - draw_action.connect ((self, cairo_context, glyph) => {- Glyph g = MainWindow.get_current_glyph ();- GlyphBackgroundImage? background_image = g.get_background_image ();- if (background_image == null) return;- - ((!) background_image).draw_handle (cairo_context, glyph);- });- }- - void move (double x, double y) {- Glyph g = MainWindow.get_current_glyph ();- GlyphBackgroundImage? background_image = g.get_background_image ();- GlyphBackgroundImage bg = (!) background_image;- - double xscale, yscale, dx, dy;-- if (background_image == null) return;- - bg.handler_move (x, y);- - if (!(move_bg || resize_bg)) return;- - dx = x - begin_x;- dy = y - begin_y;- - dx *= 1/g.view_zoom;- dy *= 1/g.view_zoom;- - dx *= PenTool.precision;- dy *= PenTool.precision;- - if (bg.selected_handle == 2) {- bg.set_img_rotation_from_coordinate (x, y);- }- - if (bg.selected_handle == 1) {- xscale = img_scale_x * ((img_width - dx) / img_width); - yscale = xscale;-- bg.set_img_scale (xscale, yscale);- bg.set_img_offset (this.img_offset_x + dx, this.img_offset_y);- } - - if (move_bg && bg.selected_handle <= 0) {- bg.set_img_offset (this.img_offset_x + dx, this.img_offset_y + dy);- }-- MainWindow.get_glyph_canvas ().redraw ();- }- - public override bool test () {- return false;- }--}--}-deleted file mode 100644@@ -1,169 +0,0 @@-/*- 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 Supplement;--namespace Birdfont {--static void print_export_help (string[] arg) {- stdout.printf (_("Usage:"));- stdout.printf (arg[0]);- stdout.printf (" [OPTION ...] FILE\n");- stdout.printf ("-h, --help " + _("print this message\n"));- stdout.printf ("-o, --output [DIRECTORY] " + _("write files to this directory\n"));- stdout.printf ("-s, --svg " + _("write svg file\n"));- stdout.printf ("-t, --ttf " + _("write ttf and eot files\n"));- stdout.printf ("\n");-}--public static int run_export (string[] arg) {- string output_directory = ".";- string file_name = "";- bool specific_formats = false; - bool write_ttf = false;- bool write_svg = false;- Supplement.Supplement supplement = new Supplement.Supplement ();- - File directory;- - if (arg.length < 2) {- print_export_help (arg);- }- - for (int i = 1; i < arg.length; i++) {-- if (arg[i] == "-f" || arg[i] == "--fatal-warnings") {- supplement.fatal_wanings = true;- return 0;- }-- if (arg[i] == "-h" || arg[i] == "--help") {- print_export_help (arg);- return 0;- }- - if ((arg[i] == "-o" || arg[i] == "--output") && i + 1 < arg.length) {- output_directory = arg[i + 1];- i++;- continue;- }-- if (arg[i] == "-s" || arg[i] == "--svg") {- write_svg = true;- specific_formats = true;- continue;- }- - if (arg[i] == "-t" || arg[i] == "--ttf") {- write_ttf = true;- specific_formats = true;- continue;- }- - if (arg[i].has_prefix ("-")) {- print_export_help (arg);- return 1;- }- - if (!arg[i].has_prefix ("-")) {- file_name = arg[i];- - if (i != arg.length - 1) {- print_export_help (arg);- return 1;- }- - break;- }- }-- if (supplement.fatal_wanings) {- LogLevelFlags levels = LogLevelFlags.LEVEL_ERROR | LogLevelFlags.LEVEL_CRITICAL | LogLevelFlags.LEVEL_WARNING;- Log.set_handler (null, levels, supplement.fatal_warning); - }-- Preferences preferences = new Preferences ();- preferences.load ();- - supplement.args = new Argument ("");- supplement.current_font = new Font ();- supplement.current_glyph = new Glyph ("");- - if (!supplement.get_current_font ().load (file_name, false)) {- stderr.printf (@"Failed to load font $file_name.\n");- - if (!file_name.has_suffix (".ffi")) {- stderr.printf (@"Is it a .ffi file?\n");- }- - return 1;- }-- directory = File.new_for_path (output_directory);- - if (!directory.query_exists ()) {- stderr.printf (_("Can't find output directory") + @"$((!)directory.get_path ())\n");- return 1;- }-- if (!specific_formats || write_svg) {- print (_("Writing") + @" $(supplement.current_font.get_name ()).svg to $output_directory\n");- ExportTool.export_svg_font_path (File.new_for_path (output_directory));- }-- if (!specific_formats || write_ttf) {- print (_("Writing") + @" $(supplement.current_font.get_name ()).ttf to $output_directory\n");- ExportTool.export_ttf_font_path (File.new_for_path (output_directory), false);- }- - return 0;-}--public static string wine_to_unix_path (string exec_path) {- bool drive_c, drive_z;- int i;- string p, q;-- p = exec_path;- p = p.replace ("\\", "/");- - drive_c = exec_path.index_of ("C:") == 0;- drive_z = exec_path.index_of ("Z:") == 0;- - i = p.index_of (":");- - if (i != -1) {- p = p.substring (i + 2);- }-- if (drive_c) {- q = @"/home/$(Environment.get_user_name ())/.wine/drive_c/" + p;- - if (File.new_for_path (q).query_exists ()) {- return q;- } else {- return p;- }- }- - if (drive_z) {- return ("/" + p).dup ();- }-- return exec_path.dup ();-}--}deleted file mode 100644@@ -1,23 +0,0 @@-/*- 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 Birdfont;--/** Stand alone command line utility for exporting fonts. */-public static int main (string[] arg) { - return run_export (arg);-}deleted file mode 100644@@ -1,62 +0,0 @@-/*- 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/>.-*/--namespace Supplement {--class CharDatabase {-- public CharDatabase () {- }-- public static bool has_ascender (unichar c) {- if (!c.islower()) return true;- - // todo: their derrivatives- switch (c) {- case 'b': return true;- case 'd': return true;- case 'f': return true;- case 'h': return true;- case 'i': return true;- case 'j': return true;- case 'k': return true;- case 'l': return true; - }- - if ('à' <= c <= 'å') return true;- if ('è' <= c <= 'ö') return true;- if ('ù' <= c <= 'ă') return true;- if ('ć' <= c <= 'ė') return true;-- return false;- }-- public static bool has_descender (unichar c) {- // todo: their derrivatives- switch (c) {- case 'g': return true;- case 'j': return true;- case 'p': return true;- case 'q': return true;- }- - return false; - }- -}--}deleted file mode 100644@@ -1,102 +0,0 @@-/*- 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/>.-*/--namespace Supplement {--public class ClipTool : Tool {-- static Glyph? glyph = null; - static List<Path> path = new List<Path> ();-- public static void copy () {- Font font = Supplement.get_current_font ();- OverView overview = MainWindow.get_overview ();- FontDisplay fd = MainWindow.get_current_display ();- string gc;- Glyph g = MainWindow.get_current_glyph ();-- glyph = null;-- while (path.length () > 0) {- path.remove_link (path.first ());- }- - if (fd is OverView) {- gc = overview.get_selected_char ();- glyph = font.get_glyph (gc);- } - - if (fd is Glyph) {- foreach (Path p in g.active_paths) {- path.append (p.copy ());- }-- if (path.length () == 0) {- glyph = g.copy ();- }- }-- }-- public static void paste () {- Font font = Supplement.get_current_font ();- OverView overview = MainWindow.get_overview ();- FontDisplay fd = MainWindow.get_current_display ();- string gc;- Glyph? destination = null;- unichar new_char;- Path inserted;- - if (fd is OverView) {- gc = overview.get_selected_char ();- destination = font.get_glyph (gc);- - if (destination == null) {- new_char = gc.get_char (0);- destination = new Glyph (gc, new_char) ;- font.add_glyph ((!) destination);- }- }- - if (fd is Glyph) {- destination = (Glyph) fd;-- if (destination == glyph) {- return;- }- - ((!)destination).store_undo_state ();- - if (glyph != null) {- foreach (Path p in ((!)glyph).path_list) {- inserted = p.copy ();- ((!)destination).add_path (inserted);- }- }-- foreach (Path p in path) {- inserted = p.copy ();- ((!)destination).add_path (inserted);- }- - ((!)destination).update_view ();- }- }--}--}deleted file mode 100644@@ -1,335 +0,0 @@-/*- 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 {--/** Kerning context. */-public class ContextDisplay : FontDisplay {-- List <Word> row;- int active_handle = -1;- int selected_handle = -1;- - double begin_handle_x = 0;- double begin_handle_y = 0;- - double last_handle_x = 0;- - public ContextDisplay () {- Word w = new Word ();- row = new List <Word> ();- row.append (w);- }-- public override string get_name () {- return "Kerning";- }- - public override void draw (Allocation allocation, Context cr) {- Glyph? g;- Glyph glyph;- double x, y, w, kern, alpha;- double x2;- int i, wi;- string prev;- - i = 0;- - // bg color- cr.save ();- cr.set_source_rgba (1, 1, 1, 1);- cr.rectangle (0, 0, allocation.width, allocation.height);- cr.fill ();- cr.restore ();- - alpha = 1;- y = 100;- x = 20;- w = 0;- prev = "";- - foreach (Word word in row) {- wi = 0;- - foreach (string s in word.glyph) { - g = (!) Supplement.get_current_font ().get_glyph (s); - - if (s == "") {- continue;- }- - kern = (prev != "") ? get_kerning (prev, s) : 0;- - // draw glyph - if (g == null) {- w = 50;- alpha = 1;- } else {- alpha = 0;- glyph = (!) g;- Svg.draw_svg_path (cr, glyph.get_svg_data (), x + kern, y);- w = glyph.get_width ();- }-- // handle- if (active_handle == i) {- //x2 = x + kern;- x2 = x + kern / 2.0;- - cr.save ();- cr.set_source_rgba (153/255.0, 153/255.0, 173/255.0, 1);- cr.move_to (x2 - 5, y + 20);- cr.line_to (x2 + 0, y + 20 - 5);- cr.line_to (x2 + 5, y + 20);- cr.fill ();- - cr.set_font_size (10);- cr.show_text (s);- cr.restore ();- } - - x += w + kern;- - // caption- if (g == null || ((!)g).is_empty ()) {- cr.save ();- cr.set_source_rgba (153/255.0, 153/255.0, 153/255.0, alpha);- cr.move_to (x - w / 2.0 - 5, y + 20);- cr.set_font_size (10);- cr.show_text (s);- cr.restore ();- }- - prev = s;- - wi++;- i++;- }- - if (y > allocation.height && i > 10) {- row.remove (word);- }- - y += MainWindow.get_current_glyph ().get_height () + 20;- x = 20; - }- }- - private void set_kerning (int handle, double val) {- string a, b;- Font font;- int wi = 0;- double kern;- - font = Supplement.get_current_font ();-- font.touch ();-- a = "";- b = "";-- foreach (Word word in row) {- foreach (string s in word.glyph) {- - if (s == "") {- continue;- }- - b = s;- - if (handle == wi) {- kern = font.get_kerning (a, b) + val;- font.set_kerning (a, b, kern);- }- - wi++;- - a = b;- }- }- - - }- - private double get_kerning (string a, string b) {- Font font = Supplement.get_current_font ();- return font.get_kerning (a, b);- }- - public override void selected_canvas () {- Glyph g;- Word w;- unowned List<Word>? r;- unowned List<string>? rw;- StringBuilder s = new StringBuilder ();- bool append_char = false;- - KeyBindings.singleton.set_require_modifier (true);- - g = MainWindow.get_current_glyph ();- s.append_unichar (g.get_unichar ());-- r = row;- return_if_fail (r != null);-- if (row.length () == 0) {- append_char = true;- }- - if (row.length () > 0) {- rw = row.first ().data.glyph;- return_if_fail (rw != null);- append_char = (row.first ().data.glyph.data.index_of (s.str) == -1);- }- - if (append_char) {- w = new Word ();- row.append (w);- w.glyph.prepend (s.str);- } - }- - public override void key_press (uint keyval) {- unichar c = (unichar) keyval;- Font f = Supplement.get_current_font ();- string name;- - if (KeyBindings.singleton.modifier == NONE || KeyBindings.singleton.modifier == SHIFT) {- - if (keyval == Key.BACK_SPACE && row.length () > 0) { - row.first ().data.glyph.remove_link (row.first ().data.glyph.last ());- }- - if (row.length () == 0 || c == Key.ENTER) {- row.prepend (new Word ());- }- - if (!is_modifier_key (c) && c.validate ()) {- name = f.get_name_for_character (c);- row.first ().data.glyph.append (name);- }- }- - MainWindow.get_glyph_canvas ().redraw ();- }- - public override void motion_notify (double ex, double ey) {- double k, y;- - if (selected_handle == -1) {- set_active_handle (ex, ey);- } else {- y = 1;- - if (Math.fabs (ey - begin_handle_y) > 20) {- y = ((Math.fabs (ey - begin_handle_y) / 100) + 1);- }- - k = (ex - last_handle_x) / y; // y-axis is variable precision- set_kerning (selected_handle, k);- MainWindow.get_glyph_canvas ().redraw ();- }- - last_handle_x = ex;- }- - public void set_active_handle (double ex, double ey) {- double y = 100;- double x = 20;- double w = 0;- double d, kern;- double min = double.MAX;- int i = 0;- int row_index = 0;- int col_index = 0;- Glyph? g;- Glyph glyph = new Glyph.no_lines ("");- - string prev = "";- string gl_name = "";- - foreach (Word word in row) {- col_index = 0;- foreach (string s in word.glyph) {- - // draw glyph- g = (!) Supplement.get_current_font ().get_glyph (s); - kern = (prev != "") ? get_kerning (prev, s) : 0;- - if (g == null) {- w = 50; - } else {- glyph = (!) g;- w = glyph.get_width ();- - }- - d = Math.pow (x + kern - ex, 2) + Math.pow (y - ey, 2);- - if (d < min) {- min = d;- gl_name = glyph.get_name ();- - if (active_handle != i - row_index) {- active_handle = i - row_index;- MainWindow.get_glyph_canvas ().redraw ();- }-- if (col_index == word.glyph.length () || col_index == 0) {- active_handle = -1;- }- }- - prev = s;- x += w + kern;- i++;- col_index++;- }- - row_index++;- y += MainWindow.get_current_glyph ().get_height () + 20;- x = 20;- }- }- - public override void button_release (int button, double ex, double ey) {- set_active_handle (ex, ey);- selected_handle = -1;- }- - public override void button_press (uint button, double ex, double ey) {- set_active_handle (ex, ey);- selected_handle = active_handle;- begin_handle_x = ex;- begin_handle_y = ey;- last_handle_x = ex;- }--}--class Word {- public List <string> glyph;- - public Word () {- glyph = new List <string> ();- glyph.append ("");- }-}--}deleted file mode 100644@@ -1,246 +0,0 @@-/*- 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;-using Math;--namespace Supplement {--class CutBackgroundTool : Tool {-- double x1 = 0;- double y1 = 0;- double x2 = 0;- double y2 = 0;- - bool is_visible = false;- bool is_set = false;- bool is_done = false;- - public signal void new_image (GlyphBackgroundImage file);- - public CutBackgroundTool (string name) {- base (name, _("Crop background image"));-- select_action.connect((self) => {- });-- deselect_action.connect ((self) => {- });- - press_action.connect((self, b, x, y) => { - if (!is_over_rectangle (x, y) || !is_visible) {- x1 = x;- y1 = y;- - x2 = x;- y2 = y;- - is_visible = true;- is_set = false;- is_done = false;- }- - if (is_set && is_visible && is_over_rectangle (x, y)) {- do_cut ();- is_set = false;- is_visible = false;- is_done = true;- - x1 = 0;- y1 = 0;- x2 = 0;- y2 = 0;- }- });-- release_action.connect((self, b, x, y) => {- if (!is_set && !is_done) {- x2 = x;- y2 = y;- is_set = true;- }- - is_done = false;- });-- move_action.connect((self, x, y) => {- if (is_visible && !is_set) {- x2 = x;- y2 = y;- - MainWindow.get_glyph_canvas ().redraw ();- }- });- - draw_action.connect ((self, cr, glyph) => {- if (is_visible) {- cr.save ();- cr.set_line_width (2.0);- cr.set_source_rgba (0, 0, 1, 0.3);- cr.rectangle (fmin (x1, x2), fmin (y1, y2), fabs (x1 - x2), fabs (y1 - y2));- cr.stroke ();- cr.restore ();- }- });-- new_image.connect ((file) => {- Glyph glyph = MainWindow.get_current_glyph ();- TabBar tb = MainWindow.get_tab_bar ();- - glyph.store_undo_state ();- - glyph.set_background_image (file);- tb.select_tab_name (glyph.get_name ());- - glyph.set_background_visible (true);- });-- }-- bool is_over_rectangle (double x, double y) {- return fmin (x1, x2) + 1 < x < fmax (x1, x2) - 1 && fmin (y1, y2) + 1 < y < fmax (y1, y2) - 1;- }-- double get_width () {- return fabs (x1 - x2);- }-- double get_height () {- return fabs (y1 - y2);- }-- void do_cut () {- double x, y;- int h, w;- double wc, hc;- - Glyph g = MainWindow.get_current_glyph ();- GlyphBackgroundImage? b = g.get_background_image ();- GlyphBackgroundImage bg = (!) b;- - ImageSurface img;- - Surface sr;- Context cr;- - double tx, ty, vx, vy; - if (b == null) {- return;- }-- // Add margin- Surface sg = new Surface.similar (bg.get_img (), bg.get_img ().get_content (), bg.size_margin, bg.size_margin);- Context cg = new Context (sg);- - wc = bg.get_margin_width ();- hc = bg.get_margin_height ();- - cg.set_source_rgba (1, 1, 1, 1);- cg.rectangle (0, 0, bg.size_margin, bg.size_margin);- cg.fill ();- - cg.set_source_surface (bg.get_img (), wc, hc);- cg.paint ();-- // find start- tx = bg.img_offset_x - g.view_offset_x;- ty = bg.img_offset_y - g.view_offset_y;- - ty *= g.view_zoom;- tx *= g.view_zoom;-- vx = g.path_coordinate_x (tx) - g.path_coordinate_x (fmin (x1, x2)); - vy = g.path_coordinate_y (ty) - g.path_coordinate_y (fmin (y1, y2));- - x = (int) (vx / bg.img_scale_x);- y = (int) (-vy / bg.img_scale_y);-- // do the cut- img = bg.get_img ();-- w = (int) (get_width () / g.view_zoom);- h = (int) (get_height () / g.view_zoom);-- sr = new Surface.similar (sg, img.get_content (), w, h);- cr = new Context (sr);-- cr.set_source_rgba (1, 1, 1, 1);- cr.rectangle (0, 0, w, h);- cr.fill ();- - cr.scale (bg.img_scale_x, bg.img_scale_y);- - cr.set_source_surface (sg, x, y);- cr.paint ();- - save_img (sr, g);- - cr.restore ();- }-- void save_img (Surface sr, Glyph g) {- GlyphBackgroundImage newbg;- Font f = Supplement.get_current_font ();- File img_dir;- File img_file;- File img_file_next;- string fn;- double wc, hc;- - img_dir = f.get_backgrounds_folder ().get_child ("parts");-- if (!img_dir.query_exists ()) {- DirUtils.create ((!) img_dir.get_path (), 0xFFFFFF);- }- - img_file = img_dir.get_child (@"NEW_BACKGROUND.png");- fn = (!) img_file.get_path ();- - sr.write_to_png (fn);- - newbg = new GlyphBackgroundImage (fn);- - fn = newbg.get_sha1 () + ".png";-- img_file_next = img_dir.get_child (fn);- - try {- if (img_file_next.query_exists ()) {- img_file_next.delete ();- }-- img_file.set_display_name (fn);- } catch (GLib.Error e) {- warning (e.message);- return;- }- - newbg = new GlyphBackgroundImage ((!) f.get_backgrounds_folder ().get_child ("parts").get_child (fn).get_path ());- - // set position for the new background- wc = newbg.get_margin_width ();- hc = newbg.get_margin_height ();- - newbg.img_x = g.path_coordinate_x (fmin (x1, x2)) - wc - 0.5;- newbg.img_y = g.path_coordinate_y (fmin (y1, y2)) + hc + 0.5;-- new_image (newbg);- }-}--}deleted file mode 100644@@ -1,61 +0,0 @@-/*- 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/>.-*/--namespace Supplement {--class CutTool : Tool {-- PenTool pen;- - public CutTool (string n) {- base (n, "Create counter path", 'u', CTRL);- - select_action.connect((self) => {- pen = (PenTool) MainWindow.get_tool ("pen_tool");- });- - press_action.connect((self, b, x, y) => {- pen.press_action (pen, b, x, y);- });-- release_action.connect((self, b, x, y) => {- pen.release_action (pen, b, x, y);- });- - move_action.connect ((self, x, y) => {- pen.move_action (pen, x, y);- });- }-- public static void force_direction () {- Glyph g = MainWindow.get_current_glyph ();- Tool current_tool = MainWindow.get_toolbox ().get_current_tool ();- - foreach (Path p in g.active_paths) {- if (p.is_open () && !p.has_direction ()) {- if (current_tool is PenTool) {- p.force_direction (Direction.CLOCKWISE);- } else if (current_tool is CutTool) {- p.force_direction (Direction.COUNTER_CLOCKWISE);- } - }- }- }- -}--}deleted file mode 100644@@ -1,201 +0,0 @@-/*- 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 {--public enum MenuDirection {- DROP_DOWN,- POP_UP;-}--public class DropMenu : GLib.Object {-- public delegate void Selected (MenuAction self);- public signal void selected (DropMenu self);- - double x = -1;- double y = -1;- bool menu_visible = false;-- List <MenuAction> actions = new List <MenuAction> ();-- const int item_height = 25;- - MenuDirection direction = MenuDirection.DROP_DOWN;- - ImageSurface? icon = null;- - public DropMenu (string icon_file = "") {- if (icon_file != "") {- icon = Icons.get_icon (@"$icon_file.png");- }- }- - public void deselect_all () {- foreach (MenuAction m in actions) {- m.set_selected (false);- }- }- - public void set_direction (MenuDirection d) {- direction = d;- }- - public void close () {- menu_visible = false;- }- - public MenuAction add_item (string label) {- MenuAction m = new MenuAction (label);- add_menu_item (m);- return m;- }- - public void add_menu_item (MenuAction m) {- m.parent = this;- actions.append (m);- }- - public bool is_over_icon (double px, double py) {- if (x == -1 || y == -1) {- return false;- }- - return x - 5 < px < x + 12 + 5 && y - 5 < py < y + 12 + 5;- }-- public bool menu_item_action (double px, double py) {- MenuAction? action;-- if (menu_visible) {- action = get_menu_action_at (px, py);- - if (action != null) {- ((!) action).action ((!)action);- selected (this);- menu_visible = false;- return true;- }- }- - return false;- }- - public bool menu_icon_action (double px, double py) { - menu_visible = is_over_icon (px, py);- return menu_visible;- }- - MenuAction? get_menu_action_at (double px, double py) {- double n = 0;- double ix, iy;- - foreach (MenuAction item in actions) {- ix = x - 100 + 12;- - if (direction == MenuDirection.DROP_DOWN) {- iy = y + 12 + n * item_height;- } else {- iy = y - 24 - n * item_height;- }- - if (ix <= px <= ix + 100 && iy <= py <= iy + item_height) {- return item;- }- - n++; - }-- return null;- }- - public void set_position (double px, double py) {- x = px;- y = py;- }- - public void draw_menu (Context cr) {- double ix, iy;- int n;- - if (likely (!menu_visible)) {- return;- }- - cr.save ();- cr.set_source_rgba (122/255.0, 150/255.0, 169/255.0, 1);- cr.set_line_join (LineJoin.ROUND);- cr.set_line_width (12);- - if (direction == MenuDirection.DROP_DOWN)- cr.rectangle (x - 100 + 18, y + 18, 88, actions.length () * item_height - 12);- else- cr.rectangle (x - 100 + 18, y + 6 - actions.length () * item_height, 88, actions.length () * item_height - 12);- - cr.fill_preserve ();- cr.stroke ();- cr.restore ();- - cr.save ();- - n = 0;- foreach (MenuAction item in actions) {- - if (direction == MenuDirection.DROP_DOWN)- iy = y + 33 + n * item_height - 5;- else - iy = y - 8 - n * item_height;- - ix = x - 100 + 20;- - item.draw (ix, iy, cr);- n++;- }- - cr.restore ();- }- - public void draw_icon (Context cr) {- double alpha = 1;- ImageSurface i = (!) icon;- - if (!menu_visible) {- alpha = 0;- }- - cr.save ();- - cr.set_source_rgba (122/255.0, 150/255.0, 169/255.0, alpha);- - cr.rectangle (x, y, 12, 12);- cr.fill_preserve ();- cr.stroke ();- - if (likely (icon != null && i.status () == Cairo.Status.SUCCESS)) {- cr.set_source_surface (i, x, y + (12 - i.get_height ()) / 2.0);- cr.paint ();- } else {- warning ("Failed to load icon.");- }- - cr.restore ();- }-}--}deleted file mode 100644@@ -1,343 +0,0 @@-/*- 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 Math;--namespace Supplement {--public enum PointType {- NONE,- NORMAL,- LINE,- CURVE,- DOUBLE_CURVE,- END,- FLOATING-}--public class EditPoint {- - public double x;- public double y;- public PointType type;- - public double r = 1;- public double g = 0;- public double b = 0;- public double a = 1;- - public unowned List<EditPoint>? prev = null;- public unowned List<EditPoint>? next = null;-- public bool active = false;- public bool selected = false;- - public int selected_handle = 0;- - public EditPointHandle right_handle;- public EditPointHandle left_handle;- - public bool tie_handles = false;- - public EditPoint (double nx = 0, double ny = 0, PointType nt = PointType.LINE) {- x = nx;- y = ny;- type = nt;- active = false;- - set_active (true);- - if (nt == PointType.FLOATING) {- r = 1;- g = 1;- b = 0;- a = 1;- active = false;- }- - right_handle = new EditPointHandle (this, 0, 7);- left_handle = new EditPointHandle (this, PI, 7);-- if (unlikely (nx.is_nan () || ny.is_nan ())) {- warning (@"Invalid point at ($nx,$ny).");- x = 0;- y = 0;- }- }-- /** Flip handles if next point on path is in the other direction. - * Used to recalculate handles after new point is inserted on a path.- */- public void recalculate_handles (double px, double py) {- double dr, dl;- EditPointHandle t;- - if (next == null || ((!)next).length () < 2) {- return;- }- - px = get_next ().nth (1).data.x;- py = get_next ().nth (1).data.y;- - dr = Math.sqrt (Math.pow (px - right_handle.x (), 2) + Math.pow (py - right_handle.y (), 2));- dl = Math.sqrt (Math.pow (px - left_handle.x (), 2) + Math.pow (py - left_handle.y (), 2));-- // flip handles- if (dl < dr) {- t = right_handle;- right_handle = left_handle;- left_handle = t;- } - }- - /** Set bezier points for linear paths. */- public void recalculate_linear_handles () {- unowned EditPointHandle h;- unowned EditPoint n;- double nx, ny;-- if (prev == null && next != null) {- prev = get_next ().last ();- }-- // left handle- if (prev != null) {- n = get_prev ().data;- h = get_left_handle ();- - if (h.type == PointType.LINE) {- nx = x + ((n.x - x) / 3);- ny = y + ((n.y - y) / 3);- - h.move_to_coordinate (nx, ny);- }-- h = n.get_right_handle ();- - // on the other side- h = n.get_right_handle ();- - if (h.type == PointType.LINE) {- nx = n.x + ((x - n.x) / 3);- ny = n.y + ((y - n.y) / 3);- - h.move_to_coordinate (nx, ny);- }- }-- // right handle- if (next != null) {- n = get_next ().data;- h = get_right_handle ();- - if (h.type == PointType.LINE) {- nx = x + ((n.x - x) / 3);- ny = y + ((n.y - y) / 3);- - h.move_to_coordinate (nx, ny);- }-- h = n.get_left_handle ();- - if (h.type == PointType.LINE) {- nx = n.x + ((x - n.x) / 3);- ny = n.y + ((y - n.y) / 3);-- h.move_to_coordinate (nx, ny);- }- }- }- - public void set_tie_handle (bool t) {- tie_handles = t;- }-- public void process_tied_handle () {- double a, b, c, length, angle;- EditPointHandle eh;- - eh = right_handle;- - a = left_handle.x () - right_handle.x ();- b = left_handle.y () - right_handle.y ();- c = a * a + b * b;- - if (c == 0) {- return;- }- - length = sqrt (fabs (c));- - if (right_handle.y () < left_handle.y ()) {- angle = acos (a / length) + PI;- } else {- angle = -acos (a / length) + PI;- }- - left_handle.type = PointType.CURVE;- right_handle.type = PointType.CURVE;- - right_handle.angle = angle;- left_handle.angle = angle;-- set_tie_handle (true);- eh.move_to_coordinate (right_handle.x (), right_handle.y ());- }-- public EditPoint copy () {- EditPoint new_point = new EditPoint ();- - new_point.x = x;- new_point.y = y;- - new_point.type = type;- new_point.set_color (r, g, b, a);- - new_point.tie_handles = tie_handles;- - new_point.right_handle.angle = right_handle.angle;- new_point.right_handle.length = right_handle.length;- new_point.right_handle.type = right_handle.type;-- new_point.left_handle.angle = left_handle.angle;- new_point.left_handle.length = left_handle.length; - new_point.left_handle.type = left_handle.type;-- return new_point;- }-- public double get_distance (double x, double y) {- return Path.distance (this.x, x, this.y, y);- }-- public unowned EditPointHandle get_left_handle () {- return left_handle;- }- - public unowned EditPointHandle get_right_handle () {- return right_handle;- }- - public void set_color (double r, double g, double b, double a) {- this.r = r;- this.g = g;- this.b = b;- this.a = a;- }- - public unowned List<EditPoint> get_prev () {- if (prev == null && next != null && get_next ().prev == get_next ().first ()) {- prev = get_next ().last ();- }- - if (unlikely (prev == null)) {- warning ("EditPoint.prev is null");- }- - return (!) prev;- }-- public unowned List<EditPoint> get_next () {- if (unlikely (next == null)) {- warning ("EditPoint.next is null");- }- - return (!) next;- }- - public unowned List<EditPoint> get_list () {- return get_next ().first ();- }- - public void set_position (double tx, double ty) {- x = tx;- y = ty;-- if (unlikely (tx.is_nan () || ty.is_nan ())) {- warning (@"Invalid point at ($tx,$ty).");- x = 0;- y = 0;- }- }- - public static void to_coordinate (ref double x, ref double y) {- double xc, yc, xt, yt, ivz;- Glyph g = MainWindow.get_current_glyph ();- - ivz = 1 / g.view_zoom;- - xc = (g.allocation.width / 2.0);- yc = (g.allocation.height / 2.0);- - x *= ivz;- y *= ivz;-- xt = x - xc + g.view_offset_x;- yt = yc - y - g.view_offset_y; - - x = xt;- y = yt;- }- - public bool is_selected () {- return selected;- }- - public void set_selected (bool s) {- selected = s;- }- - public bool set_active (bool active) {- bool update = (this.active != active);- - if (update) {- this.active = active;- - if (active) {- r = 0.5;- g = 0;- b = 1;- a = 1;- } else {- r = 0;- g = 1;- b = 0.5;- a = 1;- }- }- - return update;- }- - public int get_index () {- int i = -1;- - if (next == null) return i;- - foreach (var d in get_next ().first ()) {- i++;- - if (this == d) {- return i;- }- }- - return i;- }-}--}deleted file mode 100644@@ -1,167 +0,0 @@-/*- 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 Math;--namespace Supplement {- -public class EditPointHandle {- - public double angle;- public double length;- - public EditPoint parent;- - public PointType type;- - EditPoint? visual_handle = null;-- static EditPoint none = new EditPoint ();-- public bool active;-- public EditPointHandle.empty() {- this.parent = none;- this.angle = 0;- this.length = 10;- this.type = PointType.LINE;- this.active = false;- }- - public EditPointHandle (EditPoint parent, double angle, double length) {- this.parent = parent;- this.angle = angle;- this.length = length;- this.type = PointType.LINE;- this.active = false;- }-- public EditPointHandle copy () {- EditPointHandle n = new EditPointHandle.empty ();- n.angle = angle;- n.length = length;- n.parent = parent;- n.type = type;- n.active = active;- return n;- }-- public void set_point_type (PointType point_type) {- type = point_type;- }-- public double x () {- double r = px ();- - if (unlikely (r <= -100000)) {- print_position ();- move_to (0, 0);- }- - return r;- }- - public double y () {- double r = py ();- - if (unlikely (r <= -100000)) {- print_position ();- move_to (0, 0);- }- - return r;- }- - double px () {- assert ((EditPoint?) parent != null);- return cos (angle) * length + parent.x;- }-- double py () {- assert ((EditPoint?) parent != null);- return sin (angle) * length + parent.y;- }- - void print_position () {- warning (@"\nEdit point handle at position $(px ()),$(py ()) is not valid.\n"- + @"Type: $(parent.type), "- + @"Index: $(parent.get_index ()) of $(parent.get_list ().length ())\n"- + @"Angle: $angle, Length: $length."); - }- - public EditPoint get_point () {- EditPoint p;- - if (visual_handle == null) {- visual_handle = new EditPoint (0, 0);- }- - p = (!) visual_handle;- p.x = x ();- p.y = y ();- - return p;- }- - public void move_to_coordinate (double x, double y) {- double a, b, c;-- a = parent.x - x;- b = parent.y - y;- c = a * a + b * b;- - if (c == 0) {- return;- }- - length = sqrt (fabs (c)); - - if (c < 0) length = -length;- - if (y < parent.y) {- angle = acos (a / length) + PI;- } else {- angle = -acos (a / length) + PI;- }- - if (parent.tie_handles) {- tie_handle ();- }- }-- private void tie_handle () {- if (this == parent.get_left_handle ()) {- parent.get_right_handle ().angle = angle - PI;- } else {- parent.get_left_handle ().angle = angle - PI;- }- }- - public void move_delta (double dx, double dy) {- double px = px () + dx * Glyph.ivz ();- double py = py () - dy * Glyph.ivz ();- - move_to_coordinate (px, py);- }- - public void move_to (double x, double y) {- EditPoint.to_coordinate (ref x, ref y);- move_to_coordinate (x, y);- }-}--}deleted file mode 100644@@ -1,174 +0,0 @@-/*- 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 Math;--namespace Supplement {--class EotWriter : GLib.Object {- - string ttf_file_name;- string eot_file_name;- - public EotWriter (string ttf_file, string eot_file) {- ttf_file_name = ttf_file;- eot_file_name = eot_file;- }- - public void write () throws Error {- FontData fd = new FontData ();- FontData ttf_data = new FontData ();- OpenFontFormatReader input; - uint32 ttf_length;- File ttf_file;- File eot_file;- FileInfo file_info;- OtfInputStream dis;- DataOutputStream os;- uint8* data;- uint32 l;- NameTable names;- string tn;- - ttf_file = File.new_for_path (ttf_file_name);- - if (!ttf_file.query_exists ()) {- warning (@"EotWriter: file does not exist. $((!) ttf_file.get_path ())");- return;- }-- eot_file = File.new_for_path (eot_file_name);- - if (eot_file.query_exists ()) {- warning ("File exists in eot export.");- return;- }- - os = new DataOutputStream(eot_file.create (FileCreateFlags.REPLACE_DESTINATION));-- file_info = ttf_file.query_info ("*", FileQueryInfoFlags.NONE);- ttf_length = (uint32) file_info.get_size ();- - if (ttf_length == 0) {- warning ("TTF file is of zero length.");- return;- }-- dis = new OtfInputStream (ttf_file.read ());- ttf_data.write_table (dis, 0, ttf_length);-- // parse file to find head checksum- input = new OpenFontFormatReader ();- input.parse_index (ttf_file_name);- - names = input.directory_table.name_table;- - fd.add_littleendian_u32 (0); // table length- fd.add_littleendian_u32 (ttf_length);- - fd.add_littleendian_u32 (0x00020001); // table format version- fd.add_littleendian_u32 (0); // flags- - // panose- for (int i = 0; i < 10; i++) {- fd.add (0);- }- - // according to specification should charset and italic be added the - // other way around, but it does not work- - fd.add (1); // default charset- fd.add (0); // italic set to regular- - fd.add_littleendian_u32 (400); // weight- fd.add_littleendian_u16 (0); // 0 drm, designer must know that notified- fd.add_littleendian_u16 (0x504C); // magic number-- // FIXA:- // unicode ranges - fd.add_littleendian_u32 (0);- fd.add_littleendian_u32 (0);- fd.add_littleendian_u32 (0);- fd.add_littleendian_u32 (0);- - // FIXA:- // code page range- fd.add_littleendian_u32 (0);- fd.add_littleendian_u32 (0);- - fd.add_littleendian_u32 (input.get_head_checksum ()); // head checksum adjustment- - // reserved- fd.add_littleendian_u32 (0);- fd.add_littleendian_u32 (0);- fd.add_littleendian_u32 (0);- fd.add_littleendian_u32 (0);- - fd.add_littleendian_u16 (0); // padding- - tn = names.get_name (names.FONT_NAME);- fd.add_littleendian_u16 ((uint16) (2 * tn.char_count ())); // strlen of family name- fd.add_str_littleendian_utf16 (tn);- - fd.add_littleendian_u16 (0); // padding- - tn = names.get_name (names.SUBFAMILY_NAME);- fd.add_littleendian_u16 ((uint16) (2 * tn.char_count ())); // strlen of family name- fd.add_str_littleendian_utf16 (tn);-- fd.add_littleendian_u16 (0); // padding- - // FIXA version or name + version- tn = names.get_name (names.VERSION);- fd.add_littleendian_u16 ((uint16) (2 * tn.char_count ())); // strlen of family name- fd.add_str_littleendian_utf16 (tn);- - fd.add_littleendian_u16 (0); // padding- - tn = names.get_name (names.FULL_FONT_NAME);- fd.add_littleendian_u16 ((uint16) (2 * tn.char_count ())); // strlen of family name- fd.add_str_littleendian_utf16 (tn);- - fd.add_littleendian_u16 (0); // padding- - fd.add_littleendian_u16 (0); // length of root string- // the root string goes here.- - // update length of this table- fd.seek (0);- fd.add_littleendian_u32 (fd.length () + ttf_length);- - l = fd.length_with_padding ();- data = fd.table_data;- for (int i = 0; i < l; i++) {- os.put_byte (data[i]);- }-- l = ttf_data.length_with_padding ();- data = ttf_data.table_data;- for (int i = 0; i < l; i++) {- os.put_byte (data[i]);- }- - dis.close ();- os.close ();- input.close ();- }- -}- -}deleted file mode 100644@@ -1,225 +0,0 @@-/*- 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;-using Math;--namespace Supplement {--public class Expander : GLib.Object {- - public double x = 7;- public double y = 5;-- public double w = 6;- public double h = 5;- - public double margin = 0;- - protected double rotation = 0;- protected double opacity = 0.5;- - protected bool active = false;- protected bool open = false;-- public List<Tool> tool;-- bool persist = false;- bool unique = false;- - public Expander () {- }-- /** Returns true if tools can be used with the current canvas after- * they have been selectes and false if they are a commands to be executed.- */- public bool is_persistent () {- return persist;- }-- /** Returns true if all other tools in thid expander should be deselected - * when a tool is selected.- */- public bool is_unique () {- return unique;- }-- public void set_persistent (bool p) {- persist = p;- }-- public void set_unique (bool u) {- unique = u;- }- - private void update_tool_position () {- int i = 0;- double xt = x + 10;- double yt = y - 4;- foreach (var t in tool) {- t.set_properties (xt, yt, w, h);- - if (i == 3) {- yt += 31;- i = 0;- xt = x + 10;- } else {- i++;- xt += w + 3 + 25;- }- }- }- - public void set_offset (double ty) {- y = ty;- - if (open) {- update_tool_position ();- }- }- - public void add_tool (Tool t) {- tool.append (t);- update_tool_position ();- - t.select_action.connect ((selected) => {- if (!selected.new_selection) {- if (is_persistent ()) {- selected.set_selected (true);- } else {- selected.set_selected (false);- }- }- - MainWindow.get_toolbox ().redraw ((int) x, (int) y, (int) w + 300, (int) (h + margin));- - if (is_unique ()) {- foreach (var deselected in tool) {- if (selected.get_id () != deselected.get_id ()) {- deselected.set_selected (false);- }- }- }-- if (!is_persistent ()) {- var time = new TimeoutSource(200);- time.set_callback(() => {- selected.set_selected (false);- MainWindow.get_toolbox ().redraw ((int) x, (int) y, (int) w + 300, (int) (h + margin));- return false;- });- time.attach(null);- }-- selected.new_selection = false;- });- }- - public bool is_over (double xp, double yp) { - return (x - 7/2.0 <= xp <= x + w + 7/2.0 && y - 7/2.0<= yp <= y + w + 7/2.0); - }- - public bool set_active (bool a) {- bool r = (active != a);- opacity = (a) ? 1 : 0;- active = a;- return r;- }- - public bool is_open () {- return open;- }- - public virtual bool set_open (bool o) {- bool r = (open != o);- rotation = (o) ? Math.PI_2 : 0;- - if (o) {- margin = 35 * (int)((tool.length () / 4.0) + 1) ;- rotation = Math.PI_2; - if (tool.length () % 4 == 0) {- margin -= 35;- }- } else {- margin = 0;- rotation = 0; - }- - open = o;- return r;- }- - public void draw (int wd, int hd, Context cr) {- double lx, ly;- double ih2 = 5.4 / 2;- double iw2 = 5.4 / 2;- - // box- cr.save ();- cr.set_line_join (LineJoin.ROUND);- cr.set_line_width(7);- cr.set_source_rgba (176/255.0, 211/255.0, 230/255.0, opacity);- cr.rectangle (x, y, w, h);- cr.stroke ();- cr.restore ();- - // arrow- cr.save ();- - cr.translate (x + w/2, y + h/2);- cr.rotate (rotation);-- cr.set_line_width (1);- cr.set_source_rgba (0, 0, 0, opacity);-- cr.new_path ();- cr.move_to (-iw2, -ih2);- cr.line_to (iw2, 0); - cr.line_to (-iw2, +ih2);-- cr.close_path();- cr.stroke ();- cr.restore ();- - // separator- cr.save ();- lx = x + w + 7;- ly = y + ih2;- if (lx < wd) {- cr.set_line_width(1);- cr.set_source_rgba (0, 0, 0, 0.2);- cr.move_to (lx, ly);- cr.line_to (wd - w - x + 4, ly); - cr.stroke ();- }- cr.restore ();-- }- - public void draw_content (int w, int h, Context cr) {- if (open) {- cr.save ();- foreach (var t in tool) {- t.draw (cr);- }- cr.restore ();- }- }- -}--}deleted file mode 100644@@ -1,538 +0,0 @@-/*- 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/>.-*/--namespace Supplement {--public class ExportTool : Tool {- private static string? prefered_browser = null;- private static ExportThread export_thread;- - private static unowned Thread<void*> export_thread_handle;-- private static bool stop_export_thread = false;-- public ExportTool (string n) {- base (n, _("Export glyph to svg file"), 'e', CTRL);- - select_action.connect((self) => {- export_svg_font ();- // Fixa: add export_glyph_to_svg (); as tool in toolbox- });- - press_action.connect((self, b, x, y) => {- });-- release_action.connect((self, b, x, y) => {- });- - move_action.connect ((self, x, y) => {- });- }-- public static bool export_all () {- Font font = Supplement.get_current_font ();- bool f;- - if (font.get_ttf_export ()) {- f = export_ttf_font ();- - if (!f) {- warning ("Failed to export font");- return false;- }- }- - if (font.get_svg_export ()) {- f = export_svg_font ();-- if (!f) {- warning ("Failed to export font");- return false;- }- }- - return true;- }-- public void export_glyph_to_svg () {- Glyph glyph = MainWindow.get_current_glyph ();- Font font = Supplement.get_current_font ();- FontDisplay fd = MainWindow.get_current_display ();- string glyph_svg;- string svg_file = @"$(glyph.get_name ()).svg";- File file = font.get_folder ().get_child (svg_file);- DataOutputStream os;- string name = glyph.get_name ();- int pid = 0;- - if (!(fd is Glyph)) {- return;- }- - try {- - if (file.query_exists ()) {- file.delete ();- }- - os = new DataOutputStream (file.create(FileCreateFlags.REPLACE_DESTINATION)); - - _("Alphabet");- - os.put_string (-"""<?xml version="1.0" standalone="no"?>-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >-<svg """);-- os.put_string (@"xmlns=\"http://www.w3.org/2000/svg\"\n");- os.put_string (@"\twidth=\"$(glyph.get_width () / Glyph.SCALE)\"\n");- os.put_string (@"\theight=\"$(glyph.get_height () / Glyph.SCALE)\"\n");- os.put_string (@"\tid=\"glyph_$(name)\"\n");- os.put_string (@"\tversion=\"1.1\">\n");- - os.put_string (@"\n");- - os.put_string (@"<g id=\"$(name)\">\n");- - foreach (Path p in glyph.path_list) {- - glyph_svg = Svg.to_svg_path (p, glyph);- - os.put_string (@"<path ");- os.put_string (@"style=\"fill:#000000;stroke-width:0px\" ");- os.put_string (@"d=\"$(glyph_svg)\" id=\"path_$(name)$(pid++)\" />\n");- - }- - os.put_string ("""</g>- -</svg>""");- - } catch (Error e) {- stderr.printf (@"Export \"$svg_file\" \n");- critical (@"$(e.message)");- status (_("Can't export") + @" $svg_file.");- }- - status (_("Wrote") + @" $svg_file.");- }-- public static void generate_html_document (string html_file, Font font) {- File file = File.new_for_path (html_file);- DataOutputStream os;- string name = font.get_name ();-- try {- os = new DataOutputStream (file.create(FileCreateFlags.REPLACE_DESTINATION));--os.put_string (-"""<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">-<html>-<head>-- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">- <title>Lorem ipsum</title>- - <style type="text/css">-- @font-face {- font-family: '"""); os.put_string (@"$(name)"); os.put_string ("""SVG';- src: url('"""); os.put_string (@"$(name).svg#$(name)"); os.put_string ("""') format('svg');- }- """);-- os.put_string ("""- @font-face {- font-family: '"""); os.put_string (@"$(name)"); os.put_string ("""TTF';- src: url('"""); os.put_string (@"$(name).ttf"); os.put_string ("""') format('truetype');- } - """);-- os.put_string ("""- @font-face {- font-family: '"""); os.put_string (@"$(name)"); os.put_string ("""EOT';- src: url('"""); os.put_string (@"$(name).eot"); os.put_string ("""');- } - - """);- - os.put_string (""" - h1 {- font-weight:normal;- margin: 0 0 5px 0;- color: #afafaf;- }-- h2 {- font-weight:normal;- margin: 0 0 5px 0;- color: #afafaf;- }- - h3 {- font-weight:normal;- margin: 0 0 5px 0;- color: #afafaf;- }- - h4 {- font-weight:normal;- margin: 0 0 5px 0;- color: #afafaf;- }- - p {- margin: 0 0 5px 0;- color: #000000;- }- - body {- margin: 30px 0 0 30px;- }- - div {- font-family: """);-- os.put_string ("'");- os.put_string (@"$(name)");- os.put_string ("EOT'");- - os.put_string (", '");- os.put_string (@"$(name)");- os.put_string ("SVG'");- - os.put_string (", '");- os.put_string (@"$(name)");- os.put_string ("TTF'");-- os.put_string (";");- os.put_string ("""- margin: 0 0 30px 0;- }-- h1.bigger {- font-size: 58pt;- }-- h2.big {- font-size: 40pt;- }- - h3.small {- font-size: 32pt;- }-- h4.smaller {- font-size: 18pt;- }-- h1.percent {- font-size: 100%;- } - - p.bigger {- font-size: 32pt;- }-- p.big {- font-size: 24pt;- }- - p.small {- font-size: 18pt;- }-- p.smaller {- font-size: 12pt;- }-- </style>-""");- - os.put_string (-""" -</head>-<body>--<div>- <h1 class="bigger">Lorem ipsum</h1>- <p class="bigger">Dolor sit amet!</p>-</div>--<div>- <h2 class="big">Hamburgerfonstiv</h2>- <p class="big">Inspirerande</p>-</div>--<div>- <h3 class="small">Handgloves & Mittoms</h3>- <p class="small">Wind blows in Hauges poetry.</p>-</p>-</div>--<div>- <h4 class="smaller">Headline 16pt</h4>- <p class="smaller">Ett litet stycke svalhjärta.</p> -</div>--<div>- <h4 class="smaller">""" + _("Alphabet") + """</h4>- <p class="smaller">""" + _("a b c d e f g h i j k l m n o p q r s t u v w x y z") + """</p>- <p class="smaller">""" + _("A B C D E F G H I J K L M N O P Q R S T U V W X Y Z") + """</p>- <p class="smaller">0 1 2 3 4 5 6 7 8 9</p>-</div>--</body>-</html>-""");-- } catch (GLib.Error e) {- warning (e.message);- }- }-- public static bool export_ttf_font () {- Font font = Supplement.get_current_font ();- File file = font.get_folder ();- return export_ttf_font_path (file);- }- - /** Stop a running export thread.*/- public static void stop_export () {- lock (stop_export_thread) {- stop_export_thread = true;- }- }- - public static bool should_stop () {- bool r;- lock (stop_export_thread) {- r = stop_export_thread;- }- return r;- } - - public static string get_birdfont_export () {- File f;- - f = File.new_for_path ("birdfont-export.sh");- if (f.query_exists ()) {- return "sh birdfont-export.sh";- }-- f = File.new_for_path ("../birdfont-export.sh"); - if (f.query_exists ()) {- return "sh ../birdfont-export.sh";- }-- f = File.new_for_path ("birdfont-export.exe"); - if (f.query_exists ()) {- return (!) f.get_path ();- }-- f = File.new_for_path (@"$PREFIX/birdfont-export");- if (f.query_exists ()) {- return @"$PREFIX/bin/birdfont-export";- }-- warning ("Can't find birdfont-export.");- - return "birdfont-export";- }- - public static bool export_ttf_font_path (File folder, bool async = true) {- Font current_font = Supplement.get_current_font ();- File ttf_file;- File eot_file;- string temp_file;- bool done = true;- string export_command;- - if (Supplement.win32) {- VersionList.set_suppress_signal (true);- async = false;- }- - try {- // create a copy of current font and use it in a separate - // export thread- temp_file = current_font.save_backup ();- ttf_file = folder.get_child (current_font.get_name () + ".ttf");- eot_file = folder.get_child (current_font.get_name () + ".eot");-- if (ttf_file.query_exists ()) {- ttf_file.delete ();- }-- if (eot_file.query_exists ()) {- eot_file.delete ();- }- - assert (!is_null (temp_file));- assert (!is_null (ttf_file.get_path ()));- assert (!is_null (eot_file.get_path ()));-- export_thread = new ExportThread (temp_file, (!) ttf_file.get_path (), (!) eot_file.get_path ());-- try {- if (async) {- export_command = @"$(get_birdfont_export ()) --ttf -o $((!) folder.get_path ()) $temp_file";- - try {- Process.spawn_command_line_async (export_command);- } catch (Error e) {- stderr.printf (@"Failed to execute \"$export_command\" \n");- critical (@"$(e.message)");- }- }- - if (!async) {- export_thread.run ();- }- } catch (ThreadError e) {- warning (e.message);- done = false;- } - - } catch (Error e) {- critical (@"$(e.message)");- done = false;- }-- if (Supplement.win32) {- VersionList.set_suppress_signal (false);- }- - return done; - }- - public static bool export_svg_font () {- Font font = Supplement.get_current_font ();- return export_svg_font_path (font.get_folder ());- }- - public static bool export_svg_font_path (File folder) {- Font font = Supplement.get_current_font ();- string file_name = @"$(font.get_name ()).svg";- File file;- SvgFontFormatWriter fo;- - try {- file = folder.get_child (file_name);- - if (file.query_exists ()) {- file.delete ();- }- - fo = new SvgFontFormatWriter ();- fo.open (file);- fo.write_font_file (font);- fo.close ();- } catch (Error e) {- critical (@"$(e.message)");- status (e.message);- return false;- }- - status (_ ("Wrote font files"));- return true;- }-- public class ExportThread : GLib.Object {- private static string ffi;- private static string ttf;- private static string eot;-- public ExportThread (string ffi, string ttf, string eot) {- this.ffi = ffi.dup ();- this.ttf = ttf.dup ();- this.eot = eot.dup ();- }- - public void* run () {- assert (!is_null (ffi));- assert (!is_null (ttf));- assert (!is_null (eot));- - if (!should_stop ()) { - write_ttf ();- }- - if (!should_stop ()) { - write_eof ();- }-- return null;- }- - void write_ttf () {- OpenFontFormatWriter fo = new OpenFontFormatWriter ();- Font f = new Font ();- File file = (!) File.new_for_path (ttf);-- return_if_fail (!is_null (ffi)); - return_if_fail (!is_null (ttf));- return_if_fail (!is_null (file));- return_if_fail (!is_null (f));- return_if_fail (!is_null (fo));- - try {- if (!f.load (ffi, false)) {- warning (@"Can't read $ffi");- }- - fo.open (file);- fo.write_ttf_font (f);- fo.close ();- } catch (Error e) {- critical (@"$(e.message)");- }- }- - void write_eof () {- EotWriter fo;-- return_if_fail (!is_null (this));- return_if_fail (!is_null (ttf));- return_if_fail (!is_null (eot));- - fo = new EotWriter (ttf, eot);-- return_if_fail (!is_null (fo));-- try {- fo.write ();- } catch (Error e) {- warning ("EOF conversion falied.");- critical (@"$(e.message)");- }- }-- }- - private static void status (string s) {- TooltipArea status = MainWindow.get_tool_tip ();- - if (is_null (status)) {- return;- }- - status.show_text (s);- }-}--}deleted file mode 100644@@ -1,1439 +0,0 @@-/*- 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;-using Xml;--namespace Supplement {--public class Font : GLib.Object {- - GlyphTable glyph_cache = new GlyphTable ();- - /** Glyphs that not are tied to a unichar value. */- GlyphTable unassigned_glyphs = new GlyphTable ();-- List<string> glyph_names = new List<string> ();-- /** Last unassigned index */- int next_unindexed = 0;- - public List <string> background_images = new List <string> ();- public string background_scale = "1";- - /** Top margin */- public double top_limit;- - /** Height of upper case letters. */- public double top_position;-- /** x-height upper bearing from origo. */- public double xheight_position;-- /** Base line coordinate from origo. */- public double base_line;- - /** Descender position */- public double bottom_position;- - /** Bottom margon */- public double bottom_limit;- - public string? backup_file = null;- public string? font_file = null;- - bool modified = false;- - private string name = "typeface";- - bool ttf_export = true;- bool svg_export = true;-- OpenFontFormatReader otf;- bool otf_font = false;- - public List<string> grid_width = new List<string> ();- - public Font () {- // positions in pixels at first zoom level- // default x-height should be 60 in 1:1- top_limit = -66 * Glyph.SCALE;- top_position = -54 * Glyph.SCALE;- xheight_position = -38 * Glyph.SCALE;- base_line = 18 * Glyph.SCALE;- bottom_position = 38 * Glyph.SCALE;- bottom_limit = 45 * Glyph.SCALE;- }-- public void touch () {- modified = true;- }-- public bool get_ttf_export () {- return ttf_export;- }-- public bool get_svg_export () {- return svg_export;- }-- public File get_backgrounds_folder () {- string fn = @"$(get_name ()) backgrounds";- File f = Supplement.get_settings_directory ().get_child (fn);- return f;- }-- /** Retuns true if the current font has be modified */- public bool is_modified () {- return modified;- }-- /** Full path to this font file. */- public string get_path () {- int i = 0;- - if (font_file != null) {- return (!) font_file;- }- - StringBuilder sb = new StringBuilder ();- sb.append (Environment.get_home_dir ());- sb.append (@"/$(get_name ()).ffi");- - File f = File.new_for_path (sb.str);-- while (f.query_exists ()) {- sb.erase ();- sb.append (Environment.get_home_dir ());- sb.append (@"/$(get_name ())$(++i).ffi");- f = File.new_for_path (sb.str);- }- - return sb.str;- }-- public string get_file_name () {- string p = get_path ();- int i = p.last_index_of ("/");- - if (i == -1) {- i = p.last_index_of ("\\");- }- - p = p.substring (i + 1);- - return p;- }- - public File get_folder () {- string p = get_path ();- int i = p.last_index_of ("/");- - if (i == -1) {- i = p.last_index_of ("\\");- }- - p = p.substring (0, i);- - return File.new_for_path (p);- }- - public double get_height () {- double r = base_line - top_position;- return (r > 0) ? r : -r;- }- - public void set_name (string name) {- this.name = name;- }- - public string get_name () {- return name;- }-- public void print_all () {- stdout.printf ("Assigned:\n"); - glyph_cache.for_each((g) => {- stdout.printf (@"$(g.get_name ())\n");- });-- stdout.printf ("\n");- stdout.printf ("Unssigned:\n"); - unassigned_glyphs.for_each ((g) => {- stdout.printf (@"$(g.get_name ())\n");- });-- }-- public bool has_glyph (string n) {- return get_glyph (n) != null;- }-- public Glyph get_nonmarking_return () {- Glyph ret;- - if (has_glyph ("nonmarkingreturn")) {- return (!) get_glyph ("nonmarkingreturn");- }- - ret = new Glyph ("nonmarkingreturn", '\r');- ret.set_unassigned (false);- ret.left_limit = 0;- ret.right_limit = 0;- ret.remove_empty_paths ();- - assert (ret.path_list.length () == 0);- - return ret;- }- - public Glyph get_null_character () {- Glyph n;- - if (has_glyph ("null")) {- return (!) get_glyph ("null");- }- - n = new Glyph ("null", '\0');- n.set_unassigned (false);- n.left_limit = 0;- n.right_limit = 0;- n.remove_empty_paths ();- - assert (n.path_list.length () == 0);- - return n;- }- - public Glyph get_space () {- Glyph n;- - if (has_glyph (" ")) {- return (!) get_glyph (" ");- }-- if (has_glyph ("space")) {- return (!) get_glyph ("space");- }- - n = new Glyph ("space", ' ');- n.set_unassigned (false);- n.left_limit = 0;- n.right_limit = 27;- n.remove_empty_paths ();- - assert (n.path_list.length () == 0);- - return n; - }- - public Glyph get_not_def_character () {- Glyph g;-- Path p;- Path i;- - if (has_glyph (".notdef")) {- return (!) get_glyph (".notdef");- }- - g = new Glyph (".notdef", 0);- p = new Path ();- i = new Path ();- - g.set_unassigned (true);- g.left_limit = -33;- g.right_limit = 33;- - p.add (-20, -top_position - 5);- p.add (20, -top_position - 5);- p.add (20, -base_line + 5);- p.add (-20, -base_line + 5);- p.close ();- - i.add (-15, -top_position - 10);- i.add (15, -top_position - 10);- i.add (15, -base_line + 10);- i.add (-15, -base_line + 10);- i.reverse ();- i.close ();-- g.add_path (i);- g.add_path (p);-- return g;- }- - public void add_glyph (Glyph glyph) {- GlyphCollection? gc = get_glyph_collection (glyph.get_name ());-- if (gc == null) {- add_glyph_collection (new GlyphCollection (glyph));- }- }-- public void add_glyph_collection (GlyphCollection glyph_collection) {- GlyphCollection? gc = get_cached_glyph_collection (glyph_collection.get_name ());- - if (glyph_collection.get_name () == "") {- warning ("Refusing to insert glyph with name \"\", null character should be named null.");- return;- }- - if (gc != null) {- warning ("glyph has already been added");- return;- }- - if (glyph_collection.get_current ().is_unassigned ()) {- unassigned_glyphs.insert (glyph_collection);- } else {- glyph_cache.insert (glyph_collection);- }-- if (!has_name (glyph_collection.get_name ())) {- glyph_names.append (glyph_collection.get_name ());- glyph_names.sort (strcmp);- }- }- - public string get_name_for_character (unichar c) {- // if some glyph is already mapped to unichar, return it's name- uint i = 0;- Glyph? gl;- Glyph g;- StringBuilder sb;- - while ((gl = get_glyph_indice (i++)) != null) {- g = (!) gl;- - if (g.unichar_code == c) {- return g.name;- }- }- - // otherwise return some default name, possibly from unicode database- if (c == 0) {- return ".null".dup ();- }- - sb = new StringBuilder ();- sb.append_unichar (c);- return sb.str; - }- - public bool has_name (string name) {- foreach (string n in glyph_names) {- if (n == name) {- return true;- }- }- - return false;- }- - public void delete_glyph (string glyph) {- glyph_names.remove_all (glyph);- glyph_cache.remove (glyph);- }- - /** Obtain all versions and alterntes for this glyph. */- public GlyphCollection? get_glyph_collection (string glyph) {- GlyphCollection? gc = get_cached_glyph_collection (glyph);- Glyph? g;- - if (gc == null && otf_font) {- // load it from otf file if we need to- g = otf.read_glyph (glyph);- - if (g != null) {- add_glyph_callback ((!) g);- return get_cached_glyph_collection (glyph);- }- }- - return gc;- }-- public GlyphCollection? get_cached_glyph_collection (string glyph) {- GlyphCollection? gc = null;- - gc = glyph_cache.get (glyph);- - if (gc == null) {- gc = unassigned_glyphs.get (glyph);- }- - return gc;- }- - public Glyph? get_glyph (string glyph) {- GlyphCollection? gc = get_glyph_collection (glyph);- - if (gc == null) {- return null;- }- - return ((!)gc).get_current ();- }- - public Glyph? get_glyph_indice (unichar glyph_indice) {- GlyphCollection? gc;- string n;- - if (!(0 <= glyph_indice < glyph_names.length ())) {- return null;- }- - n = glyph_names.nth (glyph_indice).data;- gc = get_glyph_collection (n);- - if (gc != null) {- return ((!) gc).get_current ();- }- - return null;- }- - public void add_background_image (string file) {- background_images.append (file);- }-- public double get_kerning (string a, string b) {- Glyph? gl = get_glyph (a);- Glyph g;- - if (gl == null) {- warning (@"glyph \"$a\" does not exist cannot obtain kerning");- return 0;- }- - g = (!) gl;- - return g.get_kerning (b);- }-- public void set_kerning (string a, string b, double val) {- Glyph? gl;- Glyph g;- - gl = get_glyph (a);- - if (gl == null) {- warning ("glyph is not parsed yet cannot add kerning");- return;- }- - g = (!) gl;- g.add_kerning (b, val);- }- - /** Returns path to backup file. */- public string save_backup () {- File dir = Supplement.get_backup_directory ();- File? temp_file = null;- int i = 0;-- if (backup_file == null) {- temp_file = dir.get_child (@"current_font_$i.ffi");- - while (((!) temp_file).query_exists ()) {- i++;- temp_file = dir.get_child (@"current_font_$i.ffi");- }- - backup_file = ((!) temp_file).get_path ();- }-- assert (backup_file != null); - write_font_file ((!) backup_file);- - return (!) backup_file;- }- - public bool save (string path) {- bool r = write_font_file (path);- - if (r) {- font_file = path;- }- - modified = false;- add_thumbnail ();- Preferences.add_recent_files (get_path ());- - return r;- }-- public bool write_font_file (string path) {- try {- File file = File.new_for_path (path);-- if (file.query_file_type (0) == FileType.DIRECTORY) {- stderr.printf (@"Can not save font. $path is a directory.");- return false;- }- - if (file.query_exists ()) {- file.delete ();- }- - DataOutputStream os = new DataOutputStream(file.create(FileCreateFlags.REPLACE_DESTINATION));- - os.put_string ("""<?xml version="1.0" encoding="utf-8" standalone="yes"?>""");- os.put_string ("\n");- - os.put_string ("<font>\n");- - os.put_string ("\n");- os.put_string (@"<name>$(get_name ())</name>\n");- - os.put_string ("\n");- os.put_string (@"<ttf-export>$(ttf_export)</ttf-export>\n");-- os.put_string ("\n");- os.put_string (@"<svg-export>$(svg_export)</svg-export>\n");- - os.put_string ("\n");- os.put_string ("<lines>\n");- - os.put_string (@"\t<top_limit>$top_limit</top_limit>\n");- os.put_string (@"\t<top_position>$top_position</top_position>\n");- os.put_string (@"\t<x-height>$xheight_position</x-height>\n");- os.put_string (@"\t<base_line>$base_line</base_line>\n");- os.put_string (@"\t<bottom_position>$bottom_position</bottom_position>\n");- os.put_string (@"\t<bottom_limit>$bottom_limit</bottom_limit>\n");- - os.put_string ("</lines>\n\n");-- foreach (string gv in grid_width) {- os.put_string (@"<grid width=\"$(gv)\"/>\n");- }- - if (GridTool.sizes.length () > 0) {- os.put_string ("\n");- }- - os.put_string (@"<background scale=\"$(background_scale)\" />\n");- os.put_string ("\n");- - if (background_images.length () > 0) {- os.put_string (@"<images>\n");- - foreach (string f in background_images) {- os.put_string (@"\t<img src=\"$f\"/>\n");- }- - os.put_string (@"</images>\n");- os.put_string ("\n");- }- - glyph_cache.for_each ((gc) => {- try {- bool selected;- foreach (Glyph g in gc.get_version_list ().glyphs) {- selected = (g == gc.get_current ());- write_glyph (g, selected, os);- }- } catch (GLib.Error ef) {- stderr.printf (@"Failed to save $path \n");- stderr.printf (@"$(ef.message) \n");- }- });- - glyph_cache.for_each ((gc) => {- try {- Glyph glyph = gc.get_current ();- - foreach (Kerning k in glyph.kerning) {- string l, r;- Glyph? gr = get_glyph (k.glyph_right);- Glyph glyph_right;- - if (gr == null) {- warning ("kerning glyph that does not exist.");- }- - glyph_right = (!) gr;- - l = Font.to_hex_code (glyph.unichar_code);- r = Font.to_hex_code (glyph_right.unichar_code);- - os.put_string (@"<hkern left=\"U+$l\" right=\"U+$r\" kerning=\"$(k.val)\"/>\n");- }- } catch (GLib.Error e) {- warning (e.message);- }- });-- glyph_cache.for_each ((gc) => {- GlyphBackgroundImage bg;- - try {- string data;- - foreach (Glyph g in gc.get_version_list ().glyphs) {-- if (g.get_background_image () != null) {- bg = (!) g.get_background_image ();- data = bg.get_png_base64 ();- - if (!bg.is_valid ()) {- continue;- }- - os.put_string (@"<background-image sha1=\"");- os.put_string (bg.get_sha1 ());- os.put_string ("\" ");- os.put_string (" data=\"");- os.put_string (data);- os.put_string ("");- os.put_string ("\" />\n"); - }- }- } catch (GLib.Error ef) {- stderr.printf (@"Failed to save $path \n");- stderr.printf (@"$(ef.message) \n");- }- });- - os.put_string ("</font>");- - } catch (GLib.Error e) {- stderr.printf (@"Failed to save $path \n");- stderr.printf (@"$(e.message) \n");- return false;- } catch (GLib.IOError e) {- stderr.printf (@"Failed to save $path \n");- stderr.printf (@"$(e.message) \n");- return false;- }- - return true;- }-- private void write_glyph (Glyph g, bool selected, DataOutputStream os) throws GLib.Error {- os.put_string (@"<glyph unicode=\"$(to_hex (g.unichar_code))\" selected=\"$selected\" left=\"$(g.left_limit)\" right=\"$(g.right_limit)\">\n");-- foreach (var p in g.path_list) {- if (p.points.length () == 0) {- continue;- }- - os.put_string ("\t<object>");-- foreach (var ep in p.points) {- os.put_string (@"<point x=\"$(ep.x)\" y=\"$(ep.y)\" ");- - if (ep.right_handle.type == PointType.CURVE) {- os.put_string (@"right_angle=\"$(ep.right_handle.angle)\" ");- os.put_string (@"right_length=\"$(ep.right_handle.length)\" ");- }- - if (ep.left_handle.type == PointType.CURVE) {- os.put_string (@"left_angle=\"$(ep.left_handle.angle)\" ");- os.put_string (@"left_length=\"$(ep.left_handle.length)\" "); - }- - if (ep.right_handle.type == PointType.CURVE || ep.left_handle.type == PointType.CURVE) {- os.put_string (@"tie_handles=\"$(ep.tie_handles)\" ");- }- - os.put_string ("/>");- - }- - os.put_string ("</object>\n");- }- - GlyphBackgroundImage? bg = g.get_background_image ();- - if (bg != null) {- GlyphBackgroundImage background_image = (!) bg;-- double pos_x = background_image.img_x;- double pos_y = background_image.img_y;- - double scale_x = background_image.img_scale_x;- double scale_y = background_image.img_scale_y;- - double rotation = background_image.img_rotation;- - if (background_image.is_valid ()) {- os.put_string (@"\t<background sha1=\"$(background_image.get_sha1 ())\" x=\"$pos_x\" y=\"$pos_y\" scale_x=\"$scale_x\" scale_y=\"$scale_y\" rotation=\"$rotation\"/>\n");- }- }- - os.put_string ("</glyph>\n\n"); -- }-- public void set_font_file (string path) {- font_file = path;- modified = false;- }-- public uint length () {- return glyph_names.length ();- }-- public bool is_empty () {- return (glyph_names.length () == 0);- }-- public bool load (string path, bool recent = true) {- bool loaded = false;- - try {- otf_font = false;-- while (grid_width.length () > 0) {- grid_width.remove_link (grid_width.first ());- }-- while (glyph_names.length () > 0) {- glyph_names.remove_link (glyph_names.first ());- }- - glyph_cache.remove_all ();- unassigned_glyphs.remove_all ();- - if (path.has_suffix (".ffi")) {- loaded = parse_file (path);- font_file = path;- }- - if (Supplement.experimental) {- if (path.has_suffix (".ttf")) {- loaded = parse_otf_file (path);- font_file = path;- }- }- - if (recent) {- add_thumbnail ();- Preferences.add_recent_files (get_path ());- }- } catch (GLib.Error e) {- warning (e.message);- return false;- }- - return loaded;- }-- private void add_thumbnail () {- File f = Supplement.get_thumbnail_directory ().get_child (@"$((!) get_file_name ()).png");- Glyph? gl = get_glyph ("a");- Glyph g;- ImageSurface img;- ImageSurface img_scale;- Context cr;- double scale;- - if (gl == null) {- gl = get_glyph_indice (4);- } - - if (gl == null) {- gl = get_not_def_character ();- }- - g = (!) gl;-- img = g.get_thumbnail ();- scale = 70.0 / img.get_width ();- - if (scale > 70.0 / img.get_height ()) {- scale = 70.0 / img.get_height ();- }- - if (scale > 1) {- scale = 1;- }-- img_scale = new ImageSurface (Format.ARGB32, (int) (scale * img.get_width ()), (int) (scale * img.get_height ()));- cr = new Context (img_scale);- - cr.scale (scale, scale);-- cr.save ();- cr.set_source_surface (img, 0, 0);- cr.paint ();- cr.restore ();- - img_scale.write_to_png ((!) f.get_path ());- }-- /** Callback function for loading glyph in a separate thread. */- public void add_glyph_callback (Glyph g) {- GlyphCollection? gcl;- GlyphCollection gc;- - gcl = get_cached_glyph_collection (g.get_name ());- - if (gcl != null) {- warning (@"glyph \"$(g.get_name ())\" does already exist");- }- - if (g.is_unassigned ()) {- gc = new GlyphCollection (g);- - if (g.name == "") {- warning ("refusing to insert glyph without name");- g.name = @"($(++next_unindexed))";- return;- }- - add_glyph_collection ((!) gc);- } else if (gcl == null) {- gc = new GlyphCollection (g);- add_glyph_collection ((!) gc);- } else {- stderr.printf (@"Glyph collection does already have an entry for $(g.get_name ()) char $((uint64) g.unichar_code).\n");- }- - // take xheight from appropriate lower case letter- // xheight_position = estimate_xheight ();- }-- public bool parse_otf_file (string path) throws GLib.Error {- otf = new OpenFontFormatReader ();- otf_font = true;- - otf.parse_index (path);- - foreach (string n in otf.get_all_names ()) {- glyph_names.append (n);- }- - return true;- }- - public bool parse_file (string path) throws GLib.Error {- Parser.init ();- - Xml.Doc* doc;- Xml.Node* root;- Xml.Node* node;-- // set this path as file for this font- font_file = path;- - // empty cache and fill it with new glyphs from disk- glyph_cache.remove_all ();- unassigned_glyphs.remove_all ();-- while (background_images.length () > 0) {- background_images.remove_link (background_images.first ());- }-- create_background_files (path);-- doc = Parser.parse_file (path);- root = doc->get_root_element ();- - if (root == null) {- delete doc;- return false;- }-- node = root;- - for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {- - if (iter->name == "glyph") {- parse_glyph (iter);- }-- if (iter->name == "lines") {- parse_font_boundries (iter);- }-- if (iter->name == "grid") {- parse_grid (iter);- }-- if (iter->name == "background") {- parse_background (iter);- }- - if (iter->name == "images") {- parse_background_selection (iter);- }-- if (iter->name == "name") {- name = iter->children->content;- }-- if (iter->name == "hkern") {- parse_kerning (iter);- }-- if (iter->name == "ttf-export") {- ttf_export = bool.parse (iter->children->content);- } -- if (iter->name == "svg-export") {- svg_export = bool.parse (iter->children->content);- }- - }- - delete doc;- Parser.cleanup ();-- return true;- }- - private void create_background_files (string path) {- Xml.Doc* doc = Parser.parse_file (path);- Xml.Node* root;- Xml.Node* node;- - root = doc->get_root_element ();- - if (root == null) {- delete doc;- return;- }-- node = root;- - for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {- if (iter->name == "name") {- name = iter->children->content;- }- - if (iter->name == "background-image") {- parse_background_image (iter);- }- }- - delete doc;- Parser.cleanup ();- }- - private void parse_kerning (Xml.Node* node) {- string attr_name;- string attr_content;- string left = "";- string right = "";- string kern = "";-- StringBuilder b;- - for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {- attr_name = prop->name;- attr_content = prop->children->content;- - if (attr_name == "left") {- b = new StringBuilder ();- b.append_unichar (to_unichar (attr_content));- left = @"$(b.str)";- }-- if (attr_name == "right") {- b = new StringBuilder ();- b.append_unichar (to_unichar (attr_content));- right = @"$(b.str)";- }- - if (attr_name == "kerning") {- kern = attr_content;- }- }- - set_kerning (left, right, double.parse (kern));- }- - private void parse_background_image (Xml.Node* node) - requires (node != null)- {- string attr_name;- string attr_content;- - string file = "";- string data = "";- - File img_dir;- File img_file;- FileOutputStream file_stream;- DataOutputStream png_stream;- - for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {- return_if_fail (!is_null (prop->name));- return_if_fail (!is_null (prop->children));- return_if_fail (!is_null (prop->children->content));- - attr_name = prop->name;- attr_content = prop->children->content;- - if (attr_name == "sha1") {- file = attr_content;- }- - if (attr_name == "data") {- data = attr_content;- }- }- - img_dir = get_backgrounds_folder ().get_child ("parts");-- if (!img_dir.query_exists ()) {- DirUtils.create ((!) img_dir.get_path (), 0xFFFFFF);- }- - img_file = img_dir.get_child (@"$(file).png");- - if (img_file.query_exists ()) {- return;- }- - try {- file_stream = img_file.create (FileCreateFlags.REPLACE_DESTINATION);- png_stream = new DataOutputStream (file_stream);-- png_stream.write (Base64.decode (data));- png_stream.close (); - } catch (GLib.Error e) {- warning (e.message);- }- }- - private void parse_background (Xml.Node* node) - requires (node != null)- {- string attr_name;- string attr_content;- - for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {- - return_if_fail (!is_null (prop->name));- return_if_fail (!is_null (prop->children));- return_if_fail (!is_null (prop->children->content));- - attr_name = prop->name;- attr_content = prop->children->content;- - if (attr_name == "scale") {- background_scale = attr_content;- }- }- }- - private void parse_background_selection (Xml.Node* node) {- string attr_name = "";- string attr_content;- - return_if_fail (node != null);- - for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {- if (iter->name == "img") {- for (Xml.Attr* prop = iter->properties; prop != null; prop = prop->next) {- attr_name = prop->name;- attr_content = prop->children->content;- - if (attr_name == "src") {- background_images.append (attr_content);- }- }- }- }- }- - private void parse_grid (Xml.Node* node) {- for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {- string attr_name = prop->name;- string attr_content = prop->children->content;- - if (attr_name == "width") {- grid_width.append (attr_content);- }- } - }- - private void parse_font_boundries (Xml.Node* node) {- for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {- if (iter->name == "top_limit") top_limit = parse_double_from_node (iter);- if (iter->name == "top_position") top_position = parse_double_from_node (iter);- if (iter->name == "x-height") xheight_position = parse_double_from_node (iter);- if (iter->name == "base_line") base_line = parse_double_from_node (iter);- if (iter->name == "bottom_position") bottom_position = parse_double_from_node (iter);- if (iter->name == "bottom_limit") bottom_limit = parse_double_from_node (iter);- } - }- - private double parse_double_from_node (Xml.Node* iter) {- double d;- bool r = double.try_parse (iter->children->content, out d);- - if (unlikely (!r)) {- string? s = iter->content;- if (s == null) warning (@"Content is null for node $(iter->name)\n");- else warning (@"Failed to parse double for \"$(iter->content)\"\n");- }- - return (r) ? d : 0;- }- - /** Parse one glyph. */- public void parse_glyph (Xml.Node* node) {- string name = "";- int left = 0;- int right = 0;- unichar uni = 0;- int version = 0;- bool selected = false;- Glyph g;- GlyphCollection? gc;- - for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {- string attr_name = prop->name;- string attr_content = prop->children->content;- StringBuilder b;- - if (attr_name == "unicode") {- uni = to_unichar (attr_content);- b = new StringBuilder ();- b.append_unichar (uni);- name = b.str;- }-- if (attr_name == "left") {- left = int.parse (attr_content);- }- - if (attr_name == "right") {- right = int.parse (attr_content);- }- - if (attr_name == "version") {- version = int.parse (attr_content);- }- - if (attr_name == "selected") {- selected = bool.parse (attr_content);- }- }-- g = new Glyph (name, uni);- - g.left_limit = left;- g.right_limit = right;-- parse_content (g, node);- - // todo: use disk thread and idle add this:- - gc = get_glyph_collection (g.get_name ());- - if (g.get_name () == "") {- warning ("No name set for glyph.");- }- - if (gc == null) {- gc = new GlyphCollection (g);- add_glyph_collection ((!) gc);- } else {- ((!)gc).insert_glyph (g, selected);- }- - }- - /** Parse visual objects and paths */- private void parse_content (Glyph g, Xml.Node* node) {- Xml.Node* i;- return_if_fail (node != null);- - for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {- if (iter->name == "object") {- Path p = new Path ();- - for (i = iter->children; i != null; i = i->next) {- if (i->name == "point") {- parse_point (p, i);- } - }-- p.close ();- g.add_path (p);- }- - if (iter->name == "background") {- parse_background_scale (g, iter);- }- }- }- - private void parse_background_scale (Glyph g, Xml.Node* node) {- GlyphBackgroundImage img;- GlyphBackgroundImage? new_img = null;- - string attr_name = "";- string attr_content;- - File img_file = get_backgrounds_folder ().get_child ("parts");- - for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {- attr_name = prop->name;- attr_content = prop->children->content;- - if (attr_name == "sha1") {- img_file = img_file.get_child (attr_content + ".png");-- if (!img_file.query_exists ()) {- warning (@"Background file has not been created yet. $((!) img_file.get_path ())");- }- - new_img = new GlyphBackgroundImage ((!) img_file.get_path ());- g.set_background_image ((!) new_img);- }- }- - if (unlikely (new_img == null)) {- warning (@"No source for image found for $attr_name in $(g.name)");- return;- }- - img = (!) new_img;- - for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {- attr_name = prop->name;- attr_content = prop->children->content;- - if (attr_name == "x") {- img.img_x = double.parse (attr_content);- }- - if (attr_name == "y") {- img.img_y = double.parse (attr_content);- } -- if (attr_name == "scale_x") {- img.img_scale_x = double.parse (attr_content);- }-- if (attr_name == "scale_y") {- img.img_scale_y = double.parse (attr_content);- }- - if (attr_name == "rotation") {- img.img_rotation = double.parse (attr_content);- }- }- - img.set_position(img.img_x, img.img_y); - }- - private void parse_point (Path p, Xml.Node* iter) {- double x = 0;- double y = 0;- - PointType type = PointType.LINE;- - double angle_right = 0;- double angle_left = 0;- - double length_right = 0;- double length_left = 0;- - PointType type_right = PointType.LINE;- PointType type_left = PointType.LINE;- - bool tie_handles = false;- - EditPoint ep;- - for (Xml.Attr* prop = iter->properties; prop != null; prop = prop->next) {- string attr_name = prop->name;- string attr_content = prop->children->content;- - if (attr_name == "x") x = double.parse (attr_content);- if (attr_name == "y") y = double.parse (attr_content);- - if (attr_name == "right_angle") {- type = PointType.CURVE; // FIXA: delete, maybe- } - - if (attr_name == "right_angle") {- type_right = PointType.CURVE;- } -- if (attr_name == "left_angle") {- type_left = PointType.CURVE;- }- - if (attr_name == "right_angle") angle_right = double.parse (attr_content);- if (attr_name == "right_length") length_right = double.parse (attr_content);- if (attr_name == "left_angle") angle_left = double.parse (attr_content);- if (attr_name == "left_length") length_left = double.parse (attr_content);- - if (attr_name == "tie_handles") tie_handles = bool.parse (attr_content);- }- - ep = new EditPoint (x, y);- - ep.type = type;- - ep.right_handle.angle = angle_right;- ep.right_handle.length = length_right;- ep.right_handle.type = type_right;- - ep.left_handle.angle = angle_left;- ep.left_handle.length = length_left;- ep.left_handle.type = type_left;- - ep.tie_handles = tie_handles;- - p.add_point (ep);- }- - public bool restore_backup () {- string? b = present_backup_file ();- - if (b == null) return false;- - return parse_file ((!)b);- }- - private string? present_backup_file () {- try {- File dir = Supplement.get_settings_directory ();- var files = dir.enumerate_children (FILE_ATTRIBUTE_STANDARD_NAME, 0);-- // What if we have more than one backup file left?- FileInfo? file_info;- while ((file_info = files.next_file ()) != null) {- FileInfo fi = (!) file_info;- if (fi.get_name ().index_of ("current_font_") != -1) {- File f = dir.get_child (fi.get_name ());- backup_file = f.get_path ();- return backup_file;- }- }- } catch (GLib.Error e) {- stderr.printf (@"Failed to load backup\n");- stderr.printf (@"$(e.message) \n");- }- - return null;- }- - /** Delete temporary rescue files. */- public void delete_backup () {- if (backup_file == null) return;-- try {- File f = File.new_for_path ((!) backup_file);- f.delete (); - } catch (GLib.Error e) {- stderr.printf (@"Failed to delete backup\n");- stderr.printf (@"$(e.message) \n");- }- }- - public static string to_hex (unichar ch) {- StringBuilder s = new StringBuilder ();- s.append ("U+");- s.append (to_hex_code (ch));- return s.str;- }-- public static string to_hex_code (unichar ch) {- StringBuilder s = new StringBuilder ();- - uint8 a = (uint8)(ch & 0x00000F);- uint8 b = (uint8)((ch & 0x0000F0) >> 4 * 1);- uint8 c = (uint8)((ch & 0x000F00) >> 4 * 2);- uint8 d = (uint8)((ch & 0x00F000) >> 4 * 3);- uint8 e = (uint8)((ch & 0x0F0000) >> 4 * 4);- uint8 f = (uint8)((ch & 0xF00000) >> 4 * 5);- - if (e != 0 || f != 0) {- s.append (oct_to_hex (f));- s.append (oct_to_hex (e));- }- - if (c != 0 || d != 0) {- s.append (oct_to_hex (d));- s.append (oct_to_hex (c));- }- - s.append (oct_to_hex (b));- s.append (oct_to_hex (a));- - return s.str;- }-- public static unichar to_unichar (string unicode) {- int index = 2;- int i = 0;- unichar c;- unichar rc = 0;- bool r;-- if (unlikely (unicode.index_of ("U+") != 0)) {- warning ("All unicode values must begin with U+");- }- - while (r = unicode.get_next_char (ref index, out c)) {- rc <<= 4;- rc += hex_to_oct (c);- - return_val_if_fail (++i <= 6, '\0');- }-- return rc;- }- - private static string oct_to_hex (uint8 o) {- switch (o) {- case 10: return "a";- case 11: return "b";- case 12: return "c";- case 13: return "d";- case 14: return "e";- case 15: return "f";- }-- return_val_if_fail (0 <= o <= 9, "-".dup ());- - return o.to_string ();- }-- private static uint8 hex_to_oct (unichar o) {- StringBuilder s = new StringBuilder ();- s.append_unichar (o);- - switch (o) {- case 'a': return 10;- case 'b': return 11;- case 'c': return 12;- case 'd': return 13;- case 'e': return 14;- case 'f': return 15;- }- - return_val_if_fail ('0' <= o <= '9', 0);- - return (uint8) (o - '0');- }--}--}deleted file mode 100644@@ -1,239 +0,0 @@-/*- 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;-using Birdfont;--namespace Supplement {--/** Javascripts in webkit do use this callback. */-public class PropertyFunction {- public delegate void PropertyCallback (string val);- public PropertyCallback call;-}--public abstract class FontDisplay : GLib.Object {- - List<string> property = new List<string> ();- List<PropertyFunction> call = new List<PropertyFunction> ();- - /** Queue redraw area */- public signal void redraw_area (double x, double y, double w, double h);- - /** Name of symbol or set of symbols in display selections like the tab panel. */- public virtual string get_name () {- return "";- }- - public virtual bool is_html_canvas () {- return false;- }-- public static string path_to_uri (string path) {- string uri = path;- string wp;- - // wine uri hack- if (Supplement.win32) {- wp = wine_to_unix_path (uri);- - if (find_file (wp, "").query_exists ()) {- uri = wp;- }-- if (uri.index_of ("\\") > -1) {- uri = uri.replace ("\\", "/");- }- }-- if (uri.index_of ("/") == 0) {- uri = @"file://$uri";- } else {- uri = @"file:///$uri";- }- - return uri;- }-- public virtual string get_uri () {- return "";- }- - public virtual string get_html_file () {- return "index.html";- }- - public virtual string get_html () {- return "";- }- - public virtual void draw (Allocation allocation, Context cr) {- }- - public virtual void selected_canvas () {- }- - public virtual void key_press (uint keyval) {- }- - public virtual void key_release (uint keyval) {- }- - public virtual void motion_notify (double x, double y) {- }- - public virtual void button_release (int button, double x, double y) {- }- - public virtual void button_press (uint button, double x, double y) {- }-- public virtual void double_click (uint button, double ex, double ey) {- } -- public virtual void zoom_in () {- }- - public virtual void zoom_out () {- }- - public virtual void zoom_max () {- }- - public virtual void zoom_min () {- }- - public virtual void reset_zoom () {- }- - public virtual void store_current_view () {- warning ("store_current_view not implemented for this class.");- }- - public virtual void restore_last_view () {- warning ("restore_last_view not implemented for this class.");- }-- public virtual void next_view () {- warning ("next_view not implemented for this class.");- }- - public virtual void scroll_wheel_up (double x, double y) {- }- - public virtual void scroll_wheel_down (double x, double y) {- }-- public virtual void undo () {- }-- public static File find_layout_dir () {- return find_file (null, "layout");- }-- public void add_html_callback (string prop, PropertyFunction.PropertyCallback cb) {- PropertyFunction pf = new PropertyFunction ();- pf.call = cb;- property.append (prop);- call.append (pf);- }- - public void process_property (string prop) {- string k, v;- int i, j;- PropertyFunction cb;- - if (prop == "" || prop == "done") {- return;- }- - i = prop.index_of (":");- - if (i <= 0) {- return;- }- - k = prop.substring (0, i);- v = prop.substring (i + 1);- - j = 0;- - foreach (string p in property) {- if (p == k) {- break;- }- j++;- }- - if (j >= property.length ()) {- warning (@"key \"$k\" not found in property list, value: $v");- return;- }- - cb = call.nth (j).data;- cb.call (v);- }-- public static File find_file (string? dir, string name) {- File f;- string d = (dir == null) ? "" : (!) dir;- - f = get_file (Supplement.exec_path + "/", name + "/");- if (likely (f.query_exists ())) return f;- - f = get_file (Supplement.exec_path + "/" + d + "/", name);- if (likely (f.query_exists ())) return f;- - f = get_file ("./" + d + "/", name);- if (likely (f.query_exists ())) return f; -- f = get_file ("../" + d + "/", name);- if (likely (f.query_exists ())) return f;-- f = get_file (".\\" + d + "\\", name);- if (likely (f.query_exists ())) return f;-- f = get_file ("", name);- if (likely (f.query_exists ())) return f;-- f = get_file (d + "\\", name);- if (likely (f.query_exists ())) return f;-- f = get_file ("/usr/local/share/birdfont/" + d + "/", name);- if (likely (f.query_exists ())) return f;-- f = get_file ("/usr/share/birdfont/" + d + "/", name);- if (likely (f.query_exists ())) return f;-- f = get_file (@"$PREFIX/share/birdfont/" + d + "/", name);- if (likely (f.query_exists ())) return f;- - warning (@"Did not find file $name in $d");- - return f; - }-- public static File get_file (string? path, string name) {- StringBuilder fn = new StringBuilder ();- string p = (path == null) ? "" : (!) path;- fn.append (p);- fn.append ((!) name);- return File.new_for_path (fn.str);- }-}--}deleted file mode 100644@@ -1,1719 +0,0 @@-/*- 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;-using Math;--namespace Supplement {--public class Glyph : FontDisplay {-- public static const double SCALE = 1.0;-- // Background image- GlyphBackgroundImage? background_image = null;- bool background_image_visible = true;- - // Glyph zoom level- public double view_zoom = 0.01;- public double view_offset_x = 0;- public double view_offset_y = 0;- List<ZoomView> zoom_list = new List<ZoomView> ();- int zoom_list_index = 0;- - // Paths- public List<Path> path_list;- public List<Path> active_paths = new List<Path> ();-- // Control points- public EditPoint? new_point_on_path = null;- public EditPoint? flipping_point_on_path = null;- public EditPoint? last_added_edit_point = null;- - // The point where edit event begun - double pointer_begin_x = 0;- double pointer_begin_y = 0;-- // Current pointer position- double motion_x = 0;- double motion_y = 0;- - // Zoom area- double zoom_x1 = 0;- double zoom_y1 = 0;- double zoom_x2 = 0;- double zoom_y2 = 0;- bool zoom_area_is_visible = false;- - bool move_view = false;- public double move_offset_x = 0;- public double move_offset_y = 0;-- public Allocation allocation;- - public string name;-- public double left_limit;- public double right_limit;- - // x-height, lsb, etc.- public List<Line> vertical_help_lines = new List<Line> ();- public List<Line> horizontal_help_lines = new List<Line> ();- List<Line> all_lines = new List<Line> (); // vertical, horzontal and grid lines- bool show_help_lines = true;- bool xheight_lines_visible = false;- bool margin_boundries_visible = false;- - bool unassigned = false;- public unichar unichar_code = 0;- - List<Glyph> undo_list = new List<Glyph> ();- - public List<Kerning> kerning = new List<Kerning> ();- - string glyph_sequence = "";- bool open = true;- - public Glyph (string name, unichar unichar_code = 0) {- this.name = name;- this.unichar_code = unichar_code;- - path_list.append (new Path ());- - add_help_lines ();-- left_limit = -28 * SCALE;- right_limit = 28 * SCALE;- }-- public Glyph.no_lines (string name, unichar unichar_code = 0) {- this.name = name;- this.unichar_code = unichar_code;-- path_list.append (new Path ());- }- - public void clear_active_paths () {- while (active_paths.length () > 0) {- active_paths.remove_link (active_paths.first ());- }- }- - public void add_active_path (Path? p) {- if (p != null) {- - foreach (Path pi in active_paths) {- if (pi == p) {- return;- }- }- - active_paths.append ((!) p);- }- }- - public Path? get_active_path () {- return_if_fail (!is_null(active_paths));- - if (!is_null(active_paths.last ())) {- return null;- }- - return active_paths.last ().data;- }- - - public void add_kerning (string right_glyph, double val) {- Kerning? kl = null;-- foreach (Kerning k in kerning) {- if (k.glyph_right == right_glyph) {- kl = k;- }- }- - if (kl == null) {- kerning.append (new Kerning (right_glyph, val));- } else {- ((!)kl).val = val;- }- }-- public double get_kerning (string right_glyph) {- foreach (Kerning k in kerning) {- if (k.glyph_right == right_glyph) {- return k.val;- }- }- - return 0;- }- - public void set_unassigned (bool u) {- unassigned = u;- }- - public bool is_unassigned () {- return unassigned;- }-- public void boundries (out double x1, out double y1, out double x2, out double y2) {- if (path_list.length () == 0) {- x1 = 0;- y1 = 0;- x2 = 0;- y2 = 0;- return;- }-- x1 = path_list.first ().data.xmin;- y1 = path_list.first ().data.ymin;- x2 = path_list.first ().data.xmax;- y2 = path_list.first ().data.ymax;- - foreach (Path p in path_list) {- p.update_region_boundries ();- - 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 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 () {- double a, b, c, d;- - if (view_zoom > 1) { - a = vertical_help_lines.first ().data.get_coordinate ();- c = horizontal_help_lines.first ().data.get_coordinate ();- - b = vertical_help_lines.first ().data.get_coordinate ();- d = horizontal_help_lines.first ().data.get_coordinate ();- - view_offset_x -= a - b;- view_offset_y -= c - d;- }- }-- public void set_background_image (GlyphBackgroundImage? b) {- GlyphBackgroundImage bg;- - if (b == null) {- background_image = null;- }- - bg = (!) b;- - background_image = bg;- - Supplement.get_current_font ().touch ();- }- - public GlyphBackgroundImage? get_background_image () {- return (!) background_image;- }- - public override void scroll_wheel_up (double x, double y) {- if (KeyBindings.has_ctrl ()) {- zoom_in_at_point (x, y);- } else if (KeyBindings.has_alt ()) { - view_offset_x -= 10 / view_zoom;- } else {- view_offset_y -= 10 / view_zoom;- }- - redraw_area (0, 0, allocation.width, allocation.height);- }- - public override void scroll_wheel_down (double x, double y) {- if (KeyBindings.has_ctrl ()) {- zoom_out_at_point (x, y);- } else if (KeyBindings.has_alt ()) { - view_offset_x += 10 / view_zoom;- } else {- view_offset_y += 10 / view_zoom;- }- - redraw_area (0, 0, allocation.width, allocation.height);- }- - public void add_path (Path p) {- path_list.append (p);- }- - public override void selected_canvas () {- add_help_lines ();- KeyBindings.singleton.set_require_modifier (false);- glyph_sequence = Preferences.get ("glyph_sequence");- }- - private void remove_lines () {- while (vertical_help_lines.length () != 0) {- vertical_help_lines.remove_link (vertical_help_lines.first ());- }-- while (horizontal_help_lines.length () != 0) {- horizontal_help_lines.remove_link (horizontal_help_lines.first ());- } - }- - public void add_help_lines () {- remove_lines ();-- assert (!is_null (Supplement.get_current_font ()));-- double bgt = Supplement.get_current_font ().top_limit;- Line top_margin_line = new Line ("top margin", bgt, false);- top_margin_line.set_color (0.7, 0, 0, 0.5);- top_margin_line.position_updated.connect ((pos) => {- Supplement.get_current_font ().top_limit = pos;- });- - double thp = Supplement.get_current_font ().top_position;- Line top_line = new Line ("top", thp, false);- top_line.position_updated.connect ((pos) => {- Font f = Supplement.get_current_font ();- f.top_position = pos;- });- - double xhp = Supplement.get_current_font ().xheight_position;- Line xheight_line = new Line ("x-height", xhp, false);- xheight_line.set_color (33 / 255.0, 68 / 255.0, 120 / 255.0, 166 / 255.0);- xheight_line.position_updated.connect ((pos) => { - Font f = Supplement.get_current_font ();- f.xheight_position = pos;- });-- double xbl = Supplement.get_current_font ().base_line;- Line base_line = new Line ("baseline", xbl, false);- base_line.position_updated.connect ((pos) => {- Font f = Supplement.get_current_font ();- f.base_line = pos;- });-- - double bp = Supplement.get_current_font ().bottom_position;- Line bottom_line = new Line ("bottom", bp, false);- bottom_line.set_color (33 / 255.0, 68 / 255.0, 120 / 255.0, 166 / 255.0);- bottom_line.position_updated.connect ((pos) => {- Supplement.get_current_font ().bottom_position = pos;- });-- double bgb = Supplement.get_current_font ().bottom_limit;- Line bottom_margin_line = new Line ("bottom margin", bgb, false);- bottom_margin_line.set_color (0.7, 0, 0, 0.5);- bottom_margin_line.position_updated.connect ((pos) => {- Supplement.get_current_font ().bottom_limit = pos;- });- - Line left_line = new Line ("left", left_limit, true);- left_line.position_updated.connect ((pos) => {- left_limit = pos;- });- - Line right_line = new Line ("right", right_limit, true);- right_line.position_updated.connect ((pos) => {- right_limit = pos;- });- - // lists of help lines are sorted and lines are added only if - // they are important 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_boundries_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_boundries_visible);- }- - bool has_top_line () {- return !unichar_code.islower () || CharDatabase.has_ascender (unichar_code);- }- - /** 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_boundries_visible = m;- add_help_lines ();- }- - public bool get_margin_lines_visible () {- return margin_boundries_visible;- }- - public void remove_empty_paths () {- foreach (var p in path_list) {- if (p.points.length () < 2) {- delete_path (p);- }- }- }- - public void delete_path (Path p) {- path_list.remove_all (p);- }- - public string get_svg_data () {- return Svg.to_svg_glyph (this, 1 / SCALE);- }- - public int get_height () - requires (vertical_help_lines.length () >= 2)- {- unowned List<Line> a = horizontal_help_lines.first ();- unowned List<Line> b = horizontal_help_lines.last ();- - return (int) Math.fabs (a.data.pos / SCALE - b.data.pos / SCALE); - }- - public double get_width () {- return Math.fabs (right_limit / SCALE - left_limit / SCALE);- }-- public unichar get_unichar () {- return unichar_code;- }-- public bool get_show_help_lines () {- return show_help_lines;- }- - 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.append (line);- line.list_item = vertical_help_lines.last ();- } else {- horizontal_help_lines.append (line);- line.list_item = horizontal_help_lines.last ();- }-- 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) => {- return (int) (a.get_pos () - b.get_pos ());- });- - horizontal_help_lines.sort ((a, b) => {- return (int) (a.get_pos () - b.get_pos ());- });- }- - public override string get_name () {- return name;- }- - private void help_line_event (double x, double y) {- double mx = x;- double my = y;- - if (GridTool.is_visible ()) {- GridTool.tie (ref mx, ref my);- }- - foreach (Line line in get_all_help_lines ()) {- line.event_move_to (mx, my, x, y, allocation);- }- }-- public override void key_release (uint keyval) { - Tool t;- t = MainWindow.get_toolbox ().get_current_tool ();- t.key_release_action (t, keyval); - }-- public override void key_press (uint keyval) { - Tool t = MainWindow.get_toolbox ().get_current_tool ();- t.key_press_action (t, keyval);- }- - /** Delete edit point from path. */- public void delete_edit_point (EditPoint ep) {- foreach (Path p in path_list) {- if (p.points.length () > 0) {- p.delete_edit_point (ep);- } else {- delete_path (p);- }- }- }- - public override void motion_notify (double x, double y) {- Tool t;- t = MainWindow.get_toolbox ().get_current_tool ();- - if (move_view && KeyBindings.has_ctrl ()) {- move_view_offset (x, y);- return;- }-- t.move_action (t, (int) x, (int) y);-- if (Supplement.show_coordinates) {- motion_x = x * ivz () - xc () + view_offset_x;- motion_y = yc () - y * ivz () - view_offset_y;- }- - help_line_event (x, y); - }- - public override void button_release (int button, double ex, double ey) {- bool line_moving = false;- - if (KeyBindings.has_ctrl ()) {- move_view = false;- return;- }- - 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 unowned List<Line> get_all_help_lines () {- while (all_lines.length () > 0) all_lines.delete_link (all_lines.first ());- - all_lines.concat (vertical_help_lines.copy ());- all_lines.concat (horizontal_help_lines.copy ());- - if (GridTool.is_visible ()) {- all_lines.concat (GridTool.get_vertical_lines ().copy ());- all_lines.concat (GridTool.get_horizontal_lines ().copy ());- }- - return all_lines;- }- - public void update_view () {- MainWindow.get_glyph_canvas ().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) { - pointer_begin_x = ex;- pointer_begin_y = ey;- - foreach (Line line in get_all_help_lines ()) {- if (line.button_press ()) {- return;- }- }- - if (KeyBindings.has_ctrl ()) {- move_view = 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);- }- }-- /** Insert new edit point for current path on the appropriate zoom- * level.- */- public EditPoint add_new_edit_point (int x, int y) {- insert_edit_point (x, y);- - return_if_fail (active_paths.length () > 0);- - last_added_edit_point = active_paths.last ().data.get_last_point ();- return (!) last_added_edit_point;- }-- public EditPoint get_last_edit_point () {- return_val_if_fail (last_added_edit_point != null, new EditPoint ());- return (!) last_added_edit_point;- }-- /** 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;-- view_offset_x += x / view_zoom;- view_offset_y += y / view_zoom;- - view_zoom_x = allocation.width * view_zoom / w;- view_zoom_y = allocation.height * view_zoom / h;- - // TODO: there is a max zoom level: probably ivz > 0.1- - 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 ();- }- - public void show_zoom_area (int sx, int sy, int nx, int ny) {- double x, y, w, h;- - set_zoom_area (sx, sy, nx, ny);- - zoom_area_is_visible = true;- - x = Math.fmin (zoom_x1, zoom_x2) - 50;- y = Math.fmin (zoom_y1, zoom_y2) - 50;-- w = Math.fabs (zoom_x1 - zoom_x2) + 100;- h = Math.fabs (zoom_y1 - zoom_y2) + 100;- - redraw_area ((int)x, (int)y, (int)w, (int)h);- }-- 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 double path_coordinate_x (double x) {- Glyph g = MainWindow.get_current_glyph ();- return_if_fail (g.view_zoom != 0);- return x * g.ivz () - g.xc () + g.view_offset_x;- }-- public static int reverse_path_coordinate_x (double x) {- Glyph g = MainWindow.get_current_glyph ();- return_if_fail (g.view_zoom != 0);- return (int) ((x - g.view_offset_x + g.xc ()) * g.view_zoom);- }-- public static double path_coordinate_y (double y) {- Glyph g = MainWindow.get_current_glyph ();- return g.yc () - y * g.ivz () - g.view_offset_y;- }-- public static int reverse_path_coordinate_y (double y) {- Glyph g = MainWindow.get_current_glyph ();- y = ((y + g.view_offset_y - g.yc ()) * g.view_zoom);- return (int) (-y);- }-- public bool select_path (double x, double y) {- Path? p = null;- bool found = false;- - foreach (Path pt in path_list) {- if (pt.is_over (x, y)) {- p = pt;- found = true;- }- }- - if (!KeyBindings.has_shift ()) {- clear_active_paths ();- }- - add_active_path (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);-- foreach (Path p in path_list) {- if (p.is_over (xt, yt)) {- return p;- }- }-- foreach (Path p in path_list) {- if (p.points.length () == 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 (path_list.length () > 0) {- return path_list.first ().data;- }-- if (unlikely (min_distance == double.MAX)) {- warning (@"No path found in path_list. Length: $(path_list.length ())");- - if (path_list.length () > 0) {- stderr.printf (@"p.points.length () $(path_list.first ().data.points.length ()) \n");- }- }- - return min_point;- }- - private EditPoint insert_edit_point (double x, double y) {- unowned List<Path> paths;- double xt, yt;- bool added;- Path np;- EditPoint inserted;- - if (active_paths.length () == 0) {- np = new Path ();- clear_active_paths ();- active_paths.append (np);- path_list.append (np);- }- - paths = path_list;- - xt = path_coordinate_x (x);- yt = path_coordinate_y (y);- - added = false;-- if (new_point_on_path != null) {- return_if_fail (active_paths.length () > 0);-- Path p = new Path ();- EditPoint e = (!) new_point_on_path;-- return_if_fail (e.prev != null);- - p.add_point (e);- e.type = PointType.CURVE;- - e.right_handle.type = PointType.CURVE;- e.left_handle.type = PointType.CURVE;- - paths.append (p);- add_active_path (p);- flipping_point_on_path = new_point_on_path;- new_point_on_path = null;- added = true;- return e;- }- - if (!added) {- foreach (Path p in paths) {- if (p.is_open ()) {- p.add (xt, yt);- clear_active_paths ();- add_active_path (p);- added = true;- break;- }- }- }-- if (!added) {- foreach (Path p in paths) {- if (p.is_over (xt, yt)) {- p.add (xt, yt);- paths.append (p);- added = true;- redraw_area (0, 0, allocation.width, allocation.height);- break;- }- }- }- - if (!added) {- if (paths.length () > 0 && paths.last ().data.is_open ()) {- paths.last().data.add (xt, yt);- }- - foreach (var p in path_list) {- p.close ();- }- - np = new Path ();- paths.append (np);- np.add (xt, yt);- - clear_active_paths ();- add_active_path (np);- }- - assert (active_paths.length () > 0);- assert (active_paths.last ().data.points.length () > 0);- - inserted = active_paths.last ().data.points.data;- - return inserted;- }- - public void move_selected_edit_point_delta (EditPoint selected_point, double dx, double dy) {- double px = selected_point.x + xc () - view_offset_x;- double py = -1 * (selected_point.y - yc () + view_offset_y);- - px *= view_zoom;- py *= view_zoom;- - move_selected_edit_point (selected_point, dx + px, dy + py);- }- - public void move_selected_edit_point (EditPoint selected_point, double x, double y) { - double xc, yc, xt, yt;- double ivz = 1 / view_zoom;- EditPoint p;- - Supplement.get_current_font ().touch ();-- xc = (allocation.width / 2.0);- yc = (allocation.height / 2.0);- - x *= ivz;- y *= ivz;- - xt = x - xc + view_offset_x;- yt = yc - y - view_offset_y;- - // 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);- }- - if (flipping_point_on_path != null) {- p = (!) flipping_point_on_path;- p.recalculate_handles (x, y);- redraw_area (0, 0, allocation.width, allocation.height);- } - }- - 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.length () == 0) {- return;- }- - foreach (Path path in active_paths) {- EditPoint? pt = path.get_second_last_point ();- if (pt != null) {- EditPoint p = (!) pt;- - 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 Path? get_last_path () - ensures (result != null)- {- return_if_fail (path_list.length () > 0);- return path_list.last ().data;- }- - public bool has_active_path () {- return active_paths.length () > 0;- }- - public bool is_open () {- return open;- }- - /** Close all editable paths and return false if no path have boon closed. - * Paths without area (points and lines) will be deleted.- */- public bool close_path () {- bool r = false;- - foreach (var p in path_list) {- if (p.is_editable ()) {- r = true;- p.close ();- }- }- - clear_active_paths ();- new_point_on_path = null;- flipping_point_on_path = null;- - delete_invisible_paths ();- - redraw_area (0, 0, allocation.width, allocation.height);- - open = false;- - return r;- }-- public void open_path () { - foreach (var p in path_list) {- p.set_editable (true);- }- - 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_boundries ();- - 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); - }-- /** Delete all paths without area. */- private void delete_invisible_paths () {- foreach (var p in path_list) {- - if (p.points.length () < 2) {- path_list.remove (p);- continue;- }- - if (p.points.length () == 2) {- if (p.points.first ().data.type == PointType.LINE && p.points.last ().data.type == PointType.LINE) {- path_list.remove (p);- continue;- }- }- - }- }-- 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 () {- set_zoom_area (10, 10, allocation.width - 10, allocation.height - 10);- set_zoom_from_area ();- update_view ();- }- - 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 ();- }- - public override void zoom_max () {- default_zoom ();- }- - 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 path_list) {- p.update_region_boundries ();- - if (p.points.length () > 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);- - }-- public override void store_current_view () {- if (zoom_list_index + 1 < zoom_list.length ()) {- unowned List<ZoomView> n = zoom_list.nth (zoom_list_index);- while (n != zoom_list.last ()) zoom_list.delete_link (zoom_list.last ());- }- - zoom_list.append (new ZoomView (view_offset_x, view_offset_y, view_zoom, allocation));- zoom_list_index = (int) zoom_list.length () - 1;- if (zoom_list.length () > 50) zoom_list.delete_link (zoom_list.first ());- }- - public override void restore_last_view () - requires (zoom_list.length () > 0)- { - if (zoom_list_index - 1 < 0 || zoom_list.length () == 0)- return;- - zoom_list_index--;- - ZoomView z = zoom_list.nth (zoom_list_index).data;- - view_offset_x = z.x;- view_offset_y = z.y;- view_zoom = z.zoom;- allocation = z.allocation;- }-- public override void next_view () - requires (zoom_list.length () > 0)- {- if (zoom_list_index + 1 >= zoom_list.length ())- return;- - zoom_list_index++;- - ZoomView z = zoom_list.nth (zoom_list_index).data;- - view_offset_x = z.x;- view_offset_y = z.y;- view_zoom = z.zoom;- allocation = z.allocation; - }- - public override void reset_zoom () {- view_offset_x = 0;- view_offset_y = 0;- - set_zoom (1);- - store_current_view ();- }- - /** Get x-height or top line. */- public Line get_upper_line () - requires (horizontal_help_lines.length () > 2)- {- - if (has_top_line () || xheight_lines_visible) {- return get_line ("top");- }- - return get_line ("x-height");- }-- /** Get base line. */- public Line get_lower_line () - requires (horizontal_help_lines.length () > 2)- {- return horizontal_help_lines.last ().prev.data;- }- - /** Set default zoom. See default_zoom. */- public void set_default_zoom () {- int l, t, b, r;- - double bottom = 0;- double top = 0;- double left = 0;- double right = 0;- - unowned List<Line>? n = horizontal_help_lines;- unowned List<Line>? v = vertical_help_lines;- - if (unlikely (n == null)) {- warning ("n == null");- warning (@"Can not set default zoom for $name, help lines are not available.");- }- - return_if_fail (v != null);- return_if_fail (vertical_help_lines.length () != 0);- return_if_fail (horizontal_help_lines.length () != 0);-- reset_zoom ();- - bottom = -get_lower_line ().get_pos ();- top = -get_upper_line ().get_pos ();-- left = vertical_help_lines.last ().data.get_pos ();- right = vertical_help_lines.first ().data.get_pos ();- - l = reverse_path_coordinate_x (left);- t = reverse_path_coordinate_y (top);- r = reverse_path_coordinate_x (right); - b = reverse_path_coordinate_y (bottom);- - set_zoom_area (l + 10, t - 10, r - 10, b + 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 path_list) {- if (p.points.length () > 0) {- return false;- }- }- - return true;- }- - private 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;- }- - private void plot_outline (Context cr) {- foreach (unowned Path p in path_list) {- p.plot (cr, allocation, view_zoom);- } - }- - private void draw_coordinate (Context cr) {- cr.set_source_rgba (0.5, 0.5, 0.5, 1);- cr.set_font_size (12);- cr.move_to (0, 10);- cr.show_text (@"($motion_x, $motion_y)");- cr.stroke (); - }- - public void draw_path (Context cr) {- double left, baseline;-- // plot_outline (cr);- - baseline = get_line ("baseline").pos;- left = get_line ("left").pos;- - if (!is_open ()) {- Svg.draw_svg_path (cr, get_svg_data (), Glyph.xc () + left, Glyph.yc () + baseline, SCALE);- }- - if (is_open ()) {- foreach (unowned Path p in path_list) {- p.draw_outline (cr, allocation, view_zoom);- p.draw_edit_points (cr, allocation, view_zoom);- }- }-- if (!is_open ()) {- foreach (unowned Path p in active_paths) {- if (!p.is_editable ()) {- p.fill_path (cr, allocation, view_zoom);- }- }- }-- if (new_point_on_path != null) {- Path.draw_edit_point_center ((!) new_point_on_path, cr);- }- }- - private void draw_zoom_area(Context cr) {- cr.save ();- cr.set_line_width (2.0);- cr.set_source_rgba (0, 0, 1, 0.3);- cr.rectangle (Math.fmin (zoom_x1, zoom_x2), Math.fmin (zoom_y1, zoom_y2), Math.fabs (zoom_x1 - zoom_x2), Math.fabs (zoom_y1 - zoom_y2));- cr.stroke ();- cr.restore ();- }-- private void draw_background_color (Context cr, double opacity) {- cr.save ();- cr.rectangle (0, 0, allocation.width, allocation.height);- cr.set_line_width (0);- cr.set_source_rgba (1, 1, 1, opacity);- cr.fill ();- cr.stroke ();- cr.restore ();- }- - private void draw_help_lines (Context cr) {- foreach (Line line in get_all_help_lines ()) {- cr.save ();- line.draw (cr, allocation);- cr.restore ();- }- }- - public override void draw (Allocation allocation, Context cr) {- Tool tool;- - this.allocation = allocation;-- ImageSurface ps = new ImageSurface (Format.ARGB32, allocation.width, allocation.height);- Context cmp = new Context (ps);-- cr.save ();- draw_background_color (cr, 1);- cr.restore ();-- if (background_image != null && background_image_visible) {- ((!)background_image).draw (cr, allocation, view_offset_x, view_offset_y, view_zoom);- }- - juxtapose (allocation, cr);-- if (Supplement.show_coordinates) {- draw_coordinate (cmp);- }-- if (show_help_lines) {- cmp.save ();- cmp.scale (view_zoom, view_zoom);- cmp.translate (-view_offset_x, -view_offset_y);- draw_help_lines (cmp);- cmp.restore ();- }-- if (zoom_area_is_visible) {- cmp.save ();- draw_zoom_area (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 ();- - cr.save ();- cr.set_source_surface (ps, 0, 0);- cr.paint ();- cr.restore ();- } -- private void zoom_in_at_point (double x, double y) {- int n = -10;- zoom_at_point (x, y, n);- }- - private void zoom_out_at_point (double x, double y) {- int n = (int) (10.0 * ((allocation.width - 10.0) / allocation.width));- zoom_at_point (x, y, n);- }- - /** 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);- redraw_area (0, 0, allocation.width, allocation.height);- }-- public void store_undo_state () {- Glyph g = copy ();- undo_list.append (g); - }-- public Glyph copy () {- Glyph g = new Glyph.no_lines (name, unichar_code);- - 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 ());- }-- foreach (Path p in path_list) {- g.add_path (p.copy ());- }- - foreach (Path p in active_paths) {- g.active_paths.append (p);- }-- if (background_image != null) {- g.background_image = ((!) background_image).copy ();- }- - return g;- }-- public void reload () {- Glyph g;- Font f = Supplement.get_current_font ();- - if (f.has_glyph (name)) {- set_glyph_data ((!) f.get_glyph (name));- }- }-- public override void undo () {- Glyph g;- - if (undo_list.length () == 0) {- return;- }- - g = undo_list.last ().data; - set_glyph_data (g);- - undo_list.remove_link (undo_list.last ());- }- - void set_glyph_data (Glyph g) {- while (path_list.length () > 0) {- path_list.remove_link (path_list.last ());- }- - foreach (Path p in g.path_list) {- add_path (p);- }-- 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 (p);- }- - open_path ();- close_path ();- - redraw_area (0, 0, allocation.width, allocation.height); - }- - public ImageSurface get_thumbnail () {- ImageSurface img;- Context cr;- double gx, gy;- double x1, x2, y1, y2;- Font font = Supplement.get_current_font ();-- remove_empty_paths ();- boundries (out x1, out y1, out x2, out y2);-- if (x2 - x1 < 1 || y2 - y1 < 1) { // create an empty thumbnail - img = new ImageSurface (Format.ARGB32, 100, 100);- } else {- img = new ImageSurface (Format.ARGB32, (int) (x2 - x1), (int) (y2 - y1));- }- - gx = left_limit - x1;- gy = (y2 - y1) + font.base_line + y1;- - cr = new Context (img);- - Svg.draw_svg_path (cr, get_svg_data (), gx, gy, 1.0); - - return img;- }-- /** Split curve in two parts and add a new point in between. */- public void insert_new_point_on_path (double x, double y) {- double min, distance;- Path? p = null;- 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;- - delete_invisible_paths ();- - foreach (Path pp in path_list) {- 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;- }-- lep = new EditPoint ();- ((!)p).get_closest_point_on_path (lep, xt, yt);- ((!)p).insert_new_point_on_path (lep);- }- - /** Merge selected paths. */- public void merge_all () {- store_undo_state ();- delete_invisible_paths ();- - if (active_paths.length () < 2) {- return;- }- - foreach (Path p0 in active_paths) {- merge_path (p0);- }- - //open_path ();- close_path ();-- foreach (Path p in path_list) {- p.update_region_boundries ();- } - - redraw_area (0, 0, allocation.width, allocation.height);- }- - public bool merge_path (Path p0) {- Path mp;- PathList path_list;- - foreach (Path p1 in active_paths) {- if (p0 == p1) {- p0 = p1;- continue;- }- - path_list = Path.merge (p1, p0);-- if (path_list.paths.length () == 0) {- p0 = p1;- continue;- }- - mp = path_list.paths.first ().data;-- delete_path (p0);- delete_path (p1);- - // add new path + it's counter paths- foreach (Path p in path_list.paths) {- p.close (); - add_path (p.copy ());- }- - add_active_path (mp);- - active_paths.remove_all (p0);- active_paths.remove_all (p1);-- p0 = mp;- - mp.create_list ();- - return true;- }- - return false;- }-- public void juxtapose (Allocation allocation, Context cr) {- string glyph_sequence = Preferences.get ("glyph_sequence");- unichar c;- Font font = Supplement.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;- - x = 0;- - current.append_unichar (glyph.unichar_code);- pos = glyph_sequence.index_of (current.str);- - baseline = glyph.get_line ("baseline").pos;- 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 = font.get_name_for_character (c); - juxtaposed = (font.has_glyph (name)) ? (!) font.get_glyph (name) : font.get_space ();- - if (font.has_glyph (last_name) && font.has_glyph (name)) {- kern = font.get_kerning (last_name, name);- } else {- kern = 0;- }-- if (!juxtaposed.is_empty ()) {- cr.save ();- cr.scale (glyph.view_zoom, glyph.view_zoom);- cr.translate (-glyph.view_offset_x, -glyph.view_offset_y);- Svg.draw_svg_path (cr, juxtaposed.get_svg_data (), Glyph.xc () + left + x + kern, Glyph.yc () + baseline, Glyph.SCALE);- cr.restore ();- }- - x += juxtaposed.get_width () + font.get_kerning (glyph.name, name) + 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 = font.get_name_for_character (c); - juxtaposed = (font.has_glyph (name)) ? (!) font.get_glyph (name) : font.get_space ();- - if (font.has_glyph (last_name) && font.has_glyph (name)) {- kern = font.get_kerning (name, last_name);- } else {- kern = 0;- }- - x -= juxtaposed.get_width ();- x -= kern;- - if (!juxtaposed.is_empty ()) {- cr.save ();- cr.scale (glyph.view_zoom, glyph.view_zoom);- cr.translate (-glyph.view_offset_x, -glyph.view_offset_y);-- Svg.draw_svg_path (cr, juxtaposed.get_svg_data (), Glyph.xc () + left + x, Glyph.yc () + baseline, Glyph.SCALE);- cr.restore ();- }- - last_name = name;- } - }-}--}deleted file mode 100644@@ -1,793 +0,0 @@-/*- 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;-using Math;-using Gdk;--namespace Supplement {- -public class GlyphBackgroundImage {- - public double img_x = 0;- public double img_y = 0;- - public double img_scale_x = 1;- public double img_scale_y = 1;- public double img_rotation = 0;- private int size = -1;-- public int active_handle = -1;- public int selected_handle = -1;- - private ImageSurface? background_image = null;- private ImageSurface? original_image = null;- - private string path;-- private double contrast = 1.0;- private bool desaturate = false;- private double threshold = 1.0;- - private bool background_image_is_processing = false;-- public signal void updated ();-- public double img_offset_x {- get { return img_x + Glyph.xc (); }- set { img_x = value - Glyph.xc (); }- }-- public double img_offset_y {- get { return Glyph.yc () - img_y; }- set { img_y = Glyph.yc () - value; }- }- - public int size_margin {- get {- if (unlikely (size == -1)) {- size = (int) (Math.sqrt (Math.pow (get_img ().get_height (), 2) + Math.pow (get_img ().get_width (), 2)) + 0.5);- }- - return size;- }- }- - public GlyphBackgroundImage (string fn) {- path = fn;- }-- public GlyphBackgroundImage copy () {- GlyphBackgroundImage bg = new GlyphBackgroundImage (path);- - bg.img_x = img_x;- bg.img_y = img_y;-- bg.img_scale_x = img_scale_x;- bg.img_scale_y = img_scale_y;- bg.img_rotation = img_rotation;-- bg.contrast = contrast;- bg.desaturate = desaturate;- bg.threshold = threshold;-- return bg;- - }-- public double get_margin_width () {- return ((size_margin - get_img ().get_width ()) / 2.0);- }-- public double get_margin_height () {- return ((size_margin - get_img ().get_height ()) / 2.0);- }-- public void set_img_offset (double x, double y) {- img_offset_x = x;- img_offset_y = y;- }-- public void set_position (double coordinate_x, double coordinate_y) {- img_x = coordinate_x;- img_y = coordinate_y;- }- - public ImageSurface get_img () {- if (path.index_of (".png") == -1) {- create_png ();- }- - if (background_image == null) {- background_image = new ImageSurface.from_png (path);- original_image = new ImageSurface.from_png (path);- }- - return (!) background_image;- }-- public bool is_valid () {- FileInfo file_info;- File file = File.new_for_path (path);- - if (!file.query_exists ()) {- return false;- }- - try {- file_info = file.query_info ("*", FileQueryInfoFlags.NONE);- - if (file_info.get_size () == 0) {- return false;- }- } catch (GLib.Error e) {- warning (e.message);- return false;- }- - return true;- }-- public string get_png_base64 () {- try {- File file = File.new_for_path (path);- FileInfo file_info = file.query_info ("*", FileQueryInfoFlags.NONE);- uint8[] buffer = new uint8[file_info.get_size ()];- FileInputStream file_stream;- DataInputStream png_stream;- - if (!file.query_exists ()) {- warning (@"Failed to save image $path, file does not exist.");- return "";- }- - if (is_null (buffer)) {- warning (@"Colud not allocate a buffer of $(file_info.get_size ()) bytes to store $path.");- return "";- }- - file_stream = file.read ();- - png_stream = new DataInputStream (file_stream);- png_stream.read (buffer);- - return Base64.encode (buffer);- } catch (GLib.Error e) {- warning (e.message);- }- - return "";- }-- public string get_sha1 () {- try {- File file = File.new_for_path (path);- FileInfo file_info;- uint8[] buffer;- FileInputStream file_stream;- DataInputStream png_stream;- - if (!file.query_exists ()) {- warning (@"Can't save $path file does not exist.");- return "";- }- - file_info = file.query_info ("*", FileQueryInfoFlags.NONE);- - if (file_info.get_size () == 0) {- warning (@"length of image $path is zero");- return "";- }- - buffer = new uint8[file_info.get_size ()];- file_stream = file.read ();- png_stream = new DataInputStream (file_stream);-- png_stream.read (buffer);- - return Checksum.compute_for_data (ChecksumType.SHA1, buffer);- } catch (GLib.Error e) {- warning (e.message);- }- - return "";- }- - private void create_png () {- string fn = @"$path.png";- - Font font = Supplement.get_current_font ();- File folder = font.get_backgrounds_folder ();- File original = File.new_for_path (fn);- File png_image = folder.get_child (@"full_$((!)original.get_basename ())");-- Pixbuf pixbuf;- - if (png_image.query_exists ()) {- path = (!) png_image.get_path ();- return;- }- - try {- pixbuf = new Pixbuf.from_file (path);- pixbuf.save ((!) png_image.get_path (), "png");- path = (!) png_image.get_path ();- } catch (GLib.Error e) {- warning (e.message);- }- }- - public void set_img_rotation_from_coordinate (double x, double y) {- double bcx, bcy;- double a, b, c, length;-- img_center (out bcx, out bcy);-- a = bcx - x;- b = bcy - y;- c = a * a + b * b;- - if (c == 0) {- return;- }- - length = sqrt (fabs (c)); - - if (c < 0) {- length = -length;- }- - img_rotation = (y < bcy) ? acos (a / length) + PI : -acos (a / length) + PI;- }- - public void set_img_scale (double xs, double ys) {- img_scale_x = xs;- img_scale_y = ys;- }-- public void reset_scale (Glyph g) {- double w, h;-- w = g.get_width ();- h = g.get_height ();-- img_scale_x = 1; - img_scale_y = 1;- - img_offset_x = g.get_line ("left").pos;- img_offset_y = g.get_line ("top").pos; - }-- public void draw (Context cr, Allocation allocation, double view_offset_x, double view_offset_y, double view_zoom) {- double x, y;- double iw, ih;- int h, w;- double scale_x, scale_y;- double oy, ox;- - Surface s;- Context c;-- Surface sg;- Context cg;-- Surface st;- Context ct;-- double wc, hc;-- if (unlikely (get_img ().status () != Cairo.Status.SUCCESS)) {- stderr.printf (@"Background image is invalid. (\"$path\")\n");- MainWindow.get_current_glyph ().set_background_visible (false);- return;- }- - // add margin- sg = new Surface.similar (get_img (), get_img ().get_content (), size_margin, size_margin);- cg = new Context (sg);- - wc = get_margin_width ();- hc = get_margin_height ();- - cg.set_source_rgba (1, 1, 1, 1);- cg.rectangle (0, 0, size_margin, size_margin);- cg.fill ();- - cg.set_source_surface (get_img (), wc, hc);- cg.paint ();-- x = Glyph.reverse_path_coordinate_x (img_offset_x);- y = Glyph.reverse_path_coordinate_y (img_offset_y);- - ih = (int) get_img ().get_height ();- iw = (int) get_img ().get_width ();- - w = (int) iw;- h = (int) ih;-- oy = size_margin;- ox = size_margin;-- // rotate image- s = new Surface.similar (sg, sg.get_content (), (int) (ox), (int) (oy));- c = new Context (s);- - c.save ();-- c.set_source_rgba (1, 1, 1, 1);- c.rectangle (0, 0, size_margin, size_margin);- c.fill ();- - c.translate (size_margin * 0.5, size_margin * 0.5);- c.rotate (img_rotation);- c.translate (-size_margin * 0.5, -size_margin * 0.5);-- c.set_source_surface (cg.get_target (), 0, 0);- c.paint (); - c.restore ();-- // mask- st = new Surface.similar (s, s.get_content (), allocation.width, allocation.height);- ct = new Context (st);- ct.save ();-- ct.set_source_rgba (1, 1, 1, 1);- ct.rectangle (0, 0, allocation.width, allocation.height);- ct.fill ();-- // scale both canvas and image at the same time; rather tricky.- scale_x = view_zoom * img_scale_x;- scale_y = view_zoom * img_scale_y;- - ct.scale (scale_x, scale_y);- ct.translate (-view_offset_x / img_scale_x, -view_offset_y / img_scale_y);- - ct.set_source_surface (s, img_offset_x / img_scale_x, img_offset_y / img_scale_y);-- ct.paint ();- ct.restore ();- - // append it- cr.save ();- cr.set_source_surface (st, 0, 0);- cr.paint ();- cr.restore ();- }- - public void handler_release (double nx, double ny) { - selected_handle = 0;- handler_move (nx, ny);- }- - public void handler_press (double nx, double ny) { - if (is_over_rotate (nx, ny)) {- selected_handle = 2;- } else if (is_over_resize (nx, ny)) {- selected_handle = 1;- } else {- selected_handle = 0;- } - }-- void img_center (out double x, out double y) {- Glyph g = MainWindow.get_current_glyph ();- - x = img_offset_x - g.view_offset_x + (size_margin / 2) * img_scale_x;- y = img_offset_y - g.view_offset_y + (size_margin / 2) * img_scale_y;-- y *= g.view_zoom;- x *= g.view_zoom;- }-- bool is_over_rotate (double nx, double ny) {- double x, y, d;-- img_center (out x, out y);-- x += cos (img_rotation) * 75;- y += sin (img_rotation) * 75;-- d = sqrt (pow (x - nx, 2) + pow (y - ny, 2));- - return d < 10;- }-- bool is_over_resize (double nx, double ny) {- Glyph g = MainWindow.get_current_glyph ();- double x, y, size;- bool inx, iny;-- size = 12 * g.view_zoom;-- x = img_offset_x - g.view_offset_x;- y = img_offset_y - g.view_offset_y + (size_margin) * img_scale_y;- - y *= g.view_zoom;- x *= g.view_zoom;- - inx = x - size <= nx <= x + size;- iny = y - size <= ny <= y + size;- - return inx && iny;- }-- public void handler_move (double nx, double ny) {- int prev_handle = active_handle;-- if (is_over_rotate (nx, ny)) {- active_handle = 2;- } else if (is_over_resize (nx, ny)) {- active_handle = 1;- } else {- active_handle = 0;- }- - if (prev_handle != active_handle) {- MainWindow.get_glyph_canvas ().redraw ();- }- }- - public void draw_handle (Context cr, Glyph g) {- draw_resize_handle (cr, g);- draw_rotate_handle (cr, g);- }-- public void draw_resize_handle (Context cr, Glyph g) {- double x, y;- cr.save ();- - cr.set_source_rgba (1, 0, 0.3, 1);-- x = img_offset_x - g.view_offset_x;- y = img_offset_y - g.view_offset_y + (size_margin) * img_scale_y;- - y *= g.view_zoom;- x *= g.view_zoom;- - draw_handle_triangle (x, y, cr, g, 6);- - cr.restore ();- }- - public void draw_rotate_handle (Context cr, Glyph g) {- double x, y, hx, hy, x2, y2;- - double ivz = 1.0 / (g.view_zoom);- - cr.save ();- - cr.scale (g.view_zoom, g.view_zoom);- - if (selected_handle == 2) cr.set_source_rgba (1, 0, 0.3, 1);- else if (active_handle == 2) cr.set_source_rgba (0, 0, 0.3, 1);- else cr.set_source_rgba (0.7, 0.7, 0.8, 1);-- x = img_offset_x - g.view_offset_x + (size_margin / 2) * img_scale_x;- y = img_offset_y - g.view_offset_y + (size_margin / 2) * img_scale_y;- - cr.rectangle (x, y, 5 * ivz, 5 * ivz);- cr.fill ();-- hx = cos (img_rotation) * 75 * ivz;- hy = sin (img_rotation) * 75 * ivz;- - x2 = x + hx;- y2 = y + hy;-- cr.rectangle (x2, y2, 5 * ivz, 5 * ivz);- cr.fill ();-- cr.set_line_width (ivz);- cr.move_to (x + 2.5* ivz, y + 2.5* ivz);- cr.line_to (x2 + 2.5* ivz, y2 + 2.5* ivz);- cr.stroke ();- - cr.restore ();- }-- void draw_handle_triangle (double x, double y, Context cr, Glyph g, int direction, double s = 1) - requires (0 < direction < 8)- {- double ivz = 1.0 / (g.view_zoom);- double size;- - cr.save ();- cr.set_line_width (ivz);- - if (selected_handle == 1) cr.set_source_rgba (1, 0, 0.3, 1);- else if (active_handle == 1) cr.set_source_rgba (0, 0, 0.3, 1);- else cr.set_source_rgba (0.7, 0.7, 0.8, 1); -- size = (8) * s;- - cr.scale (1, 1);- cr.new_path ();- - // up + left- if (direction == 1) {- cr.move_to (x - size, y - size);- cr.line_to (x + size, y - size);- cr.line_to (x - size, y + size); - }- - if (direction == 6) {- cr.move_to (x + size, y + size); - cr.line_to (x - size, y + size);- cr.line_to (x - size, y - size); - }- - cr.close_path();- cr.fill ();-- cr.restore ();- - }-- private static uchar ftu (double c) {- if (c < 0) return 0;- if (c > 255) return 255;- return (uchar) c;- }-- private static ImageSurface? process_background_colors (ImageSurface img, double contrast, bool desaturate, double threshold) {- int i;- unowned uchar[]? pix_buff_t;- unowned uchar[] pix_buff;- int len;- uchar[] contrast_img;- ImageSurface contrast_surface;- double c, thres;- uchar nc;-- int w = img.get_width();- int h = img.get_height();-- if (unlikely (img.status () != Cairo.Status.SUCCESS)) {- warning ("Err");- return null;- }- - len = 4 * w * h;- - pix_buff_t = img.get_data ();- return_if_fail (pix_buff_t != null);- pix_buff = pix_buff_t;- - contrast_img = new uchar[len];-- pix_buff_t = contrast_img;- return_if_fail (pix_buff_t != null);- - contrast += 70;- contrast /= 71;- - if (desaturate) {- i = 0;- - while (i < len) {- c = (pix_buff[i] + pix_buff[i+1] + pix_buff[i+2]) / 3.0;- nc = ftu (Math.pow (c, contrast));-- contrast_img[i] = nc;- contrast_img[i+1] = nc; - contrast_img[i+2] = nc;- contrast_img[i+3] = 255;- - i += 4;- }- } else {- i = 0;- while (i < len) {- contrast_img[i] = ftu (Math.pow (pix_buff[i], contrast) - Math.pow (100, contrast) + 100);- contrast_img[i+1] = ftu (Math.pow (pix_buff[i+1], contrast) - Math.pow (100, contrast) + 100);- contrast_img[i+2] = ftu (Math.pow (pix_buff[i+2], contrast) - Math.pow (100, contrast) + 100);- contrast_img[i+3] = 255;- - i += 4;- }- }-- if (true) {- thres = (threshold - 0.5) * 255;- - i = 0;- while (i < len) {- c = (pix_buff[i] + pix_buff[i+1] + pix_buff[i+2]) / 3.0;- nc = (c > thres) ? 255 : 0;-- contrast_img[i] = nc;- contrast_img[i+1] = nc; - contrast_img[i+2] = nc;- contrast_img[i+3] = 255;- - i += 4;- }- }-- contrast_surface = new ImageSurface.for_data (contrast_img, img.get_format(), w, h, img.get_stride());-- if (unlikely (contrast_surface.status () != Cairo.Status.SUCCESS)) {- warning ("Err");- return null;- }- - contrast_surface.mark_dirty ();- - return contrast_surface; - }-- async ImageSurface? do_process_background_colors () {- SourceFunc callback = do_process_background_colors.callback;- ImageSurface? output = null;- double contrast = get_contrast ();- bool desaturate = get_desaturate_background ();- double threshold = this.threshold;-- if (original_image == null) {- return null;- }- - // Fixa: Add multi threading- //ThreadFunc<void*> run = () => {- output = process_background_colors ((!)original_image, contrast, desaturate, threshold);- Idle.add ((owned) callback);- //return null;- //};- - try {- //Thread.create<void*> (run, false);- yield;- } catch (ThreadError e) {- stderr.printf ("Thread error: %s\n", e.message);- }-- return output;- }-- public void update_background (double contrast, bool desaturate) {- background_image_is_processing = true;- - do_process_background_colors.begin ((obj, res) => {- ImageSurface? img;-- img = do_process_background_colors.end (res);- - if (img != null) {- background_image = (!) img;- updated ();- } else {- stderr.printf ("Error: No background image"); - }- - // update image if new value was set while background task was running - if (get_contrast () != contrast || get_desaturate_background () != desaturate) {-- do_process_background_colors.begin ((obj, res) => {- img = do_process_background_colors.end (res);- if (likely (img != null)) {- background_image = (!) img;- updated ();- } else {- stderr.printf ("Error: No background image"); - }- });- }-- background_image_is_processing = false;- updated ();- - });- }-- public double get_contrast () {- double bc;- lock (contrast) {- bc = contrast;- }- return bc;- }- - public bool get_desaturate_background () {- bool bw;- lock (desaturate) {- bw = desaturate;- }- return bw;- }- - public void set_desaturate_background (bool bw) {- lock (desaturate) {- desaturate = bw;- }- - if (background_image_is_processing) {- return;- }- - background_image_is_processing = true;- - update_background (get_contrast (), bw);- }- - public void set_threshold (double t) {- threshold = t;- }- - public Path auto_trace () {- ImageSurface img;- int len, w, h, i;- unowned uchar[] pix_buff;- Path p = new Path ();- double x, y;-- if (background_image == null) {- return p;- }-- img = (!) background_image;-- w = img.get_width();- h = img.get_height();-- if (unlikely (img.status () != Cairo.Status.SUCCESS)) {- warning ("Err");- return p;- }- - len = 4 * w * h;-- pix_buff = img.get_data ();-- i = 0;- while (i < len - 4) {- if (is_edge (pix_buff[i], pix_buff[i + 4])) {- y = i / w;- x = i - y;- - p.add (x, y);- - if (p.points.length () > 10) {- return p;- }- }- - i += 4;- }- - return p;- }- - private bool is_edge (uchar a, uchar b) {- return (a == 255 && b == 0);- }- - public void set_contrast (double contrast) {- lock (this.contrast) {- this.contrast = contrast;- }- - if (background_image_is_processing) {- return;- }- - background_image_is_processing = true;- - update_background (contrast, get_desaturate_background ());- }-}- -}deleted file mode 100644@@ -1,82 +0,0 @@-/* - 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; -using Math; - -namespace Supplement { - -public class GlyphCanvas : GLib.Object { - - public FontDisplay current_display; - public signal void signal_redraw_area (int x, int y, int w, int h); - public Allocation allocation; - - public GlyphCanvas () { - } - - public void key_release (uint e) { - current_display.key_release (e); - } - - public void key_press (uint e) { - current_display.key_press (e); - } - - public void set_current_glyph (FontDisplay fd) { - if (fd is Glyph) { - Glyph g = (Glyph) fd; - - Supplement.current_glyph = g; - Supplement.current_glyph.resized (); - - g.allocation = allocation; - - warn_if_fail (g.allocation.width != 0 && g.allocation.height != 0); - } - - current_display = fd; - - fd.selected_canvas (); - - fd.redraw_area.connect ((x, y, w, h) => { - signal_redraw_area ((int)x, (int)y, (int)w, (int)h); - }); - - redraw (); - - MainWindow.native_window.update_window_size (); - } - - public static Glyph get_current_glyph () { - return Supplement.current_glyph; - } - - public FontDisplay get_current_display () { - return current_display; - } - - public void redraw_area (int x, int y, int w, int h) { - signal_redraw_area (x, y, w, h); - } - - public void redraw () { - signal_redraw_area (0, 0, allocation.width, allocation.height); - } -} - -} deleted file mode 100644@@ -1,52 +0,0 @@-/*- 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;-using Math;--namespace Supplement {--public class GlyphCollection : GLib.Object {- VersionList versions;- - public GlyphCollection (Glyph? current = null) {- versions = new VersionList (current);- }- - public VersionList get_version_list () {- return versions;- }- - public Glyph get_current () {- return versions.get_current ();- }- - public void insert_glyph (Glyph g, bool selected) {- versions.add_glyph (g, selected); - assert (versions.glyphs.length () > 0);- }- - public uint length () {- return versions.glyphs.length ();- }- - public string get_name () {- return get_current ().get_name ();- }-}- -}deleted file mode 100644@@ -1,262 +0,0 @@-/*- 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/>.-*/--namespace Supplement {--public class GlyphRange {- - List<UniRange> ranges;- - public unowned List<string> unassigned = new List<string> ();- - uint32 len = 0;- - public GlyphRange () {- }- - public unowned List<UniRange> get_ranges () {- return ranges;- }- - public void use_default_range () {- /// All lower case letters in alphabetic order separated by space- string lower_case = _("a b c d e f g h i j k l m n o p q r s t u v w x y z");- - /// All upper case letters in alphabetic order separated by space- string upper_case = _("A B C D E F G H I J K L M N O P Q R S T U V W X Y Z");-- foreach (string c in lower_case.split (" ")) {- add_single (c.get_char ());- }-- foreach (string c in upper_case.split (" ")) {- add_single (c.get_char ());- }- - add_range ('0', '9');- - add_single (' '); // TODO: add all spaces here.- - add_single ('.');- add_single ('?');- - add_single (',');- - add_single ('’');-- add_range ('“', '”');-- add_single ('&');- - add_range (':', ';');- - add_single ('/'); - - add_range ('!', '/');- - add_single ('-');- add_range ('‐', '—');- add_range ('<', '@');- add_range ('(', ')');- }- - public void use_full_unicode_range () {- add_range ('\0', (unichar) 0xFFF8);- }- - // Todo: complete localized alphabetical sort åäö is the right order for example.- public void sort () {- ranges.sort ((a, b) => {- bool r = a.start > b.start;- - return_if_fail (a.start != b.start);- - return (r) ? 1 : -1;- });- }- - public void add_single (unichar c) {- add_range (c, c);- }- - public uint32 get_length () {- unichar l = len;- l += unassigned.length ();- return l;- }- - public void add_range (unichar start, unichar stop) {- unichar b, s;- if (unique (start, stop)) {- append_range (start, stop);- } else {- - // make sure this range does not overlap existing ranges- b = start;- s = b;- if (!unique (b, b)) { - while (b < stop) {- if (!unique (b, b)) {- b++;- } else {- if (s != b) {- add_range (b, stop);- }- - b++;- s = b;- }- }- } else {- while (b < stop) {- if (unique (b, b)) {- b++;- } else {- if (s != b) {- add_range (start, b - 1);- }- - b++;- s = b;- }- } - }- }- }- - private void append_range (unichar start, unichar stop) {- UniRange r;- - StringBuilder s = new StringBuilder ();- StringBuilder e = new StringBuilder ();- s.append_unichar (start);- e.append_unichar (stop);- - r = insert_range (start, stop); // insert a unique range- merge_range (r); // join connecting ranges- }- - private void merge_range (UniRange r) {- foreach (UniRange u in ranges) {- if (u == r) {- continue;- } - - if (u.start == r.stop + 1) {- u.start = r.start;- ranges.remove_all (r);- merge_range (u);- }- - if (u.stop == r.start - 1) {- u.stop = r.stop;- ranges.remove_all (r);- merge_range (u);- }- }- }-- public string get_char (uint32 index) {- int64 ti;- string chr;- UniRange r;- StringBuilder sb;- unichar c;- - if (index > len + unassigned.length ()) { - return "\0".dup();- }- - if (index >= len) {- - if (index - len >= unassigned.length ()) {- return "\0".dup();- } - - chr = ((!) unassigned.nth (index - len)).data;- return chr;- }-- r = ranges.first ().data;- ti = index;-- foreach (UniRange u in ranges) {- ti -= u.length ();- - if (ti < 0) {- r = u;- break;- }- }- - sb = new StringBuilder ();- c = r.get_char ((unichar) (ti + r.length ()));- sb.append_unichar (c);-- return sb.str;- }- - public uint32 length () {- return len;- }-- private bool unique (unichar start, unichar stop) {- foreach (UniRange u in ranges) {- - if (inside (start, u.start, u.stop)) return false;- if (inside (stop, u.start, u.stop)) return false;- if (inside (u.start, start, stop)) return false;- if (inside (u.stop, start, stop)) return false;- - }-- return true;- }-- private bool inside (unichar start, unichar u_start, unichar u_stop) {- return (u_start <= start <= u_stop);- }- - private UniRange insert_range (unichar start, unichar stop) {- if (unlikely (start > stop)) {- warning ("start > stop");- stop = start;- }- - UniRange ur = new UniRange (start, stop);- len += ur.length ();- ranges.append (ur);- - return ur;- }-- public void print_all () {- StringBuilder s;- - stdout.printf ("Ranges:\n");- foreach (UniRange u in ranges) {- s = new StringBuilder ();- s.append_unichar (u.start);- s.append (" - ");- s.append_unichar (u.stop);- s.append ("\n");- stdout.printf (s.str);- }- }-}--}deleted file mode 100644@@ -1,412 +0,0 @@-/*- 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/>.-*/--namespace Supplement {--/** A sorted table of glyphs with search index. */-class GlyphTable : GLib.Object {- List<Item> data = new List<Item> ();- - List<RowItem> row1 = new List<RowItem> ();- List<RowItem> row2 = new List<RowItem> ();- List<RowItem> row3 = new List<RowItem> ();- - public GlyphTable () {- }-- public void remove_all () {- while (data.length () > 0) {- data.remove_link (data.first ());- }- }-- public void @for_each (Func<GlyphCollection> func) {- data.foreach ((v) => {- func (v.glyph_collection);- });- }-- unowned List<Item>? find (string n) {- return find_index (n);- }-- unowned List<Item>? find_next (string n) {- return find_next_index (n);- }- - public void remove (string name) {- unowned List<Item>? d = find (name);- - if (d == null) {- warning (@"did not find $name");- return;- }- - data.remove_link ((!) d);- build_index ();- }-- public GlyphCollection? nth (uint i) {- unowned List<Item>? d = data.nth (i);- - if (d == null) {- warning (@"no glyph for index $i");- return null;- }- - return ((!) d).data.glyph_collection;- }-- public uint length () {- return data.length ();- }-- public new GlyphCollection? @get (string name) {- unowned List<Item>? d = find_index (name);- - if (d == null) {- return null;- }- - return ((!) d).data.glyph_collection;- }-- public bool insert (GlyphCollection g) {- Item item;- unowned List<Item>? next;- - if (g.get_name () == "") {- warning (@"No proper name for glyph collection. g.get_name (): \"$(g.get_name ())\"");- return false;- }-- if (find (g.get_name ()) != null) {- warning (@"Table does already contain character \"$(g.get_name ())\".");- return false;- }- - next = find_next (g.get_name ());- - item = new Item (g.get_name (), g);- - if (next == null) {- data.append (item);- } else {- data.insert_before ((!) next, item); // _sorted- }- - build_index ();- - if (find (g.get_name ()) == null) {- warning (@"Can not find glyph \"$(g.get_name ())\".");- print_all ();- return false;- }- - return true;- }-- public void print_all () {- int indice = 0;- foreach (Item i in data) {- print (@"$(i.name) indice: $(indice)\n");- indice++;- }- - print ("Index:\n");- print ("\n");- print ("row3\n");- foreach (RowItem i in row3) {- print (@"$(i.item.data.name)\n");- }- - print ("\n");- print ("row2\n");- foreach (RowItem i in row2) {- print (@"$(i.item.data.name)\n");- }- - print ("\n");- print ("row1\n");- foreach (RowItem i in row1) {- print (@"$(i.item.data.name)\n");- } - }-- static int compare (string an, Item b) {- string? bt = b.glyph_collection.get_name ();- string bn = (!) bt;- - uint32 ac = 0;- uint32 bc = 0;- - int i1 = 0;- int i2 = 0;- - return_if_fail (bt != null);- - int ca = an.char_count ();- int cb = bn.char_count ();- - if (ca != cb) {- return ca < cb ? 1 : -1;- }- - while (true) {- an.get_next_char (ref i1, out ac);- bn.get_next_char (ref i2, out bc);- - if (ac == bc && ac != 0) {- continue;- }- - if (ac == bc) return 0;- - return (ac < bc) ? 1 : -1;- }- }- - public void build_index () {- uint len;- RowItem r;- - while (row1.length () > 0) {- row1.remove_link (row1.first ());- }-- while (row2.length () > 0) {- row2.remove_link (row2.first ());- }-- while (row3.length () > 0) {- row3.remove_link (row3.first ());- }-- if (data.length () == 0) {- return;- }- - // row 1- len = data.length () / 5;- if (len == 0) len++;- for (int i = 0; i < len; i++) {- r = new RowItem ();- r.item = data.nth (i * 5);- r.level = 1;- row1.append (r);- }- r = new RowItem ();- r.item = data.last ();- r.level = 1;- r.last = true;- row1.append (r); - - // row 2- len = row1.length () / 5;- if (len == 0) len++;- for (int i = 0; i < len; i++) {- r = new RowItem ();- r.next_row = row1.nth (i * 5);- r.item = r.next_row.data.item; - r.level = 2;- row2.append (r);- }- r = new RowItem ();- r.item = data.last ();- r.level = 2;- r.next_row = row1.last (); - r.last = true;- row2.append (r);- - // row 3- len = row2.length () / 5;- if (len == 0) len++;- for (int i = 0; i < len; i++) {- r = new RowItem ();- r.next_row = row2.nth (i * 5);- r.item = r.next_row.data.item; - r.level = 3;- row3.append (r);- }- r = new RowItem ();- r.item = data.last ();- r.level = 3;- r.next_row = row2.last ();- r.last = true; - row3.append (r);- }-- unowned List<Item>? find_next_index (string n) {- unowned List<Item>? next = find_next_via_index (n);- return next;- }-- unowned List<Item>? find_index (string n) {- unowned List<Item>? next = find_next_via_index (n);- - if (next == null) {- return null;- }- - if (compare (n, ((!)next).data) == 0) {- return next;- }- - if (((!)next) != data.first () && compare (n, ((!)next).prev.data) == 0) {- return ((!)next).prev; // fetch previous since we are searching for the next one- }- - return null; - }- - unowned List<Item>? find_next_via_index (string n) {- unowned List<Item> i = data.first ();- unowned List<RowItem> r;- int cmp;- - if (data.length () == 0) {- return null;- }-- r = row3.first ();- int ti = 0;- while (true) {- cmp = compare (n, r.data.item.data);- - ti++;- - if (cmp == 1) {- break;- }- - if (cmp == 0) {- return r.data.item;- }- - if (r != row3.last ()) {- r = r.next;- } else {- return null;- }- } - r = r.data.next_row;-- while (true) {- cmp = compare (n, r.data.item.data);- if (cmp == -1) {- break;- }-- if (cmp == 0) {- return r.data.item;- }- - if (r == row2.first ()) {;- return r.data.item;- } else r = r.prev;- }- r = r.data.next_row;- - while (true) {- cmp = compare (n, r.data.item.data);- if (cmp == 1) {- break;- }- - if (cmp == 0) {- return r.data.item;- }- - if (r == row1.last ()) return null;- else r = r.next;- }- i = r.data.item;- - - while (true) {- cmp = compare (n, i.data);-- if (cmp == -1) {- // null?- return i.next;- }-- if (cmp == 0) {- return i;- }- - if (i == data.first ()) {- return i;- } else { - i = i.prev;- }- } - }-- public bool validate_index () {- int cmp;- Item prev;- int index;- - if (data.length () == 0) {- return true;- }- - prev = data.first ().data;- index = 0;- foreach (Item i in data) {- cmp = compare (prev.name, i);- - if (cmp == 1) {- continue;- }- - if (cmp == 0 && index != 0) {- warning (@"Value is not unique: $(i.name).");- return false;- }-- if (cmp == -1) {- warning (@"Value is not sorted: $cmp == cmp $(prev.name) $(i.name).");- return false;- }- - index++;- }- - return true;- }-- class Item : GLib.Object {- public string name;- public GlyphCollection glyph_collection;- - public Item (string n, GlyphCollection gc) {- glyph_collection = gc;- name = n;- }- }-- class RowItem : GLib.Object {- public unowned List<RowItem> next_row;- public unowned List<Item> item;- public int level = 0;- public bool last = false;- }-}--}deleted file mode 100644@@ -1,244 +0,0 @@-/*- 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;-using Math;--namespace Supplement {--class GridTool : Tool {- - static List <Line> horizontal;- static List <Line> vertical;- - static bool visible = false;- - public static double size_x;- public static double size_y;- - public static List <SpinButton> sizes;- - public GridTool (string n) {- base (n, _("Show grid"), 'g', NONE);- - horizontal = new List <Line> ();- vertical = new List <Line> ();- - sizes = new List <SpinButton> ();- - size_x = 2;- size_y = 2;- - updata_lines ();- - select_action.connect((self) => {- visible = !visible;- updata_lines ();- MainWindow.get_glyph_canvas ().redraw ();- - self.set_selected (visible);- - Toolbox tb = MainWindow.get_toolbox ();- Tool t = tb.get_tool ("help_lines");- - if (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) {- size_x = w;- size_y = w;- - updata_lines ();- }- - private static void updata_lines () {- Glyph g = MainWindow.get_current_glyph ();- double step = size_y * Glyph.SCALE;- double i;- int max_lines = 400;- 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");-- while (vertical.length () > 0) vertical.delete_link (vertical.first ());- while (horizontal.length () > 0) horizontal.delete_link (horizontal.first ());- - 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 (0.2, 0.6, 0.2, 0.2);- horizontal.append (l);- - if (++n >= max_lines) {- break;- }- }- - n = 0;- for (i = baseline.get_pos () - step; i >= top_margin.get_pos () - 7 * step; i -= step) {- t = new Line ("grid", i, Line.HOROIZONTAL);- t.set_moveable (false);- t.set_color (0.2, 0.6, 0.2, 0.2);- vertical.append (t); -- if (++n >= max_lines) {- break;- }-- }- - for (i = baseline.get_pos (); i <= bottom_margin.get_pos () + 7 * step; i += step) {- u = new Line ("grid", i, Line.HOROIZONTAL);- u.set_moveable (false);- u.set_color (0.2, 0.6, 0.2, 0.2);- vertical.append (u); -- if (++n >= max_lines) {- break;- }-- } - }- - /** Sets x and y the point closest on grid. */- public static void tie (ref double x, ref double y) {- tie_point (ref x, ref y, false);- }-- public static void tie_coordinate (ref double x, ref double y) {- tie_point (ref x, ref y, true);- }- - private static void tie_point (ref double x, ref double y, bool coordinate)- requires (horizontal.length () > 0 && vertical.length () > 0)- {- double d, m;- - Glyph g = MainWindow.get_current_glyph ();- - Line xmin = horizontal.first ().data;- Line ymin = vertical.first ().data;-- Line xpos;- Line ypos;- - if (!coordinate) {- xpos = new Line ("", 0, Line.VERTICAL);- ypos = new Line ("", 0, Line.HOROIZONTAL);-- xpos.set_move (true);- ypos.set_move (true);- - xpos.move_line_to (x, y, g.allocation);- ypos.move_line_to (x, y, g.allocation);- } else {- xpos = new Line ("", x, Line.VERTICAL);- ypos = new Line ("", y, Line.HOROIZONTAL);- }-- m = double.MAX;-- foreach (Line line in vertical) {- d = Math.fabs (line.get_pos () - ypos.get_pos ());- - if (d <= m) {- m = d;- ymin = line;- }- - }-- m = double.MAX;- foreach (Line line in horizontal) {- 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 ());- y = Glyph.reverse_path_coordinate_y (-ymin.get_pos ());- } else {- x = xmin.get_pos ();- y = ymin.get_pos ();- }- }-- public static void tie_to_prev (EditPoint ep, double tx, double ty) {- EditPoint pp;- double a, b;- - return_if_fail (ep.prev != null);- - pp = ep.get_prev ().data;- - ep.to_coordinate (ref tx, ref ty);- - a = fabs (tx - pp.x);- b = fabs (ty - pp.y);- - if (a > b) {- ep.y = pp.y;- } else {- ep.x = pp.x;- }- } -- public static bool is_visible () {- return visible;- }- - public static unowned List<Line> get_horizontal_lines () {- return horizontal;- }- - public static unowned List<Line> get_vertical_lines () {- return vertical;- }--}--}-deleted file mode 100644@@ -1,808 +0,0 @@-/*- 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;-using Gtk;-using Gdk;-using Supplement;-using WebKit;--namespace Supplement {--public class GtkWindow : Gtk.Window, NativeWindow {-- HBox list_box;- HBox canvas_box;- - WebView html_canvas;- ScrolledWindow html_box;-- VBox tab_box;-- static DrawingArea margin_bottom;- static DrawingArea margin_right;- - GlyphCanvasArea glyph_canvas_area;- - static List<uint> key_pressed = new List<uint> ();- - public GtkWindow (string title) {- // set_title (title);- }- - public void init () {- margin_bottom = new DrawingArea ();- margin_right = new DrawingArea ();- - margin_bottom.set_size_request (0, 0);- margin_right.set_size_request (0, 0);- - delete_event.connect (quit);- - set_size_and_position ();- - html_canvas = new WebView ();- WebKit.set_cache_model (CacheModel.DOCUMENT_VIEWER);- html_canvas.get_settings ().enable_default_context_menu = false;- - html_canvas.title_changed.connect ((p, s) => {- webkit_callback (s);- });- - html_box = new ScrolledWindow (null, null);- html_box.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);- html_box.add (html_canvas);- html_canvas.set_editable (true);- - MainWindow.get_tab_bar ().signal_tab_selected.connect ((f, tab) => {- bool n;- File layout_dir;- File layout_uri;- string uri = "";- FontDisplay fd = tab.get_display ();- - MainWindow.glyph_canvas.set_current_glyph (fd);- n = fd.is_html_canvas ();- - if (n) {- layout_dir = FontDisplay.find_layout_dir ();- uri = fd.get_uri ();- - if (uri == "") {- layout_uri = layout_dir.get_child (fd.get_html_file ());- uri = fd.path_to_uri ((!) layout_uri.get_path ());- }- - if (fd.get_html () == "") {- - if (fd.get_name () == "Preview") {- // hack: force webkit to ignore cache in preview - html_box.set_visible (false);- glyph_canvas_area.set_visible (true);- - try {- Preview preview = (Preview) fd;- DataInputStream dis = new DataInputStream (preview.get_html_file ().read ());- string? line;- StringBuilder sb = new StringBuilder ();- uint rid = Random.next_int ();- Font font = Supplement.get_current_font ();- - File preview_directory = Supplement.get_preview_directory ();- - File f_ttf = font.get_folder ().get_child (@"$(font.get_name ()).ttf");- File f_eot = font.get_folder ().get_child (@"$(font.get_name ()).eot");- File f_svg = font.get_folder ().get_child (@"$(font.get_name ()).svg");-- if (f_ttf.query_exists ()) {- f_ttf.delete ();- }- - if (f_eot.query_exists ()) {- f_eot.delete ();- }- - if (f_svg.query_exists ()) {- f_svg.delete ();- }- - ExportTool.export_ttf_font (); - ExportTool.export_svg_font ();- - File r_ttf = preview_directory.get_child (@"$(font.get_name ())$rid.ttf");- File r_svg = preview_directory.get_child (@"$(font.get_name ())$rid.svg");- - if (Supplement.win32) {- f_ttf.copy (r_ttf, FileCopyFlags.NONE);- }- - f_svg.copy (r_svg, FileCopyFlags.NONE);-- while ((line = dis.read_line (null)) != null) {- line = ((!) line).replace (@"$(font.get_name ()).ttf", @"$(FontDisplay.path_to_uri ((!) f_ttf.get_path ()))?$rid");- line = ((!) line).replace (@"$(font.get_name ()).eot", @"$(FontDisplay.path_to_uri ((!) f_eot.get_path ()))?$rid");- line = ((!) line).replace (@"$(font.get_name ()).svg", @"$(FontDisplay.path_to_uri ((!) f_svg.get_path ()))?$rid");- sb.append ((!) line);- }- - html_canvas.load_html_string (sb.str, uri); - } catch (Error e) {- warning (e.message);- warning ("Failed to load html into canvas.");- }- - } else {- // normal way to load a uri for all other pages- html_canvas.load_uri (uri);- html_canvas.reload_bypass_cache (); - }- - } else {- html_canvas.load_html_string (fd.get_html (), uri);- }- - html_box.set_visible (n);- glyph_canvas_area.set_visible (!n);- } else {- html_box.set_visible (false);- glyph_canvas_area.set_visible (true);- }- });-- // Hide this canvas when window is realized and flip canvas - // visibility in tab selection signal.- html_canvas.expose_event.connect ((t, e) => {- glyph_canvas_area.set_visible (false);- return false;- });- - MainWindow.tabs.add_unique_tab (MainWindow.menu_tab, 60, true);- - MainWindow.tabs.select_tab_name ("Menu");-- glyph_canvas_area = new GlyphCanvasArea (MainWindow.glyph_canvas);-- canvas_box = new HBox (false, 0);- canvas_box.pack_start (glyph_canvas_area, true, true, 0);- canvas_box.pack_start (html_box, true, true, 0);- - tab_box = new VBox (false, 0);- tab_box.pack_start (new TabbarCanvas (MainWindow.tabs), false, false, 0); - - tab_box.pack_start (canvas_box, true, true, 0);-- tab_box.pack_start (new TooltipCanvas (MainWindow.tool_tip), false, false, 0);- tab_box.pack_start (margin_bottom, false, false, 0);- - list_box = new HBox (false, 0);- list_box.pack_start (tab_box, true, true, 0);- list_box.pack_start (new ToolboxCanvas (MainWindow.tools), false, false, 0);- list_box.pack_start (margin_right, false, false, 0);-- VBox vbox = new VBox (false, 0);- - vbox.pack_start(create_menu (), false, false, 0);- vbox.pack_start(list_box, true, true, 0);-- add (vbox);- - try {- set_icon_from_file ((!) Icons.find_icon ("window_icon.png").get_path ());- } catch (GLib.Error e) {- warning (e.message);- }-- key_press_event.connect ((t, event) => {- MainWindow window = MainWindow.get_singleton ();- FontDisplay fd = window.glyph_canvas.current_display;- - if (fd is Glyph) {- window.tools.key_press (event.keyval);- }- - window.glyph_canvas.key_press (event.keyval);- return false;- });- - key_release_event.connect ((t, event) => {- MainWindow window = MainWindow.get_singleton ();- if (window.glyph_canvas is Glyph) {- window.glyph_canvas.key_release (event.keyval);- }- return false;- });- - show_all ();- }-- MenuBar create_menu () {- MenuBar menubar = new MenuBar ();- Gtk.Menu file_menu = new Gtk.Menu ();- Gtk.Menu edit_menu = new Gtk.Menu ();- Gtk.Menu tab_menu = new Gtk.Menu ();- Gtk.Menu tool_menu = new Gtk.Menu ();-- AccelGroup accel_group = new Gtk.AccelGroup();- add_accel_group (accel_group);- - // File- Gtk.MenuItem new_item = new Gtk.MenuItem.with_mnemonic (_("_New"));- file_menu.append (new_item);- new_item.activate.connect (() => { MenuTab.new_file (); });- new_item.add_accelerator ("activate", accel_group, 'N', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);-- Gtk.MenuItem load_item = new Gtk.MenuItem.with_mnemonic (_("_Open"));- file_menu.append (load_item);- load_item.activate.connect (() => { MenuTab.load (); });- load_item.add_accelerator ("activate", accel_group, 'O', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);-- Gtk.MenuItem save_item = new Gtk.MenuItem.with_mnemonic (_("_Save"));- file_menu.append (save_item);- save_item.activate.connect (() => { MenuTab.save (); });- save_item.add_accelerator ("activate", accel_group, 'S', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);-- Gtk.MenuItem save_as_item = new Gtk.MenuItem.with_mnemonic (_("Save _as"));- file_menu.append (save_as_item);- save_as_item.activate.connect (() => { MenuTab.save_as (); });- - Gtk.MenuItem export_item = new Gtk.MenuItem.with_mnemonic (_("_Export"));- file_menu.append (export_item);- export_item.activate.connect (() => { ExportTool.export_all (); });- export_item.add_accelerator ("activate", accel_group, 'E', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);-- Gtk.MenuItem preview_item = new Gtk.MenuItem.with_mnemonic(_("_Preview"));- file_menu.append (preview_item);- preview_item.activate.connect (() => { MenuTab.preview (); });- preview_item.add_accelerator ("activate", accel_group, 'P', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);-- Gtk.MenuItem kerning_item = new Gtk.MenuItem.with_mnemonic (_("_Kerning"));- file_menu.append (kerning_item);- kerning_item.activate.connect (() => { MenuTab.show_kerning_context (); });- kerning_item.add_accelerator ("activate", accel_group, 'K', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);-- Gtk.MenuItem quit_item = new Gtk.MenuItem.with_mnemonic (_("_Quit"));- file_menu.append (quit_item);- quit_item.activate.connect (() => { quit(); });-- // Edit- Gtk.MenuItem undo_item = new Gtk.MenuItem.with_mnemonic (_("_Undo"));- edit_menu.append (undo_item);- undo_item.activate.connect (() => { MainWindow.get_current_display ().undo (); }); - undo_item.add_accelerator ("activate", accel_group, 'Z', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);- - Gtk.MenuItem copy_item = new Gtk.MenuItem.with_mnemonic (_("_Copy"));- edit_menu.append (copy_item);- copy_item.activate.connect (() => { ClipTool.copy (); }); - copy_item.add_accelerator ("activate", accel_group, 'C', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);-- Gtk.MenuItem paste_item = new Gtk.MenuItem.with_mnemonic (_("_Paste"));- edit_menu.append (paste_item);- paste_item.activate.connect (() => { ClipTool.paste (); }); - paste_item.add_accelerator ("activate", accel_group, 'V', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);- - // Tab- Gtk.MenuItem next_tab_item = new Gtk.MenuItem.with_mnemonic (_("_Next tab"));- tab_menu.append (next_tab_item);- next_tab_item.activate.connect (() => { - TabBar tb = MainWindow.get_tab_bar ();- int n = tb.get_selected () + 1;- - if (!(0 <= n < tb.get_length ())) {- return;- }- - tb.select_tab (n);- }); -- Gtk.MenuItem prevoius_tab_item = new Gtk.MenuItem.with_mnemonic (_("_Previous tab"));- tab_menu.append (prevoius_tab_item);- prevoius_tab_item.activate.connect (() => { - TabBar tb = MainWindow.get_tab_bar ();- int n = tb.get_selected () - 1;-- if (!(0 <= n < tb.get_length ())) {- return;- }- - tb.select_tab (n);- }); -- Gtk.MenuItem close_tab_item = new Gtk.MenuItem.with_mnemonic (_("_Close tab"));- tab_menu.append (close_tab_item);- close_tab_item.activate.connect (() => { - TabBar tb = MainWindow.get_tab_bar ();- int n = tb.get_selected ();-- if (!(0 <= n < tb.get_length ())) {- return;- }- - tb.close_tab (n);- }); - close_tab_item.add_accelerator ("activate", accel_group, 'W', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);-- // Tool- Gtk.MenuItem pen_item = new Gtk.MenuItem.with_mnemonic (_("_Create path"));- tool_menu.append (pen_item);- pen_item.activate.connect (() => { - Toolbox.select_tool_by_name ("pen_tool");- });- pen_item.add_accelerator ("activate", accel_group, ',', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);-- Gtk.MenuItem zoom_item = new Gtk.MenuItem.with_mnemonic (_("_Zoom"));- tool_menu.append (zoom_item);- zoom_item.activate.connect (() => { - Toolbox.select_tool_by_name ("zoom_tool");- });-- Gtk.MenuItem counter_item = new Gtk.MenuItem.with_mnemonic (_("_Create counter path"));- tool_menu.append (counter_item);- counter_item.activate.connect (() => { - Toolbox.select_tool_by_name ("cut");- });- counter_item.add_accelerator ("activate", accel_group, 'U', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);-- Gtk.MenuItem move_item = new Gtk.MenuItem.with_mnemonic (_("_Move"));- tool_menu.append (move_item);- move_item.activate.connect (() => { - Toolbox.select_tool_by_name ("move");- });- move_item.add_accelerator ("activate", accel_group, 'M', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);-- Gtk.MenuItem full_unicode_item = new Gtk.MenuItem.with_mnemonic (_("Show _full unicode characters set"));- tool_menu.append (full_unicode_item);- full_unicode_item.activate.connect (() => { - Toolbox.select_tool_by_name ("utf_8");- });- full_unicode_item.add_accelerator ("activate", accel_group, 'F', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);-- Gtk.MenuItem default_charset_item = new Gtk.MenuItem.with_mnemonic (_("Show de_fault characters set"));- tool_menu.append (default_charset_item);- default_charset_item.activate.connect (() => { - Toolbox.select_tool_by_name ("custom_character_set");- });- default_charset_item.add_accelerator ("activate", accel_group, 'R', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);-- Gtk.MenuItem avalilable_characters_item = new Gtk.MenuItem.with_mnemonic (_("Show characters in font"));- tool_menu.append (avalilable_characters_item);- avalilable_characters_item.activate.connect (() => { - Toolbox.select_tool_by_name ("available_characters");- });- avalilable_characters_item.add_accelerator ("activate", accel_group, 'D', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);-- Gtk.MenuItem add_grid_item = new Gtk.MenuItem.with_mnemonic (_("Add new _grid item"));- tool_menu.append (add_grid_item);- add_grid_item.activate.connect (() => { - MainWindow.get_toolbox ().add_new_grid ();- });-- Gtk.MenuItem remove_grid_item = new Gtk.MenuItem.with_mnemonic (_("Remove gr_id item"));- tool_menu.append (remove_grid_item);- remove_grid_item.activate.connect (() => { - MainWindow.get_toolbox ().remove_current_grid ();- });- - Gtk.MenuItem zoom_in_item = new Gtk.MenuItem.with_mnemonic (_("_Zoom in"));- tool_menu.append (zoom_in_item);- zoom_in_item.activate.connect (() => { - Toolbox.select_tool_by_name ("zoom_in");- });- zoom_in_item.add_accelerator ("activate", accel_group, '+', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);-- Gtk.MenuItem zoom_out_item = new Gtk.MenuItem.with_mnemonic (_("Zoom _out"));- tool_menu.append (zoom_out_item);- zoom_out_item.activate.connect (() => { - Toolbox.select_tool_by_name ("zoom_out");- });- zoom_out_item.add_accelerator ("activate", accel_group, '-', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);-- Gtk.MenuItem zoom_1_1_item = new Gtk.MenuItem.with_mnemonic (_("Zoom to _scale 1:1"));- tool_menu.append (zoom_1_1_item);- zoom_1_1_item.activate.connect (() => { - Toolbox.select_tool_by_name ("zoom_1_1");- });- zoom_1_1_item.add_accelerator ("activate", accel_group, '0', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);-- // Add menus- Gtk.MenuItem file_launcher = new Gtk.MenuItem.with_mnemonic (_("_File"));- file_launcher.set_submenu (file_menu);-- Gtk.MenuItem edit_launcher = new Gtk.MenuItem.with_mnemonic (_("_Edit"));- edit_launcher.set_submenu (edit_menu);-- Gtk.MenuItem tab_launcher = new Gtk.MenuItem.with_mnemonic (_("_Tab"));- tab_launcher.set_submenu (tab_menu);-- Gtk.MenuItem tool_launcher = new Gtk.MenuItem.with_mnemonic (_("T_ool"));- tool_launcher.set_submenu (tool_menu);- - menubar.append (file_launcher);- menubar.append (edit_launcher);- menubar.append (tab_launcher);- menubar.append (tool_launcher);- - return menubar; - }-- internal void toggle_expanded_margin_bottom () {- int w, h;- margin_bottom.get_size_request (out w, out h);- - if (h == 1) h = 2; - else h = 1;- - margin_bottom.set_size_request (w, h);- }- - internal void toggle_expanded_margin_right () { - int w, h;- margin_right.get_size_request (out w, out h);-- if (w == 1) w = 2; - else w = 1;-- margin_right.set_size_request (w, h);- }-- public static void hide_cursor () {- /*- Pixmap pixmap = new Pixmap (null, 1, 1, 1);- Color color = { 0, 0, 0, 0 };- Cursor cursor = new Cursor.from_pixmap (pixmap, pixmap, color, color, 0, 0);- */- - // Fixa: But why?- // (Supplement.exe:1300): Gdk-CRITICAL **: gdk_window_set_cursor: assertion `GDK_IS_WINDOW (window)' failed - // singleton.frame.set_cursor (cursor);- }-- public void update_window_size () {- int w, h;- get_size (out w, out h);- - Preferences.set ("window_width", @"$w");- Preferences.set ("window_height", @"$h");- }- - private void set_size_and_position () {- int w = Preferences.get_window_width ();- int h = Preferences.get_window_height ();- - set_default_size (w, h);- // move (10, 240);- }- - public bool quit () {- bool added;- SaveDialog s = new SaveDialog ();- - if (Supplement.get_current_font ().is_modified ()) {- added = MainWindow.get_tab_bar ().add_unique_tab (s, 50);- } else {- added = false;- }- - if (!added) {- Supplement.get_current_font ().save_backup ();- Gtk.main_quit ();- }- - s.finished.connect (() => {- Supplement.get_current_font ().delete_backup ();- Gtk.main_quit ();- });- - return true;- }- - public string? file_chooser (string title) {- string? fn = null;- FileChooserDialog file_chooser = new FileChooserDialog (title, this, FileChooserAction.SAVE, Stock.CANCEL, ResponseType.CANCEL, Stock.SAVE, ResponseType.ACCEPT);- Font font = Supplement.get_current_font ();- - try {- file_chooser.set_current_folder_file (font.get_folder ());- } catch (GLib.Error e) {- stderr.printf (e.message);- }- - if (file_chooser.run () == ResponseType.ACCEPT) { - MainWindow.get_glyph_canvas ().redraw ();- fn = file_chooser.get_filename ();- }-- file_chooser.destroy ();- - return fn;- } -}--class TabbarCanvas : DrawingArea {- TabBar tabbar;- - public TabbarCanvas (TabBar tb) { - tabbar = tb;-- set_extension_events (ExtensionMode.CURSOR | EventMask.POINTER_MOTION_MASK);- add_events (EventMask.BUTTON_PRESS_MASK | EventMask.POINTER_MOTION_MASK | EventMask.LEAVE_NOTIFY_MASK);- - motion_notify_event.connect ((t, e)=> {- Gtk.Allocation alloc;- tabbar.motion (e.x, e.y);- get_allocation (out alloc);- queue_draw_area (0, 0, alloc.width, alloc.height);- return true;- }); - - button_press_event.connect ((t, e)=> {- Gtk.Allocation alloc;- get_allocation (out alloc);- tabbar.select_tab_click (e.x, e.y, alloc.width, alloc.height);- queue_draw_area (0, 0, alloc.width, alloc.height);- return true;- });-- expose_event.connect ((t, e)=> {- Context cr = cairo_create (get_window ());-- Gtk.Allocation alloc;- get_allocation (out alloc);-- tabbar.draw (cr, alloc.width, alloc.height);- return true;- });- - tabbar.signal_tab_selected.connect ((t) => {- Gtk.Allocation alloc;- get_allocation (out alloc);- queue_draw_area (0, 0, alloc.width, alloc.height); - });- - set_size_request (20, 25);- }- -}--class ToolboxCanvas : DrawingArea {- Toolbox tb;- - public ToolboxCanvas (Toolbox toolbox) {- tb = toolbox;- - tb.redraw.connect ((x, y, w, h) => {- queue_draw_area (x, y, w, h);- });- - button_press_event.connect ((se, e)=> {- tb.press (e.button, e.x, e.y);- return true;- }); - - button_release_event.connect ((se, e)=> {- tb.release (e.button, e.x, e.y);- return true;- });-- motion_notify_event.connect ((sen, e)=> {- tb.move (e.x, e.y);- return true;- });- - expose_event.connect ((t, e)=> {- Gtk.Allocation allocation;- get_allocation (out allocation);- - Context cw = cairo_create(get_window());- tb.allocation_width = allocation.width;- tb.allocation_height = allocation.height;- tb.draw (allocation.width, allocation.height, cw);- - return true;- });- - add_events (EventMask.BUTTON_PRESS_MASK | EventMask.BUTTON_RELEASE_MASK | EventMask.POINTER_MOTION_MASK | EventMask.LEAVE_NOTIFY_MASK);-- set_size_request (160, 100);-- leave_notify_event.connect ((t, e)=> {- tb.reset_active_tool ();- return true;- });- - }-}--public class GlyphCanvasArea : DrawingArea {- GlyphCanvas glyph_canvas;- Gtk.Allocation alloc;-- /* State on linux */- public static const int L_SHIFT = 17;- public static const int L_CTRL = 20;- public static const int L_SHIFT_CTRL = 21;-- /* State on windows */- public static const int W_SHIFT = 1;- public static const int W_CTRL = 4;- public static const int W_SHIFT_CTRL = 5;- - public GlyphCanvasArea (GlyphCanvas gc) {- int event_flags;- - glyph_canvas = gc;- - event_flags = EventMask.BUTTON_PRESS_MASK;- event_flags |= EventMask.BUTTON_RELEASE_MASK;- event_flags |= EventMask.POINTER_MOTION_MASK;- event_flags |= EventMask.LEAVE_NOTIFY_MASK;- event_flags |= EventMask.SCROLL_MASK;- - add_events (event_flags);-- glyph_canvas.signal_redraw_area.connect ((x, y, w, h) => {- queue_draw_area ((int)x, (int)y, (int)w, (int)h);- });-- expose_event.connect ((t, e)=> { - Gtk.Allocation allocation;- get_allocation (out allocation);- - glyph_canvas.allocation.width = allocation.width;- glyph_canvas.allocation.height = allocation.height;- glyph_canvas.allocation.y = allocation.x;- glyph_canvas.allocation.y = allocation.y;- - if (unlikely (allocation != alloc && alloc.width != 0)) {- // Set size of glyph widget to an even number and notify - // set new allocation for glyph- bool ug = false;- - if (allocation.height % 2 != 0) {- MainWindow.native_window.toggle_expanded_margin_bottom ();- ug = true;- }- - if (allocation.width % 2 != 0) {- MainWindow.native_window.toggle_expanded_margin_right ();- ug = true;- } - - if (ug) {- //redraw_area (1, 1, 2, 2);- } else if (unlikely (allocation.width % 2 != 0 || allocation.height % 2 != 0)) {- warning (@"\nGlyph canvas is not divisible by two.\nWidth: $(allocation.width)\nHeight: $(allocation.height)");- }- - Supplement.current_glyph.resized ();- }- - alloc = allocation;- - Context cw = cairo_create (get_window());- - Surface s = new Surface.similar (cw.get_target (), Cairo.Content.COLOR_ALPHA, allocation.width, allocation.height);- Context c = new Context (s); -- glyph_canvas.current_display.draw (glyph_canvas.allocation, c);-- cw.save ();- cw.set_source_surface (c.get_target (), 0, 0);- cw.paint ();- cw.restore ();- - return true;- });-- button_press_event.connect ((t, e)=> {- set_modifier ((int) e.state);- - if (e.type == EventType.BUTTON_PRESS) {- glyph_canvas.current_display.button_press (e.button, e.x, e.y); - } else if (e.type == EventType.2BUTTON_PRESS) {- glyph_canvas.current_display.double_click (e.button, e.x, e.y);- }- - return true;- });- - button_release_event.connect ((t, e)=> {- set_modifier ((int) e.state);- glyph_canvas.current_display.button_release ((int) e.button, e.x, e.y);- return true;- });- - motion_notify_event.connect ((t, e)=> {- glyph_canvas.current_display.motion_notify (e.x, e.y); - return true;- });- - scroll_event.connect ((t, e)=> {- set_modifier ((int) e.state);- - if (e.direction == Gdk.ScrollDirection.UP) {- glyph_canvas.current_display.scroll_wheel_up (e.x, e.y);- } else if (e.direction == Gdk.ScrollDirection.DOWN) {- glyph_canvas.current_display.scroll_wheel_down (e.x, e.y);- }- - glyph_canvas.current_display.button_release (2, e.x, e.y);- return true;- }); - }-- static void set_modifier (int k) {- switch (k) {- case L_SHIFT:- KeyBindings.set_modifier (SHIFT);- break;- case L_CTRL:- KeyBindings.set_modifier (CTRL);- break;- case L_SHIFT_CTRL:- KeyBindings.set_modifier (SHIFT | CTRL);- break;- case W_SHIFT:- KeyBindings.set_modifier (SHIFT);- break;- case W_CTRL:- KeyBindings.set_modifier (CTRL);- break;- case W_SHIFT_CTRL:- KeyBindings.set_modifier (SHIFT | CTRL);- break;-- default:- KeyBindings.set_modifier (NONE);- break;- } - }-}--public class TooltipCanvas : DrawingArea {- TooltipArea tooltip_area;- - public TooltipCanvas (TooltipArea ta) {- tooltip_area = ta;-- expose_event.connect ((t, e)=> {- Allocation allocation = {0, 0, 0, 0};- Gtk.Allocation alloc;- Context cr = cairo_create (get_window ());- - get_allocation (out alloc);-- allocation.width = alloc.width;- allocation.height = alloc.height;- allocation.y = alloc.x;- allocation.y = alloc.y;- - tooltip_area.draw (cr, allocation);-- return true;- });- - tooltip_area.redraw.connect (() => {- Gtk.Allocation alloc;- get_allocation (out alloc);- queue_draw_area (0, 0, alloc.width, alloc.height);- });- - set_size_request (10, 20);- }-}--}deleted file mode 100644@@ -1,32 +0,0 @@-/*- 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/>.-*/--namespace Supplement {--class IOThread {-- public static void add (ThreadFunc<weak void*> func) {- try {- unowned Thread<void*> thread = Thread.create<void*> (func, false);- } catch (Error e) {- warning (@"e.message");- }- }- -}--}deleted file mode 100644@@ -1,50 +0,0 @@-/*- 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/>.-*/--namespace Supplement {--using Cairo; --public class Icons {- - public static ImageSurface? get_icon (string? name) {- ImageSurface? img = null;- File f;- - if (name == null) {- warning ("Can't find a file for name \"null\".");- return null;- }-- f = find_icon ((!) name);- - if (!f.query_exists ()) {- warning (@"Can't load icon: $((!)f.get_path ())");- return null;- }- - img = new ImageSurface.from_png ((!)f.get_path ());- - return img;- }-- public static File find_icon (string name) {- return FontDisplay.find_file ("icons", name);- }-}- -}deleted file mode 100644@@ -1,36 +0,0 @@-/*- 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 Math;--namespace Supplement {--class Intersection {- public double x;- public double y;- public double distance;- - public EditPoint editpoint_a;- public EditPoint editpoint_b;- - public Intersection (double x, double y, double distance) {- this.x = x;- this.y = y;- }-}--}deleted file mode 100644@@ -1,236 +0,0 @@-/*- 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 Math;--namespace Supplement {--class IntersectionList {- public List<Intersection> points = new List<Intersection> ();- - public void add (Intersection i) {- double d;- Intersection l;- bool close = false;- - if (points.length () == 0) {- points.append (i);- return;- }- - if (has_point (i)) {- return;- }- - for (unowned List<Intersection> pl = points.first (); ; pl = pl.next) {- l = pl.data;- d = Math.fabs (Math.sqrt (Math.pow (i.x - l.x, 2) + Math.pow (i.y - l.y, 2)));-- if (l.distance >= i.distance) {- close = true;- - if (d < 0.3) {- points.remove_link (points.last ());- points.append (i);- return;- }- }- - if (pl == points.last ()) {- break;- }- }- - if (!close) {- points.append (i);- }- }- - public void clear () {- while (points.length () > 0) {- points.remove_link (points.first ());- }- }- - public void remove_point (EditPoint e) {- foreach (Intersection n in points) {- if (n.editpoint_a == e || n.editpoint_b == e) {- points.remove_all (n);- }- } - }- - public bool has_edit_point (EditPoint e) {- foreach (Intersection n in points) {- if (n.editpoint_a == e || n.editpoint_b == e) {- return true;- }- }- - return false;- }- - bool has_point (Intersection i) {- return has_point_at (i.x, i.y);- }-- bool has_point_at (double x, double y) {- foreach (Intersection n in points) {- if (x == n.x && y == n.y) {- return true;- }- }- - return false;- }- - public int get_point_index (EditPoint e) {- int i = 0;- foreach (Intersection n in points) {- if (e.x == n.x && e.y == n.y) {- return i;- }- i++;- }- return -1;- }- - public Intersection? get_next_intersection (EditPoint e) {- bool found = false;- foreach (Intersection n in points) {- if (found) {- return n;- }- - if (n.editpoint_a == e || n.editpoint_b == e) {- found = true;- }- }- return null;- }- - public Intersection? get_intersection (EditPoint e) {- Intersection? p = null;- foreach (Intersection n in points) {- if (n.editpoint_a == e || n.editpoint_b == e) {- assert (p == null);- p = n;- }- }- return p;- }- - public void append (IntersectionList i) {- foreach (Intersection inter in i.points) {- points.append (inter);- }- }-- public static IntersectionList create_intersection_list (Path p1, Path p2) {- IntersectionList il = new IntersectionList ();- - unowned List<EditPoint> a_start, a_stop;- unowned List<EditPoint> b_start, b_stop;- - if (p1 == p2) {- return il;- }- - // find crossing paths- a_start = p1.points.first ();- for (int i = 0; i < p1.points.length (); i++) {- - if (a_start.data.next == null) {- a_stop = p1.points.first ();- } else {- a_stop = a_start.data.get_next ();- }- - b_start = p2.points.first ();- for (int j = 0; j < p2.points.length (); j++) {- - if (b_start.data.next == null) {- b_stop = p2.points.first ();- } else {- b_stop = b_start.data.get_next ();- }-- il.append (find_intersections (a_start.data, a_stop.data, b_start.data, b_stop.data));-- b_start = b_stop;- }- - a_start = a_stop;- }- - return il;- }-- static IntersectionList find_intersections (EditPoint a0, EditPoint a1, EditPoint b0, EditPoint b1) {- IntersectionList il = new IntersectionList ();- - double mind = double.MAX; ;- - Path.all_of (a0, a1, (ax, ay, at) => {- if (at == 0 || at == 1) {- return true;- }- - Path.all_of (b0, b1, (bx, by, bt) => {- double d = Math.fabs (Math.sqrt (Math.pow (ax - bx, 2) + Math.pow (ay - by, 2)));- - if (d < 0.2) {- il.add (new Intersection (ax, ay, d));- }- - if (d < mind) mind = d;- return true;- });- - return true;- });-- return il;- }- - /** return true if ranges does overlap */- bool has_intersection (double a0, double a1, double b0, double b1) {- if (a0 <= b0 <= a1 || a1 <= b0 <= a0) {- if (a0 <= b1 <= a1 || a1 <= b1 <= a0) {- return true;- }- - if (b0 <= a1 <= b1 || b1 <= a1 <= b0) {- return true;- }- }- - if (b0 <= a0 <= b1 || b1 <= a0 <= b0) {- if (a0 <= a0 <= a1 || a1 <= a0 <= a0) {- return true;- }- - if (b0 <= a0 <= b1 || b1 <= a0 <= b0) {- return true;- }- }- - return false;- }-}--}deleted file mode 100644@@ -1,33 +0,0 @@-/*- 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;-using Xml;--namespace Supplement {--public class Kerning : GLib.Object {- public string glyph_right;- public double val;- - public Kerning (string right, double v) {- glyph_right = right;- val = v;- }-}--}deleted file mode 100644@@ -1,366 +0,0 @@-/*- 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 Gdk;--namespace Supplement {--// FIXA a lot of these things have been replaced and can safely be removed--public enum Key {- NONE = 0,- UP = 65362,- RIGHT = 65363,- DOWN = 65364,- LEFT = 65361,- PG_UP = 65365,- PG_DOWN = 65366,- ENTER = 65293,- BACK_SPACE = 65288,- SHIFT_LEFT = 65505,- SHIFT_RIGHT = 65506,- CTRL_LEFT = 65507,- CTRL_RIGHT = 65508,- CAPS_LOCK = 65509,- ALT_LEFT = 65513,- ALT_RIGHT = 65514,- ALT_GR = 65027,- LOGO_LEFT = 65515,- LOGO_RIGHT = 65516,- CONTEXT_MENU = 65383,- TAB = 65289,- DEL = 65535-}--bool is_modifier_key (uint i) {- return Key.UP == i ||- Key.RIGHT == i ||- Key.DOWN == i ||- Key.LEFT == i ||- Key.PG_UP == i ||- Key.PG_DOWN == i ||- Key.ENTER == i ||- Key.BACK_SPACE == i ||- Key.SHIFT_LEFT == i ||- Key.SHIFT_RIGHT == i ||- Key.CTRL_LEFT == i ||- Key.CTRL_RIGHT == i ||- Key.ALT_LEFT == i ||- Key.ALT_RIGHT == i ||- Key.ALT_GR == i || - Key.LOGO_LEFT == i || - Key.LOGO_RIGHT == i || - Key.TAB == i || - Key.CAPS_LOCK == i || - Key.LOGO_RIGHT == i;-}--/** Modifier flags */-public static const uint NONE = 0;-public static const uint CTRL = 1 << 0;-public static const uint ALT = 1 << 2;-public static const uint SHIFT = 1 << 3;--/** A list of all valid key bindings. */-public class BindingList {- static ShortCut[]? nc = null;- - public static ShortCut[] get_default_bindings () { - - if (nc == null) {- - // add all bindings to this list- ShortCut[] default_bindings = { };-- nc = default_bindings;- }- - return nc;- }-}---/** Function to be executed for each global key binding. */-public abstract class ShortCut : GLib.Object {- uint modifier = 0;- uint key = 0;- - public abstract void run ();- - public void set_modifier (uint m) {- modifier = m;- }-- public void set_key (uint k) {- key = k;- }- - public uint get_modifier () {- return modifier;- }- - public uint get_key () {- return key;- }- - public abstract uint get_default_modifier ();- public abstract uint get_default_key ();- - public abstract unowned string get_description ();- - public static EqualFunc<ShortCut> equal = (a, b) => {- if (a.get_key () != b.get_key ()) return false;- if (a.get_modifier () != b.get_modifier ()) return false;- if (a.get_description () != b.get_description ()) return false;- return true;- };- -}--public class KeyBindings {- - bool modifier_ctrl = false;- bool modifier_alt = false;- bool modifier_shift = false;- - public static uint modifier = 0;- - bool require_modifier = false;- - /** First uint is modifer flag, second uint is keyval */- HashTable<uint, HashTable<uint, ShortCut>> short_cuts = new HashTable<uint, HashTable<uint, ShortCut>> (null, null);- - public static KeyBindings singleton;- - public KeyBindings () {- - HashFunc<uint> hash = (v) => {- return v;- };- - HashTable<uint, ShortCut> alt_ctrl_shift = new HashTable<uint, ShortCut> (hash, ShortCut.equal);- HashTable<uint, ShortCut> alt_shift = new HashTable<uint, ShortCut> (hash, ShortCut.equal);- HashTable<uint, ShortCut> ctrl_shift = new HashTable<uint, ShortCut> (hash, ShortCut.equal);- HashTable<uint, ShortCut> alt = new HashTable<uint, ShortCut> (hash, ShortCut.equal);- HashTable<uint, ShortCut> ctrl = new HashTable<uint, ShortCut> (hash, ShortCut.equal);- - // Possible modifiers- short_cuts.insert (ALT|CTRL|SHIFT, alt_ctrl_shift);- short_cuts.insert (ALT|SHIFT, alt_shift);- short_cuts.insert (CTRL|SHIFT, ctrl_shift);- short_cuts.insert (ALT, alt);- short_cuts.insert (CTRL, ctrl);- - // Add default bindings- foreach (var b in BindingList.get_default_bindings ()) {- add_binding (b);- }- - save ();- load ();- - singleton = this;- }- - public void reset () {- modifier = NONE;- modifier_ctrl = false;- modifier_alt = false;- modifier_shift = false;- }- - public void set_require_modifier (bool m) {- require_modifier = m;- }- - void load () {- File home = File.new_for_path (Environment.get_home_dir ());- File settings = home.get_child (".birdfont");- File bindings = settings.get_child ("keybindings");-- if (!bindings.query_exists ()) {- return;- }-- FileStream? bindings_file = FileStream.open ((!) bindings.get_path (), "r");- - if (bindings_file == null) {- stderr.printf ("Failed to load keybindings from file %s.\n", (!) bindings.get_path ());- return;- }- - return_if_fail (bindings_file != null);- - unowned FileStream b = (!) bindings_file;- - string? l;- l = b.read_line ();- while ((l = b.read_line ())!= null) {- string line;- - line = (!) l;- - if (line.get_char (0) == '#') {- continue;- }- - int i = 0;- int s = 0;- - i = line.index_of_char(' ', s);- string mod = line.substring (s, i - s);- - s = i + 1;- i = line.index_of_char(' ', s);- string key = line.substring (s, i - s);- - s = i + 1;- i = line.index_of_char('\n', s);- string description = line.substring (s, i - s);-- update_short_cut (mod, key, description);- }- - }- - void save () {- try {- File settings = Supplement.get_settings_directory ();- File bindings = settings.get_child ("keybindings");- - if (bindings.query_exists ()) {- bindings.delete ();- }-- DataOutputStream os = new DataOutputStream(bindings.create(FileCreateFlags.REPLACE_DESTINATION));- uint8[] data;- long written = 0;- - StringBuilder sb = new StringBuilder ();- - sb.append_printf ("# Supplement keybindings\n");- sb.append_printf ("# Version: 1.0\n");- - short_cuts.foreach ( (k, v) => {- v.foreach ( (k, s) => {- sb.append_printf ("%u %u %s\n", s.get_modifier (), s.get_key (), s.get_description ());- });- });- - data = sb.str.data;- - while (written < data.length) { - written += os.write (data[written:data.length]);- }- - } catch (Error e) {- stderr.printf ("Can not save key bindings. (%s)", e.message); - }- - }- - void update_short_cut (string mod, string key, string description) {- ShortCut? sc = null;- ShortCut binding;- short_cuts.foreach ( (k, ht) => {- ht.foreach ((ki, s) => {- if (s.get_description () == description) {- sc = s;- }- - // ht.remove (ki); // FIXME! why does it remove all items?- - });- });- - if (unlikely (sc == null)) {- stderr.printf ("Can not set bindings for \"%s-%s-%s-\"\n", mod, key, description);- return;- }- - binding = (!) sc;-- binding.set_modifier ((uint) uint64.parse (mod));- binding.set_key ((uint) uint64.parse (key));- - add_binding (binding);- }- - void add_binding (ShortCut binding) {- uint m = binding.get_modifier ();- - if (m == 0) {- m = binding.get_default_modifier ();- binding.set_modifier (m);- }-- if (binding.get_key () == 0) {- binding.set_key (binding.get_default_key ());- }- - HashTable<uint, ShortCut>? e = short_cuts.lookup (m);- - if (e == null) {- stderr.printf ("Invalid modifier in key binding: expecting ctrl, alt or shift. Flag: (%u) \n", modifier);- return;- }- - var events = (!) e;- events.insert (binding.get_key (), binding);- }- - bool get_short_cut (uint modifier, uint key, out ShortCut? sc) {- sc = null;- - foreach (var v in BindingList.get_default_bindings ()) {- if (v.get_default_modifier () == modifier && v.get_default_key () == key) {- sc = v;- return true;- }- }- - return false;- }-- public static void set_modifier (uint mod) {- modifier = mod;-- singleton.modifier_ctrl = ((modifier & CTRL) > 0);- singleton.modifier_alt = ((modifier & ALT) > 0);- singleton.modifier_shift = ((modifier & SHIFT) > 0);- }-- public static bool has_alt () {- return singleton.modifier_alt;- }- - public static bool has_shift () {- return singleton.modifier_shift;- }- - public static bool has_ctrl () {- return singleton.modifier_ctrl;- }- - public void key_release (uint keyval) {- }- - public void key_press (uint keyval) {- }-}--}deleted file mode 100644@@ -1,342 +0,0 @@-/*- 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 {--/** Help line */-public class Line : GLib.Object {- public static const bool VERTICAL = true;- public static const bool HOROIZONTAL = false;- - string label;- bool vertical;- - public double pos;- - bool active = false;- bool move = false;- - public signal void queue_draw_area (int x, int y, int w, int h);- public signal void position_updated (double pos);- - public unowned List<Line>? list_item = null;- - double r;- double g;- double b;- double a;- - bool visible = true;- bool moveable = true;- - public Line (string label = "No label set", double position = 10, bool vertical = false) {- this.label = label;- this.vertical = vertical;- this.pos = position;- - r = 0.7;- g = 0.7;- b = 0.8;- a = 1;- }- - public Line copy () {- Line l = new Line (label, pos, vertical);- - l.r = r;- l.g = g;- l.b = b;- l.a = a;- - l.visible = visible;- - return l;- }- - public void set_visible (bool v) {- visible = v;- }- -- public void set_moveable (bool m) {- moveable = m;- }- - public void set_color (double r, double g, double b, double a) {- this.r = r;- this.g = g;- this.b = b;- this.a = a;- }- - // Valabug, it should be: public “void set_list_item (unowned List<Line> ll) {”- // Keeping list_item public for now- public void set_list_item (void* ll) {- // list_item = ll;- }- - public bool is_moving () {- return move;- }- - public bool set_move (bool moving) {- bool r = move;- move = moving;- return (r == moving);- }- - public bool button_press () {- Glyph g;- if (get_active ()) {- move = true;- - g = MainWindow.get_current_glyph ();- g.store_undo_state ();- - return true;- }- - return false;- }-- public void redraw_line () {- double p;- Glyph g = MainWindow.get_current_glyph ();- - if (vertical) {- p = g.reverse_path_coordinate_x (pos);- queue_draw_area ((int)p - 10, 0, 200, g.allocation.height);- } else {- p = g.reverse_path_coordinate_y (pos);- queue_draw_area (0, (int)p - 10, g.allocation.width, 300);- }- } - - public void move_line_to (double x, double y, Allocation allocation) {- set_move (true);- event_move_to (x, y, x, y, allocation);- }- - public void event_move_to (double x, double y, double ax, double ay, Allocation allocation) {- double p, c;- bool a = false;- Glyph g = MainWindow.get_current_glyph ();- double ivz = 1/g.view_zoom;- double margin = 10;-- if (!moveable) return;-- if (move) {- double np = pos;- - redraw_line (); // clear old position- - if (is_vertical ()) {- pos = Glyph.path_coordinate_x (x);- redraw_line (); // draw at new position- } else {- pos = -Glyph.path_coordinate_y (y);- redraw_line ();- }-- if (Math.fabs (np - pos) > 10) {- queue_draw_area (0, 0, g.allocation.width, g.allocation.height);- }-- position_updated (pos); // sinal update-- Supplement.get_current_font ().touch ();- - swap_lines ();- }-- if (is_vertical ()) {- if (ay > g.allocation.height - 10) {- - p = get_coordinate ();-- c = ax * ivz + g.view_offset_x;- a = (p - margin * ivz <= c <= p + margin * ivz);- }- - if (a != get_active ()) {- redraw_line ();- }-- set_active (a);- - } else {- if (ax > g.allocation.width - 10) {- p = get_coordinate ();- c = ay * ivz + g.view_offset_y;- a = (p - margin * ivz <= c <= p + margin * ivz);- }- - if (a != get_active ()) {- redraw_line ();- }- - set_active (a);- }- }- - private void swap_lines () {- double ivz = 1 / MainWindow.get_current_glyph ().view_zoom;- - if (!moveable) return;- - // switch to correct line if this line passes the next one in the sorted list- if (list_item != null) {- unowned List<Line> ll = (!) list_item;- - if (ll != ll.first ()) {- if (ll.prev.data.pos > pos) {- ll.prev.data.set_move (true);- ll.prev.data.set_visible (true);- set_move (false);- pos = ll.prev.data.pos;- ll.prev.data.pos += 1 * ivz;- }- }- - if (ll != ll.last ()) {- if (ll.next.data.pos < pos) {- ll.next.data.set_move (true);- ll.next.data.set_visible (true);- pos = ll.next.data.pos;- ll.next.data.pos -= 1 * ivz;- set_move (false);- }- }- - }- - }- - public bool get_active () {- return active;- }- - protected void set_active (bool active) {- this.active = active;- }- - public string get_label () {- return label;- }- - public bool is_vertical () {- return vertical;- }-- public double get_coordinate () {- Glyph g = MainWindow.get_current_glyph ();- double t = (is_vertical ()) ? (g.allocation.width / 2.0) : (g.allocation.height / 2.0);- return pos + t;- }- - public double get_pos () {- return pos;- }- - private double get_handle_size () {- Glyph g = MainWindow.get_current_glyph ();- double ivz = 1/g.view_zoom;- return (get_active ()) ? (10 * ivz) : (5 * ivz);- }- - public void draw (Context cr, Allocation allocation) {- Glyph g = MainWindow.get_current_glyph ();- - double p, h, w;- double ivz = 1/g.view_zoom;-- double size = get_handle_size ();- - if (!visible) return;- - cr.save ();- cr.set_line_width (ivz);- - if (active) cr.set_source_rgba (0, 0, 0.3, 1);- else cr.set_source_rgba (r, this.g, b, a);- - // Line- if (is_vertical ()) {- p = get_coordinate ();- - h = g.allocation.height * ivz + g.view_offset_y;- - cr.move_to (p, g.view_offset_y);- cr.line_to (p, h);- cr.stroke ();-- cr.scale (1, 1);-- if (moveable) {- cr.new_path ();- cr.move_to (p - size, h); - cr.line_to (p, h - size); - cr.line_to (p + size, h);- cr.close_path();- cr.fill ();- }- - } else {- p = get_coordinate ();- - w = g.allocation.width * ivz + g.view_offset_x;- - cr.move_to (g.view_offset_x, p);- cr.line_to (w, p); - cr.stroke ();- - if (moveable) {- cr.new_path ();- cr.move_to (w, p - size); - cr.line_to (w - size, p); - cr.line_to (w, p + size);- cr.close_path();- cr.fill ();- }- }- - // Label- if (get_active ()) { - if (is_vertical ()) {- h = g.allocation.height * ivz + g.view_offset_y;- cr.move_to (p + 8*ivz, h - 30*ivz);- } else {- w = g.allocation.width * ivz + g.view_offset_x;- cr.move_to (w - 70*ivz, p + 15*ivz);- }-- cr.set_font_size (12 * ivz);- cr.select_font_face ("Cantarell", FontSlant.NORMAL, FontWeight.BOLD);- - cr.show_text (get_label ());- cr.stroke ();- }- - cr.restore ();- }--}--}deleted file mode 100644@@ -1,53 +0,0 @@-/*- 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 Birdfont;-using Supplement;--const string GETTEXT_PACKAGE = "birdfont"; --void parse_gtk_rc () {- File f = FontDisplay.find_file ("layout", "birdfont.rc");- Gtk.rc_parse ((!) f.get_path ());-}--public static int main (string[] arg) {- GtkWindow native_window;- MainWindow window;- string file;- Supplement.Supplement supplement = new Supplement.Supplement ();- supplement.init (arg);- - Gtk.init (ref arg);- parse_gtk_rc ();-- native_window = new GtkWindow ("birdfont"); - window = new MainWindow ();- - window.set_native (native_window);- native_window.init ();-- file = supplement.args.get_file ();- if (file != "") {- window.menu_tab.load_font (file);- }- - Gtk.main ();-- return 0;-}-deleted file mode 100644@@ -1,158 +0,0 @@-/* - 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/>. -*/ - -namespace Supplement { - -public class MainWindow { - - public static Toolbox tools; - public static GlyphCanvas glyph_canvas; - public static MainWindow singleton; - public static KeyBindings key_bindings; - public static MenuTab menu_tab; - public static TooltipArea tool_tip; - public static OverView over_view; - public static TabBar tabs; - public static NativeWindow native_window; - - public MainWindow () { - singleton = this; - - key_bindings = new KeyBindings (); - glyph_canvas = new GlyphCanvas (); - tools = new Toolbox (glyph_canvas); - tabs = new TabBar (); - menu_tab = new MenuTab (); - tool_tip = new TooltipArea (); - over_view = new OverView(); - } - - public void set_native (NativeWindow nw) { - native_window = nw; - } - - internal static NativeWindow get_native () { - return native_window; - } - - internal static MainWindow get_current_window () { - return singleton; - } - - public static FontDisplay get_current_display () { - return get_glyph_canvas ().get_current_display (); - } - - public static GlyphCanvas get_glyph_canvas () { - return glyph_canvas; - } - - internal static Glyph get_current_glyph () { - if (unlikely (is_null (Supplement.current_glyph))) { - warning ("No default glyph have been set yet.\n"); - return new Glyph ("no_glyph_created"); - } - - return Supplement.current_glyph; - } - - public static Toolbox get_toolbox () { - return get_singleton ().tools; - } - - internal static Tool get_tool (string n) { - return get_singleton ().tools.get_tool (n); - } - - public static TabBar get_tab_bar () { - return get_singleton ().tabs; - } - - internal static Tab get_current_tab () { - return get_singleton ().tabs.get_selected_tab (); - } - - internal static TooltipArea get_tool_tip () { - return tool_tip; - } - - internal static bool select_tab (Tab t) { - return get_singleton ().tabs.selected_open_tab (t); - } - - internal static OverView get_overview () { - OverView over_view; - - foreach (var t in tabs.tabs) { - if (t.get_display () is OverView) { - return (OverView) t.get_display (); - } - } - - over_view = new OverView(); - tabs.add_unique_tab (over_view, 100, false); - - return over_view; - } - - internal static MenuTab get_content_display () { - if (unlikely ((MenuTab?) get_singleton ().menu_tab == null)) { - warning ("MenuTab not instantiated."); - } - - return get_singleton ().menu_tab; - } - - public static MainWindow get_singleton () { - return singleton; - } - - public static string? file_chooser (string title) { - return get_singleton ().native_window.file_chooser (title); - } - - public void set_title (string title) { - // FIXA: - // native_window.set_title (title); - } - - /** Reaload all paths and help lines from disk. */ - internal static void clear_glyph_cache () { - Glyph g; - foreach (Tab t in get_tab_bar ().tabs) { - if (t.get_display () is Glyph) { - g = (Glyph) t.get_display (); - g.add_help_lines (); - } - } - - get_glyph_canvas ().redraw (); - } - - internal static void close_all_tabs () { - uint i = 0; - uint len = get_tab_bar ().get_length (); - - while (i < len) { - if (!get_tab_bar ().close_tab ((int) i)) { - i++; - } - } - } -} - -} deleted file mode 100644@@ -1,62 +0,0 @@-/*- 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 {--public class MenuAction : GLib.Object {- public string label;- public DropMenu.Selected action;- public DropMenu? parent = null;- public int index = -1;- bool selected = false;- - public MenuAction (string label) {- this.label = label;- }- - public void set_selected (bool s) {- selected = s;- }- - public virtual void draw (double x, double y, Context cr) {- if (selected) {- cr.save ();- cr.set_line_join (LineJoin.ROUND);- cr.set_line_width (12);- cr.set_source_rgba (102/255.0, 120/255.0, 149/255.0, 1);- cr.rectangle (x - 2, y - 9, 88, 8);- cr.fill_preserve ();- cr.stroke ();- cr.restore (); - }- - cr.save ();- cr.set_source_rgba (1, 1, 1, 1);- - cr.set_font_size (12);- cr.select_font_face ("Cantarell", FontSlant.NORMAL, FontWeight.NORMAL);- - cr.move_to (x, y);-- cr.show_text (label);- cr.restore ();- }-}--}deleted file mode 100644@@ -1,339 +0,0 @@-/*- 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 WebKit;--namespace Supplement {--public class MenuTab : FontDisplay { - - List<Font> recent_fonts = new List<Font> ();-- public MenuTab () {- // html callbacks:- add_html_callback ("export_name", (val) => {- Font f = Supplement.get_current_font ();- f.set_name (val);- f.touch ();- });-- add_html_callback ("export", (val) => {- ExportTool.export_all ();- });-- add_html_callback ("preview", (val) => {- preview ();- }); -- add_html_callback ("load", (val) => {- load_font (val);- }); -- add_html_callback ("glyph_sequence", (val) => {- Preferences.set ("glyph_sequence", val);- });-- add_html_callback ("help", (val) => {- MainWindow.get_tool_tip ().show_text (val);- });- }- - public override string get_name () {- return "Menu";- }- - public override bool is_html_canvas () {- return true;- }-- public override string get_html () {- Font f = Supplement.get_current_font ();- StringBuilder c = new StringBuilder ();- string fn;- - propagate_recent_files ();- - c.append (-"""-<html>-<head>- <script type="text/javascript" src="supplement.js"></script>- <style type="text/css">@import url("style.css");</style>- <script type="text/javascript">- document.onkeyup = update_text_fields; - </script>-</head>-<body>- <div class="inner_format_box">- <div class="content">- <div class="heading"><h2>""" + _("Preferences") + """</h2></div>- <form>-""");- -c.append ("""- <h3>""" + _("Glyph sequence") + """</h3>- <input class="text" type="text" id="glyph_sequence" value=""" + "\"" + Preferences.get ("glyph_sequence") + "\"" + """ onchange="update_text_fields ();"/><br />-- <h3>""" + _("Name") + """</h3>- <input class="text" type="text" id="fontname" value=""" + "\"" + f.get_name () + "\"" + """ onchange="update_text_fields ();"/><br />- - <input class="button" type="button" value=""" + "\"" + _("Export") + "\"" + """ id="export_button" onclick="call ('export:fonts');" onmouseover="call ('help:(Ctrl+e) """ + _("Export SVG, TTF & EOT fonts") + """');"/>- <input class="button" type="button" value=""" + "\"" + _("Preview") + "\"" + """ id="preview_button" onclick="call ('preview:fonts');" onmouseover="call ('help:(Ctrl+p) """ + _("Export SVG font and view the result") + """');"/><br />-""");- -c.append ("""-- </form> -- </div>- </div>- - <img src="birdfont_logo.png" alt="" style="float:right;margin: 50px 0 0 0;">- - <br class="clearBoth" />- - <div class="recent_list">-""");--if (recent_fonts.length () > 0) {- c.append ("""<div class="heading"><h2>""" + _("Recent files") + """</h2></div>""");- c.append ("\n");-}--foreach (Font font in recent_fonts) {- fn = (!) font.font_file;- fn = fn.substring (fn.replace ("\\", "/").last_index_of ("/") + 1); - - c.append ("""<div class="recent_font" """ + "onclick=\"call ('load:" + ((!) font.font_file).replace ("\\", "\\\\") + "');\">");-- c.append ("<div class=\"one_line\">");- c.append (fn);- c.append ("</div>");-- c.append ("<img src=\"");- c.append (path_to_uri ((!) Supplement.get_thumbnail_directory ().get_path ()));- c.append ("/");- c.append (fn);- c.append (@".png?$(Random.next_int ())\" alt=\"\">");- - c.append ("<br /><br />");- c.append ("</div>\n");-}--c.append ("</div>\n");--c.append ("""-</body>-</html>-""");-- return c.str;- }- - void translations () {- // xgettext needs these lines in order to extract strings properly- // TODO: update xgettext to handle vala strings well.- _("Preferences");- _("Export SVG, TTF & EOT fonts");- _("Name");- _("Glyph sequence");- _("Export SVG font and view the result");- _("Export SVG font and view the result");- }-- public void propagate_recent_files () {- Font font;-- while (recent_fonts.length () != 0) {- recent_fonts.delete_link (recent_fonts.first ());- }- - foreach (var f in Preferences.get_recent_files ()) {- if (f == "") continue;- - File file = File.new_for_path (f);-- font = new Font ();- - font.set_font_file (f);- - if (file.query_exists ()) { - recent_fonts.append (font);- }- }- - recent_fonts.reverse ();- }- - public void load_font (string fn) {- Font font = Supplement.get_current_font ();- - SaveDialog save = new SaveDialog ();- save.finished.connect (() => {- Font f;- bool loaded;- - f = Supplement.get_current_font ();- f.delete_backup ();- - MainWindow.clear_glyph_cache ();- MainWindow.close_all_tabs ();- - loaded = f.load (fn);- - if (!unlikely (loaded)) {- warning (@"Failed to load fond $fn");- return;- }- - MainWindow.get_singleton ().set_title (f.get_name ());- - MainWindow.get_toolbox ().remove_all_grid_buttons ();- foreach (string v in f.grid_width) {- MainWindow.get_toolbox ().parse_grid (v);- }- - MainWindow.get_toolbox ().background_scale.set_value (f.background_scale);- - select_overview ();- });- - MainWindow.get_tab_bar ().add_unique_tab (save, 80);- - if (!font.is_modified ()) {- save.finished ();- }- }- - private static void select_overview () {- Toolbox tb = MainWindow.get_toolbox ();- - if (Supplement.get_current_font ().is_empty ()) {- tb.select_tool_by_name ("custom_character_set");- } else {- tb.select_tool_by_name ("available_characters"); - }- }-- public static bool save_as () {- string? fn = null;- string f;- bool saved = false;- Font font = Supplement.get_current_font ();- - fn = MainWindow.file_chooser (_("Save"));- - if (fn != null) {- f = (!) fn;- - if (!f.has_suffix (".ffi")) {- f += ".ffi";- }- - font.font_file = f;- save ();- saved = true;- }-- return saved;- }-- public static bool save () {- Font f = Supplement.get_current_font ();- string fn;- bool saved = false;- - fn = (!) f.font_file;- - if (f.font_file != null && fn.has_suffix (".ffi")) {- f.background_scale = MainWindow.get_toolbox ().background_scale.get_display_value ();- - while (f.grid_width.length () > 0) {- f.grid_width.remove_link (f.grid_width.first ());- }- - foreach (SpinButton s in GridTool.sizes) {- f.grid_width.append (s.get_display_value ());- }- - f.save (fn);- saved = true;- } else {- saved = save_as ();- }- - return saved;- }- - public static void new_file () {- Font font = Supplement.get_current_font ();- SaveDialog save = new SaveDialog ();- save.finished.connect (() => {- Font f = Supplement.get_current_font ();- f.delete_backup ();- - Supplement.new_font ();- MainWindow.close_all_tabs ();- - MainWindow.get_toolbox ().remove_all_grid_buttons ();- MainWindow.get_toolbox ().add_new_grid ();- MainWindow.get_toolbox ().add_new_grid ();- - select_overview ();- });- - MainWindow.get_tab_bar ().add_unique_tab (save, 80);- - if (!font.is_modified ()) {- save.finished ();- }- }- - public static void load () {- SaveDialog save = new SaveDialog ();- save.finished.connect (() => {- load_new_font ();- });- MainWindow.get_tab_bar ().add_unique_tab (save, 80);- }-- private static void load_new_font () {- string? fn = MainWindow.file_chooser ("load");- Font f = Supplement.get_current_font ();- - if (fn != null) {- f.delete_backup ();- - MainWindow.clear_glyph_cache ();- MainWindow.close_all_tabs ();- f.load ((!)fn);- - MainWindow.get_singleton ().set_title (f.get_name ());- select_overview (); - }- }- - public static void show_kerning_context () {- MainWindow.get_tab_bar ().add_unique_tab (new ContextDisplay (), 85, false);- }- - public static void preview () {- TabBar t = MainWindow.get_tab_bar ();- t.add_unique_tab (new Preview (), 80); - }-}-}deleted file mode 100644@@ -1,271 +0,0 @@-/*- 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/>.-*/--namespace Supplement {--class MergeTool : Tool {-- public MergeTool (string n) {- base (n, _("Merge paths"));- - select_action.connect((self) => {- Glyph g = MainWindow.get_current_glyph ();- g.merge_all ();- });- }-- public override bool test () {- test_merge_second_triangle ();- test_merge_simple_path_box_and_triangle ();- test_merge_simple_path_box ();- test_merge_odd_paths ();- return true;- }- - private void test_merge_simple_path_box () {- 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 ();-- int x_offset = 10;- int y_offset = 350;- - // draw it- 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); - pen_tool.test_click_action (3, 0, 0);-- pen_tool.test_press_action (1, 1, 1);- pen_tool.test_move_action (15 + x_offset, 15 + y_offset);- pen_tool.test_release_action (1, 15 + x_offset, 15 + y_offset);- - pen_tool.test_click_action (1, 25 + x_offset, 15 + y_offset);- pen_tool.test_click_action (1, 25 + x_offset, 25 + y_offset);- pen_tool.test_click_action (1, 15 + x_offset, 25 + y_offset); - pen_tool.test_click_action (3, 0, 0);- - // merge it- this.test_select_action ();- - // test result- Path merged_outline = new Path ();- add_point_on_path (merged_outline, 10 + x_offset, 10 + y_offset);- add_point_on_path (merged_outline, 20 + x_offset, 10 + y_offset);- add_point_on_path (merged_outline, 20 + x_offset, 15 + y_offset);- add_point_on_path (merged_outline, 25 + x_offset, 15 + y_offset);- add_point_on_path (merged_outline, 25 + x_offset, 25 + y_offset);- add_point_on_path (merged_outline, 15 + x_offset, 25 + y_offset);- add_point_on_path (merged_outline, 15 + x_offset, 20 + y_offset);- add_point_on_path (merged_outline, 10 + x_offset, 20 + y_offset);- merged_outline.close ();- - // select path- pen_tool.test_click_action (3, 12 + x_offset, 12 + y_offset);- - Glyph g = MainWindow.get_current_glyph ();- Path? l = g.get_active_path ();- - if (l == null) {- critical ("No path found in merge test, it did not merge correctly.");- return;- }- - Path last = (!) l;- bool merged_path_looks_good = false;- merged_path_looks_good = last.test_is_outline (merged_outline);- - if (!merged_path_looks_good) critical ("Failed to merge path correctly.");- }-- private void test_merge_simple_path_box_and_triangle () {- 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 ();-- int x_offset = 40;- int y_offset = 350;- - // draw it- pen_tool.test_click_action (1, 0 + x_offset, -50 + y_offset);- pen_tool.test_click_action (1, 25 + x_offset, -50 + y_offset);- pen_tool.test_click_action (1, 25 + x_offset, 0 + y_offset);- pen_tool.test_click_action (1, 0 + x_offset, 0 + y_offset); - pen_tool.test_click_action (3, 0, 0);- - pen_tool.test_click_action (1, 0 + x_offset, 0 + y_offset);- pen_tool.test_click_action (1, 50 + x_offset, -50 + y_offset);- pen_tool.test_click_action (1, 50 + x_offset, 0 + y_offset); - pen_tool.test_click_action (3, 0, 0);-- // merge it- this.test_select_action ();- - // test result- Path merged_outline = new Path ();- add_point_on_path (merged_outline, 0 + x_offset, -50 + y_offset);- add_point_on_path (merged_outline, 25 + x_offset, -50 + y_offset);- add_point_on_path (merged_outline, -50 + x_offset, -50 + y_offset);- add_point_on_path (merged_outline, -50 + x_offset, 0 + y_offset);- add_point_on_path (merged_outline, 0 + x_offset, 0 + y_offset);- merged_outline.close ();-- // select path- pen_tool.test_click_action (3, 21 + x_offset, -11 + y_offset);- - Glyph g = MainWindow.get_current_glyph ();- Path? l = g.get_active_path ();- - if (l == null) {- critical ("No path found in test_merge_simple_path_box_and_triangle, it did not merge correctly.");- return;- }- - Path last = (!) l;- bool merged_path_looks_good = false;- merged_path_looks_good = last.test_is_outline (merged_outline);- - if (!merged_path_looks_good) critical ("Failed to merge path correctly.");- }-- private void test_merge_second_triangle () {- 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 ();-- int x_offset = 100;- int y_offset = 350;- - // draw it- pen_tool.test_click_action (1, 25 + x_offset, 0 + y_offset);- pen_tool.test_click_action (1, 25 + x_offset, -50 + y_offset);- pen_tool.test_click_action (1, 0 + x_offset, -50 + y_offset);- pen_tool.test_click_action (1, 0 + x_offset, 0 + y_offset); -- pen_tool.test_click_action (3, 0, 0);- - pen_tool.test_click_action (1, 40 + x_offset, -50 + y_offset);- pen_tool.test_click_action (1, 10 + x_offset, -20 + y_offset);- pen_tool.test_click_action (1, 25 + x_offset, -20 + y_offset); - pen_tool.test_click_action (3, 0, 0);- - // merge it- this.test_select_action ();- - // test result- Path merged_outline = new Path ();- add_point_on_path (merged_outline, 0 + x_offset, -50 + y_offset);- add_point_on_path (merged_outline, 25 + x_offset, -50 + y_offset);- add_point_on_path (merged_outline, -50 + x_offset, -50 + y_offset);- add_point_on_path (merged_outline, -50 + x_offset, 0 + y_offset);- add_point_on_path (merged_outline, 0 + x_offset, 0 + y_offset);- merged_outline.close ();-- // select path- pen_tool.test_click_action (3, 21 + x_offset, -11 + y_offset);- - Glyph g = MainWindow.get_current_glyph ();- Path? l = g.get_active_path ();- - if (l == null) {- critical ("No path found in triangle_right, it did not merge correctly.");- return;- }- - Path last = (!) l;- bool merged_path_looks_good = false;- merged_path_looks_good = last.test_is_outline (merged_outline);- - if (!merged_path_looks_good) critical ("Failed to merge path correctly.");- }- - private void add_point_on_path (Path p, int x, int y) {- Glyph g = MainWindow.get_current_glyph ();- p.add (g.path_coordinate_x (x), g.path_coordinate_y (y));- }- - - private void test_merge_odd_paths () {- 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- int x_offset = 100;- int y_offset = 100;- - // rectangle- pen_tool.test_click_action (1, 100 + x_offset, 100 + y_offset);- pen_tool.test_click_action (1, 170 + x_offset, 100 + y_offset);- pen_tool.test_click_action (1, 170 + x_offset, 120 + y_offset);- pen_tool.test_click_action (1, 100 + x_offset, 120 + y_offset); - pen_tool.test_click_action (3, 0, 0); // close- - // triangle- pen_tool.test_click_action (1, 100 + x_offset, 110 + y_offset);- pen_tool.test_click_action (1, 180 + x_offset, 130 + y_offset);- pen_tool.test_click_action (1, -10 + x_offset, 140 + y_offset);- pen_tool.test_click_action (3, 0, 0);-- // several triangles- pen_tool.test_click_action (1, 198, 379); - pen_tool.test_click_action (1, 274, 328); - pen_tool.test_click_action (1, 203, 286); - pen_tool.test_click_action (3, 230, 333);-- pen_tool.test_click_action (1, 233, 429); - pen_tool.test_click_action (1, 293, 382); - pen_tool.test_click_action (1, 222, 322); - pen_tool.test_click_action (3, 225, 406);-- pen_tool.test_click_action (1, 164, 316); - pen_tool.test_click_action (1, 262, 289); - pen_tool.test_click_action (1, 203, 260); - pen_tool.test_click_action (3, 203, 260); -- this.yield ();- - test_select_action ();- } -}--}deleted file mode 100644@@ -1,187 +0,0 @@-/*- 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 Math;-using Cairo;--namespace Supplement {--class MoveTool : Tool {-- bool move_path = false;- double last_x = 0;- double last_y = 0;-- bool resize_path = false;- Path? resized_path = null;- double last_resize_y;- - ImageSurface? resize_handle;- - public MoveTool (string n) {- base (n, _("Move paths"), 'm', CTRL);- - resize_handle = Icons.get_icon ("resize_handle.png");- - select_action.connect((self) => {- });-- deselect_action.connect((self) => {- Glyph glyph = MainWindow.get_current_glyph ();- glyph.clear_active_paths ();- });- - press_action.connect((self, b, x, y) => {- Glyph glyph = MainWindow.get_current_glyph ();- - glyph.store_undo_state ();- - foreach (Path p in glyph.active_paths) {- if (is_over_resize_handle (p, x, y)) {- resize_path = true;- resized_path = p;- last_resize_y = y;- return;- }- }- - if (resized_path != null) {- if (is_over_resize_handle ((!) resized_path, x, y)) {- resize_path = true;- last_resize_y = y;- return; - }- }- - if (!glyph.is_over_selected_path (x, y)) {- if (!glyph.select_path (x, y)) {- glyph.clear_active_paths ();- }- }- - move_path = true;- - last_x = x;- last_y = y;- });-- release_action.connect((self, b, x, y) => {- Glyph glyph = MainWindow.get_current_glyph ();- - move_path = false;- resize_path = false;- - if (GridTool.is_visible ()) {- foreach (Path p in glyph.active_paths) {- tie_path_to_grid (p, x, y);- }- }- });- - move_action.connect ((self, x, y) => {- Glyph glyph = MainWindow.get_current_glyph ();- double dx = last_x - x;- double dy = last_y - y; - double p = PenTool.precision;- Path rp;- double h, ratio;- - if (move_path) {- foreach (Path path in glyph.active_paths) {- path.move (glyph.ivz () * -dx * p, glyph.ivz () * dy * p);- }- }- - if (resize_path) {- return_if_fail (!is_null (resized_path));- rp = (!) resized_path;- h = rp.xmax - rp.xmin;- - ratio = 1;- ratio -= 0.7 * p * (Glyph.path_coordinate_y (last_resize_y) - Glyph.path_coordinate_y (y)) / h;- - rp.resize (ratio);- last_resize_y = y;- }-- last_x = x;- last_y = y;-- MainWindow.get_glyph_canvas ().redraw ();- });- - key_press_action.connect ((self, keyval) => {- Glyph g = MainWindow.get_current_glyph ();- - if (keyval == Key.DEL) {- foreach (Path p in g.active_paths) {- g.path_list.remove (p);- g.update_view ();- }- }- });- - draw_action.connect ((self, cr, glyph) => {- Glyph g = MainWindow.get_current_glyph ();- ImageSurface img = (!) resize_handle;- - foreach (Path p in g.active_paths) {- cr.set_source_surface (img, Glyph.reverse_path_coordinate_x (p.xmax) - 10, Glyph.reverse_path_coordinate_y (p.ymax) - 10);- cr.paint (); - }- - if (resized_path != null) {- cr.set_source_surface (img, Glyph.reverse_path_coordinate_x (((!) resized_path).xmax) - 10, Glyph.reverse_path_coordinate_y (((!)resized_path).ymax) - 10);- cr.paint ();- }- });- }-- bool is_over_resize_handle (Path p, double x, double y) {- double handle_x = Math.fabs (Glyph.reverse_path_coordinate_x (p.xmax)); - double handle_y = Math.fabs (Glyph.reverse_path_coordinate_y (p.ymax));- return fabs (handle_x - x + 10) < 20 && fabs (handle_y - y + 10) < 20;- }-- void tie_path_to_grid (Path p, double x, double y) {- double sx, sy, qx, qy; - - // tie to grid- sx = p.xmax;- sy = p.ymax;- qx = p.xmin;- qy = p.ymin;- - GridTool.tie_coordinate (ref sx, ref sy);- GridTool.tie_coordinate (ref qx, ref qy);- - if (Math.fabs (qy - p.ymin) < Math.fabs (sy - p.ymax)) {- p.move (0, qy - p.ymin);- } else {- p.move (0, sy - p.ymax);- }-- if (Math.fabs (qx - p.xmin) < Math.fabs (sx - p.xmax)) {- p.move (qx - p.xmin, 0);- } else {- p.move (sx - p.xmax, 0);- } - }--}--}deleted file mode 100644@@ -1,35 +0,0 @@-/*- 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/>.-*/--namespace Supplement {--public interface NativeWindow : GLib.Object {- public abstract string? file_chooser (string title);- public abstract string? set_title (string title);-- public abstract void toggle_expanded_margin_bottom ();- public abstract void toggle_expanded_margin_right ();- - public abstract void update_window_size ();-- protected void webkit_callback (string s) {- FontDisplay fd = MainWindow.get_current_display ();- fd.process_property (s);- }-}--}deleted file mode 100644@@ -1,136 +0,0 @@-/*- 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/>.-*/--namespace Supplement {--[SimpleType]-[CCode (has_type_id = false)] // Vala bug-internal struct Fixed : uint32 {-- public void @set (uint16 upper, uint16 lower) {- uint32* p = &this;- *p = (upper << 16) + lower;- }-- public bool @equals (uint16 upper, uint16 lower) {- uint32 t = (upper << 16) + lower;- return (t == this); - }-- public string get_string () {- uint a = this >> 16;- return @"$(a).$(this - (a << 16))";- }-}--[SimpleType]-[CCode (has_type_id = false)] // same as above-internal struct F2Dot14 : uint32 {-}--errordomain BadFormat {- PARSE-}--class OpenFontFormatReader : Object {- - public DirectoryTable directory_table;- - OtfInputStream dis;- File file;- - public OpenFontFormatReader () {- directory_table = new DirectoryTable ();- }- - public void close () {- dis.close ();- }- - public Glyph? read_glyph (string name) {- try {- return directory_table.glyf_table.read_glyph (name);- } catch (GLib.Error e) {- warning (e.message);- }- - return null;- }- - public void parse_index (string file_name) throws Error {- file = File.new_for_path (file_name);- if (!file.query_exists ()) {- throw new FileError.EXIST(@"OpenFontFormatReader: file does not exist. $((!) file.get_path ())");- }- - dis = new OtfInputStream (file.read ());- - parse_index_tables ();- }- - void parse_index_tables () throws Error {- OffsetTable offset_table;- FontData fd = new FontData ();- - FileInfo file_info = file.query_info ("*", FileQueryInfoFlags.NONE);- uint32 file_size = (uint32) file_info.get_size ();-- try {- fd.write_table (dis, 0, file_size);- } catch (GLib.Error e) {- warning (@"Failed to read font data. $(e.message)");- }-- offset_table = new OffsetTable (directory_table);- offset_table.parse (fd);- - directory_table = new DirectoryTable ();- directory_table.set_offset_table (offset_table);- directory_table.parse (fd, file, this);- }-- public void set_limits () {- Font f = OpenFontFormatWriter.font;- - if (is_null (f)) {- f = Supplement.get_current_font ();- }- - f.top_position = -get_ascender ();- f.top_limit = f.top_position - 5;- f.xheight_position = f.top_position - 5;- f.bottom_position = -get_descender ();- f.bottom_limit = f.bottom_position + 5; - }-- public double get_ascender () {- return directory_table.hhea_table.get_ascender ();- }- - public double get_descender () {- return directory_table.hhea_table.get_descender ();- }-- public unowned List<string> get_all_names () {- return directory_table.post_table.get_all_names ();- }- - public uint32 get_head_checksum () {- return directory_table.head_table.get_adjusted_checksum ();- }-}-}deleted file mode 100644@@ -1,4875 +0,0 @@-/* - 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 Math; - -namespace Supplement { - -const double UNITS = 10; - -class OpenFontFormatWriter : Object { - - DataOutputStream os; - DirectoryTable directory_table; - - public static Font font; - - public OpenFontFormatWriter () { - directory_table = new DirectoryTable (); - } - - public static Font get_current_font () { - return font; - } - - public void open (File file) throws Error { - assert (!is_null (file)); - - if (file.query_exists ()) { - warning ("File exists in export."); - throw new FileError.EXIST("OpenFontFormatWriter: file exists."); - } - - os = new DataOutputStream(file.create (FileCreateFlags.REPLACE_DESTINATION)); - } - - public void write_ttf_font (Font font) throws Error { - long dl; - uint8* data; - unowned List<Table> tables; - FontData fd; - uint l; - - this.font = font; - - directory_table.process (); - tables = directory_table.get_tables (); - - dl = directory_table.get_font_file_size (); - - if (dl == 0) { - warning ("font is of zero size."); - return; - } - - foreach (Table t in tables) { - fd = t.get_font_data (); - data = fd.table_data; - l = fd.length_with_padding (); - - for (int j = 0; j < l; j++) { - os.put_byte (data[j]); - } - } - } - - public void close () throws Error { - os.close (); - } -} - -/** Reader for otf data types. */ -class OtfInputStream : Object { - - public FileInputStream fin; - public DataInputStream din; - - public OtfInputStream (FileInputStream i) throws Error { - din = new DataInputStream (i); - fin = i; - } - - public void seek (int64 pos) throws Error - requires (fin.can_seek ()) { - int64 p = fin.tell (); - fin.seek (pos - p, SeekType.CUR); - } - - public uint8 read_byte () throws Error { - return din.read_byte (); - } - - public void close () { - try { - fin.close (); - din.close (); - } catch (GLib.IOError e) { - warning (e.message); - } - } -} - -class FontData : Object { - - // Read pointer - uint rp = 0; - - // Write pointer - uint wp = 0; - - // length without padding - uint32 len = 0; - uint32 padding = 0; - - uint32 capacity; - - public uint8* table_data = null; - - public FontData (uint32 size = 1024) { - capacity = size; - table_data = malloc (size); - } - - ~FontData () { - if (table_data != null) { - delete table_data; - table_data = null; - } - } - - public void write_at (uint pos, uint8 new_data) throws GLib.Error - requires (pos <= capacity) - { - if (unlikely (pos >= len)) { - warning ("end of table reached"); - assert (false); - } - - table_data[pos]= new_data; - } - - public void write_table_data (FontData fd, uint32 offset, uint32 length) { - fd.seek (offset); - for (uint32 i = 0; i < length; i++) { - add (table_data [rp++]); - } - fd.seek (0); - } - - public void write_table (OtfInputStream dis, uint32 offset, uint32 length) throws Error { - uint32 l = length + (length % 4); // padding after end of table - uint8 b; - - if (length >= l) { - expand (l); - } - - if (table_data == null) { - warning ("Failed to allocate memory for ttf data."); - return; - } - - seek (0); - dis.seek (offset); - - wp = 0; - while (wp < l) { - b = dis.read_byte (); - add (b); - } - - rp = 0; - } - - public uint length_with_padding () { - return len; - } - - public uint length () { - return len - padding; - } - - public void pad () { - while (len % 4 != 0) { - add (0); - padding++; - } - } - - /** Add additional checksum data to this checksum. */ - public void continous_check_sum (ref uint32 current_check_sum) { - uint trp = rp; - uint l; - - if (length_with_padding () % 4 != 0) { - warning ("Table is not padded to correct size."); - } - - seek (0); - - l = (length () % 4 > 0) ? length () / 4 + 1 : length () / 4; - - for (uint32 i = 0; i < l; i++) { - current_check_sum += read_uint32 (); - } - - rp = trp; - - return; - } - - public uint32 check_sum () { - uint32 sum = 0; - continous_check_sum (ref sum); - return sum; - } - - public void seek_end () { - seek (len); - } - - public void seek (uint i) { - rp = i; - wp = i; - } - - public uint8 read () { - if (unlikely (rp >= len)) { - warning ("end of table reached"); - return 0; - } - - return table_data [rp++]; - } - - public Fixed read_fixed () { - return (Fixed) read_uint32 (); - } - - public uint32 read_ulong () { - return read_uint32 (); - } - - public uint16 read_ushort () { - uint16 f; - f = read () << 8; - f += read (); - return f; - } - - public int16 read_int16 () { - int16 f; - f = read () << 8; - f += read (); - return f; - } - - public int16 read_short () throws Error { - return read_int16 (); - } - - public uint32 read_uint32 () { - uint32 f; - f = read () << 8 * 3; - f += read () << 8 * 2; - f += read () << 8 * 1; - f += read () << 8 * 0; - return f; - } - - public uint64 read_uint64 () { - uint64 f; - f = read () << 8 * 7; - f += read () << 8 * 6; - f += read () << 8 * 5; - f += read () << 8 * 4; - f += read () << 8 * 3; - f += read () << 8 * 2; - f += read () << 8 * 1; - f += read () << 8 * 0; - return f; - } - - public F2Dot14 read_f2dot14 () throws Error { - F2Dot14 f = (F2Dot14) read_int16 (); - return f; - } - - public uint64 read_udate () throws Error { - return read_uint64 (); - } - - public uint8 read_byte () throws Error { - return read (); - } - - public char read_char () throws Error { - return (char) read_byte (); - } - - public void add_udate (int64 d) throws Error { - add_64 (d); - } - - public void add_fixed (Fixed f) throws Error { - add_u32 (f); - } - - public void add_fword (int16 word) { - add_16 (word); - } - - public void add_f2dot14 (F2Dot14 f) throws Error { - add_u32 (f); - } - - public void add_short (int16 d) throws Error { - add_16 (d); - } - - public void add_ushort (uint16 d) throws Error { - add_u16 (d); - } - - public void add_ulong (uint32 d) throws Error { - add_u32 (d); - } - - public void add_byte (uint8 b) throws Error { - add (b); - } - - private void expand (uint extra_bytes = 1024) { - capacity += extra_bytes; - table_data = (uint8*) try_realloc (table_data, capacity); - - if (table_data == null) { - warning ("Out of memory."); - capacity = 0; - } - } - - public void add (uint8 d) { - if (unlikely (len == capacity)) { - expand (); - } - - table_data[wp] = d; - - if (wp == len) { - len++; - } - - wp++; - } - - public void add_littleendian_u16 (uint32 i) { - add ((int8) (i & 0x00FF)); - add ((int8) ((i & 0xFF00) >> 8)); - } - - public void add_u16 (uint16 d) { - uint16 n = d >> 8; - add ((uint8)n); - add ((uint8)(d - (n << 8))); - } - - public void add_16 (int16 i) { - uint8 s = (uint8) (i >> 8); - - add ((uint8) s); - add ((uint8) (i - (s << 8))); - } - - public void add_littleendian_u32 (uint32 i) { - add ((int8) (i & 0x000000FF)); - add ((int8) ((i & 0x0000FF00) >> 8)); - add ((int8) ((i & 0x00FF0000) >> 16)); - add ((int8) ((i & 0xFF000000) >> 24)); - } - - public void add_u32 (uint32 i) { - uint32 s = (uint16) (i >> 16); - - add_u16 ((uint16) s); - add_u16 ((uint16) (i - (s << 16))); - } - - public void add_32 (int32 i) { - int32 s = (int16) (i >> 16); - - add_16 ((int16) s); - add_16 ((int16) (i - (s << 16))); - } - - public void add_u64(uint64 i) { - uint64 s = (uint32) (i >> 32); - - add_u32 ((uint32) s); - add_u32 ((uint32)(i - (s << 32))); - } - - public void add_64(int64 i) { - int64 s = (int32) (i >> 32); - - add_u32 ((int32) s); - add_u32 ((int32)(i - (s << 32))); - } - - public void add_str_littleendian_utf16 (string s) { - add_str_utf16 (s, true); - } - - public void add_str_utf16 (string s, bool little_endian = false) { - int index = 0; - unichar c; - uint8 c0; - uint8 c1; - int l = 0; - - while (s.get_next_char (ref index, out c)) { - c0 = (uint8) (c >> 8); - c1 = (uint8) (c - (c0 << 8)); - - if (little_endian) { - add (c1); - add (c0); - } else { - add (c0); - add (c1); - } - - l += 2; - } - - assert (l == 2 * s.char_count ()); - } - - public void add_str (string s) { - uint8[] data = s.data; - for (int n = 0; n < data.length; n++) { - add (data[n]); - } - } - - public void add_tag (string s) - requires (s.length == 4 && s.data.length == 4) { - add_str (s); - } -} - -class Coordinate { - /** TTF coordinate flags. */ - - public static const uint8 NONE = 0; - public static const uint8 ON_PATH = 1 << 0; - public static const uint8 X_SHORT_VECTOR = 1 << 1; - public static const uint8 Y_SHORT_VECTOR = 1 << 2; - public static const uint8 REPEAT = 1 << 3; - - // same flag or short vector sign flag - public static const uint8 X_IS_SAME = 1 << 4; - public static const uint8 Y_IS_SAME = 1 << 5; - public static const uint8 X_SHORT_VECTOR_POSITIVE = 1 << 4; - public static const uint8 Y_SHORT_VECTOR_POSITIVE = 1 << 5; - -} - -class Table : Object { - - public string id = "NO_ID"; - - public uint32 checksum = 0; - public uint32 offset = 0; - public uint32 length = 0; - - protected FontData? font_data = null; - - public virtual string get_id () { - return id; - } - - public FontData get_font_data () { - if (font_data == null) { - warning (@"No font data for $(id)."); - font_data = new FontData (); - } - - return (!) font_data; - } - - public bool has_data () { - return length > 0; - } - - public virtual void parse (FontData dis) throws GLib.Error { - warning (@"Parse is not implemented for $(id)."); - } - - /** Validate table checksum. */ - public bool validate (FontData dis) { - bool valid; - - if (length == 0) { - stderr.printf (@"Table $id is of zero length.\n"); - valid = false; - } else { - valid = Table.validate_table (dis, checksum, offset, length, id); - } - - if (!valid) { - stderr.printf (@"Table $id is invalid.\n"); - } - - return valid; - } - - public static bool validate_table (FontData dis, uint32 checksum, uint32 offset, uint32 length, string name) { - uint32 ch = calculate_checksum (dis, offset, length, name); - bool c; - - c = (ch == checksum); - - if (!c) { - stderr.printf(@"Checksum does not match data for $(name).\n"); - stderr.printf(@"name: $name, checksum: $checksum, offset: $offset, length: $length\n"); - stderr.printf(@"calculated checksum $(ch)\n"); - } - - return c; - } - - public static uint32 calculate_checksum (FontData dis, uint32 offset, uint32 length, string name) { - uint32 checksum = 0; - uint32 l; - - dis.seek (offset); - - l = (length % 4 > 0) ? length / 4 + 1 : length / 4; - - for (uint32 i = 0; i < l; i++) { - checksum += dis.read_ulong (); - } - - return checksum; - } - - public static uint16 max_pow_2_less_than_i (uint16 ind) { - uint16 last = 0; - uint16 i = 1; - - while ((i <<= 1) < ind) { - last = i; - } - - return last; - } - - public static uint16 max_log_2_less_than_i (uint16 ind) { - return (uint16) (Math.log (ind) / Math.log (2)); - } -} - -class LocaTable : Table { - - uint32* glyph_offsets = null; - public uint32 size = 0; - - public LocaTable () { - id = "loca"; - } - - ~LocaTable () { - if (glyph_offsets != null) delete glyph_offsets; - } - - public uint32 get_offset (uint32 i) { - return_if_fail (glyph_offsets != null); - - if (size == 0) { - warning ("No glyphs in loca table"); - } - - if (!(0 <= i < size + 1)) { - warning (@"No offset for glyph $i. Requires (0 <= $i < $(size + 1)"); - } - - return glyph_offsets [i]; - } - - /** Returns true if glyph at index i is empty and have no body to parse. */ - public bool is_empty (uint32 i) { - return_if_fail (glyph_offsets != null); - - if (size == 0) { - warning ("No glyphs in loca table"); - } - - if (!(0 <= i < size + 1)) { - warning (@"No offset for glyph $i. Requires (0 <= $i < $(size + 1)"); - } - - return glyph_offsets[i] == glyph_offsets[i + 1]; - } - - public new void parse (FontData dis, HeadTable head_table, MaxpTable maxp_table) throws GLib.Error { - size = maxp_table.num_glyphs; - glyph_offsets = new uint32[size + 1]; - - dis.seek (offset); - - printd (@"size: $size\n"); - printd (@"length: $length\n"); - printd (@"length/4-1: $(length / 4 - 1)\n"); - printd (@"length/2-1: $(length / 2 - 1)\n"); - printd (@"head_table.loca_offset_size: $(head_table.loca_offset_size)\n"); - - switch (head_table.loca_offset_size) { - case 0: - for (long i = 0; i < size + 1; i++) { - glyph_offsets[i] = 2 * dis.read_ushort (); - - if (0 < i < size && glyph_offsets[i - 1] > glyph_offsets[i]) { - warning (@"Invalid loca table, it must be sorted. ($(glyph_offsets[i - 1]) > $(glyph_offsets[i]))"); - } - } - break; - - case 1: - for (long i = 0; i < size + 1; i++) { - glyph_offsets[i] = dis.read_ulong (); - - if (0 < i < size && glyph_offsets[i - 1] > glyph_offsets[i]) { - warning (@"Invalid loca table, it must be sorted. ($(glyph_offsets[i - 1]) > $(glyph_offsets[i]))"); - } - } - - break; - - default: - warning ("unknown size for offset in loca table"); - break; - } - } - - public void print_offsets () { - for (int i = 0; i < size; i++) { - print (@"get_offset ($i): $(get_offset (i))\n"); - } - } - - public void process (GlyfTable glyf_table, HeadTable head_table) { - FontData fd = new FontData (); - uint32 last = 0; - uint32 prev = 0; - int i = 0; - - foreach (uint32 o in glyf_table.location_offsets) { - if (i != 0 && (o - prev) % 4 != 0) { - warning (@"glyph length is not a multiple of four in gid $i"); - } - - if (o % 4 != 0) { - warning ("glyph is not on a four byte boundry"); - assert_not_reached (); - } - - prev = o; - i++; - } - - if (head_table.loca_offset_size == 0) { - foreach (uint32 o in glyf_table.location_offsets) { - fd.add_u16 ((uint16) (o / 2)); - - if (o < last) { - warning (@"Loca table must be sorted. ($o < $last)"); - } - - last = o; - } - - } else if (head_table.loca_offset_size == 1) { - foreach (uint32 o in glyf_table.location_offsets) { - fd.add_u32 (o); - - if (o < last) { - warning (@"Loca table must be sorted. ($o < $last)"); - } - - last = o; - } - - } else { - warn_if_reached (); - } - - if (!(glyf_table.location_offsets.length () == glyf_table.glyphs.length () + 1)) { - warning (@"(glyf_table.location_offsets.length () == glyf_table.glyphs.length () + 1) ($(glyf_table.location_offsets.length ()) == $(glyf_table.glyphs.length () + 1))"); - } - - fd.pad (); - font_data = fd; - } -} - -class GlyfTable : Table { - - // Flags for composite glyph - static const uint16 BOTH_ARE_WORDS = 1 << 0; - static const uint16 BOTH_ARE_XY_VALUES = 1 << 1; - static const uint16 ROUND_TO_GRID = 1 << 2; - static const uint16 SCALE = 1 << 3; - static const uint16 RESERVED = 1 << 4; - static const uint16 MORE_COMPONENTS = 1 << 5; - static const uint16 SCALE_X_Y = 1 << 6; - static const uint16 SCALE_WITH_ROTATTION = 1 << 7; - static const uint16 INSTRUCTIONS = 1 << 8; - - public int16 xmin = int16.MAX; - public int16 ymin = int16.MAX; - public int16 xmax = int16.MIN; - public int16 ymax = int16.MIN; - - public FontData dis; - public HeadTable head_table; - public HmtxTable hmtx_table; - public LocaTable loca_table; - public CmapTable cmap_table; // hack, hack, hack. cmap and post is null when inistialized and set in parse method - public PostTable post_table; - public KernTable kern_table; - - public List<uint32> location_offsets; - - // list of glyphs sorted in the order we expect to find them in a - // ttf font. notdef is the firs glyph followed by null and nonmarkingreturn. - // after that will all assigned glyphs appear in sorted order, all - // remaining unassigned glyphs live in the last part of the file. - public List<Glyph> glyphs; - - uint16 max_points = 0; - uint16 max_contours = 0; - - double total_width = 0; - int non_zero_glyphs = 0; - - public GlyfTable (LocaTable l) { - id = "glyf"; - loca_table = l; - location_offsets = new List<uint32> (); - glyphs = new List<Glyph> (); - } - - public int get_gid (string name) { - int i = 0; - foreach (Glyph g in glyphs) { - if (g.name == name) { - return i; - } - - i++; - } - - return -1; - } - - public int16 get_average_width () { - return (int16) (total_width / non_zero_glyphs); - } - - public uint16 get_max_contours () { - return max_contours; - } - - public uint16 get_max_points () { - return max_points; - } - - public uint16 get_first_char () { - return 32; // space - } - - public uint16 get_last_char () { - unowned List<Glyph> gl = glyphs.last (); - Glyph g = (!) gl.data; - - while (gl != glyphs.first ()) { - g = (!) gl.data; - - if (!g.is_unassigned ()) { - break; - } - - gl = gl.prev; - } - - return (uint16)g.unichar_code; - } - - public Glyph? read_glyph (string name) throws GLib.Error { - Glyph? glyph; - Glyph g; - int i; - KernList kl; - string right; - double units_per_em; - double kern_val; - - i = post_table.get_gid (name); - - if (i == -1) { - return null; - } - - glyph = parse_index (i, dis, loca_table, hmtx_table, head_table, post_table); - - if (glyph != null) { - printd (@"kern: "); - units_per_em = head_table.get_units_per_em (); - kl = kern_table.get_all_pairs (i); - foreach (Kern k in kl.kernings) { - g = (!) glyph; - right = post_table.get_name (k.right); - kern_val = k.kerning * 1000.0 / units_per_em; - g.add_kerning (right, kern_val); - } - } - - return glyph; - } - - public new void parse (FontData dis, CmapTable cmap_table, LocaTable loca, HmtxTable hmtx_table, HeadTable head_table, PostTable post_table, KernTable kern_table) throws GLib.Error { - this.cmap_table = cmap_table; - this.post_table = post_table; - this.loca_table = loca; - this.hmtx_table = hmtx_table; - this.head_table = head_table; - this.kern_table = kern_table; - this.dis = dis; - } - - Glyph parse_index (int index, FontData dis, LocaTable loca, HmtxTable hmtx_table, HeadTable head_table, PostTable post_table) throws GLib.Error { - Glyph glyph = new Glyph (""); - double xmin, xmax; - double units_per_em = head_table.get_units_per_em (); - unichar character = 0; - string name; - uint32 end; - - character = cmap_table.get_char (index); - name = post_table.get_name (index); - - if (name == "") { - StringBuilder name_c = new StringBuilder (); - name_c.append_unichar (character); - name = name_c.str; - } - - printd (@"name: $(name)\n"); - - if (!loca.is_empty (index)) { - glyph = parse_next_glyf (dis, character, index, out xmin, out xmax, units_per_em); - - glyph.left_limit = xmin - hmtx_table.get_lsb (index); - glyph.left_limit = 0; - glyph.right_limit = glyph.left_limit + hmtx_table.get_advance (index); - } else { - // add empty glyph - glyph = new Glyph (name, character); - glyph.left_limit = -hmtx_table.get_lsb (index); - glyph.right_limit = hmtx_table.get_advance (index) - hmtx_table.get_lsb (index); - } - - glyph.name = name; - - if (character == 0) { - glyph.set_unassigned (true); - } - - if (character == 0 && name != "") { - stderr.printf (@"Got null character\n"); - stderr.printf (@"gid: $index\n"); - stderr.printf (@"char: $((uint) character)\n"); - stderr.printf (@"name: $(name)\n"); - } - - return glyph; - } - - Glyph parse_next_composite_glyf (FontData dis, unichar character, int pgid) throws Error { - uint16 component_flags = 0; - uint16 glyph_index; - int16 arg1 = 0; - int16 arg2 = 0; - uint16 arg1and2; - F2Dot14 scale; - - F2Dot14 scalex; - F2Dot14 scaley; - - F2Dot14 scale01; - F2Dot14 scale10; - - Glyph glyph, linked_glyph; - List<int> x = new List<int> (); - List<int> y = new List<int> (); - List<int> gid = new List<int> (); - - double xmin, xmax; - double units_per_em = head_table.get_units_per_em (); - - int glid; - - StringBuilder name = new StringBuilder (); - name.append_unichar (character); - - glyph = new Glyph (name.str, character); - - do { - component_flags = dis.read_ushort (); - glyph_index = dis.read_ushort (); - - if ((component_flags & BOTH_ARE_WORDS) > 0) { - arg1 = dis.read_short (); - arg2 = dis.read_short (); - } else if ((component_flags & BOTH_ARE_XY_VALUES) > 0) { - arg1 = dis.read_byte (); - arg2 = dis.read_byte (); - } - - gid.append (glyph_index); - x.append (arg1); - y.append (arg2); - - // if ((component_flags & RESERVED) > 0) - - if ((component_flags & SCALE) > 0) { - scale = dis.read_f2dot14 (); - } else if ((component_flags & SCALE_X_Y) > 0) { - scalex = dis.read_f2dot14 (); - scaley = dis.read_f2dot14 (); - } else if ((component_flags & SCALE_WITH_ROTATTION) > 0) { - scalex = dis.read_f2dot14 (); - scale01 = dis.read_f2dot14 (); - scale10 = dis.read_f2dot14 (); - scaley = dis.read_f2dot14 (); - } - - } while ((component_flags & MORE_COMPONENTS) > 0); - - - for (int i = 0; i < gid.length (); i++) { - // compensate xmax ymax with coordinate - glid = gid.nth (i).data; - - if (glid == pgid) { - warning ("Cannot link a glyph to it self."); - continue; - } - - linked_glyph = parse_next_glyf (dis, character, glid, out xmin, out xmax, units_per_em); - } - - - /* - if ((component_flags & INSTRUCTIONS) > 0) { - num_instructions = dis.read_ushort (); - - for (int i = 0; i < num_instructions; i++) { - dis.read_byte (); - } - } - */ - - return glyph; - } - - Glyph parse_next_glyf (FontData dis, unichar character, int gid, out double xmin, out double xmax, double units_per_em) throws Error { - - uint16* end_points = null; - uint8* instructions = null; - uint8* flags = null; - int16* xcoordinates = null; - int16* ycoordinates = null; - - int npoints = 0; - - int16 ncontours; - int16 ixmin; // set boundries - int16 iymin; - int16 ixmax; - int16 iymax; - uint16 ninstructions; - - int16 rxmin = int16.MAX; // real xmin - int16 rymin = int16.MAX;; - int16 rxmax = int16.MIN; - int16 rymax = int16.MIN; - - int nflags; - - Error? error = null; - - uint start, end, len; - - StringBuilder name = new StringBuilder (); - name.append_unichar (character); - - start = loca_table.get_offset (gid); - end = loca_table.get_offset (gid + 1); - len = start - end; - - dis.seek (offset + start); - - ncontours = dis.read_short (); - - return_val_if_fail (start < end, new Glyph ("")); - - if (ncontours == 0) { - warning (@"Got zero contours in glyph $(name.str)."); - - // should skip body - } - - if (ncontours == -1) { - return parse_next_composite_glyf (dis, character, gid); - } - - return_val_if_fail (ncontours < len, new Glyph ("")); - - if (ncontours < -1) { - warning (@"Got $ncontours contours in glyf table."); - error = new BadFormat.PARSE ("Invalid glyf"); - throw error; - } - - ixmin = dis.read_short (); - iymin = dis.read_short (); - ixmax = dis.read_short (); - iymax = dis.read_short (); - - end_points = new uint16[ncontours + 1]; - for (int i = 0; i < ncontours; i++) { - end_points[i] = dis.read_ushort (); // FIXA: mind shot vector is negative - - if (i > 0 && end_points[i] < end_points[i -1]) { - warning (@"Next endpoint has bad value in $(name.str). (end_points[i] > end_points[i -1]) ($(end_points[i]) > $(end_points[i -1])) i: $i ncontours: $ncontours"); - } - } - - if (ncontours > 0) { - npoints = end_points[ncontours - 1] + 1; - } else { - npoints = 0; - } - - return_val_if_fail (npoints < len, new Glyph.no_lines ("")); - - ninstructions = dis.read_ushort (); - - return_val_if_fail (ninstructions < len, new Glyph.no_lines ("")); - - instructions = new uint8[ninstructions + 1]; - uint8 repeat; - for (int i = 0; i < ninstructions; i++) { - instructions[i] = dis.read_byte (); - } - - nflags = 0; - flags = new uint8[npoints + 1]; - for (int i = 0; i < npoints; i++) { - flags[i] = dis.read_byte (); - - if ((flags[i] & Coordinate.REPEAT) > 0) { - repeat = dis.read_byte (); - - if (i + repeat >= npoints) { - error = new BadFormat.PARSE ("Too many flags in glyf in glyph $(name.str). (i >= ninstructions)."); - break; - } - - for (int j = 0; j < repeat; j++) { - flags[j + i + 1] = flags[i]; - } - - nflags += repeat; - i += repeat; - } - - nflags++; - } - - if (nflags != npoints) { - warning (@"(nflags != npoints) ($nflags != $npoints) in $(name.str)"); - error = new BadFormat.PARSE (@"Wrong number of flags in glyph $(name.str). (nflags != npoints) ($nflags != $npoints)"); - } - - warn_if_fail (nflags == npoints); - - printd (@"npoints: $npoints\n"); - printd (@"ncontours: $ncontours\n"); - printd (@"ninstructions: $ninstructions\n"); - printd (@"nflags: $nflags\n"); - - int16 last = 0; - xcoordinates = new int16[npoints + 1]; - for (int i = 0; i < npoints; i++) { - if ((flags[i] & Coordinate.X_SHORT_VECTOR) > 0) { - if ((flags[i] & Coordinate.X_SHORT_VECTOR_POSITIVE) > 0) { - xcoordinates[i] = last + dis.read_byte (); - } else { - xcoordinates[i] = last - dis.read_byte (); - } - } else { - if ((flags[i] & Coordinate.X_IS_SAME) > 0) { - xcoordinates[i] = last; - } else { - xcoordinates[i] = last + dis.read_short (); - } - } - - last = xcoordinates[i]; - - if (last > rxmax) rxmax = last; - if (last < rxmin) rxmin = last; - - if (!(ixmin <= last <= ixmax)) { - stderr.printf (@"x is out of bounds in glyph $(name.str). ($ixmin <= $last <= $ixmax) char $((uint)character)\n"); - } - - if (!(head_table.xmin <= last <= head_table.xmax)) { - stderr.printf (@"x is outside of of font bounding box in glyph $(name.str). ($(head_table.xmin) <= $last <= $(head_table.xmax)) char $((uint)character)\n"); - } - } - - last = 0; - ycoordinates = new int16[npoints + 1]; - for (int i = 0; i < npoints; i++) { - if ((flags[i] & Coordinate.Y_SHORT_VECTOR) > 0) { - if ((flags[i] & Coordinate.Y_SHORT_VECTOR_POSITIVE) > 0) { - ycoordinates[i] = last + dis.read_byte (); - } else { - ycoordinates[i] = last - dis.read_byte (); - } - } else { - if ((flags[i] & Coordinate.Y_IS_SAME) > 0) { - ycoordinates[i] = last; - } else { - ycoordinates[i] = last + dis.read_short (); - } - } - - last = ycoordinates[i]; - - if (last > rymax) rymax = last; - if (last < rymin) rymin = last; - - if (!(iymin <= last <= iymax)) { - stderr.printf (@"y is out of bounds in glyph $(name.str). ($iymin <= $last <= $iymax) char $((uint)character)\n"); - } - - if (!(head_table.ymin <= last <= head_table.ymax)) { - stderr.printf (@"y is outside of of font bounding box in glyph $(name.str). ($(head_table.ymin) <= $last <= $(head_table.ymax)) char $((uint)character)\n"); - } - } - - if (rymin != iymin || rxmin != ixmin || rxmax != ixmax || rymax != iymax) { - warning (@"Warning real boundry for glyph does not match boundry set in glyph header for glyph $(name.str)."); - stderr.printf (@"ymin: $rymin header: $iymin\n"); - stderr.printf (@"xmin: $rxmin header: $ixmin\n"); - stderr.printf (@"ymax: $rymax header: $iymax\n"); - stderr.printf (@"xmax: $rxmax header: $ixmax\n"); - } - - int j = 0; - int first_point; - int last_point = 0; - Glyph glyph; - double x, y; - - glyph = new Glyph (name.str, character); - - xmin = ixmin * 1000.0 / units_per_em; - xmax = ixmax * 1000.0 / units_per_em; - - for (int i = 0; i < ncontours; i++) { - x = 0; - y = 0; - - Path path = new Path (); - EditPoint edit_point = new EditPoint (); - bool prev_is_curve = false; - - first_point = j; - last_point = end_points[i]; - for (; j <= end_points[i]; j++) { - - if (j >= npoints) { - warning (@"j >= npoints in glyph $(name.str). (j: $j, end_points[i]: $(end_points[i]), npoints: $npoints)"); - break; - } - - x = xcoordinates[j] * 1000.0 / units_per_em; // in proportion to em width - y = ycoordinates[j] * 1000.0 / units_per_em; - - if ((flags[j] & Coordinate.ON_PATH) > 0) { - // Point - edit_point = new EditPoint (); - edit_point.set_position (x, y); - path.add_point (edit_point); - - if (prev_is_curve) { - edit_point.get_left_handle ().set_point_type (PointType.NONE); - edit_point.get_left_handle ().length = 0; - } else { - edit_point.recalculate_linear_handles (); - } - - prev_is_curve = false; - } else { - - if (prev_is_curve) { - x = x - (x - edit_point.right_handle.x ()) / 2; - y = y - (y - edit_point.right_handle.y ()) / 2; - - edit_point = new EditPoint (); - edit_point.set_position (x, y); - path.add_point (edit_point); - } - - x = xcoordinates[j] * 1000.0 / units_per_em; // in proportion to em width - y = ycoordinates[j] * 1000.0 / units_per_em; - - edit_point.get_left_handle ().set_point_type (PointType.NONE); - edit_point.get_left_handle ().length = 0; - - edit_point.type = PointType.CURVE; - edit_point.get_right_handle ().set_point_type (PointType.CURVE); - edit_point.get_right_handle ().move_to_coordinate (x, y); - - prev_is_curve = true; - } - } - - // last to first point - if (prev_is_curve) { - x = xcoordinates[first_point] * 1000.0 / units_per_em; // in proportion to em width - y = ycoordinates[first_point] * 1000.0 / units_per_em; - - x = x - (x - edit_point.right_handle.x ()) / 2; - y = y - (y - edit_point.right_handle.y ()) / 2; - - edit_point = new EditPoint (); - edit_point.set_position (x, y); - path.add_point (edit_point); - - x = xcoordinates[first_point] * 1000.0 / units_per_em; // in proportion to em width - y = ycoordinates[first_point] * 1000.0 / units_per_em; - - edit_point.get_left_handle ().set_point_type (PointType.CURVE); - edit_point.get_left_handle ().length = 0; - - edit_point.type = PointType.CURVE; - edit_point.get_right_handle ().set_point_type (PointType.CURVE); - edit_point.get_right_handle ().move_to_coordinate (x, y); - } - - // curve last to first - x = xcoordinates[first_point] * 1000.0 / units_per_em; // in proportion to em width - y = ycoordinates[first_point] * 1000.0 / units_per_em; - edit_point.type = PointType.CURVE; - edit_point.get_right_handle ().set_point_type (PointType.CURVE); - edit_point.get_right_handle ().move_to_coordinate (x, y); - - path.close (); - - glyph.add_path (path); - } - - // glyphs with no bounding boxes - if (ixmax <= ixmin) { - warning (@"Bounding box is bad. (xmax == xmin) ($xmax == $xmin)"); - - if (glyph.path_list.length () > 0) { - - Path ps = ((!) glyph.path_list.first ()).data; - - ps.update_region_boundries (); - xmin = ps.xmin; - xmax = ps.xmax; - - foreach (Path p in glyph.path_list) { - p.update_region_boundries (); - - if (p.xmin < xmin) { - xmin = p.xmin; - } - - if (p.xmax > xmax) { - xmax = p.xmax; - } - } - - } - } - - if (end_points != null) delete end_points; - if (instructions != null) delete instructions; - if (flags != null) delete flags; - if (xcoordinates != null) delete xcoordinates; - if (ycoordinates != null) delete ycoordinates; - - if (error != null) { - warning ("failed to parse glyph"); - throw (!) error; - } - - 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; - - double x, y; - - Font font = OpenFontFormatWriter.get_current_font (); - 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); - - 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 (10); - fd.add_16 (20); - fd.add_16 (30); - fd.add_16 (40); - - // end points - end_point = 0; - last_end_point = 0; - foreach (Path p in g.path_list) { - p = p.get_quadratic_points (); - foreach (EditPoint e in p.points) { - end_point++; - - if (e.get_right_handle ().type == PointType.CURVE) { - end_point++; - } - } - fd.add_u16 (end_point - 1); - - 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; - } - - fd.add_u16 (0); // instruction length - - uint glyph_header = 12 + ncontours * 2; - - printd (@"\next glyf: $(g.name)\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++; - - if (e.get_right_handle ().type == PointType.CURVE) { - fd.add_byte (Coordinate.NONE); - 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.CURVE) { - x = rint (e.get_right_handle ().x () * UNITS - prev - g.left_limit * UNITS); - - fd.add_16 ((int16) x); - coordinate_x.append ((int16) x); - - prev = rint (e.get_right_handle ().x () * UNITS - g.left_limit * 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.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); - } - } - } - - 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; - } - - 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; - } - - 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; - - // 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"); - } - - // necessary in order to have glyphs sorted according to ttf specification - public void create_glyph_table () { - Glyph? gl; - Glyph g; - Font font = OpenFontFormatWriter.get_current_font (); - uint32 indice; - List<Glyph> unassigned_glyphs; - - // add notdef. character at index zero + other special chars first - glyphs.append (font.get_not_def_character ()); - glyphs.append (font.get_null_character ()); - glyphs.append (font.get_nonmarking_return ()); - glyphs.append (font.get_space ()); - - unassigned_glyphs = new List<Glyph> (); - - // add glyphs, first all assigned then the unassigned ones - for (indice = 0; (gl = font.get_glyph_indice (indice)) != null; indice++) { - g = (!) gl; - - if (g.unichar_code <= 27) { // skip control characters - continue; - } - - if (g.unichar_code == 32) { // skip space - continue; - } - - if (g.name == ".notdef") { - continue; - } - - g.remove_empty_paths (); - - if (!g.is_unassigned ()) { - glyphs.append (g); - } else { - unassigned_glyphs.append (g); - } - } - - foreach (Glyph ug in unassigned_glyphs) { - // glyphs.append (ug); - } - - } - - public void process () throws GLib.Error { - FontData fd = new FontData (); - - create_glyph_table (); - uint last_len = 0; - foreach (Glyph g in glyphs) { - // set values for loca table - assert (fd.length () % 4 == 0); - location_offsets.append (fd.length ()); - process_glyph (g, fd); - - printd (@"glyf length: $(fd.length () - last_len)\n"); - printd (@"loca fd.length (): $(fd.length ())\n"); - - last_len = fd.length (); - } - - location_offsets.append (fd.length ()); // last entry in loca table is special - - // every glyph is padded, no padding to be done here - assert (fd.length () % 4 == 0); - - font_data = fd; - } -} - -class CmapSubtable : Table { - - // Override these methods in subtables for each format. - - /** Obtain length of subtable in bytes. */ - public virtual uint get_length () { - warning ("Invalid CmapSubtable"); - return 0; - } - - /** Get char code for a glyph id. */ - public virtual unichar get_char (uint32 i) { - warning ("Invalid CmapSubtable"); - return 0; - } - - public void print_cmap () { - StringBuilder s; - unichar c; - for (uint32 i = 0; i < get_length (); i++) { - s = new StringBuilder (); - c = get_char (i); - s.append_unichar (c); - printd (@"Char: $(s.str) val ($((uint32)c))\tindice: $(i)\n"); - } - } -} - -/** Format 4 cmap subtable */ -class CmapSubtableWindowsUnicode : CmapSubtable { - uint16 format = 0; - HashTable <uint64?, unichar> table = new HashTable <uint64?, unichar> (int64_hash, int_equal); - - public CmapSubtableWindowsUnicode () { - } - - ~CmapSubtableWindowsUnicode () { - - } - - public override uint get_length () { - return table.size (); - } - - public override unichar get_char (uint32 indice) { - int64? c = table.lookup (indice); - - if (c == 0 && indice == 0) { - return 0; - } - - if (c == 0) { - while (table.lookup (--indice) == 0) { - if (indice == 0) { - return 0; - } - } - - warning (@"There is no char for glyph number $indice in cmap table. table.size: $(table.size ()))"); - return 0; - } - - return (unichar) c; - } - - public override void parse (FontData dis) throws GLib.Error { - dis.seek (offset); - - format = dis.read_ushort (); - - switch (format) { - case 4: - parse_format4 (dis); - break; - - default: - stderr.printf (@"CmapSubtable is in format $format, it is not supportet (yet).\n"); - break; - } - } - - public void parse_format4 (FontData dis) throws GLib.Error { - uint16 lang; - uint16 seg_count_x2; - uint16 seg_count; - uint16 search_range; - uint16 entry_selector; - uint16 range_shift; - - uint16* end_char = null; - uint16* start_char = null; - int16* id_delta = null; - uint16* id_range_offset = null; - uint16* glyph_id_array = null; - - uint32 gid_len; - - length = dis.read_ushort (); - lang = dis.read_ushort (); - seg_count_x2 = dis.read_ushort (); - search_range = dis.read_ushort (); - entry_selector = dis.read_ushort (); - range_shift = dis.read_ushort (); - - return_if_fail (seg_count_x2 % 2 == 0); - - seg_count = seg_count_x2 / 2; - - end_char = new uint16[seg_count]; - for (int i = 0; i < seg_count; i++) { - end_char[i] = dis.read_ushort (); - } - - if (end_char[seg_count - 1] != 0xFFFF) { - warning ("end_char is $(end_char[seg_count - 1]), expecting 0xFFFF."); - } - - dis.read_ushort (); // Reserved - - start_char = new uint16[seg_count]; - for (int i = 0; i < seg_count; i++) { - start_char[i] = dis.read_ushort (); - } - - id_delta = new int16[seg_count]; - for (int i = 0; i < seg_count; i++) { - id_delta[i] = dis.read_short (); - } - - id_range_offset = new uint16[seg_count]; - for (int i = 0; i < seg_count; i++) { - id_range_offset[i] = dis.read_ushort (); - } - - if (length == 0) { - warning ("cmap subtable version 4 has length 0."); - return; - } - - gid_len = (length - 16 - 8 * seg_count) / 2; - glyph_id_array = new uint16[gid_len]; - for (int i = 0; i < gid_len; i++) { - glyph_id_array[i] = dis.read_ushort (); - } - - // map all values in a hashtable - int indice = 0; - unichar character = 0; - uint32 id; - for (uint16 i = 0; i < seg_count && start_char[i] != 0xFFFF; i++) { - - // print_range (start_char[i], end_char[i], id_delta[i], id_range_offset[i]); - - uint16 j = 0; - do { - character = start_char[i] + j; - indice = start_char[i] + id_delta[i] + j; - - if (id_range_offset[i] == 0) { - table.insert (indice, character); - } else { - // the awkward indexing trick: - id = id_range_offset[i] / 2 + j + i - seg_count; - - if (!(0 <= id < gid_len)) { - warning (@"(0 <= id < gid_len) (0 <= $id < $gid_len)"); - break; - } - - indice = glyph_id_array [id] + id_delta[i]; - - StringBuilder s = new StringBuilder (); - s.append_unichar (character); - - table.insert (indice, character); - } - - j++; - } while (character != end_char[i]); - - } - - if (end_char != null) delete end_char; - if (start_char != null) delete start_char; - if (id_delta != null) delete id_delta; - if (id_range_offset != null) delete id_range_offset; - if (glyph_id_array != null) delete glyph_id_array; - - // it has a character for every glyph indice - // assert (validate_subtable ()); - } - - void print_range (unichar start_char, unichar end_char, uint16 delta_offset, uint16 range_offset) { - StringBuilder s = new StringBuilder (); - StringBuilder e = new StringBuilder (); - - s.append_unichar (start_char); - e.append_unichar (end_char); - - print (@"New range $(s.str) - $(e.str) delta: $delta_offset, range: $range_offset\n"); - } - - public void process (FontData fd, GlyfTable glyf_table) throws GLib.Error { - GlyphRange glyph_range = new GlyphRange (); - unowned List<UniRange> ranges; - - uint16 seg_count_2; - uint16 seg_count; - uint16 search_range; - uint16 entry_selector; - uint16 range_shift; - - uint16 gid_length = 0; - - uint32 indice; - uint32 first_assigned = 1; - - foreach (Glyph g in glyf_table.glyphs) { - if (!g.is_unassigned ()) { - glyph_range.add_single (g.unichar_code); - } - } - - // glyph_range.print_all (); - - ranges = glyph_range.get_ranges (); - seg_count = (uint16) ranges.length () + 1; - seg_count_2 = seg_count * 2; - search_range = 2 * largest_pow2 (seg_count); - entry_selector = largest_pow2_exponent (seg_count); - range_shift = seg_count_2 - search_range; - - // format - fd.add_ushort (4); - - // length of subtable - fd.add_ushort (16 + 8 * seg_count + gid_length); - - // language - fd.add_ushort (0); - - fd.add_ushort (seg_count_2); - fd.add_ushort (search_range); - fd.add_ushort (entry_selector); - fd.add_ushort (range_shift); - - // end codes - indice = first_assigned; - foreach (UniRange u in ranges) { - if (u.stop >= 0xFFFF) { - warning ("Not implemented yet."); - } - - fd.add_ushort ((uint16) u.stop); - indice += u.length (); - } - fd.add_ushort (0xFFFF); - - fd.add_ushort (0); // Reserved - - // start codes - indice = first_assigned; // since first glyph are notdef, null and nonmarkingreturn - foreach (UniRange u in ranges) { - if (u.start >= 0xFFFF) { - warning ("Not implemented yet."); - } - - fd.add_ushort ((uint16) u.start); - indice += u.length (); - } - fd.add_ushort (0xFFFF); - - // delta - indice = first_assigned; - foreach (UniRange u in ranges) { - - if ((u.start - indice) > 0xFFFF && u.start > indice) { - warning ("Need range offset."); - } - - fd.add_ushort ((uint16) (indice - u.start)); - indice += u.length (); - } - fd.add_ushort (1); - - // range offset - foreach (UniRange u in ranges) { - if (u.stop <= 0xFFFF) { - fd.add_ushort (0); - } else { - warning ("Not implemented yet."); - } - } - fd.add_ushort (0); - - // Fixa: implement rest of type 4 (mind gid_length in length field) - } - - public bool validate_subtable () { - uint32 length = get_length (); - unichar c; - unichar prev; - uint32 i = 0; - uint32 err = 0; - StringBuilder s; - - c = get_char (i); - if (c != 0) { - s = new StringBuilder (); - s.append_unichar (c); - warning ("nodef. is mapped to $(s.str)"); - } - - i++; - - while (i < length) { - if (c == 0) { - s = new StringBuilder (); - s.append_unichar (c); - warning (@"No entry in cmap for index $i. Last avalable char is $(s.str) Got $(s.str), ($((uint32)c))"); - err++; - return false; - } else { - prev = c; - } - - c = get_char (i); - i++; - } - - if (err > 0) { - stderr.printf (@"$err glyphs without mapping to a charactercode were found in this font.\n"); - } - - return true; - } -} - -class CmapTable : Table { - - GlyfTable glyf_table; - List<CmapSubtable> subtables; - - public CmapTable(GlyfTable gt) { - glyf_table = gt; - subtables = new List<CmapSubtable> (); - id = "cmap"; - } - - public uint32 get_length () { - return get_prefered_table ().get_length (); - } - - public unichar get_char (uint32 i) { - return get_prefered_table ().get_char (i) ; - } - - CmapSubtable get_prefered_table () { - if (subtables.length () == 0) { - warning ("No cmap table has been parsed."); - return new CmapSubtable (); - } - - return subtables.first ().data; - } - - public override string get_id () { - return "cmap"; - } - - public override void parse (FontData dis) throws GLib.Error { - uint16 version; - uint16 nsubtables; - - uint16 platform; - uint16 encoding; - uint32 sub_offset; - - CmapSubtable subtable; - - return_if_fail (offset > 0 && length > 0); - - dis.seek (offset); - - version = dis.read_ushort (); - nsubtables = dis.read_ushort (); - - printd (@"cmap version: $version\n"); - printd (@"cmap subtables: $nsubtables\n"); - - if (version != 0) { - warning (@"Bad version for cmap table: $version expecting 0. Number of subtables: $nsubtables"); - return; - } - - for (uint i = 0; i < nsubtables; i++) { - platform = dis.read_ushort (); - encoding = dis.read_ushort (); - sub_offset = dis.read_ulong (); - - if (platform == 3 && encoding == 1) { - printd (@"Parsing Unicode BMP (UCS-2) Platform: $platform Encoding: $encoding\n"); - subtable = new CmapSubtableWindowsUnicode (); - subtable.offset = offset + sub_offset; - subtables.append (subtable); - } else { - stderr.printf (@"Unknown cmap format. Platform: $platform Encoding: $encoding.\n"); - } - - if (encoding == 3) { - stderr.printf ("Font contains a cmap table with the obsolete encoding 3.\n"); - } - } - - if (subtables.length () == 0) { - warning ("No suitable cmap subtable found."); - } - - foreach (CmapSubtable t in subtables) { - t.parse (dis); - t.print_cmap (); - } - - } - - /** Character to glyph mapping */ - public void process (GlyfTable glyf_table) throws GLib.Error { - FontData fd = new FontData (); - CmapSubtableWindowsUnicode cmap = new CmapSubtableWindowsUnicode (); - uint16 n_encoding_tables; - uint32 subtable_offset = 0; - - n_encoding_tables = 1; - - fd.add_u16 (0); // table version - fd.add_u16 (n_encoding_tables); - - fd.add_u16 (3); // platform - fd.add_u16 (1); // encoding (Format Unicode UCS-4) - - subtable_offset = fd.length () + 4; - printd (@"subtable_offset: $(subtable_offset)\n"); - - fd.add_ulong (subtable_offset); - cmap.process (fd, glyf_table); - - // padding - fd.pad (); - - this.font_data = fd; - } -} - -class HeadTable : Table { - - public int16 xmin = int16.MIN; - public int16 ymin = int16.MIN; - public int16 xmax = int16.MAX; - public int16 ymax = int16.MAX; - - uint32 adjusted_checksum = 0; - - uint16 mac_style; - uint16 lowest_PPEM; - int16 font_direction_hint; - - public int16 loca_offset_size = 0; // 0 for int16 1 for int32 - int16 glyph_data_format; - - Fixed version; - Fixed font_revision; - - uint32 magic_number; - - uint16 flags; - - uint64 created; - uint64 modified; - - uint16 units_per_em = 2048; - - const uint8 BASELINE_AT_ZERO = 1 << 0; - const uint8 LSB_AT_ZERO = 1 << 1; - - GlyfTable glyf_table; - - public HeadTable (GlyfTable gt) { - glyf_table = gt; - id = "head"; - } - - public uint32 get_adjusted_checksum () { - return adjusted_checksum; - } - - public double get_units_per_em () { - return units_per_em * 10; // Fixa: we can refactor this number - } - - public override void parse (FontData dis) throws GLib.Error { - return_if_fail (offset > 0 && length > 0); - - dis.seek (offset); - - font_data = new FontData (); - - version = dis.read_fixed (); - - if (!version.equals (1, 0)) { - warning (@"Expecting head version 1.0 got $(version.get_string ())\n"); - } - - font_revision = dis.read_fixed (); - adjusted_checksum = dis.read_ulong (); - magic_number = dis.read_ulong (); - - if (magic_number != 0x5F0F3CF5) { - warning (@"Magic number is invalid. Got $(magic_number)."); - return; - } - - flags = dis.read_ushort (); - - if ((flags & BASELINE_AT_ZERO) > 0) { - printd ("Flag BASELINE_AT_ZERO has been set.\n"); - } - - if ((flags & LSB_AT_ZERO) > 0) { - printd ("Flags LSB_AT_ZERO has been set.\n"); - } - - units_per_em = dis.read_ushort (); - - created = dis.read_udate (); - modified = dis.read_udate (); - - xmin = dis.read_short (); - ymin = dis.read_short (); - - xmax = dis.read_short (); - ymax = dis.read_short (); - - printd (@"font boundries:\n"); - printd (@"xmin: $xmin\n"); - printd (@"ymin: $ymin\n"); - printd (@"xmax: $xmax\n"); - printd (@"ymax: $ymax\n"); - - mac_style = dis.read_ushort (); - lowest_PPEM = dis.read_ushort (); - font_direction_hint = dis.read_short (); - - loca_offset_size = dis.read_short (); - glyph_data_format = dis.read_short (); - - if (glyph_data_format != 0) { - warning (@"Unknown glyph data format. Expecting 0 got $glyph_data_format."); - } - - // print_values (); - // Some deprecated values follow here ... - } - - void print_values () { - print (@"Version: $(version.get_string ())\n"); - print (@"flags: $flags\n"); - print (@"font_revision: $(font_revision.get_string ())\n"); - print (@"flags: $flags\n"); - print (@"Units per em: $units_per_em\n"); - print (@"lowest_PPEM: $lowest_PPEM\n"); - print (@"font_direction_hint: $font_direction_hint\n"); - print (@"loca_offset_size: $loca_offset_size\n"); - print (@"glyph_data_format: $glyph_data_format\n"); - } - - public uint32 get_font_checksum () { - return adjusted_checksum; - } - - public void set_check_sum_adjustment (uint32 csa) { - this.adjusted_checksum = csa; - } - - public uint32 get_checksum_position () { - return 8; - } - - public void process () throws GLib.Error { - FontData font_data = new FontData (); - Fixed version = 1 << 16; - Fixed font_revision = 1 << 16; - - font_data.add_fixed (version); - font_data.add_fixed (font_revision); - - // Zero on the first run and updated by directory tables checksum calculation - // for the entire font. - font_data.add_u32 (adjusted_checksum); - - font_data.add_u32 (0x5F0F3CF5); // magic number - - //font_data.add_u16 (BASELINE_AT_ZERO | LSB_AT_ZERO); - font_data.add_u16 (0); // flags - - font_data.add_u16 (1000); // units per em (should be a power of two for ttf fonts) - - font_data.add_64 (0); // creation time since 1904-01-01 - font_data.add_64 (0); // modified time since 1904-01-01 - - xmin = glyf_table.xmin; - ymin = glyf_table.ymin; - xmax = glyf_table.xmax; - ymax = glyf_table.ymax; - - printd (@"font boundries:\n"); - printd (@"xmin: $xmin\n"); - printd (@"ymin: $ymin\n"); - printd (@"xmax: $xmax\n"); - printd (@"ymax: $ymax\n"); - - font_data.add_short (xmin); - font_data.add_short (ymin); - font_data.add_short (xmax); - font_data.add_short (ymax); - - font_data.add_u16 (0); // mac style - font_data.add_u16 (2); // smallest recommended size in pixels, ppem - font_data.add_16 (2); // deprecated direction hint - font_data.add_16 (loca_offset_size); // long offset - font_data.add_16 (0); // Use current glyph data format - - font_data.pad (); - - this.font_data = font_data; - } -} - -class HheaTable : Table { - - Fixed version; - int16 ascender; - int16 descender; - int16 linegap; - uint16 max_advance; - int16 min_lsb; - int16 min_rsb; - int16 xmax_extent; - int16 carret_slope; - int16 carret_slope_run; - int16 carret_offset; - - int16 metric_format; - public int16 num_horizontal_metrics; - - GlyfTable glyf_table; - HeadTable head_table; - HmtxTable hmtx_table; - - public HheaTable (GlyfTable g, HeadTable h, HmtxTable hm) { - glyf_table = g; - head_table = h; - hmtx_table = hm; - id = "hhea"; - } - - public double get_ascender () { - return ascender * 1000 / head_table.get_units_per_em (); - } - - public double get_descender () { - return descender * 1000 / head_table.get_units_per_em (); - } - - public override void parse (FontData dis) throws GLib.Error { - dis.seek (offset); - - version = dis.read_fixed (); - - if (!version.equals (1, 0)) { - warning (@"wrong version in hhea table $(version.get_string ())"); - } - - ascender = dis.read_short (); - descender = dis.read_short (); - linegap = dis.read_short (); - max_advance = dis.read_ushort (); - min_lsb = dis.read_short (); - min_rsb = dis.read_short (); - xmax_extent = dis.read_short (); - carret_slope = dis.read_short (); - carret_slope_run = dis.read_short (); - carret_offset = dis.read_short (); - - // reserved x 4 - dis.read_short (); - dis.read_short (); - dis.read_short (); - dis.read_short (); - - metric_format = dis.read_short (); - num_horizontal_metrics = dis.read_short (); - } - - public void process () throws GLib.Error { - Font font = OpenFontFormatWriter.get_current_font (); - FontData fd = new FontData (); - Fixed version = 1 << 16; - - fd.add_fixed (version); // table version - - fd.add_16 ((int16) (-1 * (font.top_position - font.base_line) * UNITS)); // Ascender - fd.add_16 ((int16) (-1 * font.bottom_position * UNITS)); // Descender - fd.add_16 (0); // LineGap - - fd.add_u16 (hmtx_table.max_advance); // maximum advance width value in 'hmtx' table. - - fd.add_16 (hmtx_table.min_lsb); // min left side bearing - fd.add_16 (hmtx_table.min_rsb); // min right side bearing - fd.add_16 (hmtx_table.max_extent); // x max extent Max(lsb + (xMax - xMin)) - - fd.add_16 (1); // caretSlopeRise - fd.add_16 (0); // caretSlopeRun - fd.add_16 (0); // caretOffset - - // reserved - fd.add_16 (0); - fd.add_16 (0); - fd.add_16 (0); - fd.add_16 (0); - - fd.add_16 (0); // metricDataFormat 0 for current format. - - fd.add_u16 ((uint16) glyf_table.glyphs.length()); // numberOfHMetrics Number of hMetric entries in 'hmtx' table - - // padding - fd.pad (); - this.font_data = fd; - } -} - -class HmtxTable : Table { - - uint32 nmetrics; - uint32 nmonospaced; - - uint16* advance_width = null; - uint16* left_side_bearing = null; - uint16* left_side_bearing_monospaced = null; - - public int16 max_advance = 0; - public int16 max_extent = 0; - public int16 min_lsb = 0; - public int16 min_rsb = 0; - - HeadTable head_table; - GlyfTable glyf_table; - - public HmtxTable (HeadTable h, GlyfTable gt) { - head_table = h; - glyf_table = gt; - id = "hmtx"; - } - - ~HmtxTable () { - if (advance_width != null) delete advance_width; - if (left_side_bearing != null) delete left_side_bearing; - } - - public double get_advance (uint32 i) { - if (i >= nmetrics) { - warning (@"i >= nmetrics $i >= $nmetrics"); - return 0; - } - - return_val_if_fail (advance_width != null, 0.0); - - return advance_width[i] * 1000 / head_table.get_units_per_em (); - } - - /** Get left side bearing relative to xmin. */ - public double get_lsb (uint32 i) { - return_val_if_fail (i < nmetrics, 0.0); - return_val_if_fail (left_side_bearing != null, 0.0); - - return left_side_bearing[i] * 1000 / head_table.get_units_per_em (); - } - - /** Get left side bearing relative to xmin for monospaces fonts. */ - public double get_lsb_mono (uint32 i) - requires (i < nmonospaced && left_side_bearing_monospaced != null) { - return left_side_bearing_monospaced[i] * 1000 / head_table.get_units_per_em (); - } - - public void print_all () { - return_if_fail (advance_width != null); - return_if_fail (left_side_bearing != null); - - for (int i = 0; i < nmetrics; i++) { - print (@"get_lsb ($i): $(get_lsb(i))\n"); - print (@"get_advance ($i): $(get_advance(i))\n"); - } - - if (left_side_bearing_monospaced != null) { - for (int i = 0; i < nmonospaced; i++) { - print (@"get_lsb_mono ($i): $(get_lsb_mono(i))\n"); - } - } - - } - - public new void parse (FontData dis, HheaTable hhea_table, LocaTable loca_table) throws GLib.Error { - nmetrics = hhea_table.num_horizontal_metrics; - nmonospaced = loca_table.size - nmetrics; - - dis.seek (offset); - - if (nmetrics > loca_table.size) { - warning (@"(nmetrics > loca_table.size) ($nmetrics > $(loca_table.size))"); - return; - } - - printd (@"nmetrics: $nmetrics\n"); - printd (@"loca_table.size: $(loca_table.size)\n"); - - advance_width = new uint16[nmetrics]; - left_side_bearing = new uint16[nmetrics]; - left_side_bearing_monospaced = new uint16[nmonospaced]; - - for (int i = 0; i < nmetrics; i++) { - advance_width[i] = dis.read_ushort (); - left_side_bearing[i] = dis.read_short (); - } - - for (int i = 0; i < nmonospaced; i++) { - left_side_bearing_monospaced[i] = dis.read_short (); - } - } - - public void process () { - FontData fd = new FontData (); - - int16 advance; - int16 extent; - int16 rsb; - int16 lsb; - - double xmin; - double ymin; - double xmax; - double ymax; - - // advance and lsb - foreach (Glyph g in glyf_table.glyphs) { - g.boundries (out xmin, out ymin, out xmax, out ymax); - lsb = (int16) (-1 * (g.left_limit - xmin) * UNITS); - advance = (int16) ((g.right_limit - g.left_limit) * UNITS); - extent = (int16) (lsb + (xmax - xmin) * UNITS); - rsb = (int16) (advance - extent); - - fd.add_u16 (advance); - fd.add_16 (lsb); - - if (advance > max_advance) { - max_advance = advance; - } - - if (extent > max_extent) { - max_extent = extent; - } - - if (rsb < min_rsb) { - min_rsb = rsb; - } - - if (lsb < min_lsb) { - min_lsb = lsb; - } - } - - // monospaced lsb ... - - font_data = fd; - - warn_if_fail (max_advance != 0); - } -} - - -class MaxpTable : Table { - - GlyfTable glyf_table; - - public uint16 num_glyphs = 0; - - public MaxpTable (GlyfTable g) { - glyf_table = g; - id = "maxp"; - } - - public override void parse (FontData dis) - requires (offset > 0 && length > 0) { - Fixed format; - - dis.seek (offset); - - format = dis.read_fixed (); - printd (@"Maxp version: $(format.get_string ())\n"); - - num_glyphs = dis.read_ushort (); - - if (format == 0x00005000) { - return; - } - - // Format 1.0 continues here - } - - public void process () { - FontData fd = new FontData(); - - // Version 0.5 for fonts with cff data and 1.0 for ttf - fd.add_u32 (0x00010000); - - if (glyf_table.glyphs.length () == 0) { - warning ("Zero glyphs in maxp table."); - } - - fd.add_u16 ((uint16) glyf_table.glyphs.length ()); // numGlyphs in the font - - fd.add_u16 (glyf_table.get_max_points ()); // max points - fd.add_u16 (glyf_table.get_max_contours ()); // max contours - fd.add_u16 (0); // max composite points - fd.add_u16 (0); // max composite contours - fd.add_u16 (1); // max zones - fd.add_u16 (0); // twilight points - fd.add_u16 (0); // max storage - fd.add_u16 (0); // max function defs - fd.add_u16 (0); // max instruction defs - fd.add_u16 (0); // max stack elements - fd.add_u16 (0); // max size of instructions - fd.add_u16 (0); // max component elements - fd.add_u16 (0); // component depth - - fd.pad (); - - this.font_data = fd; - } -} - -class OffsetTable : Table { - DirectoryTable directory_table; - - public uint16 num_tables = 0; - uint16 search_range = 0; - uint16 entry_selector = 0; - uint16 range_shift = 0; - - public OffsetTable (DirectoryTable t) { - id = "Offset table"; - directory_table = t; - } - - public override void parse (FontData dis) throws Error { - Fixed version; - - dis.seek (offset); - - version = dis.read_fixed (); - num_tables = dis.read_ushort (); - search_range = dis.read_ushort (); - entry_selector = dis.read_ushort (); - range_shift = dis.read_ushort (); - - printd (@"Font file version $(version.get_string ())\n"); - printd (@"Number of tables $num_tables\n"); - } - - public void process () throws GLib.Error { - FontData fd = new FontData (); - Fixed version = 0x00010000; // sfnt version 1.0 for TTF CFF else use OTTO - - - num_tables = (uint16) directory_table.get_tables ().length () - 2; // number of tables, skip DirectoryTable and OffsetTable - - search_range = max_pow_2_less_than_i (num_tables) * 16; - entry_selector = max_log_2_less_than_i (num_tables); - range_shift = 16 * num_tables - search_range; - - fd.add_fixed (version); - fd.add_u16 (num_tables); - fd.add_u16 (search_range); - fd.add_u16 (entry_selector); - fd.add_u16 (range_shift); - - // skip padding for offset table - - this.font_data = fd; - } -} - -class NameTable : Table { - - public static const uint16 COPYRIGHT_NOTICE = 0; - public static const uint16 FONT_NAME = 1; - public static const uint16 SUBFAMILY_NAME = 2; - public static const uint16 UNIQUE_IDENTIFIER = 3; - public static const uint16 FULL_FONT_NAME = 4; // name + subfamily - public static const uint16 VERSION = 5; - public static const uint16 DESCRIPTION = 10; - public static const uint16 PREFERED_FAMILY = 16; - public static const uint16 PREFERED_SUB_FAMILY = 17; - - List<uint16> identifiers; - List<string> text; - - public NameTable () { - id = "name"; - text = new List<string> (); - identifiers = new List<uint16> (); - } - - public string get_name (uint16 identifier) { - int i = 0; - - foreach (uint16 n in identifiers) { - if (n == identifier) { - return text.nth (i).data; - } - i++; - } - - return ""; - } - - public void print_all () { - stdout.printf (@"$(text.length ()) name table texts:\n"); - foreach (string s in text) { - stdout.printf (s); - stdout.printf ("\n"); - } - } - - public override void parse (FontData dis) throws Error { - uint16 format; - - dis.seek (offset); - - format = dis.read_ushort (); - - switch (format) { - case 0: - parse_format0 (dis); - break; - - case 1: - warning ("name table format 1 is not implemented yet"); - break; - - default: - warning (@"unknown format $format in name table"); - break; - } - } - - public void parse_format0 (FontData dis) throws Error { - uint16 count; - uint16 storage_offset; - - List<uint16> strlen = new List<uint16> (); - List<uint16> off = new List<uint16> (); - List<uint16> name_id = new List<uint16> (); - List<uint16> encoding_id = new List<uint16> (); - List<uint16> platform = new List<uint16> (); - List<uint16> lang = new List<uint16> (); - - count = dis.read_ushort (); - storage_offset = dis.read_ushort (); - - for (int i = 0; i < count; i++) { - platform.append (dis.read_ushort ()); - encoding_id.append (dis.read_ushort ()); - lang.append (dis.read_ushort ()); - name_id.append (dis.read_ushort ()); - strlen.append (dis.read_ushort ()); - off.append (dis.read_ushort ()); - - identifiers.append (name_id.last ().data); - } - - int plat; - StringBuilder str; - for (int i = 0; i < count; i++) { - plat = platform.nth (i).data; - dis.seek (offset + storage_offset + off.nth (i).data); - str = new StringBuilder (); - - switch (plat) { - case 1: - for (int j = 0; j < strlen.nth (i).data; j++) { - char c = dis.read_char (); - str.append_c (c); - } - break; - - case 3: - for (int j = 0; j < strlen.nth (i).data; j += 2) { - unichar c; - char c0 = dis.read_char (); - char c1 = dis.read_char (); - - c = c0 << 8; - c += c1; - - str.append_unichar (c); - } - break; - - default: - break; - } - - text.append (str.str); - printd (@"Name id: $(name_id.nth (i).data) platform: $(platform.nth (i).data) enc: $(encoding_id.nth (i).data) lang: $(lang.nth (i).data) len: $(strlen.nth (i).data) str: \"$(str.str)\"\n"); - } - } - - public void process () throws GLib.Error { - FontData fd = new FontData (); - Font font = OpenFontFormatWriter.get_current_font (); - uint16 len = 0; - string t; - uint16 p; - uint16 l; - uint16 num_records; - - List<uint16> type = new List<uint16> (); - List<string> text = new List<string> (); - - text.append ("Copyright"); - type.append (COPYRIGHT_NOTICE); - - text.append (font.get_name ()); - type.append (FONT_NAME); - - text.append ("Regular"); - type.append (SUBFAMILY_NAME); - - text.append (font.get_name ()); // TODO: validate - type.append (UNIQUE_IDENTIFIER); - - text.append (font.get_name ()); - type.append (FULL_FONT_NAME); - - // This does for some reason cause an internal error in ms fontvalidatior utility. - // Head table can't parse integer from string. - text.append ("Version 1.0"); - type.append (VERSION); - - text.append (""); - type.append (DESCRIPTION); - - text.append (font.get_name ()); - type.append (PREFERED_FAMILY); - - text.append ("Regular"); - type.append (PREFERED_SUB_FAMILY); - - num_records = (uint16) text.length (); - - fd.add_ushort (0); // format 1 - fd.add_ushort (2 * num_records); // nplatforms * nrecords - fd.add_ushort (6 + 12 * 2 * num_records); // string storage offset - - for (int i = 0; i < num_records; i++) { - t = (!) text.nth (i).data; - p = (!) type.nth (i).data; - l = (uint16) t.length; - - fd.add_ushort (1); // platform - fd.add_ushort (0); // encoding id - fd.add_ushort (0); // language - fd.add_ushort (p); // name id - fd.add_ushort (l); // strlen - fd.add_ushort (len); // offset from begining of string storage - len += l; - } - - for (int i = 0; i < num_records; i++) { - t = (!) text.nth (i).data; - p = (!) type.nth (i).data; - l = (uint16) (2 * t.char_count ()); - - fd.add_ushort (3); // platform - fd.add_ushort (1); // encoding id - fd.add_ushort (0x0409); // language - fd.add_ushort (p); // name id - fd.add_ushort (l); // strlen - fd.add_ushort (len); // offset from begining of string storage - len += l; - } - - // platform 1 - foreach (string s in text) { - fd.add_str (s); - } - - // platform 3 - foreach (string s in text) { - fd.add_str_utf16 (s); - } - - fd.pad (); - - this.font_data = fd; - } -} - -class Os2Table : Table { - - public Os2Table () { - id = "OS/2"; - } - - public override void parse (FontData dis) throws Error { - - } - - public void process (GlyfTable glyf_table) { - FontData fd = new FontData (); - Font font = OpenFontFormatWriter.get_current_font (); - - int16 ascender; - int16 descender; - - fd.add_u16 (0x0002); // USHORT Version 0x0000, 0x0001, 0x0002, 0x0003, 0x0004 - - fd.add_16 (glyf_table.get_average_width ()); // SHORT xAvgCharWidth - - fd.add_u16 (400); // USHORT usWeightClass (400 is normal) - fd.add_u16 (5); // USHORT usWidthClass (5 is normal) - fd.add_u16 (0); // USHORT fsType - - fd.add_16 (40); // SHORT ySubscriptXSize - fd.add_16 (40); // SHORT ySubscriptYSize - fd.add_16 (40); // SHORT ySubscriptXOffset - fd.add_16 (40); // SHORT ySubscriptYOffset - fd.add_16 (40); // SHORT ySuperscriptXSize - fd.add_16 (40); // SHORT ySuperscriptYSize - fd.add_16 (40); // SHORT ySuperscriptXOffset - fd.add_16 (40); // SHORT ySuperscriptYOffset - fd.add_16 (40); // SHORT yStrikeoutSize - fd.add_16 (200); // SHORT yStrikeoutPosition - fd.add_16 (40); // SHORT sFamilyClass - - // PANOSE - fd.add (0); - fd.add (0); - fd.add (0); - fd.add (0); - fd.add (0); - fd.add (0); - fd.add (0); - fd.add (0); - fd.add (0); - fd.add (0); - - // FIXA: - 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 - fd.add_u32 (0); // ULONG ulUnicodeRange4 Bits 96-127 - - fd.add_tag ("----"); // VendID - - fd.add_u16 (0); // USHORT fsSelection - - fd.add_u16 (glyf_table.get_first_char ()); // USHORT usFirstCharIndex - fd.add_u16 (glyf_table.get_last_char ()); // USHORT usLastCharIndex - - ascender = (int16) (glyf_table.xmax + font.base_line * UNITS); - descender = (int16) (-glyf_table.xmin + font.base_line * UNITS); - - fd.add_16 (ascender); // SHORT sTypoAscender - fd.add_16 (descender); // SHORT sTypoDescender - fd.add_16 (3); // SHORT sTypoLineGap - - fd.add_u16 (ascender); // USHORT usWinAscent - fd.add_u16 (descender); // USHORT usWinDescent - - // FIXA: - fd.add_u32 (0); // ULONG ulCodePageRange1 Bits 0-31 - fd.add_u32 (0); // ULONG ulCodePageRange2 Bits 32-63 - - fd.add_16 (ascender); // SHORT sxHeight version 0x0002 and later - fd.add_16 (ascender); // SHORT sCapHeight version 0x0002 and later - - fd.add_16 (0); // USHORT usDefaultChar version 0x0002 and later - fd.add_16 (0x0020); // USHORT usBreakChar version 0x0002 and later, also known as space - - // FIXA: calculate these values - fd.add_16 (1); // USHORT usMaxContext version 0x0002 and later - - // padding - fd.pad (); - - this.font_data = fd; - } - -} - -class PostTable : Table { - - GlyfTable glyf_table; - - List<uint16> index = new List<uint16> (); - List<string> names = new List<string> (); - - List<string> available_names = new List<string> (); - - public PostTable (GlyfTable g) { - id = "post"; - glyf_table = g; - } - - public unowned List<string> get_all_names () { - return available_names; - } - - public int get_gid (string name) { // fixa do fast lookup - int i = 0; - int j = 0; - foreach (string n in names) { - if (n == name) { - j = 0; - foreach (uint16 k in index) { - if (k == i) { - return j; - } - j++; - } - - return i; - } - i++; - } - - // warn_if_reached (); - return -1; - } - - public string get_name (int gid) { - int k; - - if (!(0 <= gid < index.length ())) { - warning ("gid is out of range."); - return ""; - } - - k = (!) index.nth (gid).data; - - if (gid != 0 && k == 0) { - warning (@"Glyph $gid is assigned to name .notdef, only gid 0 can be .notdef character."); - return ""; - } - - if (!(0 <= k < names.length ())) { - warning ("k is out of range."); - return ""; - } - - return (!) names.nth (k).data; - } - - public override void parse (FontData dis) throws Error { - dis.seek (offset); - - Fixed format = dis.read_fixed (); - Fixed italic = dis.read_fixed (); - - int16 underlie_pos = dis.read_short (); - int16 underlie_thickness = dis.read_short (); - uint32 is_fixed_pitch = dis.read_ulong (); - - uint32 mem_min42 = dis.read_ulong (); - uint32 mem_max42 = dis.read_ulong (); - uint32 mem_min1 = dis.read_ulong (); - uint32 mem_max1 = dis.read_ulong (); - - uint16 nnames = dis.read_ushort (); - - if (format != 0x00020000) { - warning ("Only post tables of version 2 will parset got $(format.get_string ())"); - return; - } - - printd (@"format: $(format.get_string ())\n"); - printd (@"italic: $(italic.get_string ())\n"); - printd (@"underlie_pos: $(underlie_pos)\n"); - printd (@"underlie_thickness: $(underlie_thickness)\n"); - printd (@"is_fixed_pitch: $(is_fixed_pitch)\n"); - printd (@"mem_min42: $(mem_min42)\n"); - printd (@"mem_max42: $(mem_max42)\n"); - printd (@"mem_min1: $(mem_min1)\n"); - printd (@"mem_max1: $(mem_max1)\n"); - printd (@"\n"); - - printd (@"Num names: $(nnames)\n"); - - uint16 k; - int non_standard_names = 0; - for (uint16 i = 0; i < nnames; i++) { - k = dis.read_ushort (); - index.append (k); - - if (k >= 258) { - non_standard_names++; - } - } - - add_standard_names (); - - // read non standard names - for (int i = 0; i < non_standard_names; i++) { - uint8 len = dis.read_byte (); - StringBuilder name = new StringBuilder (); - - for (int j = 0; j < len; j++) { - name.append_c (dis.read_char ()); - } - - names.append (name.str); - } - - populate_available (); - } - - void populate_available () { - for (int i = 0; i < index.length (); i++) { - available_names.append (get_name (i)); - } - } - - public void print_all () { - print (@"PostScript glyph mapping:\n"); - for (int i = 0; i < index.length (); i++) { - print (@"gid $i -> $(get_name (i))\n"); - } - } - - public void print_all_names () { - print (@"Post table names:\n"); - foreach (var n in names) { - print (@"$n\n"); - } - } - - // the Macintosh standard order - private void add_standard_names () { - names.append (".notdef"); - names.append (".null"); - names.append ("nonmarkingreturn"); - names.append ("space"); - names.append ("exclam"); - names.append ("quotedbl"); - names.append ("numbersign"); - names.append ("dollar"); - names.append ("percent"); - names.append ("ampersand"); - names.append ("quotesingle"); - names.append ("parenleft"); - names.append ("parenright"); - names.append ("asterisk"); - names.append ("plus"); - names.append ("comma"); - names.append ("hyphen"); - names.append ("period"); - names.append ("slash"); - names.append ("zero"); - names.append ("one"); - names.append ("two"); - names.append ("three"); - names.append ("four"); - names.append ("five"); - names.append ("six"); - names.append ("seven"); - names.append ("eight"); - names.append ("nine"); - names.append ("colon"); - names.append ("semicolon"); - names.append ("less"); - names.append ("equal"); - names.append ("greater"); - names.append ("question"); - names.append ("at"); - names.append ("A"); - names.append ("B"); - names.append ("C"); - names.append ("D"); - names.append ("E"); - names.append ("F"); - names.append ("G"); - names.append ("H"); - names.append ("I"); - names.append ("J"); - names.append ("K"); - names.append ("L"); - names.append ("M"); - names.append ("N"); - names.append ("O"); - names.append ("P"); - names.append ("Q"); - names.append ("R"); - names.append ("S"); - names.append ("T"); - names.append ("U"); - names.append ("V"); - names.append ("W"); - names.append ("X"); - names.append ("Y"); - names.append ("Z"); - names.append ("bracketleft"); - names.append ("backslash"); - names.append ("bracketright"); - names.append ("asciicircum"); - names.append ("underscore"); - names.append ("grave"); - names.append ("a"); - names.append ("b"); - names.append ("c"); - names.append ("d"); - names.append ("e"); - names.append ("f"); - names.append ("g"); - names.append ("h"); - names.append ("i"); - names.append ("j"); - names.append ("k"); - names.append ("l"); - names.append ("m"); - names.append ("n"); - names.append ("o"); - names.append ("p"); - names.append ("q"); - names.append ("r"); - names.append ("s"); - names.append ("t"); - names.append ("u"); - names.append ("v"); - names.append ("w"); - names.append ("x"); - names.append ("y"); - names.append ("z"); - names.append ("braceleft"); - names.append ("bar"); - names.append ("braceright"); - names.append ("asciitilde"); - names.append ("Adieresis"); - names.append ("Aring"); - names.append ("Ccedilla"); - names.append ("Eacute"); - names.append ("Ntilde"); - names.append ("Odieresis"); - names.append ("Udieresis"); - names.append ("aacute"); - names.append ("agrave"); - names.append ("acircumflex"); - names.append ("adieresis"); - names.append ("atilde"); - names.append ("aring"); - names.append ("ccedilla"); - names.append ("eacute"); - names.append ("egrave"); - names.append ("ecircumflex"); - names.append ("edieresis"); - names.append ("iacute"); - names.append ("igrave"); - names.append ("icircumflex"); - names.append ("idieresis"); - names.append ("ntilde"); - names.append ("oacute"); - names.append ("ograve"); - names.append ("ocircumflex"); - names.append ("odieresis"); - names.append ("otilde"); - names.append ("uacute"); - names.append ("ugrave"); - names.append ("ucircumflex"); - names.append ("udieresis"); - names.append ("dagger"); - names.append ("degree"); - names.append ("cent"); - names.append ("sterling"); - names.append ("section"); - names.append ("bullet"); - names.append ("paragraph"); - names.append ("germandbls"); - names.append ("registered"); - names.append ("copyright"); - names.append ("trademark"); - names.append ("acute"); - names.append ("dieresis"); - names.append ("notequal"); - names.append ("AE"); - names.append ("Oslash"); - names.append ("infinity"); - names.append ("plusminus"); - names.append ("lessequal"); - names.append ("greaterequal"); - names.append ("yen"); - names.append ("mu"); - names.append ("partialdiff"); - names.append ("summation"); - names.append ("product"); - names.append ("pi"); - names.append ("integral"); - names.append ("ordfeminine"); - names.append ("ordmasculine"); - names.append ("Omega"); - names.append ("ae"); - names.append ("oslash"); - names.append ("questiondown"); - names.append ("exclamdown"); - names.append ("logicalnot"); - names.append ("radical"); - names.append ("florin"); - names.append ("approxequal"); - names.append ("Delta"); - names.append ("guillemotleft"); - names.append ("guillemotright"); - names.append ("ellipsis"); - names.append ("nonbreakingspace"); - names.append ("Agrave"); - names.append ("Atilde"); - names.append ("Otilde"); - names.append ("OE"); - names.append ("oe"); - names.append ("endash"); - names.append ("emdash"); - names.append ("quotedblleft"); - names.append ("quotedblright"); - names.append ("quoteleft"); - names.append ("quoteright"); - names.append ("divide"); - names.append ("lozenge"); - names.append ("ydieresis"); - names.append ("Ydieresis"); - names.append ("fraction"); - names.append ("currency"); - names.append ("guilsinglleft"); - names.append ("guilsinglright"); - names.append ("fi"); - names.append ("fl"); - names.append ("daggerdbl"); - names.append ("periodcentered"); - names.append ("quotesinglbase"); - names.append ("quotedblbase"); - names.append ("perthousand"); - names.append ("Acircumflex"); - names.append ("Ecircumflex"); - names.append ("Aacute"); - names.append ("Edieresis"); - names.append ("Egrave"); - names.append ("Iacute"); - names.append ("Icircumflex"); - names.append ("Idieresis"); - names.append ("Igrave"); - names.append ("Oacute"); - names.append ("Ocircumflex"); - names.append ("apple"); - names.append ("Ograve"); - names.append ("Uacute"); - names.append ("Ucircumflex"); - names.append ("Ugrave"); - names.append ("dotlessi"); - names.append ("circumflex"); - names.append ("tilde"); - names.append ("macron"); - names.append ("breve"); - names.append ("dotaccent"); - names.append ("ring"); - names.append ("cedilla"); - names.append ("hungarumlaut"); - names.append ("ogonek"); - names.append ("caron"); - names.append ("Lslash"); - names.append ("lslash"); - names.append ("Scaron"); - names.append ("scaron"); - names.append ("Zcaron"); - names.append ("zcaron"); - names.append ("brokenbar"); - names.append ("Eth"); - names.append ("eth"); - names.append ("Yacute"); - names.append ("yacute"); - names.append ("Thorn"); - names.append ("thorn"); - names.append ("minus"); - names.append ("multiply"); - names.append ("onesuperior"); - names.append ("twosuperior"); - names.append ("threesuperior"); - names.append ("onehalf"); - names.append ("onequarter"); - names.append ("threequarters"); - names.append ("franc"); - names.append ("Gbreve"); - names.append ("gbreve"); - names.append ("Idotaccent"); - names.append ("Scedilla"); - names.append ("scedilla"); - names.append ("Cacute"); - names.append ("cacute"); - names.append ("Ccaron"); - names.append ("ccaron"); - names.append ("dcroat"); - } - - // mapping with char code to standard order - int get_standard_index (unichar c) { - switch (c) { - // entry 0 is the .notdef - - case '\0': - return 1; - - case '\r': - return 2; - - case ' ': // space - return 3; - - case '!': - return 4; - - case '"': - return 5; - - case '#': - return 6; - - case '$': - return 7; - - case '%': - return 8; - - case '&': - return 9; - - case '\'': - return 10; - - case '(': - return 11; - - case ')': - return 12; - - case '*': - return 13; - - case '+': - return 14; - - case ',': - return 15; - - case '-': - return 16; - - case '.': - return 17; - - case '/': - return 18; - - case '0': - return 19; - - case '1': - return 20; - - case '2': - return 21; - - case '3': - return 22; - - case '4': - return 23; - - case '5': - return 24; - - case '6': - return 25; - - case '7': - return 26; - - case '8': - return 27; - - case '9': - return 28; - - case ':': - return 29; - - case ';': - return 30; - - case '<': - return 31; - - case '=': - return 32; - - case '>': - return 33; - - case '?': - return 34; - - case '@': - return 35; - - case 'A': - return 36; - - case 'B': - return 37; - - case 'C': - return 38; - - case 'D': - return 39; - - case 'E': - return 40; - - case 'F': - return 41; - - case 'G': - return 42; - - case 'H': - return 43; - - case 'I': - return 44; - - case 'J': - return 45; - - case 'K': - return 46; - - case 'L': - return 47; - - case 'M': - return 48; - - case 'N': - return 49; - - case 'O': - return 50; - - case 'P': - return 51; - - case 'Q': - return 52; - - case 'R': - return 53; - - case 'S': - return 54; - - case 'T': - return 55; - - case 'U': - return 56; - - case 'V': - return 57; - - case 'W': - return 58; - - case 'X': - return 59; - - case 'Y': - return 60; - - case 'Z': - return 61; - - case '[': - return 62; - - case '\\': - return 63; - - case ']': - return 64; - - case '^': - return 65; - - case '_': - return 66; - - case '`': - return 67; - - case 'a': - return 68; - - case 'b': - return 69; - - case 'c': - return 70; - - case 'd': - return 71; - - case 'e': - return 72; - - case 'f': - return 73; - - case 'g': - return 74; - - case 'h': - return 75; - - case 'i': - return 76; - - case 'j': - return 77; - - case 'k': - return 78; - - case 'l': - return 79; - - case 'm': - return 80; - - case 'n': - return 81; - - case 'o': - return 82; - - case 'p': - return 83; - - case 'q': - return 84; - - case 'r': - return 85; - - case 's': - return 86; - - case 't': - return 87; - - case 'u': - return 88; - - case 'v': - return 89; - - case 'w': - return 90; - - case 'x': - return 91; - - case 'y': - return 92; - - case 'z': - return 93; - - case '{': - return 94; - - case '|': - return 95; - - case '}': - return 96; - - case '~': - return 97; - - case 'Ä': - return 98; - - case 'Å': - return 99; - - case 'Ç': - return 100; - - case 'É': - return 101; - - case 'Ñ': - return 102; - - case 'Ö': - return 103; - - case 'Ü': - return 104; - - case 'á': - return 105; - - case 'à': - return 106; - - case 'â': - return 107; - - case 'ä': - return 108; - - case 'ã': - return 109; - - case 'å': - return 110; - - case 'ç': - return 111; - - case 'é': - return 112; - - case 'è': - return 113; - - case 'ê': - return 114; - - case 'ë': - return 115; - - case 'í': - return 116; - - case 'ì': - return 117; - - case 'î': - return 118; - - case 'ï': - return 119; - - case 'ñ': - return 120; - - case 'ó': - return 121; - - case 'ò': - return 122; - - case 'ô': - return 123; - - case 'ö': - return 124; - - case 'õ': - return 125; - - case 'ú': - return 126; - - case 'ù': - return 127; - - case 'û': - return 128; - - case 'ü': - return 129; - - case '†': - return 130; - - case '°': - return 131; - - case '¢': - return 132; - - case '£': - return 133; - - case '§': - return 134; - - case '•': - return 135; - - case '¶': - return 136; - - case 'ß': - return 137; - - case '®': - return 138; - - case '©': - return 139; - - case '™': - return 140; - - case '´': - return 141; - - case '¨': - return 142; - - case '≠': - return 143; - - case 'Æ': - return 144; - - case 'Ø': - return 145; - - case '∞': - return 146; - - case '±': - return 147; - - case '≤': - return 148; - - case '≥': - return 149; - - case '¥': - return 150; - - case 'µ': - return 151; - - case '∂': - return 152; - - case '∑': - return 153; - - case '∏': - return 154; - - case 'π': - return 155; - - case '∫': - return 156; - - case 'ª': - return 157; - - case 'º': - return 158; - - case 'Ω': - return 159; - - case 'æ': - return 160; - - case 'ø': - return 161; - - case '¿': - return 162; - - case '¡': - return 163; - - case '¬': - return 164; - - case '√': - return 165; - - case 'ƒ': - return 166; - - case '≈': - return 167; - - case '∆': - return 168; - - case '«': - return 169; - - case '»': - return 170; - - case '…': - return 171; - - case ' ': // non breaking space - return 172; - - case 'À': - return 173; - - case 'Ã': - return 174; - - case 'Õ': - return 175; - - case 'Œ': - return 176; - - case 'œ': - return 177; - - case '–': - return 178; - - case '—': - return 179; - - case '“': - return 180; - - case '”': - return 181; - - case '‘': - return 182; - - case '’': - return 183; - - case '÷': - return 184; - - case '◊': - return 185; - - case 'ÿ': - return 186; - - case 'Ÿ': - return 187; - - case '⁄': - return 188; - - case '¤': - return 189; - - case '‹': - return 190; - - case '›': - return 191; - - case 'fi': - return 192; - - case 'fl': - return 193; - - case '‡': - return 194; - - case '·': - return 195; - - case '‚': - return 196; - - case '„': - return 197; - - case '‰': - return 198; - - case 'Â': - return 199; - - case 'Ê': - return 200; - - case 'Á': - return 201; - - case 'Ë': - return 202; - - case 'È': - return 203; - - case 'Í': - return 204; - - case 'Î': - return 205; - - case 'Ï': - return 206; - - case 'Ì': - return 207; - - case 'Ó': - return 208; - - case 'Ô': - return 209; - - // Machintosh apple goes here - // return 210; - - case 'Ò': - return 211; - - case 'Ú': - return 212; - - case 'Û': - return 213; - - case 'Ù': - return 214; - - case 'ı': - return 215; - - case 'ˆ': - return 216; - - case '˜': - return 217; - - case '¯': - return 218; - - case '˘': - return 219; - - case '˙': - return 220; - - case '˚': - return 221; - - case '¸': - return 222; - - case '˝': - return 223; - - case '˛': - return 224; - - case 'ˇ': - return 225; - - case 'Ł': - return 226; - - case 'ł': - return 227; - - case 'Š': - return 228; - - case 'š': - return 229; - - case 'Ž': - return 230; - - case 'ž': - return 231; - - case '¦': - return 232; - - case 'Ð': - return 233; - - case 'ð': - return 234; - - case 'Ý': - return 235; - - case 'ý': - return 236; - - case 'Þ': - return 237; - - case 'þ': - return 238; - - case '−': - return 239; - - case '×': - return 240; - - case '¹': - return 241; - - case '²': - return 242; - - case '³': - return 243; - - case '½': - return 244; - - case '¼': - return 245; - - case '¾': - return 246; - - case '₣': - return 247; - - case 'Ğ': - return 248; - - case 'ğ': - return 249; - - case 'İ': - return 250; - - case 'Ş': - return 251; - - case 'ş': - return 252; - - case 'Ć': - return 253; - - case 'ć': - return 254; - - case 'Č': - return 255; - - case 'č': - return 256; - - case 'đ': - return 257; - } - - return 0; - } - - public void process () throws Error { - FontData fd = new FontData (); - string n; - - fd.add_fixed (0x00020000); // Version - fd.add_fixed (0x00000000); // italicAngle - - fd.add_short (-2); // underlinePosition - fd.add_short (1); // underlineThickness - - fd.add_ulong (0); // non zero for monospaced font - - // mem boundries may be omitted - fd.add_ulong (0); // min mem for type 42 - fd.add_ulong (0); // max mem for type 42 - - fd.add_ulong (0); // min mem for Type1 - fd.add_ulong (0); // max mem for Type1 - - fd.add_ushort ((uint16) glyf_table.glyphs.length ()); - - // this part of the spec is so weird - - fd.add_ushort ((uint16) 0); // first index is .notdef - index.append (0); - - assert (names.length () == 0); - add_standard_names (); - - int index; - Glyph g; - for (int i = 1; i < glyf_table.glyphs.length (); i++) { - g = (!) glyf_table.glyphs.nth (i).data; - index = get_standard_index (g.unichar_code); - - if (index != 0) { - fd.add_ushort ((uint16) index); // use standard name - } else { - printd ("Adding non standard postscript name $(g.get_name ())\n"); - - index = (int) names.length (); // use font specific name - fd.add_ushort ((uint16) index); - names.append (g.get_name ()); - } - - this.index.append ((uint16) index); - } - - for (int i = 258; i < names.length (); i++) { - n = (!) names.nth (i).data; - - if (n.length > 0xFF) { - warning ("too long name for glyph $n"); - } - - fd.add ((uint8) n.length); // length of string - fd.add_str (n); - } - - fd.pad (); - - this.font_data = fd; - } - -} - -class GaspTable : Table { - - public GaspTable () { - id = "gasp"; - } - - public override void parse (FontData dis) throws Error { - } - - public void process () throws GLib.Error { - FontData fd = new FontData (); - - fd.add_ushort (0); - fd.add_ushort (0); - - fd.pad (); - - this.font_data = fd; - } - -} - -class GdefTable : Table { - - public GdefTable () { - id = "GDEF"; - } - - public override void parse (FontData dis) throws Error { - } - - public void process () throws GLib.Error { - FontData fd = new FontData (); - - fd.add_ulong (0x00010002); - fd.add_ushort (0); // class def - fd.add_ushort (0); // attach list - fd.add_ushort (0); // ligature carret - fd.add_ushort (0); // mark attach - fd.add_ushort (0); // mark glyf - fd.add_ushort (0); // mark glyf set def - - fd.pad (); - - this.font_data = fd; - } - -} - -class CvtTable : Table { - - public CvtTable () { - id = "cvt "; - } - - public override void parse (FontData dis) throws Error { - } - - public void process () throws GLib.Error { - FontData fd = new FontData (); - - fd.add_ushort (0); - fd.pad (); - - this.font_data = fd; - } - -} - -class Kern : GLib.Object { - public uint16 left; - public uint16 right; - public int16 kerning; - - public Kern (uint16 l, uint16 r, int16 k) { - left = l; - right = r; - kerning = k; - } -} - -class KernList : GLib.Object { - public List<Kern> kernings; - - public KernList () { - kernings = new List<Kern> (); - } -} - -class KernTable : Table { - - public static const uint16 HORIZONTAL = 1; - public static const uint16 MINIMUM = 1 << 1; - public static const uint16 CROSS_STREAM = 1 << 2; - public static const uint16 OVERRIDE = 1 << 3; - public static const uint16 FORMAT = 1 << 8; - - GlyfTable glyf_table; - - List<Kern> kernings = new List<Kern> (); - - public int kerning_pairs = 0; - - public KernTable (GlyfTable gt) { - glyf_table = gt; - id = "kern"; - } - - public KernList get_all_pairs (int gid) { - KernList kl = new KernList (); - - foreach (Kern k in kernings) { - if (k.left == gid) { - kl.kernings.append (k); - } - } - - return kl; - } - - public override void parse (FontData dis) throws GLib.Error { - uint16 version; - uint16 sub_tables; - - uint16 subtable_version; - uint16 subtable_length; - uint16 subtable_flags; - - uint16 search_range; - uint16 entry_selector; - uint16 range_shift; - - uint16 n_pairs; - - dis.seek (offset); - - version = dis.read_ushort (); - warn_if_fail (version == 0); - sub_tables = dis.read_ushort (); - - for (uint16 i = 0; i < sub_tables; i++) { - subtable_version = dis.read_ushort (); - subtable_length = dis.read_ushort (); - subtable_flags = dis.read_ushort (); - - n_pairs = dis.read_ushort (); - search_range = dis.read_ushort (); - entry_selector = dis.read_ushort (); - range_shift = dis.read_ushort (); - - // TODO: check more flags - if ((subtable_flags & HORIZONTAL) > 0 && (subtable_flags & CROSS_STREAM) == 0 && (subtable_flags & MINIMUM) == 0) { - parse_pairs (dis, n_pairs); - } - } - } - - public void parse_pairs (FontData dis, uint16 n_pairs) throws Error { - uint16 left; - uint16 right; - int16 kerning; - - for (int i = 0; i < n_pairs; i++) { - left = dis.read_ushort (); - right = dis.read_ushort (); - kerning = dis.read_short (); - - kernings.append (new Kern (left, right, kerning)); - } - } - - public void process () throws GLib.Error { - FontData fd = new FontData (); - uint16 n_pairs = 0; - - uint16 gid_left; - int gid_right; - - uint16 range_shift = 0; - uint16 entry_selector = 0; - uint16 search_range = 0; - - fd.add_ushort (0); // version - fd.add_ushort (1); // n subtables - - fd.add_ushort (0); // subtable version - - foreach (Glyph g in glyf_table.glyphs) { - foreach (Kerning k in g.kerning) { - n_pairs++; - } - } - - this.kerning_pairs = n_pairs; - - fd.add_ushort (6 * n_pairs + 14); // subtable length - fd.add_ushort (HORIZONTAL); // subtable flags - - fd.add_ushort (n_pairs); - - search_range = 6 * largest_pow2 (n_pairs); - entry_selector = largest_pow2_exponent (n_pairs); - range_shift = 6 * n_pairs - search_range; - - fd.add_ushort (search_range); - fd.add_ushort (entry_selector); - fd.add_ushort (range_shift); - - gid_left = 0; - foreach (Glyph g in glyf_table.glyphs) { - - foreach (Kerning k in g.kerning) { - gid_right = glyf_table.get_gid (k.glyph_right); - - if (gid_right == -1) { - warning ("right glyph not found in kerning table"); - } - - fd.add_ushort (gid_left); - fd.add_ushort ((uint16)gid_right); - fd.add_short ((int16)k.val); - } - - gid_left++; - } - - fd.pad (); - this.font_data = fd; - } - -} - -/** Table with list of tables sorted by table tag. */ -class DirectoryTable : Table { - - public CmapTable cmap_table; - public CvtTable cvt_table; - public GaspTable gasp_table; - public GdefTable gdef_table; - public GlyfTable glyf_table; - public HeadTable head_table; - public HheaTable hhea_table; - public HmtxTable hmtx_table; - public KernTable kern_table; - public MaxpTable maxp_table; - public NameTable name_table; - public Os2Table os_2_table; - public PostTable post_table; - public LocaTable loca_table; - - public OffsetTable offset_table; - - List<Table> tables; - - public DirectoryTable () { - offset_table = new OffsetTable (this); - - loca_table = new LocaTable (); - gasp_table = new GaspTable (); - gdef_table = new GdefTable (); - glyf_table = new GlyfTable (loca_table); - cmap_table = new CmapTable (glyf_table); - cvt_table = new CvtTable (); - head_table = new HeadTable (glyf_table); - hmtx_table = new HmtxTable (head_table, glyf_table); - hhea_table = new HheaTable (glyf_table, head_table, hmtx_table); - kern_table = new KernTable (glyf_table); - maxp_table = new MaxpTable (glyf_table); - name_table = new NameTable (); - os_2_table = new Os2Table (); - post_table = new PostTable (glyf_table); - - id = "Directory table"; - } - - public void process () throws GLib.Error { - // generate font data - glyf_table.process (); - gasp_table.process (); - gdef_table.process (); - cmap_table.process (glyf_table); - cvt_table.process (); - hmtx_table.process (); - hhea_table.process (); - maxp_table.process (); - name_table.process (); - os_2_table.process (glyf_table); - head_table.process (); - loca_table.process (glyf_table, head_table); - post_table.process (); - kern_table.process (); - - offset_table.process (); - process_directory (); // this table - } - - public unowned List<Table> get_tables () { - if (tables.length () == 0) { - tables.append (offset_table); - tables.append (this); - - // tables.append (gdef_table); // invalid table - tables.append (os_2_table); - tables.append (cmap_table); - // tables.append (cvt_table); - // tables.append (gasp_table); - tables.append (glyf_table); - tables.append (head_table); - tables.append (hhea_table); - tables.append (hmtx_table); - - if (kern_table.kerning_pairs > 0) { - tables.append (kern_table); - } - - tables.append (loca_table); - tables.append (maxp_table); - tables.append (name_table); - tables.append (post_table); - } - - return tables; - } - - public void set_offset_table (OffsetTable ot) { - offset_table = ot; - } - - public new void parse (FontData dis, File file, OpenFontFormatReader reader_callback) throws Error { - StringBuilder tag = new StringBuilder (); - uint32 checksum; - uint32 offset; - uint32 length; - - return_if_fail (offset_table.num_tables > 0); - - for (uint i = 0; i < offset_table.num_tables; i++) { - tag.erase (); - - tag.append_unichar ((unichar) dis.read_byte ()); - tag.append_unichar ((unichar) dis.read_byte ()); - tag.append_unichar ((unichar) dis.read_byte ()); - tag.append_unichar ((unichar) dis.read_byte ()); - - checksum = dis.read_ulong (); - offset = dis.read_ulong (); - length = dis.read_ulong (); - - printd (@"$(tag.str) \toffset: $offset \tlength: $length \tchecksum: $checksum.\n"); - - if (tag.str == "cvt") { - cvt_table.id = tag.str; - cvt_table.checksum = checksum; - cvt_table.offset = offset; - cvt_table.length = length; - } else if (tag.str == "hmtx") { - hmtx_table.id = tag.str; - hmtx_table.checksum = checksum; - hmtx_table.offset = offset; - hmtx_table.length = length; - } else if (tag.str == "hhea") { - hhea_table.id = tag.str; - hhea_table.checksum = checksum; - hhea_table.offset = offset; - hhea_table.length = length; - } else if (tag.str == "loca") { - loca_table.id = tag.str; - loca_table.checksum = checksum; - loca_table.offset = offset; - loca_table.length = length; - } else if (tag.str == "cmap") { - cmap_table.id = tag.str; - cmap_table.checksum = checksum; - cmap_table.offset = offset; - cmap_table.length = length; - } else if (tag.str == "maxp") { - maxp_table.id = tag.str; - maxp_table.checksum = checksum; - maxp_table.offset = offset; - maxp_table.length = length; - } else if (tag.str == "gasp") { - gasp_table.id = tag.str; - gasp_table.checksum = checksum; - gasp_table.offset = offset; - gasp_table.length = length; - } else if (tag.str == "gdef") { - gdef_table.id = tag.str; - gdef_table.checksum = checksum; - gdef_table.offset = offset; - gdef_table.length = length; - } else if (tag.str == "glyf") { - glyf_table.id = tag.str; - glyf_table.checksum = checksum; - glyf_table.offset = offset; - glyf_table.length = length; - } else if (tag.str == "head") { - head_table.id = tag.str; - head_table.checksum = checksum; - head_table.offset = offset; - head_table.length = length; - } else if (tag.str == "name") { - name_table.id = tag.str; - name_table.checksum = checksum; - name_table.offset = offset; - name_table.length = length; - } else if (tag.str == "OS/2") { - os_2_table.id = tag.str; - os_2_table.checksum = checksum; - os_2_table.offset = offset; - os_2_table.length = length; - } else if (tag.str == "post") { - post_table.id = tag.str; - post_table.checksum = checksum; - post_table.offset = offset; - post_table.length = length; - } else if (tag.str == "kern") { - kern_table.id = tag.str; - kern_table.checksum = checksum; - kern_table.offset = offset; - kern_table.length = length; - } - } - - head_table.parse (dis); - - if (!validate_tables (dis, file)) { - warning ("Missing required table or bad checksum."); - } - - hhea_table.parse (dis); - reader_callback.set_limits (); - - name_table.parse (dis); - post_table.parse (dis); - os_2_table.parse (dis); - maxp_table.parse (dis); - loca_table.parse (dis, head_table, maxp_table); - hmtx_table.parse (dis, hhea_table, loca_table); - cmap_table.parse (dis); - - if (kern_table.has_data ()) { - kern_table.parse (dis); - } - - glyf_table.parse (dis, cmap_table, loca_table, hmtx_table, head_table, post_table, kern_table); - - if (kern_table.has_data ()) { - gasp_table.parse (dis); - } - - if (kern_table.has_data ()) { - cvt_table.parse (dis); - } - } - - public bool validate_tables (FontData dis, File file) { - bool valid = true; - - try { - dis.seek (0); - - if (!validate_checksum_for_entire_font (dis, file)) { - warning ("file has invalid checksum"); - } else { - printd ("Font file has valid checksum.\n"); - } - - // Skip validation of head table for now it should be realy simple but seems to - // be broken in some funny way. - - if (!glyf_table.validate (dis)) { - warning ("glyf_table has invalid checksum"); - valid = false; - } - - if (!maxp_table.validate (dis)) { - warning ("maxp_table has is invalid checksum"); - valid = false; - } - - if (!loca_table.validate (dis)) { - warning ("loca_table has invalid checksum"); - valid = false; - } - - if (!cmap_table.validate (dis)) { - warning ("cmap_table has invalid checksum"); - valid = false; - } - - if (!hhea_table.validate (dis)) { - warning ("hhea_table has invalid checksum"); - valid = false; - } - - if (!hmtx_table.validate (dis)) { - warning ("hmtx_table has invalid checksum"); - valid = false; - } - - if (!name_table.validate (dis)) { - warning ("name_table has invalid checksum"); - valid = false; - } - - if (!os_2_table.validate (dis)) { - warning ("os_2_table has invalid checksum"); - valid = false; - } - - if (!post_table.validate (dis)) { - warning ("post_table has invalid checksum"); - valid = false; - } - - if (kern_table.has_data () && !kern_table.validate (dis)) { - warning ("kern_table has invalid checksum"); - valid = false; - } - - } catch (GLib.Error e) { - warning (e.message); - valid = false; - } - - return valid; - } - - bool validate_checksum_for_entire_font (FontData dis, File f) throws GLib.Error { - uint p = head_table.offset + head_table.get_checksum_position (); - uint32 checksum_font, checksum_head; - - checksum_head = head_table.get_font_checksum (); - - dis.seek (0); - - // zero out checksum entry in head table before validating it - dis.write_at (p + 0, 0); - dis.write_at (p + 1, 0); - dis.write_at (p + 2, 0); - dis.write_at (p + 3, 0); - - checksum_font = (uint32) (0xB1B0AFBA - dis.check_sum ()); - - if (checksum_font != checksum_head) { - warning (@"Fontfile checksum in head table does not match calculated checksum. checksum_font: $checksum_font checksum_head: $checksum_head"); - return false; - } - - return true; - } - - public long get_font_file_size () { - long length = 0; - - foreach (Table t in tables) { - length += t.get_font_data ().length_with_padding (); - } - - return length; - } - - public void process_directory () throws GLib.Error { - create_directory (); // create directory without offsets to calculate length of offset table and checksum for entre file - create_directory (); // generate a valid directory - } - - // Check sum adjustment for the entire font - public uint32 get_font_file_checksum () { - uint32 check_sum = 0; - foreach (Table t in tables) { - t.get_font_data ().continous_check_sum (ref check_sum); - } - return check_sum; - } - - public void create_directory () throws GLib.Error { - FontData fd; - - uint32 table_offset = 0; - uint32 table_length = 0; - - uint32 check_sum = 0; - - fd = new FontData (); - - return_val_if_fail (offset_table.num_tables > 0, fd); - - table_offset += offset_table.get_font_data ().length_with_padding (); - - if (this.font_data != null) { - table_offset += this.get_font_data ().length_with_padding (); - } - - head_table.set_check_sum_adjustment (0); // Set this to zero, calculate checksums and update the value - head_table.process (); - - // write the directory - foreach (Table t in tables) { - - if (t is DirectoryTable || t is OffsetTable) { - continue; - } - - printd (@"c $(t.id) offset: $(table_offset) len with pad $(t.get_font_data ().length_with_padding ())\n"); - - table_length = t.get_font_data ().length (); // without padding - - fd.add_tag (t.get_id ()); // name of table - fd.add_u32 (t.get_font_data ().check_sum ()); - fd.add_u32 (table_offset); - fd.add_u32 (table_length); - - table_offset += t.get_font_data ().length_with_padding (); - } - - // padding - fd.pad (); - - this.font_data = fd; - - check_sum = get_font_file_checksum (); - head_table.set_check_sum_adjustment ((uint32)(0xB1B0AFBA - check_sum)); - head_table.process (); // update the value - } - -} - -/** Largest power of two less than max. */ -internal static uint16 largest_pow2 (uint16 max) { - uint16 x = 1; - uint16 l = 0; - - while (x <= max) { - l = x; - x = x << 1; - } - - return l; -} - -internal static uint16 largest_pow2_exponent (uint16 max) { - uint16 exp = 0; - uint16 l = 0; - uint16 x = 0; - - while (x <= max) { - l = exp; - exp++; - x = 1 << exp; - } - - return l; -} - -void printd (string s) { - // print (s); -} - -} deleted file mode 100644@@ -1,1069 +0,0 @@-/*- 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 {--/** A display with all glyphs present in this font. */-public class OverView : FontDisplay {- Allocation allocation;- - int rows = 0;- int items_per_row = 0;- uint32 first_character = 0;- int64 first_visible = 0;- uint32 selected = 0;- - double view_offset_y = 0;- double view_offset_x = 0;- - int nail_height = 0;- int nail_width = 0;-- double nail_zoom_height = 0;- double nail_zoom_width = 0;- - public signal void open_glyph_signal (string c);-- GlyphRange glyph_range;-- List<GlyphCollection> visible_characters = new List<GlyphCollection> ();-- List<unichar> zoom_list = new List<unichar> ();- int zoom_list_index = 0;-- List<GlyphCollection> deleted_glyphs = new List<GlyphCollection> ();-- Scrollbar scrollbar;- - bool all_avail = true;- - public OverView (GlyphRange? range = null) {- GlyphRange gr;- - if (range == null) {- gr = new GlyphRange ();- set_glyph_range (gr);- }- - reset_zoom ();-- this.open_glyph_signal.connect ((t, s) => {- TabBar tabs = MainWindow.get_tab_bar ();- Toolbox tools = MainWindow.get_toolbox ();- bool selected = tabs.select_char (s);- unichar new_char = s.get_char (0);- Font f = Supplement.get_current_font ();- - if (!selected) {- GlyphCollection? fg = f.get_glyph_collection (s);- Glyph g = (fg == null) ? new Glyph (s, new_char) : ((!) fg).get_current ();- ZoomTool z = (ZoomTool) tools.get_tool ("zoom_tool");- - stdout.printf (@"Open '%s' %u (%s)\n", s, new_char, Font.to_hex (new_char));- - z.store_current_view ();- f.add_glyph (g);- tabs.add_tab (g);- - MainWindow.get_glyph_canvas ().set_current_glyph (g);- - g.close_path ();- g.default_zoom ();- z.store_current_view ();- }- });- - scrollbar = new Scrollbar ();- scrollbar.signal_scroll.connect ((delta, delta_last, absolute) => {- int64 adjustement = (int64) (-delta_last * get_height ());- int64 pixels = (int64) (-5.0 * delta_last * nail_height);- - if (absolute <= 0.002) {- first_character = 0;- first_visible = 0;- selected = 0;- scroll_top ();- } else if (absolute >= 1) {- scroll_bottom ();- } else {- scroll_to (absolute);- } - - redraw_area (0, 0, allocation.width, allocation.height);- });- - update_scrollbar ();- }- - void scroll_bottom () {- Font f;- - if (all_avail) {- f = Supplement.get_current_font ();- scroll_to_position (f.length () - items_per_row * (rows - 1));- } else {- scroll_to_position (glyph_range.length () - items_per_row * (rows - 1));- }- }- - public double get_height () {- double l;- Font f;- - if (rows == 0) {- return 0;- }- - if (all_avail) {- f = Supplement.get_current_font ();- l = f.length ();- } else {- l = glyph_range.length ();- }- - return 2.0 * nail_height * (l / rows);- }-- public bool selected_char_is_visible () {- return first_visible <= selected <= first_visible + items_per_row * rows;- }-- public override void scroll_wheel_up (double x, double y) {- key_up ();- close_menus ();- redraw_area (0, 0, allocation.width, allocation.height);- }- - public override void scroll_wheel_down (double x, double y) {- key_down ();- close_menus ();- redraw_area (0, 0, allocation.width, allocation.height);- }- - public override void selected_canvas () {- redraw_area (0, 0, allocation.width, allocation.height);- KeyBindings.singleton.set_require_modifier (true);- }- - public override void zoom_min () {- int minw = 26 * 2;- int minh = 46 * 2;- - if (zoom_value_in_range (minw, minh)) {- nail_width = minw;- nail_height = minh;- - nail_zoom_height = minw; - nail_zoom_width = minh; - - adjust_scroll ();- redraw_area (0, 0, allocation.width, allocation.height);- } else {- warn_if_reached ();- } - }- - public override void reset_zoom () {- nail_height = 210;- nail_width = 150;- - nail_zoom_height = nail_height; - nail_zoom_width = nail_width;- - adjust_scroll ();- redraw_area (0, 0, allocation.width, allocation.height);- }-- public override void zoom_max () {- if (zoom_value_in_range (allocation.width, allocation.height)) {- nail_width = allocation.width;- nail_height = allocation.height;- - nail_zoom_height = allocation.height; - nail_zoom_width = allocation.width; - - adjust_scroll ();- redraw_area (0, 0, allocation.width, allocation.height);- } else {- warn_if_reached ();- }- }- - public override void zoom_in () {- double w = (1.1 * nail_width);- double h = (1.1 * nail_height);- int nw = (int) w;- int nh = (int) h;- - if (zoom_value_in_range (nw, nh)) {- nail_width = nw;- nail_height = nh;- - nail_zoom_height = h; - nail_zoom_width = w;- - adjust_scroll ();- redraw_area (0, 0, allocation.width, allocation.height);- }- }- - public override void zoom_out () {- double w = (nail_width / 1.1);- double h = (nail_height / 1.1);- int nw = (int) w;- int nh = (int) h;- - if (zoom_value_in_range (nw, nh)) {- nail_width = nw;- nail_height = nh;-- nail_zoom_height = h; - nail_zoom_width = w;- - adjust_scroll (); - redraw_area (0, 0, allocation.width, allocation.height);- }- }- - private bool zoom_value_in_range (int w, int h) {- return (25 < w <= allocation.width && 45 < h <= allocation.height);- }-- public override void store_current_view () {- if (zoom_list_index + 1 < zoom_list.length ()) {- unowned List<unichar> n = zoom_list.nth (zoom_list_index);- while (n != zoom_list.last ()) zoom_list.delete_link (zoom_list.last ());- }- - zoom_list.append (selected);- zoom_list_index = (int) zoom_list.length () - 1;- - if (zoom_list.length () > 50) zoom_list.delete_link (zoom_list.first ());- }- - public override void restore_last_view () - requires (zoom_list.length () > 0)- { - if (zoom_list_index - 1 < 0 || zoom_list.length () == 0)- return;- - zoom_list_index--;- }-- public override void next_view () - requires (zoom_list.length () > 0)- {- if (zoom_list_index + 1 >= zoom_list.length ())- return;- - zoom_list_index++;- }-- - public override string get_name () {- return "Overview";- }- - public void display_all_available_glyphs () {- all_avail = true;- - first_character = 0;- first_visible = 0;- selected = 0;- }- - private unowned List<GlyphCollection> get_visible_glyphs () {- return visible_characters;- }- - public void draw_caption (int row, double width, Context cr, double y, uint64 index_begin) {- string character_string;- double left_margin, x, caption_y; // for glyph caption- uint64 index = index_begin;- int i = row * items_per_row;- Font f = Supplement.get_current_font ();- - cr.save ();- cr.set_line_width (1);- - x = 0;- - cr.set_font_size (14);-- left_margin = 0;- - for (int j = 0; j < items_per_row; j++) {- - if (all_avail) {- if (! (0 <= index < f.length ())) {- break;- }- - character_string = ((!) f.get_glyph_indice ((uint32) index)).get_name ();- } else {- if (!(0 <= index < glyph_range.get_length ())) {- break;- }- - character_string = glyph_range.get_char ((uint32) index);- }- - cr.save ();- draw_thumbnail (cr, character_string, x, y);- cr.restore ();-- cr.save ();- - if (index == selected) {- cr.set_source_rgba (142/255.0, 158/255.0, 190/255.0, 1);- } else {- cr.set_source_rgba (164/255.0, 185/255.0, 215/255.0, 1);- }- - cr.set_line_join (LineJoin.ROUND);- cr.set_line_width(12);- - cr.rectangle (x, y + nail_height - 20, nail_width - 30, 10); - cr.stroke ();- cr.restore ();- - draw_menu (cr, character_string, x, y);- - caption_y = y + 10 + nail_height - 20;- - cr.move_to (x, caption_y);- cr.show_text (character_string);- - x += nail_width;- index++;- i++;- }- - cr.stroke();- }- - private void draw_menu (Context cr, string name, double x, double y) {- GlyphCollection? gl = Supplement.get_current_font ().get_glyph_collection (name);- GlyphCollection g;- DropMenu menu;- - if (gl == null) {- return;- }- - g = (!) gl;- - menu = g.get_version_list ();- menu.set_position (x + nail_width - 40, y + nail_height - 21);- menu.draw_icon (cr);- menu.draw_menu (cr);- - visible_characters.append (g);- }- - private bool draw_thumbnail (Context cr, string name, double x, double y) {- Glyph? gl = Supplement.get_current_font ().get_glyph (name);- Glyph g;- - double gx, gy;- double x1, x2, y1, y2;- double scale = nail_zoom_width / 150.0;- double w, h;-- w = nail_width - 19;- h = nail_height - 22;- - scale = nail_zoom_height / (210.0 - 15.0);- - if (gl == null) {- return false;- }- - g = (!) gl;- g.boundries (out x1, out y1, out x2, out y2);-- gx = 0;- gy = h / scale - 40;- - Surface s = new Surface.similar (cr.get_target (), Content.COLOR_ALPHA, (int) w, (int) h);- Context c = new Context (s);-/*- c.set_source_rgb (200/255.0, 200/255.0, 229/255.0);- c.rectangle (0, 0, w, h); - c.fill ();-*/- c.scale (scale, scale); - Svg.draw_svg_path (c, g.get_svg_data (), gx, gy, 1.0);- - cr.save ();- cr.set_source_surface (s, x - 6, y);- cr.paint ();- cr.restore ();-- return true;- }-- // 150 * 190 px- public override void draw (Allocation allocation, Context cr) {- double width;- double y;- uint64 t;- int i;- int n_items;- - while (visible_characters.length () > 9) {- visible_characters.remove_link (visible_characters.first ());- }-- this.allocation = allocation;- - cr.save ();- cr.set_source_rgba (1, 1, 1, 1);- cr.rectangle (0, 0, allocation.width, allocation.height + nail_height);- cr.fill ();- cr.restore ();- - if (Supplement.experimental) {- scrollbar.draw (cr, allocation);- }- - n_items = ((allocation.width - 40) / nail_width);- rows = (allocation.height / nail_height);-- if (items_per_row != n_items) { - if (items_per_row == 0) {- items_per_row = n_items;- first_visible = first_character;- - key_down ();- key_up ();- } else {- items_per_row = n_items;- first_visible = first_character;- }- }-- width = nail_width * items_per_row;- view_offset_x = (allocation.width - width) / 2.0;- - cr.translate(view_offset_x, view_offset_y);- - y = 0;- t = first_visible;- i = 0;- for (int j = 0; j < rows + 1; j++) {- draw_caption (i, width, cr, y, t);-- t += items_per_row;- y += nail_height;- i++;- }- - draw_menus (allocation, cr);- }-- private void draw_menus (Allocation allocation, Context cr) {- }-- private void close_menus () {- }- - public void scroll_rows (int row_adjustment) {- for (int i = 0; i < row_adjustment; i++) {- scroll (-nail_height);- }- - for (int i = 0; i > row_adjustment; i--) {- scroll (nail_height);- }- }- - public void scroll_adjustment (double pixel_adjustment) {- double l;- Font f;- - if (all_avail) {- f = Supplement.get_current_font ();- l = f.length ();- } else {- l = glyph_range.length ();- }- - if (first_visible <= 0) {- return;- }-- if (first_visible + rows * items_per_row >= l) {- return;- }- - scroll ((int64) pixel_adjustment);- }- - void default_position () {- scroll_top ();- scroll_rows (1);- }- - void scroll_to_position (int64 r) {- int64 l;- Font f;- - if (all_avail) {- f = Supplement.get_current_font ();- l = f.length ();- } else {- l = glyph_range.length ();- }- - // bottom- if (r > l - items_per_row * rows) {- return;- }- - if (r < 0) {- scroll_top ();- return;- }- - default_position ();-- first_visible = r;- selected = (uint32) r;- - adjust_scroll ();- }- - public void scroll_to (double percent) {- int64 r;- double l;- Font f;- - if (all_avail) {- f = Supplement.get_current_font ();- l = f.length ();- } else {- l = glyph_range.length ();- }- - l /= rows;- r = (int64) (percent * l * items_per_row);- scroll_to_position (r);- }- - private void scroll (int64 pixel_adjustment) {-- if (first_visible < 0 && pixel_adjustment < 0) {- scroll_top ();- return;- }- - view_offset_y += pixel_adjustment;- - if (view_offset_y >= 0) {- while (view_offset_y > nail_height) { - view_offset_y -= nail_height;- first_visible -= items_per_row;- }-- first_visible -= items_per_row;- view_offset_y -= nail_height;- } else if (view_offset_y < -nail_height) {- view_offset_y = 0;- first_visible += items_per_row;- }- - }- - public void scroll_top () {- first_visible = first_character;- view_offset_y = 0;- }- - private int selected_offset_y (unichar item) {- int64 di;- int64 a, b;- if (unlikely (items_per_row == 0)) return 0;- - a = (int64) item;- b = (int64) first_visible;- di = (a - b);- - return (int) (view_offset_y + ((di / items_per_row) * nail_height));- }-- /** Make selected item fully visible if it partly off screen. */- private void adjust_scroll () {- int off = selected_offset_y (selected);-- if (off < 0) {- close_menus ();- scroll (-1 * off);- } else if (off > allocation.height - nail_height) {- scroll ((allocation.height - nail_height) - off);- }- }-- public void key_down () {- double l;- Font f;- - if (all_avail) {- f = Supplement.get_current_font ();- l = f.length ();- } else {- l = glyph_range.length ();- }- - update_scrollbar ();- - if (l == 0) return;- - if (at_bottom ()) {- int len = (int) l;- int s = (int) selected;- - if (len - items_per_row >= s) {- selected += items_per_row; - } else if (s < len) {- selected++;- }- - if (selected >= len) {- selected = len - 1;- }- - adjust_scroll ();- - return;- }- - selected += items_per_row;- - if (selected > rows * items_per_row + first_visible) {- scroll (-nail_height);- }- - adjust_scroll ();- }-- public void key_up () {- update_scrollbar ();-- if (selected < first_character) { - warn_if_reached ();- }- - if (selected < first_character + items_per_row) {- scroll_top ();- return;- }- - if (selected - items_per_row >= first_character) {- selected -= items_per_row;- if (selected < first_visible) {- scroll (-nail_height);- }- } - - if (first_visible < first_character) {- selected = first_character;- }- - adjust_scroll ();-- if (first_visible < first_character) {- scroll_top ();- }- } - - public void open_current_glyph () {- key_press (Key.ENTER);- }-- public void select_next_glyph () {- key_press (Key.RIGHT);- }-- public void key_right () { - uint len;- Font f;- - if (all_avail) {- f = Supplement.get_current_font ();- len = f.length ();- } else {- len = glyph_range.length ();- }- - if (len == 0) return;- - selected++;- - if (selected >= len) {- selected = len - 1;- }-- if (selected_offset_y (selected) > allocation.height - nail_height) {- selected -= items_per_row;- key_down ();- }- }- - public void key_left () {- if (selected > first_character) {- selected--; - if (selected_offset_y (selected) < 0) {- selected -= items_per_row;- key_up ();- }- }- }- - public string get_selected_char () {- Font f;- Glyph? g;- - if (all_avail) {- f = Supplement.get_current_font ();- g = f.get_glyph_indice (selected);- return_val_if_fail (g != null, "".dup ());- return ((!) g).get_name ();- }- - return glyph_range.get_char (selected);- }- - public override void key_press (uint keyval) {- redraw_area (0, 0, allocation.width, allocation.height);- close_menus ();-- if (KeyBindings.modifier == CTRL) {- return;- }-- switch (keyval) {- case Key.ENTER:- open_glyph_signal (get_selected_char ());- return;- - case Key.UP:- key_up ();- return;- - case Key.RIGHT:- key_right ();- return;- - case Key.LEFT:- key_left ();- return;- - case Key.DOWN:- key_down ();- return;- - case Key.PG_UP:- for (int i = 0; i++ < rows; i++) {- key_up ();- }- return;- - case Key.PG_DOWN:- for (int i = 0; i++ < rows; i++) {- key_down ();- }- return;- - case Key.DEL:- delete_selected_glyph ();- return;- }-- scroll_to_char (keyval);- }-- public void delete_selected_glyph () {- delete_glyph (glyph_range.get_char (selected));- }- - public void delete_glyph (string g) {- GlyphCollection? gc;- Font f = Supplement.get_current_font ();- - gc = f.get_glyph_collection (g);-- if (gc != null) {- deleted_glyphs.append ((!) gc);- }-- f.delete_glyph (g);- MainWindow.get_tab_bar ().close_by_name (g);- set_glyph_range (glyph_range);- f.touch ();- }- - public override void undo () {- Font f = Supplement.get_current_font ();- - if (deleted_glyphs.length () == 0) {- return;- }- - f.add_glyph_collection (deleted_glyphs.last ().data);- deleted_glyphs.remove_link (deleted_glyphs.last ());- }- - public void scroll_to_char (unichar c) {- GlyphRange gr = glyph_range;- GlyphRange full = new GlyphRange ();- - if (is_modifier_key (c)) {- return;- }- - full.use_full_unicode_range ();- - set_glyph_range (full);- selected = c;- set_glyph_range (gr);- }- - public override void motion_notify (double x, double y) {- if (x > allocation.x - 10) {- scrollbar.motion_notify ((int) x, (int) y);- }- }- - public override void button_release (int button, double ex, double ey) {- if (ex > allocation.x - 10) {- scrollbar.button_release (button, ex, ey);- }- }- - private void selection_click (uint button, double ex, double ey) {- double x = view_offset_x;- double y = view_offset_y;-- unichar current = selected;-- // click in margin- int n_items = (allocation.width - 40 / nail_width);- double m = (allocation.width - 40 - (n_items * nail_width)) / 2.0;- - bool menu_action = false;- - Font f;- uint len;-- redraw_area (0, 0, allocation.width, allocation.height);-- if (all_avail) {- f = Supplement.get_current_font ();- len = f.length ();- } else {- len = glyph_range.length ();- }- - foreach (GlyphCollection g in get_visible_glyphs ()) {- bool a;-- a = g.get_version_list ().menu_item_action (ex - view_offset_x, ey - view_offset_y); // select one item on the menu- - if (a) {- return;- }- - a = g.get_version_list ().menu_icon_action (ex - view_offset_x, ey - view_offset_y); // open menu- - if (a) {- menu_action = true;- }- }- - if (ex < m) {- ex = m + 1;- }- - if (ex > allocation.width - m) {- ex = allocation.width - m - 1;- }- - if (ey < 5) {- ey = 5;- }- - if (ey > allocation.height - 5) {- ey = allocation.height - 5;- }- - // empty set- if (len == 0) return;-- // scroll to the selected glyph- selected = (uint32) first_visible;-- while (y < ey < allocation.height) {- selected += items_per_row;- y += nail_height;- }- selected -= items_per_row;-- while (x < ex < allocation.width) {- selected++;- x += nail_width;- }- - selected--;- - if (at_bottom () && selected >= len) {- selected = len - 1;- }- - adjust_scroll ();- - if (current == selected && !menu_action) {- open_glyph_signal (get_selected_char ());- }- - adjust_scroll ();- adjust_scroll ();- }- - public override void button_press (uint button, double x, double y) {- if (x > allocation.width - 10) {- if (Supplement.experimental) {- scrollbar.button_press (button, x, y);- }- } else {- selection_click (button, x, y);- }- }-- /** Returns true if overview shows the last character. */- private bool at_bottom () {- Font f;- double t = rows * items_per_row + items_per_row + first_visible;- - if (all_avail) {- f = Supplement.get_current_font ();- return t >= f.length ();- }- - return t >= glyph_range.length ();- }-- public void set_glyph_range (GlyphRange range) {- GlyphRange? current = glyph_range;- string c = "";- bool done = false;- uint32 i;- uint32 len = 0;- - if (current != null) {- c = glyph_range.get_char (selected);- }- - all_avail = false;- - glyph_range = range;-- // Todo: optimized search when full range is selected.- - // scroll to the selected character- if (c == "") {- c = "a";- }- - if (items_per_row == 0) {- return;- }- - scroll_top ();-- // Skip scroll if this is a unassigned character- if (c.char_count () > 1) {- return;- }-- while (true) {- for (i = (uint32) first_visible; i < first_visible + items_per_row * (rows + 1); i++) {- if (range.get_char (i) == c) {- selected = i;- done = true;- break;- }- }- - if (done) break;- if (at_bottom ()) break;-- scroll_rows (rows);- }-- if (unlikely (!done)) {- MainWindow.get_toolbox ().select_tool_by_name ("custom_character_set");- warning (@"No glyph \"$c\"\n");- return;- }- - adjust_scroll ();- - if (at_bottom ()) {- while (c != glyph_range.get_char (selected) && glyph_range.get_length () >= items_per_row) {- key_right (); - if (selected >= glyph_range.get_length () - items_per_row) {- break;- }- }-- len = glyph_range.get_length ();- for (i = selected; i < len; i++) {- if (c == glyph_range.get_char (i)) {- break;- }- }-- if (i == len) {- selected = first_character;- scroll_top ();- }- }-- update_scrollbar ();- redraw_area (0, 0, allocation.width, allocation.height);- }-- public void update_scrollbar () {- double len;- Font f;- - if (all_avail) {- f = Supplement.get_current_font ();- len = f.length ();- } else {- len = glyph_range.get_length ();- }- - scrollbar.set_handle_size (items_per_row * rows / len);- scrollbar.set_handle_position (first_visible / len);- }-}--}deleted file mode 100644@@ -1,1879 +0,0 @@-/*- 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;-using Math;--namespace Supplement {--public enum Direction {- CLOCKWISE,- COUNTER_CLOCKWISE-}--public class Path {- - public List<EditPoint> points = new List<EditPoint> ();-- EditPoint? last_point = null;- EditPoint? second_last_point = null;-- /** Region boundries */- public double xmax = double.MIN;- public double xmin = double.MAX;- public double ymax = double.MIN;- public double ymin = double.MAX;-- bool edit = true;- bool open = true;- bool direction_is_set = false;-- bool no_derived_direction = false;- bool clockwise_direction = true;-- public delegate bool RasterIterator (double x, double y, double step); // iterate over each pixel at a given zoom level- - bool selected = false;- - private static ImageSurface? edit_point_image = null;- private static ImageSurface? active_edit_point_image = null;- - private static ImageSurface? edit_point_handle_image = null;- private static ImageSurface? active_edit_point_handle_image = null;-- private static ImageSurface? selected_edit_point_image = null;- private static ImageSurface? active_selected_edit_point_image = null;- - Path quadratic_path; // quadratic points for ttf export- - public Path () {- if (edit_point_image == null) {- edit_point_image = Icons.get_icon ("edit_point.png");- active_edit_point_image = Icons.get_icon ("active_edit_point.png");- edit_point_handle_image = Icons.get_icon ("edit_point_handle.png");- active_edit_point_handle_image = Icons.get_icon ("active_edit_point_handle.png");- selected_edit_point_image = Icons.get_icon ("selected_edit_point.png");- active_selected_edit_point_image = Icons.get_icon ("active_selected_edit_point.png");- }- }-- public bool has_direction () {- return direction_is_set;- }-- public bool empty () {- return points.length () == 0;- }-- public void draw_outline (Context cr, Allocation allocation, double view_zoom) {- unowned List<EditPoint> ep = points;- - unowned EditPoint? n = null;- unowned EditPoint en;- unowned EditPoint em;- - if (ep.length () < 2) {- return;- }- - cr.new_path ();- - // draw lines- foreach (EditPoint e in ep) {- if (n != null) {- en = (!) n;- draw_next (e, en, cr);- }- - n = e;- }-- // close path- if (!is_open () && n != null) {- en = (!) n;- em = ep.first ().data;- - draw_next (em, en, cr);- }-- cr.stroke ();- }- - public void draw_edit_points (Context cr, Allocation allocation, double view_zoom) {- unowned List<EditPoint> ep = points;- - if (is_editable ()) {- // control points for curvature- foreach (EditPoint e in ep) {- if (e.selected || e.selected_handle > 0)- draw_edit_point_handles (e, cr);- }- - // control points- foreach (EditPoint e in ep) {- draw_edit_point (e, cr);- }- }- }-- public void fill_path (Context cr, Allocation allocation, double view_zoom) {- unowned List<EditPoint> ep = points;- - unowned EditPoint? n = null;- unowned EditPoint en;- unowned EditPoint em;- - cr.new_path ();- - // draw lines- foreach (EditPoint e in ep) {- if (n != null) {- en = (!) n;- draw_next (e, en, cr);- }- - n = e;- }- - // close path- if (!is_open () && ep.length () >= 2 && n != null) {- en = (!) n;- em = ep.first ().data;- draw_next (em, en, cr);- }-- // fill path- cr.close_path ();- - if (is_clockwise ()) {- cr.set_source_rgba (80/255.0, 95/255.0, 137/255.0, 0.5);- } else {- cr.set_source_rgba (144/255.0, 145/255.0, 236/255.0, 0.5);- }- - cr.fill ();- }-- private void draw_next (EditPoint e, EditPoint en, Context cr) {- draw_curve (e, 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;- - get_bezier_points (e, en, out xa, out ya, out xb, out yb, out xc, out yc, out xd, out yd);-- cr.set_source_rgba (0, 0, 0, alpha);- cr.set_line_width (1 / g.view_zoom);- - cr.line_to (xa, ya); // this point makes sense only if it is the first or last position, the other points are meaning less don't export them-- cr.curve_to (xb, yb, xc, yc, xd, yd);- }- - /** Curve relative to window center. */- public static void get_bezier_points (EditPoint e, EditPoint en, out double xa, out double ya, out double xb, out double yb, out double xc, out double yc, out double xd, out double yd) {- Glyph g = MainWindow.get_current_glyph ();- - double center_x, center_y;- - center_x = g.allocation.width / 2.0;- center_y = g.allocation.height / 2.0;- - xa = center_x + en.x;- ya = center_y - en.y;-- xb = center_x + en.get_right_handle ().x ();- yb = center_y - en.get_right_handle ().y ();- - xc = center_x + e.get_left_handle ().x ();- yc = center_y - e.get_left_handle ().y ();- - xd = center_x + e.x;- yd = center_y - e.y; - }-- /** Curve absolute glyph data. */- public static void get_abs_bezier_points (EditPoint e, EditPoint en, out double xa, out double ya, out double xb, out double yb, out double xc, out double yc, out double xd, out double yd) {- xa = + en.x;- ya = - en.y;-- xb = + en.get_right_handle ().x ();- yb = - en.get_right_handle ().y ();- - xc = + e.get_left_handle ().x ();- yc = - e.get_left_handle ().y ();- - xd = + e.x;- yd = - e.y; - }- - /** Line points relative to center. */- public static void get_line_points (EditPoint e, EditPoint en, out double xa, out double ya, out double xb, out double yb) {- double xc = Glyph.xc ();- double yc = Glyph.yc ();- - xa = xc + en.x;- ya = yc - en.y;- - xb = xc + e.x;- yb = yc - e.y;- }- - public void draw_line (EditPoint e, EditPoint en, Context cr, double alpha = 1) { - Glyph g = MainWindow.get_current_glyph ();- double ax, ay, bx, by;-- get_line_points (e, en, out ax, out ay, out bx, out by);- - cr.set_source_rgba (0/255.0, 0/255.0, 0/255.0, 1);- cr.set_line_width (1.7 * (1/g.view_zoom));-- cr.line_to (ax, ay);- cr.line_to (bx, by);- - cr.stroke ();- }- - public void draw_edit_point (EditPoint e, Context cr) {- draw_edit_point_center (e, cr);- }- - public void draw_edit_point_handles (EditPoint e, Context cr)- requires (active_edit_point_handle_image != null && edit_point_handle_image != null)- {- ImageSurface img_right, img_left;-- EditPoint handle_right = e.get_right_handle ().get_point ();- EditPoint handle_left = e.get_left_handle ().get_point ();-- cr.stroke ();- - img_right = (e.get_right_handle ().active) ? (!) active_edit_point_handle_image : (!) edit_point_handle_image;- img_left = (e.get_left_handle ().active) ? (!) active_edit_point_handle_image : (!) edit_point_handle_image;- - if (!(is_open () && e == points.last ().data)) {- draw_line (handle_right, e, cr, 0.15);- draw_img_center (cr, img_right, e.get_right_handle ().x (), e.get_right_handle ().y ());- }- - if (!(is_open () && e == points.first ().data)) {- draw_line (handle_left, e, cr, 0.15);- draw_img_center (cr, img_left, e.get_left_handle ().x (), e.get_left_handle ().y ());- }- }-- public static void draw_edit_point_center (EditPoint e, Context cr) - requires (active_edit_point_image != null && edit_point_image != null)- { - ImageSurface img;- - if (e.is_selected ()) {- img = (e.active) ? (!) active_selected_edit_point_image : (!) selected_edit_point_image;- } else {- img = (e.active) ? (!) active_edit_point_image : (!) edit_point_image;- }- - draw_img_center (cr, img, e.x, e.y);- }- - public static void draw_img_center (Context cr, ImageSurface img, double x, double y) {- Glyph g = MainWindow.get_current_glyph ();- - double ivz = 1 / g.view_zoom;-- double xc = g.allocation.width / 2.0;- double yc = g.allocation.height / 2.0;-- cr.save ();-- cr.scale (ivz, ivz);- - x = xc + x - (img.get_width () / 2.0) * ivz; - y = yc - y - (img.get_height () / 2.0) * ivz;- - cr.set_source_surface (img, x * g.view_zoom, y * g.view_zoom);- cr.paint ();- - cr.restore (); - }- - /** Returns true if there is an edit point at (p.x, p.y). */- private bool has_edit_point (EditPoint p) {- foreach (var t in points) {- if (p.x == t.x && p.y == t.y) {- return true;- }- }- return false;- }-- private EditPoint? get_edit_point_at (double x, double y) {- foreach (var p in points) {- if (p.x == x && p.y == y) return p;- }- - return null;- }- - private void remove_all_edit_points () {- while (points.length () > 0) {- points.remove_link (points.first ());- }- }- - /** Replace edit points in this path with @param new_path.points. */- public void replace_path (Path new_path) {- while (points.length () > 0) {- points.remove_link (points.first ());- }- - foreach (var np in new_path.points) {- add (np.x, np.y);- }- - close ();- }- - /** Returns true if this path can be merged with @param union */ - private bool is_valid_union (Path union) {- if (union == this) {- warning ("Path can't union with it self.");- return false;- }- - if (union.points.length () <= 2 || points.length () <= 2) {- return false;- }-- if (!has_overlapping_boundry (union)) {- return false;- }-- return true;- }- - /** Set direction for this path to clockwise for outline and - * counter clockwise for inline paths.- */- public bool force_direction (Direction direction) {- bool c = (direction == Direction.CLOCKWISE);- direction_is_set = true;- - if (c != is_clockwise ()) {- this.reverse ();- }- - if (unlikely (is_clockwise () != c)) {- warning ("Failed to set direction for path in force_direction.");- return true;- }- - return false;- }-- /** Switch direction from clockwise path to counter clockwise path or vise versa. */- public void reverse () {- bool direction = is_clockwise ();- - if (no_derived_direction) {- clockwise_direction = !clockwise_direction;- }- - reverse_points ();- - if (unlikely (direction == is_clockwise ())) {- stderr.printf ("Error: Direction did not change after reversing path.\n");- stderr.printf (@"Length: $(points.length ())\n");- stderr.printf (@"No particular direction can be derived: $no_derived_direction \n");- warning ("Path.reverse () failed.\n");- }- }-- private void reverse_points () {- points.reverse ();- - EditPointHandle t;- foreach (var e in points) {- t = e.right_handle;- e.right_handle = e.left_handle;- e.left_handle = t;- }- - create_list ();- }-- public void print_all_points () {- int i = 0;- foreach (var p in points) {- ++i;- string t = (p.type == PointType.END) ? " endpoint" : "";- stdout.printf (@"Point $i at ($(p.x), $(p.y)) $t \n");- }- }- - private bool validate_list () {- unowned List<EditPoint> ep = points.first ();- - if (points.length () < 3) {- warning ("points.length () < 3");- }- - if (!is_open ()) {- for (int i = 0; i < points.length (); i++) {- if (ep.data.next == null) {- return false;- }-- if (ep.data.prev == null) {- return false;- }- - ep = (!) ep.data.next;- }- }-- if (is_open ()) {- for (int i = 1; i < points.length () - 1; i++) {- if (ep.data.next == null) {- return false;- }-- if (ep.data.prev == null) {- return false;- }- - ep = (!) ep.data.next;- }- - if (ep.data.next != null) {- return false;- }- }- - return true;- }- - private double clockwise_sum () {- return_if_fail (points.length () >= 3);- - double sum = 0;- EditPoint prev = points.last ().data;- foreach (EditPoint e in points) {- sum += (e.x - prev.x) * (e.y + prev.y);- prev = e;- }- - return sum;- }- - public bool is_clockwise () {- double s;- - if (unlikely (points.length () <= 2)) {- no_derived_direction = true;- return clockwise_direction;- }- - s = clockwise_sum ();- - if (s == 0) {- no_derived_direction = true;- return clockwise_direction; - }- - return s > 0;- }- - public bool is_editable () {- return edit;- }-- /** Show control points on outline path. */- public void set_editable (bool e) {- edit = e;- }- - public bool is_open () {- return open;- }- - private bool direction_x (EditPoint start, EditPoint stop) {- return (start.x < stop.x);- }-- private bool direction_y (EditPoint start, EditPoint stop) {- return (start.y < stop.y);- }- - public void thinner (double ratio) - requires (points.length () > 2)- { - update_region_boundries ();- - double w = Math.fabs(xmax - xmin);- double h = Math.fabs(ymax - ymin);- double cx = w / 2 + xmin; - double cy = h / 2 + ymin;- - ratio = 1 - ratio;-- unowned List<EditPoint> i = points.first ();- unowned List<EditPoint> prev = points.last ();- - bool dirx = true;- bool ldirx = direction_x (prev.data, i.data);- bool ndirx;-- bool diry = true;- bool ldiry = direction_y (prev.data, i.data);- bool ndiry;- - while (true) {- ndirx = direction_x (prev.data, i.data);- - if (ndirx != ldirx) {- dirx = !dirx;- }- - ldirx = ndirx;-- ndiry = direction_y (prev.data, i.data);- - if (ndiry != ldiry) {- diry = !diry;- }- - ldiry = ndiry;- - EditPoint p = i.data;- - if (dirx) {- if (p.x < cx)- p.x += (cx - p.x) * ratio;- else- p.x -= (p.x - cx) * ratio;- } else {- if (p.x < cx)- p.x += (cx - p.x) * ratio * 2;- else- p.x -= (p.x - cx) * ratio * 2;- }-- // err borde vara -y- if (p.y < cy && diry || p.y > cy && ! diry)- p.y += (cy - p.y) * ratio;- else- p.y -= (p.y - cy) * ratio; - - if (i == points.last ()) {- break;- }- - prev = i;- i = i.next;- }- }- - public void resize (double ratio) {- double h = ymax - ymin;- double px, py, nx, ny;-- px = xmin;- py = ymin;- - if (ratio < 1 && h < 2) {- return;- }- - foreach (var p in points) {- p.x *= ratio;- p.y *= ratio;- p.right_handle.length *= ratio;- p.left_handle.length *= ratio;- }- - xmin *= ratio; - xmax *= ratio;- ymin *= ratio;- ymax *= ratio;-- nx = xmin;- ny = ymin;- - move (px - nx, py - ny);- }- - public Path copy () {- Path new_path = new Path ();- - foreach (var ep in points) {- new_path.add_point (ep.copy ());- }- - new_path.edit = edit;- new_path.open = open;- - new_path.create_list ();- - return new_path;- } - - public bool is_over (double x, double y) {- Glyph g = MainWindow.get_current_glyph ();- - x = x * Glyph.ivz () + g.view_offset_x - Glyph.xc ();- y = y * Glyph.ivz () + g.view_offset_y - Glyph.yc ();-- y *= -1;- - return is_over_coordinate (x, y);- }- - public bool is_over_coordinate (double x, double y) {- return is_over_coordinate_var (x, y, 0.1);- }- - public static double distance (double ax, double bx, double ay, double by) {- return Math.fabs (Math.sqrt (Math.pow (ax - bx, 2) + Math.pow (ay - by, 2)));- }- - /** Estimate length of path. */- private double get_length () {- double len = 0;- - return_if_fail (points.length () > 2);- - EditPoint prev = points.last ().data;- foreach (EditPoint p in points) {- len += get_length_from (prev, p);- prev = p;- }- - return len;- }- - private static double get_length_from (EditPoint a, EditPoint b) {- double x, y;- - x = Math.fabs (a.x - a.get_right_handle ().x ());- x += Math.fabs (a.get_right_handle ().x () - b.get_left_handle ().x ());- x += Math.fabs (b.get_left_handle ().x () - b.x);-- y = Math.fabs (a.y - a.get_right_handle ().y ());- y += Math.fabs (a.get_right_handle ().y () - b.get_left_handle ().y ());- y += Math.fabs (b.get_left_handle ().y () - b.y);- - return Math.fabs (Math.sqrt (x * x + y * y));- } - - /** Variable precision */- public bool is_over_coordinate_var (double x, double y, double tolerance) {- List<EditPoint> ycoordinates = new List<EditPoint> ();- double last = 0;- bool on_edge = false;- double last_x = 0;-- if (points.length () < 3) {- return false;- }- - if (!is_over_boundry_precision (x, y, tolerance)) {- return false;- }-- foreach (EditPoint e in points) {- if (distance (e.x, x, e.y, y) < tolerance) {- return true;- }- }-- all_of_path ((cx, cy, ct) => {- double distance = Math.fabs (Math.sqrt (Math.pow (cx - x, 2) + Math.pow (cy - y, 2)));- - if (distance < tolerance) {- on_edge = true;- return false;- }- - if (Math.fabs (cx - x) < tolerance && Math.fabs (last - cy) > 2 * tolerance) {- ycoordinates.append (new EditPoint (cx, cy));- last = cy;- }- - last_x = cx;- return true;- });-- if (on_edge) {- return true;- }-- if (ycoordinates.length () == 0) {- warning ("No ycoordinates is empty");- return true;- }-- ycoordinates.sort ((a, b) => {- return (a.y < b.y) ? 1 : -1;- });- - for (int i = 0; i < ycoordinates.length () - 1; i++) {- if (Math.fabs (ycoordinates.nth (i).data.y - ycoordinates.nth (i + 1).data.y) < 4 * tolerance) {- ycoordinates.remove_link (ycoordinates.nth (i));- }- }- - if (unlikely (ycoordinates.length () % 2 != 0)) {- warning (@"not an even number of coordinates ($(ycoordinates.length ()))");- stderr.printf (@"(ymin <= y <= ymax) && (xmin <= x <= xmax);\n");- stderr.printf (@"($ymin <= $y <= $ymax) && ($xmin <= $x <= $xmax);\n");- - stderr.printf (@"tolerance: $(tolerance)\n");- - stderr.printf ("ycoordinates:\n");- foreach (EditPoint e in ycoordinates) {- stderr.printf (@"$(e.y)\n");- }-- ycoordinates.append (ycoordinates.last ().data.copy ());- - return true;- }- - for (unowned List<EditPoint> e = ycoordinates.first (); true; e = e.next) {- return_val_if_fail (!is_null (e), true);- - if (y <= e.data.y + tolerance) {- return_val_if_fail (!is_null (e.next), true);- - e = e.next;-- if (y >= e.data.y - tolerance) {- return true;- }- }- - if (e == ycoordinates.last ()) {- break;- }- }-- return false;- }- - public bool is_over_boundry_precision (double x, double y, double p) {- if (unlikely (ymin == double.MAX || ymin == 10000)) {- warning (@"no bounding box. length ($(points.length ()))");- update_region_boundries ();- }- - return (ymin - p <= y <= ymax + p) && (xmin - p <= x <= xmax + p);- }- - public bool is_over_boundry (double x, double y) {- if (unlikely (ymin == double.MAX)) {- warning ("bounding box is not calculated, run update_region_boundries first.");- }-- return (ymin <= y <= ymax) && (xmin <= x <= xmax);- }-- public bool has_overlapping_boundry (Path p) {- return !(xmax <= p.xmin || ymax <= p.ymin) || (xmin >= p.xmax || ymin >= p.ymax);- }- - public void add (double x, double y) {- add_after (x, y, points.last ());- }-- public void add_point (EditPoint p) {- add_point_after (p, points.last ());- }-- /** Insert a new point after @param previous_point and return a reference - * to the new item in list.- */- public unowned List<EditPoint> add_after (double x, double y, List<EditPoint>? previous_point) {- EditPoint p = new EditPoint (x, y, PointType.LINE); - return add_point_after (p, previous_point);- }- - public unowned List<EditPoint> add_point_after (EditPoint p, List<EditPoint>? previous_point) {- unowned List<EditPoint> np;- int prev_index;-- if (points.length () > 0 && previous_point != null && ((!)previous_point).data.type == PointType.END) {- points.delete_link ((!) previous_point);- }-- if (unlikely (previous_point == null && points.length () != 0)) warning ("previous_point == null");-- if (points.length () == 0) {- points.append (p);- np = points.last ();- p.prev = points.last ();- p.next = points.last ();- } else {- p.prev = (!) previous_point;- p.next = ((!) previous_point).next;-- points.insert_before (((!) previous_point).next, p);- - prev_index = points.position ((!) previous_point);- np = points.nth (prev_index + 1);- }- - second_last_point = last_point;- last_point = p;- - PenTool.set_default_handle_positions ();- - return np;- }-- public void close () {- open = false;- edit = false;-- create_list ();- - if (points.length () > 2) {- points.first ().data.recalculate_linear_handles ();- points.last ().data.recalculate_linear_handles ();- }- }- - public void reopen () {- open = true;- edit = true;- }- - public EditPoint? get_last_point () {- return last_point;- }-- public EditPoint? get_second_last_point () {- return second_last_point;- }- - /** Move path. */- public void move (double delta_x, double delta_y) {- foreach (var ep in points) {- ep.x += delta_x;- ep.y += delta_y; // kanske -y- }- - update_region_boundries ();- }-- public void update_region_boundries () {- if (points.length () == 0) {- xmax = 0;- xmin = 0;- ymax = 0;- ymin = 0; - }-- // inside and outside in vala lambda functions reveals a tricky problem- // (look at c code). that's the reason for the !new_val expression-- xmax = -10000;- xmin = 10000;- ymax = -10000;- ymin = 10000;-- double txmax = -10000;- double txmin = 10000;- double tymax = -10000;- double tymin = 10000;-- bool new_val = false;- - all_of_path ((cx, cy) => { - if (!new_val) {- txmax = cx;- txmin = cx;- tymax = cy;- tymin = cy;- new_val = true;- }- - if (cx < txmin) {- txmin = cx;- }-- if (cx > txmax) {- txmax = cx;- }- - if (cy < tymin) {- tymin = cy;- }-- if (cy > tymax) {- tymax = cy;- }-- return true;- });- - xmax = txmax;- xmin = txmin;- ymax = tymax;- ymin = tymin;-- if (unlikely (!new_val)) {- // only one point- } else if (unlikely (!got_region_boundries ())) {- warning (@"No new region boundries.\nPoints.length: $(points.length ())");- print_boundries ();- }- }- - /** Test if @param path is a valid outline for this object. */ - public bool test_is_outline (Path path) {- assert (false);- return this.test_is_outline_of_path (path) && path.test_is_outline_of_path (this);- }- - private bool test_is_outline_of_path (Path outline)- requires (outline.points.length () >= 2 || points.length () >= 2)- { - // rather slow use it for testing, only- unowned List<EditPoint> i = outline.points.first ();- unowned List<EditPoint> prev = i.last ();-- double tolerance = 1;- bool g = false;- - EditPoint ep = new EditPoint (0, 0);- double min = double.MAX;- - while (true) {- min = 10000;- - all_of (prev.data, i.data, (cx, cy) => {- get_closest_point_on_path (ep, cx, cy);-- double n = pow (ep.x - cx, 2) + pow (cy - ep.y, 2);- - if (n < min) min = n;- - if (n < tolerance) {- g = true;- return false;- }-- return true;- });- - if (!g) {- critical (@"this path does not seem to be the outline. (min $min)");- }- - g = false;- - if (i == i.last ()) {- break;- }- - i = i.next;- }- - return true;- }-- /** Convert quadratic bezier points to cubic representation of the glyph- * suitable for ttf-export.- */ - public Path get_quadratic_points () {- EditPoint middle = new EditPoint ();- unowned List<EditPoint> e;- EditPointHandle eh;- - quadratic_path = copy ();- quadratic_path.close ();- - if (quadratic_path.points.length () < 2) {- return quadratic_path;- }-- // split all curves in as many regions as we need- split_cubic_in_parts (quadratic_path); -- // estimate quatdratic form- middle.prev = quadratic_path.points.last ();- middle.next = quadratic_path.points.first ();-- convert_to_quadratic (quadratic_path, middle);- - e = (!) quadratic_path.points.first ();- while (e != quadratic_path.points.last ().prev) {- middle.prev = e;- middle.next = e.next;- - convert_to_quadratic (quadratic_path, middle); - - e = (!) e.next;- }- - eh = ((!)quadratic_path.points.last ().prev).data.get_left_handle ();- eh.set_point_type (PointType.NONE);- eh.length = 0;-- eh = ((!)quadratic_path.points.last ().prev).data.get_right_handle ();- eh.set_point_type (PointType.CURVE);- eh.length *= 1.6;-- return quadratic_path;- }-- void convert_to_quadratic (Path cubic_path, EditPoint middle) {- double curve_x, curve_y; - EditPointHandle eh;- - EditPoint start = middle.get_prev ().data;- EditPoint stop = middle.get_next ().data;-- estimate_quadratic (start, stop, out curve_x, out curve_y);- - eh = start.get_left_handle ();- eh.set_point_type (PointType.NONE);- eh.length = 0;- - eh = start.get_right_handle ();- eh.set_point_type (PointType.CURVE);- eh.move_to_coordinate (curve_x, curve_y);- }-- public void split_cubic_in_parts (Path cubic_path) {- int j = 0;- while (split_all_cubic_in_half (cubic_path)) {- j++;- - if (j > 5) {- warning ("too many iterations in split path");- break;- }- } - }-- public bool split_all_cubic_in_half (Path cubic_path) {- EditPoint middle = new EditPoint ();- unowned List<EditPoint> e;- bool need_split = false;- uint len = cubic_path.points.length ();- - if (len < 2) {- return false;- }-- middle.prev = cubic_path.points.last ();- middle.next = cubic_path.points.first ();- - if (split_cubic_in_half (cubic_path, middle)) {- need_split = true;- }- - e = (!) cubic_path.points.first ();- for (uint i = 0; i < len - 1; i++) {- middle.prev = e;- middle.next = e.next;- - middle.get_prev ().data.recalculate_linear_handles ();- middle.get_next ().data.recalculate_linear_handles ();- - if (split_cubic_in_half (cubic_path, middle)) {- e = (!) e.next.next;- need_split = true;- } else {- e = (!) e.next;- }- }- - return need_split; - }-- public void estimate_quadratic (EditPoint start, EditPoint stop, out double curve_x, out double curve_y) {- curve_x = -0.25 * start.x + 0.75 * stop.get_left_handle ().x () + 0.75 * start.get_right_handle ().x () - 0.25 * stop.x;- curve_y = -0.25 * start.y + 0.75 * stop.get_left_handle ().y () + 0.75 * start.get_right_handle ().y () - 0.25 * stop.y; - }-- bool split_cubic_in_half (Path cubic_path, EditPoint middle) {- double curve_x, curve_y, cx, cy, qx, qy, nx, ny, distance; - - EditPoint start = middle.get_prev ().data;- EditPoint stop = middle.get_next ().data;- - estimate_quadratic (start, stop, out curve_x, out curve_y);- - qx = quadratic_bezier_path (0.75, start.x, curve_x, stop.x);- qy = quadratic_bezier_path (0.75, start.y, curve_y, stop.y);-- cx = bezier_path (0.75, start.x, start.get_right_handle ().x (), stop.get_left_handle ().x (), stop.x);- cy = bezier_path (0.75, start.y, start.get_right_handle ().y (), stop.get_left_handle ().y (), stop.y);-- distance = Math.sqrt (Math.pow (cx - qx, 2) + Math.pow (cy - qy, 2));-- nx = bezier_path (0.5, start.x, start.get_right_handle ().x (), stop.get_left_handle ().x (), stop.x);- ny = bezier_path (0.5, start.y, start.get_right_handle ().y (), stop.get_left_handle ().y (), stop.y);-- if (Math.fabs (distance) > 0.01) {- EditPoint new_edit_point = new EditPoint (nx, ny, PointType.CURVE);-- new_edit_point.next = middle.get_next ();- new_edit_point.prev = middle.get_prev ();- cubic_path.insert_new_point_on_path (new_edit_point);- cubic_path.create_list ();-- return true; - }- - return false;- }-- double distance_to_path (EditPoint start, EditPoint stop, double x, double y) {- double min = double.MAX;- - all_of (start, stop, (cx, cy, t) => {- double n = pow (x - cx, 2) + pow (y - cy, 2);- - if (n < min) {- min = n;- }- - return true;- });- - return min;- }-- public void insert_new_point_on_path (EditPoint? epp) {- EditPoint start, stop;- double x0, x1, y0, y1;- double px, py;- - double position, t, d, min;- double steps = 500;- - EditPoint ep;-- if (epp == null) {- return;- }-- ep = (!) epp;-- start = ep.get_prev ().data;- stop = ep.get_next ().data;-- add_point_after (ep, ep.get_prev ());-- min = double.MAX;-- position = 0.5;-- for (int i = 0; i < steps; i++) {- t = i / steps;- - px = bezier_path (t, start.x, start.get_right_handle ().x (), stop.get_left_handle ().x (), stop.x);- py = bezier_path (t, start.y, start.get_right_handle ().y (), stop.get_left_handle ().y (), stop.y);- - d = Math.sqrt (Math.pow (ep.x - px, 2) + Math.pow (ep.y - py, 2));- - if (d < min) {- min = d;- position = t;- }- }- - bezier_vector (position, start.x, start.get_right_handle ().x (), stop.get_left_handle ().x (), stop.x, out x0, out x1);- bezier_vector (position, start.y, start.get_right_handle ().y (), stop.get_left_handle ().y (), stop.y, out y0, out y1);-- ep.get_left_handle ().set_point_type (PointType.CURVE);- ep.get_left_handle ().move_to_coordinate (x0, y0);- ep.get_left_handle ().parent = ep;- - ep.get_right_handle ().set_point_type (PointType.CURVE);- ep.get_right_handle ().move_to_coordinate (x1, y1);- ep.get_right_handle ().parent = ep;-- stop.get_left_handle ().length *= 1 - position;- start.get_right_handle ().length *= position;- }- - /** Get a point on the this path closest to x and y coordinates. */- public void get_closest_point_on_path (EditPoint edit_point, double x, double y) {- return_if_fail (points.length () != 0);- - double min = double.MAX;- double n = 0;- bool g = false;- - double ox = 0;- double oy = 0;- - double handle_x0, handle_x1;- double handle_y0, handle_y1;- - unowned List<EditPoint> i = points.first ();- unowned List<EditPoint> prev = i.next;-- bool done = false;- bool exit = false;- - unowned List<EditPoint>? previous_point = null;- unowned List<EditPoint>? next_point = null;-- EditPoint previous;- EditPoint next;- double step = 0;-- if (points.length () == 1) {- edit_point.x = i.data.x;- edit_point.y = i.data.y;- - edit_point.prev = i;- edit_point.next = i;- - exit = true;- - return;- }- - if (points.length () != 1) {- edit_point.x = i.data.x;- edit_point.y = i.data.y;- }- - while (!exit) {- - if (i == points.last ()) {- done = true;- }- - if (!done) {- i = i.next;- prev = i.prev;- } else if (done && !is_open ()) {- i = points.first ();- prev = points.last ();- exit = true;- } else {- break;- }- - all_of (prev.data, i.data, (cx, cy, t) => {- n = pow (x - cx, 2) + pow (y - cy, 2);- - if (n < min) {- min = n;- - ox = cx;- oy = cy;- - previous_point = prev;- next_point = i;- - step = t;- - g = true;- }- - return true;- });- }-- if (previous_point == null) {- warning (@"previous_point == null, points.length (): $(points.length ())");- return;- }- - if (next_point == null) {- warning ("next_point != null");- return;- }-- previous = ((!) previous_point).data;- next = ((!) next_point).data;-- bezier_vector (step, previous.x, previous.get_right_handle ().x (), next.get_left_handle ().x (), next.x, out handle_x0, out handle_x1);- bezier_vector (step, previous.y, previous.get_right_handle ().y (), next.get_left_handle ().y (), next.y, out handle_y0, out handle_y1);-- // position - edit_point.set_position (ox, oy);- edit_point.prev = previous_point;- edit_point.next = next_point;-- // curve (angle)- edit_point.get_right_handle ().move_to_coordinate (handle_x0, handle_y0);- edit_point.get_left_handle ().move_to_coordinate (handle_x1, handle_y1);- // FIXA: maybe just edit_point.set_tie_handle (true);-- if (unlikely (!g)) {- warning (@"Error: Got no coordinates for point on path. Num points $(points.length ())\n");- }- }-- public static void all_of (EditPoint start, EditPoint stop, RasterIterator iter, int steps = -1) {- if (steps == -1) {- steps = (int) (10 * get_length_from (start, stop));- }- - all_of_curve (start.x, start.y, start.get_right_handle ().x (), start.get_right_handle ().y (), stop.get_left_handle ().x (), stop.get_left_handle ().y (), stop.x, stop.y, iter, steps);- }-- private static void all_of_curve (double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3, RasterIterator iter, double steps = 400) {- double px = x1;- double py = y1;- - double t;- - for (int i = 0; i < steps; i++) {- t = i / steps;- - px = bezier_path (t, x0, x1, x2, x3);- py = bezier_path (t, y0, y1, y2, y3);- - if (!iter (px, py, t)) {- return;- }- - } - }- - void point_on_bezier_path (double t, EditPoint start, EditPoint stop, out double x, out double y) { - double x0 = start.x;- double y0 = start.y; - double x1 = start.get_right_handle ().x ();- double y1 = start.get_right_handle ().y ();- double x2 = stop.get_left_handle ().x ();- double y2 = stop.get_left_handle ().y (); - double x3 = stop.x;- double y3 = stop.y;-- x = bezier_path (t, x0, x1, x2, x3);- y = bezier_path (t, y0, y1, y2, y3);- }-- private void all_of_path (RasterIterator iter) {- unowned List<EditPoint> i, next;- - if (points.length () < 2) {- return;- }-- i = points.first ();- next = i.next;-- while (i != points.last ()) {- all_of (i.data, next.data, iter);- i = i.next;- next = i.next;- }- - if (!is_open ()) {- all_of (points.last ().data, points.first ().data, iter);- }- }-- public static double bezier_path (double step, double p0, double p1, double p2, double p3) {- double q0, q1, q2;- double r0, r1;-- q0 = step * (p1 - p0) + p0;- q1 = step * (p2 - p1) + p1;- q2 = step * (p3 - p2) + p2;-- r0 = step * (q1 - q0) + q0;- r1 = step * (q2 - q1) + q1;-- return step * (r1 - r0) + r0;- }-- public static void bezier_vector (double step, double p0, double p1, double p2, double p3, out double a0, out double a1) {- double q0, q1, q2;-- q0 = step * (p1 - p0) + p0;- q1 = step * (p2 - p1) + p1;- q2 = step * (p3 - p2) + p2;-- a0 = step * (q1 - q0) + q0;- a1 = step * (q2 - q1) + q1;- }-- public static double quadratic_bezier_path (double step, double p0, double p1, double p2) {- double q0 = step * (p1 - p0) + p0;- double q1 = step * (p2 - p1) + p1;- - return step * (q1 - q0) + q0;- }- - public void plot (Context cr, Allocation allocation, double view_zoom) {- double px = 0, py = 0;- double xc = allocation.width / 2.0;- double yc = allocation.height / 2.0;-- cr.save ();- - all_of_path ((x, y) => {- //cr.set_source_rgba (0.3, 0.3, 0.3, 1);- cr.move_to (px + xc, -py + yc);- cr.line_to (x + xc, -y + yc);- - px = x;- py = y;- - return true;- });-- cr.stroke ();- cr.restore ();- }- - public void print_boundries () {- stderr.printf (@"xmax $xmax \n");- stderr.printf (@"xmin $xmin \n");- stderr.printf (@"ymax $ymax \n");- stderr.printf (@"ymin $ymin \n"); - }- - public bool got_region_boundries () {- return !(xmax == -10000 || xmin == 10000 || ymax == -10000 || ymin == 10000);- }- - public void create_list () {- unowned List<EditPoint> ep = points.first ();- unowned List<EditPoint> prev = ep;- - if (points.length () == 0) {- return;- }- - if (points.length () == 1) {- ep.data.next = null;- ep.data.prev = null;- return;- }- - ep.data.next = ep.next;- ep.data.prev = points.last ();- prev = ep;- - assert (ep.data.next != null);- assert (ep.data.prev != null);- - while (ep != ep.last ()) {- ep.data.next = ep.next;- ep.data.prev = prev;- prev = ep;- - assert (ep.data.next != null);- assert (ep.data.prev != null);- - ep = ep.next;- }- - if (is_open ()) {- ep.data.next = points.first ();- } else {- ep.data.next = points.first ();- assert (ep.data.next != null);- }- - ep.data.prev = prev;- assert (ep.data.prev != null);- }-- public void delete_edit_point (EditPoint ep) - requires (points.length () > 0)- {- unowned List<EditPoint>? pl = null;- EditPoint p;- - if (ep.prev != null) {- set_new_start (ep);- }- - p = points.first ().data;- - for (uint i = 0; i < points.length (); i++) {- p = points.nth (i).data;- - if (p == ep) {- p.prev = null;- p.next = null;- - pl = points.nth (i);- break;- }- }-- if (pl != null) {- points.delete_link ((!) pl);- reopen ();- MainWindow.get_current_glyph ().add_active_path (this);- create_list ();- }- }- - public void set_new_start (EditPoint ep) {- List<EditPoint> list = new List<EditPoint> ();- uint len = points.length ();- unowned List<EditPoint> iter = points.first ();- unowned List<EditPoint>? ni = null;- bool found = false;- - foreach (EditPoint it in points) {- if (it == ep) {- found = true;- break;- }- - iter = iter.next;- ni = (!) iter;- }- - if (unlikely (!found)) {- warning ("Could not find edit point.");- }- - if (ni == null) {- return; - }- - iter = (!) ni;- - for (uint i = 0; i < len; i++) {- list.append (iter.data);- - if (iter == iter.last ())- iter = iter.first ();- else- iter = iter.next;- - }- - while (points.length () > 0) {- points.remove_link (points.first ());- }- - foreach (EditPoint p in list) {- points.append (p);- }- - }- - public static PathList merge (Path p0, Path p1) {- bool done;- PathList path_list;- - done = try_merge (p0.copy(), p1.copy(), out path_list);- - if (!done) {- warning ("failed to merge paths");- }- - return path_list;- }- - private static bool try_merge (Path p0, Path p1, out PathList path_list) {- EditPoint e;- IntersectionList il;- int i;- Path np;- - unowned List<Path> pi;- - il = IntersectionList.create_intersection_list (p0, p1);- - path_list = new PathList ();- - if (p0 == p1) {- return false;- }- - // add editpoints on intersections - p0.update_region_boundries ();- p1.update_region_boundries ();- foreach (Intersection inter in il.points) {- e = new EditPoint ();- p0.get_closest_point_on_path (e, inter.x, inter.y);- inter.editpoint_a = e;- - e = inter.editpoint_a;- if (!p0.has_edit_point (e)) {- p0.insert_new_point_on_path (e);- }-- e = new EditPoint ();- p1.get_closest_point_on_path (e, inter.x, inter.y);- inter.editpoint_b = e;- - inter.editpoint_b.x = inter.editpoint_a.x;- inter.editpoint_b.y = inter.editpoint_a.y;- - e = inter.editpoint_b;- if (!p1.has_edit_point (e)) {- p1.insert_new_point_on_path (e);- }- }- - if (il.points.length () < 2) {- return false;- }- - //path_list.paths.append (p0);- //path_list.paths.append (p1);- //return false;- - // get all parts- foreach (Intersection inter in il.points) {- p0.set_new_start (inter.editpoint_a);- get_merge_part (il, p0, p1, out np);- path_list.paths.append (np);- }- - foreach (Path pp in path_list.paths) {- pp.update_region_boundries ();- }-- // remove duplicate paths- for (i = 0; i < path_list.paths.length (); i++) {- pi = path_list.paths.nth (i);-- if (is_duplicated (path_list, pi.data)) {- path_list.paths.remove_link (pi);- --i; - }- }- - // remove paths contained in other paths- for (i = 0; i < path_list.paths.length (); i++) {- pi = path_list.paths.nth (i);- - if (pi.data.is_clockwise () && is_clasped (path_list, pi.data)) {- path_list.paths.remove_link (pi);- i--;- }- }- - return true;- }- - private static bool is_duplicated (PathList pl, Path p) {- bool duplicate = false;- foreach (Path pd in pl.paths) {- if (pd == p) {- continue;- }- - if (is_duplicated_path (p, pd)) {- duplicate = true;- }- }- - return duplicate;- }- - private static bool is_duplicated_path (Path p0, Path p1) {- bool eq;- - assert (p1 != p0); - - foreach (EditPoint ep in p0.points) {- eq = false;- - foreach (EditPoint e in p1.points) {- eq = (Math.fabs (ep.x - e.x) < 0.04 && Math.fabs (ep.y - e.y) < 0.04);- - if (eq) {- break;- }- }- - if (eq) {- continue;- } else {- return false;- }- }- - return true;- }- - private static bool is_clasped (PathList pl, Path p) {- foreach (Path o in pl.paths) {- if (o == p) {- continue;- }- - if (is_clasped_path (o, p)) {- return true;- }- }- - return false;- }-- private static bool is_clasped_path (Path outside, Path inside) {- bool i = true;- foreach (EditPoint e in inside.points) {- if (!outside.is_over_coordinate_var (e.x, e.y, 0.5)) { // high tolerance since point may be off curve in both paths- i = false;- break;- }- }- return i;- }- - private static bool get_merge_part (IntersectionList il, Path p0, Path p1, out Path new_path) {- EditPoint ex;- EditPoint ix;- EditPoint en;- uint offset_i = 0;- uint offset_j;- uint len_i;- int i, j;- uint len_j;- Path np = new Path ();- Intersection s = new Intersection (0, 0, 1);- - ex = p0.points.last ().data;- ix = p0.points.last ().data;- len_i = p0.points.length ();- - for (i = 0; i < p0.points.length (); i++) {- ex = p0.points.nth ((i + offset_i) % len_i).data;-- if (ex == p0.points.first ().data && i != 0) { - s = (!) il.get_intersection (ex); - break;- }-- // add new point for path a- if (np.has_edit_point (ex)) {- // SPLIT- warning ("Merged path need split");- np.close ();- new_path = np;- - return false;- } else {- en = ex.copy ();- np.add_point (en);- en.recalculate_linear_handles ();- }- - // swap paths- if (il.has_edit_point (ex)) {- s = (!) il.get_intersection (ex);- - en.type = PointType.CURVE;- en.right_handle.type = PointType.CURVE;- en.right_handle.angle = s.editpoint_b.right_handle.angle;- en.right_handle.length = s.editpoint_b.right_handle.length;- - // read until we find ex- for (j = 0; j < p1.points.length (); j++) {- ix = p1.points.nth (j).data;- - if (ix == s.editpoint_b) {- break;- }- }- - offset_j = j + 1;- len_j = p1.points.length ();- for (j = 0; j < p1.points.length (); j++) {- - ix = p1.points.nth ((j + offset_j) % len_j).data;- - // add- if (np.has_edit_point (ix)) {- // SPLIT- warning ("Merged path need split");- np.close ();- new_path = np;- - break;- } else {- en = ix.copy ();- np.add_point (en);- ix.recalculate_linear_handles ();- }- - if (il.has_edit_point (ix)) {- s = (!) il.get_intersection (ix);- break;- }- }-- en.type = PointType.CURVE;- en.right_handle.type = PointType.CURVE;- en.right_handle.angle = s.editpoint_a.right_handle.angle;- en.right_handle.length = s.editpoint_a.right_handle.length;- - if (j == p0.points.length ()) {- np.close ();- new_path = np;- return true;- }-- // skip to next intersection- int k;- for (k = 0; k < p0.points.length (); k++) {- ix = p0.points.nth (k).data; -- if (ix == s.editpoint_a) {- break;- }- }- - if (k == p0.points.length ()) {- new_path = np;- return true;- }- - offset_i = 0;- i = k;- }- }-- new_path = np;- - return true; - }--}--public class PathList : GLib.Object {- public List<Path> paths = new List<Path> ();- - public PathList () {- }- - public void clear () {- while (paths.length () > 0) {- paths.remove_link (paths.first ());- }- }-}--}deleted file mode 100644@@ -1,1001 +0,0 @@-/*- 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/>.-*/--namespace Supplement {--class PenTool : Tool {-- private static const double CONTACT_SURFACE = 50;-- public static bool move_selected = false;- public static bool begin_new_point_on_path = false;- public static bool move_point_on_path = false;-- public static bool edit_active_corner = false;- - public static List<EditPoint> selected_points = new List<EditPoint> (); -- public static EditPointHandle active_handle = new EditPointHandle.empty ();- public static EditPointHandle selected_handle = new EditPointHandle.empty ();- - public static EditPoint? active_edit_point = new EditPoint ();-- /** Move handle vertical or horizontal. */- public static bool tie_x_or_y_coordinates = false;-- private static bool move_selected_handle = false;-- private static double last_point_x = 0;- private static double last_point_y = 0;-- public static double precision = 1;- - public PenTool (string name) {- base (name, _("Right click to add new points, left click to move points") + " " + _("and double click to add new point on path."), ',', CTRL);- - select_action.connect ((self) => {- });-- deselect_action.connect ((self) => {- Glyph glyph = MainWindow.get_current_glyph ();- - CutTool.force_direction ();- glyph.close_path ();- - move_point_on_path = false;- begin_new_point_on_path = false;- });- - press_action.connect ((self, b, x, y) => {- last_point_x = x;- last_point_y = y;-- press (b, x, y, false);- });- - double_click_action.connect ((self, b, x, y) => {- last_point_x = x;- last_point_y = y;-- press (b, x, y, true);- });-- release_action.connect ((self, b, ix, iy) => {- double x = ix;- double y = iy;- Glyph g = MainWindow.get_current_glyph ();- EditPoint selected_corner;- - if (move_selected && GridTool.is_visible () && selected_points.length () > 0) {- selected_corner = selected_points.last ().data;- GridTool.tie (ref x, ref y);- g.move_selected_edit_point (selected_corner, x, y);- }-- move_selected = false;- move_selected_handle = false;- edit_active_corner = false;- - active_handle = new EditPointHandle.empty ();- - move (x, y);- });-- move_action.connect ((self, x, y) => {- move (x, y);- });- - key_press_action.connect ((self, keyval) => {- Glyph g = MainWindow.get_current_glyph ();- - if (keyval == Key.DEL) {- foreach (EditPoint p in selected_points) {- g.delete_edit_point (p);- }- - g.update_view ();- }- });- - key_release_action.connect ((self, keyval) => {- });- }- - public double get_precision () {- return precision;- }- - public void set_precision (double p) {- precision = p;- MainWindow.get_toolbox ().precision.set_value_round (p, false, false);- }- - public void move (double x, double y) {- Glyph glyph = MainWindow.get_current_glyph ();- - control_point_event (x, y);- curve_active_corner_event (x, y);- set_default_handle_positions ();-- // show new point on path- if (glyph.new_point_on_path != null) {- move_current_point_on_path (x, y);- }- - // move curve handles- if (move_selected_handle) {- if (GridTool.is_visible ()) {- GridTool.tie (ref x, ref y);- selected_handle.set_point_type (PointType.CURVE);- selected_handle.move_to (x, y);- } else {- selected_handle.set_point_type (PointType.CURVE);- selected_handle.move_delta ((x - last_point_x) * precision, (y - last_point_y) * precision);- }- - selected_handle.parent.recalculate_linear_handles ();- - // Fixa: redraw line only- glyph.redraw_area (0, 0, glyph.allocation.width, glyph.allocation.height);- - last_point_x = x;- last_point_y = y;- - return;- }- - // move edit point- if (move_selected) {- foreach (EditPoint p in selected_points) {- glyph.move_selected_edit_point_delta (p, (x - last_point_x) * precision, (y - last_point_y) * precision);- - if (tie_x_or_y_coordinates) {- GridTool.tie_to_prev (p, x, y);- }- - p.recalculate_linear_handles ();- }- }- - last_point_x = x;- last_point_y = y;- }- - public void press (int button, int x, int y, bool double_click) {- Glyph? g = MainWindow.get_current_glyph ();- Glyph glyph = (!) g;- - return_if_fail (g != null);-- if (button == 2) {- if (glyph.is_open ()) {- CutTool.force_direction ();- glyph.close_path ();- } else {- glyph.open_path ();- }- - return;- }-- if (double_click) {- glyph.insert_new_point_on_path (x, y);- return;- }-- if (is_new_point_from_path_selected ()) {- new_point_on_path_at (x, y);- return; - }-- // add new point on path - if (is_new_point_from_path_selected ()) {- move_selected = true;- move_point_on_path = true;- glyph.new_point_on_path = null;- return;- }- - // add new point- if (button == 3) {- remove_all_selected_points ();- new_point_action (x, y);- glyph.store_undo_state ();- return;- }- - control_point_event (x, y);- curve_corner_event (x, y);- - if (!move_selected_handle) {- select_active_point (x, y);- }-- glyph.store_undo_state ();- }- - public void select_active_point (double x, double y) {- Glyph? g = MainWindow.get_current_glyph ();- Glyph glyph = (!) g;- - if (KeyBindings.modifier != SHIFT) {- if (active_edit_point != null) {- ((!)active_edit_point).set_active (false);- }-- remove_all_selected_points ();- }- - move_selected = true;- move_point_on_path = true;-- if (active_edit_point != null) {- ((!)active_edit_point).set_selected (true);- }-- if (!is_over_handle (x, y)) {- edit_active_corner = true;- set_default_handle_positions ();- - if (active_edit_point != null) {- add_selected_point ((!) active_edit_point);- }- }- - // continue adding points from the selected one- foreach (Path p in glyph.active_paths) {- if (p.is_open () && p.points.length () > 0 && active_edit_point == p.points.first ().data) {- p.reverse ();- }- }- }- - public void move_current_point_on_path (double x, double y) {- Glyph g = MainWindow.get_current_glyph ();-- EditPoint e;- - double rax, ray;- double pax, pay;-- double distance, min;- - return_if_fail (g.new_point_on_path != null);- - if (GridTool.is_visible ()) {- GridTool.tie (ref x, ref y);- }-- min = double.MAX;-- foreach (Path p in g.path_list) {- if (p.points.length () < 2) {- continue;- }- - e = new EditPoint ();- - pax = x * Glyph.ivz () + g.view_offset_x - Glyph.xc ();- pay = y * Glyph.ivz () + g.view_offset_y - Glyph.yc ();-- pay *= -1;-- p.get_closest_point_on_path (e, pax, pay);- - distance = Math.sqrt (Math.pow (Math.fabs (pax - e.x), 2) + Math.pow (Math.fabs (pay - e.y), 2));- - if (distance < min) {- min = distance;- - g.new_point_on_path = e;- - rax = (pax + g.view_offset_x - Glyph.xc ()) / Glyph.ivz ();- ray = (pax + g.view_offset_x - Glyph.xc ()) / Glyph.ivz ();- - g.redraw_area (rax - 5, ray - 5, 10, 10); - }- }- }-- public void set_new_point_on_path (Path ap, int x, int y) {- Glyph g = MainWindow.get_current_glyph ();- - return_if_fail (ap.is_editable ());- - g.clear_active_paths ();- g.add_active_path (ap);-- if (g.new_point_on_path == null) {- g.new_point_on_path = new EditPoint (0, 0, PointType.FLOATING);- }- - move_current_point_on_path (x, y);- }- - public static void set_active_edit_point (EditPoint? e) {- Glyph g = MainWindow.get_current_glyph ();- foreach (var p in g.path_list) {- foreach (var ep in p.points) {- ep.set_active (false);- }- }- - active_edit_point = e;- - if (e != null) {- ((!)e).set_active (true);- }-- g.redraw_area (0, 0, g.allocation.width, g.allocation.height);- }-- EditPoint? get_closest_point (double ex, double ey, out Path? path) {- double x = Glyph.path_coordinate_x (ex);- double y = Glyph.path_coordinate_y (ey);- double d = double.MAX;- double nd;- EditPoint? ep = null;- Glyph g = MainWindow.get_current_glyph ();- - path = null;- - foreach (Path current_path in g.path_list) {- foreach (EditPoint e in current_path.points) {- nd = e.get_distance (x, y);- - if (nd < d) {- d = nd;- ep = e;- path = current_path;- }- } - }- - return ep;- }-- public double get_distance_to_closest_edit_point (double event_x, double event_y) {- Path? p;- EditPoint e;- EditPoint? ep = get_closest_point (event_x, event_y, out p);-- double x = Glyph.path_coordinate_x (event_x);- double y = Glyph.path_coordinate_y (event_y);- - if (ep == null) {- return double.MAX;- }- - e = (!) ep;- - return e.get_distance (x, y);- }-- public void control_point_event (double event_x, double event_y) {- Path? p;- EditPoint? ep = get_closest_point (event_x, event_y, out p);- Glyph g = MainWindow.get_current_glyph ();- double x = Glyph.path_coordinate_x (event_x);- double y = Glyph.path_coordinate_y (event_y);- double distance;- EditPoint e;- - set_active_edit_point (null);- - if (ep == null) {- return; - }- - e = (!) ep;- distance = e.get_distance (x, y) * g.view_zoom;-- if (distance < CONTACT_SURFACE) {- set_active_edit_point (ep);- g.add_active_path (p);- }- }-- public static bool insert_new_point_on_path_selected () {- Tool t = MainWindow.get_toolbox ().get_tool ("insert_point_on_path");- return t.is_selected ();- }-- public static bool is_new_point_from_path_selected () {- if (!Supplement.experimental) {- return false;- }- - Tool t = MainWindow.get_toolbox ().get_tool ("new_point_on_path");- return t.is_selected ();- }-- public static bool is_erase_selected () {- Tool t = MainWindow.get_toolbox ().get_tool ("erase_tool");- return t.is_selected ();- }- - public void new_point_action (int x, int y) {- Glyph glyph;- EditPoint new_point;- glyph = MainWindow.get_current_glyph ();- glyph.open_path ();- - new_point = glyph.add_new_edit_point (x, y);- new_point.set_selected (true);- - if (KeyBindings.modifier != SHIFT) {- remove_all_selected_points ();- }- - add_selected_point (new_point);- - move_selected = true;- }-- void set_linear_handles_for_last_point () {- Glyph glyph;- glyph = MainWindow.get_current_glyph ();- return_if_fail (glyph.active_paths.length () > 0);- return_if_fail (glyph.active_paths.last ().data.points.length () > 0);- glyph.active_paths.last ().data.points.last ().data.right_handle.parent.recalculate_linear_handles ();- }-- public static void set_default_handle_positions () {- Glyph g = MainWindow.get_current_glyph ();- foreach (var p in g.path_list) {- if (p.is_editable ()) {- p.create_list ();- set_default_handle_positions_on_path (p);- }- }- }-- static void set_default_handle_positions_on_path (Path path) {- foreach (EditPoint e in path.points) {- e.recalculate_linear_handles ();- }- }-- bool new_point_on_path_at (int x, int y) {- EditPoint ep;- Glyph glyph = MainWindow.get_current_glyph ();- - if (is_new_point_from_path_selected ()) {- - if (glyph.new_point_on_path != null) {- ep = (!)glyph.new_point_on_path; - - begin_new_point_on_path = false;- - glyph.add_new_edit_point (glyph.reverse_path_coordinate_x (ep.x), glyph.reverse_path_coordinate_x (ep.y));- glyph.new_point_on_path = null;- - MainWindow.get_toolbox ().select_tool_by_name ("new_point");-- return false;- }- - start_from_new_point_on_path (x, y);- begin_new_point_on_path = true;- - return false;- } else {- begin_new_point_on_path = false;- }- - return true;- }-- void start_from_new_point_on_path (double x, double y) { - Glyph g = MainWindow.get_current_glyph ();- Path? p = null;- Path pn;- - p = g.get_closeset_path (x, y);-- pn = (!)p;-- if (pn.points.length () >= 2) {- pn.set_editable (true);- set_new_point_on_path (pn, 0, 0);- g.open_path ();- }- - begin_new_point_on_path = false;- }- - public void begin_from_new_point_on_path () {- begin_new_point_on_path = true;- }- - private bool is_over_handle (double event_x, double event_y) {- double x = Glyph.path_coordinate_x (event_x);- double y = Glyph.path_coordinate_y (event_y);- Glyph g = MainWindow.get_current_glyph (); - double d_point = g.view_zoom * get_distance_to_closest_edit_point (event_x, event_y);- - double dl, dr;- - foreach (EditPoint selected_corner in selected_points) {- dl = g.view_zoom * selected_corner.get_left_handle ().get_point ().get_distance (x, y);- dr = g.view_zoom * selected_corner.get_right_handle ().get_point ().get_distance (x, y);- - if (dl < CONTACT_SURFACE && dl < d_point) {- return true;- }-- if (dr < CONTACT_SURFACE && dr < d_point) {- return true;- }- }- - return false;- }-- EditPointHandle get_closest_handle (double event_x, double event_y) {- EditPointHandle left, right;- double x = Glyph.path_coordinate_x (event_x);- double y = Glyph.path_coordinate_y (event_y); - EditPointHandle eh = new EditPointHandle.empty();-- double d = double.MAX;- double dn;- - foreach (EditPoint selected_corner in selected_points) {- left = selected_corner.get_left_handle ();- right = selected_corner.get_right_handle ();-- dn = left.get_point ().get_distance (x, y);- - if (dn < d) {- eh = left;- d = dn;- }-- dn = right.get_point ().get_distance (x, y);- - if (dn < d) {- eh = right;- d = dn;- }- }- - return eh;- }-- private void curve_active_corner_event (double event_x, double event_y) {- EditPointHandle eh;- - active_handle.active = false;- - if (!is_over_handle (event_x, event_y)) {- return;- } - - eh = get_closest_handle (event_x, event_y);- eh.active = true;- active_handle = eh;- }-- private void curve_corner_event (double event_x, double event_y) {- MainWindow.get_current_glyph ().open_path ();-- if (!is_over_handle (event_x, event_y)) {- return;- }-- move_selected_handle = true;- selected_handle = get_closest_handle (event_x, event_y);- }-- void add_selected_point (EditPoint p) {- foreach (EditPoint ep in selected_points) {- if (p == ep) {- return;- }- }- - selected_points.append (p);- }- - void remove_all_selected_points () {- foreach (EditPoint e in selected_points) {- e.set_active (false);- e.set_selected (false);- }- - while (selected_points.length () > 0) {- selected_points.remove_link (selected_points.first ());- }- }- - /** Draw a test glyph. */- public override bool test () {- test_select_action ();- - test_open_next_glyph ();- - // paint- test_click_action (1, 30, 30); - test_click_action (1, 60, 30);- test_click_action (1, 60, 60);- test_click_action (1, 30, 60);- - // close- test_click_action (3, 0, 0);-- // reopen- test_click_action (3, 35, 35);- - // move around- test_move_action (100, 200);- test_move_action (20, 300);- test_move_action (0, 0);- - // add to path- test_move_action (70, 50);- - test_click_action (1, 70, 50);- test_click_action (1, 70, 50);- test_click_action (1, 70, 100);- test_click_action (1, 50, 100); - test_click_action (1, 50, 50);- - // close- test_click_action (3, 0, 0);- - // merge it- - - this.yield ();- - return true;- }-- public void test_active_edit_point () {- Glyph g;- EditPoint epa, epb;- - // paint- test_select_action ();- test_open_next_glyph ();-- g = MainWindow.get_current_glyph ();-- test_click_action (1, 130, 130); // open path- test_click_action (1, 130, 130); // add point- epa = g.get_last_edit_point ();- - test_click_action (1, 160, 130);- test_click_action (1, 160, 160);- epb = g.get_last_edit_point ();- - test_click_action (1, 130, 160);- - // validate active point- test_move_action (130, 130);- warn_if_fail (active_edit_point == epa);- - test_move_action (161, 161); // close but not on- warn_if_fail (active_edit_point == epb);- - warn_if_fail (epa != epb);- - // TODO: Test move handle here.- }-- /** Test path coordinates and reverse path coordinates. */- public void test_coordinates () {- int x, y, xc, yc;- double px, py, mx, my;- string n;- - Tool.test_open_next_glyph ();- Glyph g = MainWindow.get_current_glyph ();- - xc = (int) (g.allocation.width / 2.0);- yc = (int) (g.allocation.height / 2.0);-- g.default_zoom ();- - x = 10;- y = 15;- - px = g.path_coordinate_x (x);- py = g.path_coordinate_y (y);-- mx = x * g.ivz () - g.xc () + g.view_offset_x;- my = g.yc () - y * g.ivz () - g.view_offset_y;- - if (mx != px || my != py) {- warning (@"bad coordinate $mx != $px || $my != $py");- }- - test_reverse_coordinate (x, y, px, py, "ten fifteen");- test_click_action (1, x, y);- - // offset no zoom- n = "Offset no zoom";- g.reset_zoom ();- - px = g.path_coordinate_x (x);- py = g.path_coordinate_y (y);- - test_reverse_coordinate (x, y, px, py, n);- test_click_action (1, x, y);- - // close path- test_click_action (3, x, y);- }- - private void test_coordinate (double x, double y, double px, double py, string n) {- if (!(px - 0.5 <= x <= px + 0.5|| py - 0.5 <= y <= py + 0.5)) {- warning (@"Expecting (x == px || y == py) got ($x == $px || $y == $py) in \"$n\"\n");- }- }- - private void test_reverse_coordinate (int x, int y, double px, double py, string n) {- Glyph g = MainWindow.get_current_glyph ();- if (x != g.reverse_path_coordinate_x (px) || g.reverse_path_coordinate_y (py) != y) {- warning (@"Reverse coordinates does not match current point for test case \"$n\".\n $x != $(g.reverse_path_coordinate_x (px)) || $(g.reverse_path_coordinate_y (py)) != $y (x != g.reverse_path_coordinate_x (px) || g.reverse_path_coordinate_y (py) != y)");- }- }-- private void test_last_is_counter_clockwise (string name) {- bool d = ((!)MainWindow.get_current_glyph ().get_last_path ()).is_clockwise ();- - if (d) {- critical (@"\nPath $name is clockwise, in test_last_is_counter_clockwise");- } - }- - private void test_last_is_clockwise (string name) {- bool d = ((!)MainWindow.get_current_glyph ().get_last_path ()).is_clockwise ();- - if (!d) {- critical (@"\nPath $name is counter clockwise, in test_last_is_clockwise");- }-- }- - private bool test_reverse_last (string name) - requires (MainWindow.get_current_glyph ().get_last_path () != null)- {- Glyph g = MainWindow.get_current_glyph ();- Path p = (!) g.get_last_path ();- bool direction = p.is_clockwise ();-- p.reverse ();- - if (direction == p.is_clockwise ()) {- critical (@"Direction did not change after reverseing path \"$name\"\n");- stderr.printf (@"Path length: $(p.points.length ()) \n");- return false;- }-- this.yield ();- return true;- }- - - class Point {- - public int x;- public int y;- - public Point (int x, int y) {- this.x = x;- this.y = y;- }- }- - private Point p (int x, int y) {- return new Point (x, y);- }- - private void test_triangle (Point a, Point b, Point c, string name = "") {- 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 ();- - pen_tool.test_click_action (1, 0, 0); // open path- - pen_tool.test_click_action (1, a.x, a.y);- pen_tool.test_click_action (1, b.x, b.y);- pen_tool.test_click_action (1, c.x, c.y);- pen_tool.test_click_action (3, 0, 0);- - test_reverse_last (@"Triangle reverse \"$name\" ($(a.x), $(a.y)), ($(b.x), $(b.y)), ($(c.x), $(c.y)) failed.");- - this.yield ();- }- - private void test_various_triangles () {- test_triangle (p (287, 261), p (155, 81), p (200, 104), "First");- test_triangle (p (65, 100), p (168, 100), p (196, 177), "Second");- test_triangle (p (132, 68), p (195, 283), p (195, 222), "Third");- test_triangle (p (144, 267), p (147, 27), p (296, 267), "Fourth");- }- - public bool test_reverse_path () {- // 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- int x_offset = 10;- int y_offset = 10;- - test_open_next_glyph ();- test_various_triangles ();- - test_open_next_glyph ();- // draw clockwise and check direction -- y_offset += 160;- pen_tool.test_click_action (1, 10 + x_offset, 20 + y_offset);- pen_tool.test_click_action (1, 17 + x_offset, 17 + y_offset);- pen_tool.test_click_action (1, 20 + x_offset, 0 + y_offset);- pen_tool.test_click_action (3, 0, 0);- test_last_is_clockwise ("Clockwise triangle 1.2");-- // draw paths clockwise / counter clockwise and reverse them- - pen_tool.test_click_action (1, 115, 137);- pen_tool.test_click_action (1, 89, 74);- pen_tool.test_click_action (1, 188, 232);- pen_tool.test_click_action (3, 0, 0);- test_reverse_last ("Triangle 0");-- // draw incomplete paths- y_offset += 20;- pen_tool.test_click_action (1, 10 + x_offset, 20 + y_offset);- pen_tool.test_click_action (3, 0, 0);- test_reverse_last ("Point");-- y_offset += 20;- pen_tool.test_click_action (1, 10 + x_offset, 20 + y_offset);- pen_tool.test_click_action (1, 10 + x_offset, 20 + y_offset);- pen_tool.test_click_action (3, 0, 0);- test_reverse_last ("Double point");-- y_offset += 20;- pen_tool.test_click_action (1, 10 + x_offset, 30 + y_offset);- pen_tool.test_click_action (1, 10 + x_offset, 20 + y_offset);- pen_tool.test_click_action (3, 0, 0);- test_reverse_last ("Vertical line");-- y_offset += 20;- pen_tool.test_click_action (1, 30 + x_offset, 20 + y_offset);- pen_tool.test_click_action (1, 10 + x_offset, 20 + y_offset);- pen_tool.test_click_action (3, 0, 0);- test_reverse_last ("Horisontal line");-- // triangle 1- y_offset += 20;- pen_tool.test_click_action (1, 10 + x_offset, -10 + y_offset);- pen_tool.test_click_action (1, 20 + x_offset, 20 + y_offset);- pen_tool.test_click_action (1, 30 + x_offset, 0 + y_offset);- pen_tool.test_click_action (3, 0, 0);- test_reverse_last ("Triangle reverse 1");-- // box- y_offset += 20;- pen_tool.test_click_action (1, 100 + x_offset, 150 + y_offset);- pen_tool.test_click_action (1, 150 + x_offset, 150 + y_offset);- pen_tool.test_click_action (1, 150 + x_offset, 100 + y_offset);- pen_tool.test_click_action (1, 100 + x_offset, 100 + y_offset); - pen_tool.test_click_action (3, 0, 0); // close- test_reverse_last ("Box 1");- - return true;- }- - private Tool select_pen () {- Tool pen_tool = MainWindow.get_toolbox ().get_tool ("pen_tool");- pen_tool.test_select_action ();- return pen_tool;- }- - public bool test_delete_points () {- PenTool pen;- Tool delete_tool = MainWindow.get_toolbox ().get_tool ("erase_tool");- - test_open_next_glyph ();- - pen = (PenTool) select_pen ();- pen.test_click_action (1, 0, 0); // open path- - // draw a line with ten points- for (int i = 1; i <= 10; i++) {- pen.test_click_action (1, 20*i, 20);- } - - // TODO: it would be nice to test if points were created here- - // delete points- delete_tool.test_select_action ();- for (int i = 1; i <= 10; i++) {- pen.move (20*i, 20);- pen.test_click_action (1, 20*i, 20);- }- - return true;- }- - public bool test_reverse_random_triangles () {- Tool pen;- - int ax, bx, cx;- int ay, by, cy;-- bool r = true;-- test_open_next_glyph ();- pen = select_pen ();-- for (int i = 0; i < 30; i++) {- this.yield ();- - ax = Random.int_range (0, 300);- bx = Random.int_range (0, 300);- cx = Random.int_range (0, 300);-- ay = Random.int_range (0, 300);- by = Random.int_range (0, 300);- cy = Random.int_range (0, 300);-- pen.test_click_action (1, ax, ay);- pen.test_click_action (1, bx, by);- pen.test_click_action (1, cx, cy);- pen.test_click_action (3, 0, 0);- - r = test_reverse_last (@"Random triangle № $(i + 1) ($ax, $ay), ($bx, $by), ($cx, $cy)");- if (!r) {- test_open_next_glyph ();- pen = select_pen ();-- pen.test_click_action (1, ax, ay);- pen.test_click_action (1, bx, by);- pen.test_click_action (1, cx, cy);- - return false;- }- - test_open_next_glyph ();- }- - if (r) test_open_next_glyph ();- - return true;- }-}--}deleted file mode 100644@@ -1,175 +0,0 @@-/*- 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/>.-*/-namespace Supplement {--public class Preferences {- - static HashTable<string, string> data;-- public Preferences () {- data = new HashTable<string, string> (str_hash, str_equal);- }-- public static void set_last_file (string fn) {- set ("last_file", fn);- }-- public static string @get (string k) {- string? s;- - lock (data) {- s = data.lookup (k);- }- - return (s != null) ? (!) s : "";- }-- public static void @set (string k, string v) {- lock (data) {- data.replace (k, v);- save (); // Fixa: save in separate io thread instead.- }- }-- public static string[] get_recent_files () {- string recent = get ("recent_files");- string[] files = recent.split ("\t");- - for (uint i = 0; i < files.length; i++) {- files[i] = files[i].replace ("\\t", "\t");- }- - return files;- }-- public static void add_recent_files (string file) {- string escaped_string = file.replace ("\t", "\\t");- StringBuilder recent = new StringBuilder ();-- foreach (string f in get_recent_files ()) {- if (f != file) {- recent.append (f.replace ("\t", "\\t"));- recent.append ("\t");- }- }-- recent.append (escaped_string);-- set ("recent_files", @"$(recent.str)");- }-- public static int get_window_width() {- string wp = get ("window_width");- int w = int.parse (wp);- return (w == 0) ? 860 : w;- }-- public static int get_window_height() {- int h = int.parse (get ("window_height"));- return (h == 0) ? 500 : h;- }- - public static void load () {- File app_dir = Supplement.get_settings_directory ();- File settings = app_dir.get_child ("settings");-- data = new HashTable<string, string> (str_hash, str_equal);-- if (!settings.query_exists ()) {- stderr.printf ("No settings file.\n");- return;- }-- FileStream? settings_file = FileStream.open ((!) settings.get_path (), "r");- - if (settings_file == null) {- stderr.printf ("Failed to load settings from file %s.\n", (!) settings.get_path ());- return;- }- - return_if_fail (settings_file != null);- - unowned FileStream b = (!) settings_file;- - string? l;- l = b.read_line ();- while ((l = b.read_line ())!= null) {- string line;- - line = (!) l;- - if (line.get_char (0) == '#') {- continue;- }- - int i = 0;- int s = 0;- - i = line.index_of_char(' ', s);- string key = line.substring (s, i - s);-- s = i + 1;- i = line.index_of_char('"', s);- s = i + 1;- i = line.index_of_char('"', s);- string val = line.substring (s, i - s);- - // (key, val, description);- - data.insert (key, val);- }- }- - public static void save () {- try {- File app_dir = Supplement.get_settings_directory ();- File settings = app_dir.get_child ("settings");-- return_if_fail (app_dir.query_exists ());- - if (settings.query_exists ()) {- settings.delete ();- }-- DataOutputStream os = new DataOutputStream(settings.create(FileCreateFlags.REPLACE_DESTINATION));- uint8[] d;- long written = 0;- - StringBuilder sb = new StringBuilder ();- - sb.append ("# Supplement settings\n");- sb.append ("# Version: 1.0\n");- - foreach (var k in data.get_keys ()) {- sb.append (k);- sb.append (" \"");- sb.append (data.lookup (k));- sb.append ("\"\n");- }- - d = sb.str.data;- - while (written < d.length) { - written += os.write (d[written:d.length]);- }- } catch (Error e) {- stderr.printf ("Can not save key settings. (%s)", e.message); - } - }-}--}deleted file mode 100644@@ -1,55 +0,0 @@-/*- 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 WebKit;--namespace Supplement {--public class Preview : FontDisplay { - - public Preview () {- }- - public override string get_name () {- return "Preview";- }- - public override bool is_html_canvas () {- return true;- }-- public override void selected_canvas () {- }-- public File get_html_file () {- Font font = Supplement.get_current_font ();- string path = @"$(font.get_name ()).html";- File dir = font.get_folder ();- File file = dir.get_child (path);- - if (!file.query_exists ()) {- ExportTool.generate_html_document ((!)file.get_path (), font); - }- - return file; - }-- public override string get_uri () {- return path_to_uri ((!) get_html_file ().get_path ());- }-}-}deleted file mode 100644@@ -1,249 +0,0 @@-/*- 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);- }- -}--}deleted file mode 100644@@ -1,98 +0,0 @@-/*- 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 {--public class SaveDialog : FontDisplay {-- public signal void finished ();-- public SaveDialog () {- add_html_callback ("save_dialog", (val) => {- if (val == "save") {- if (MenuTab.save ()) {- finished ();- }- }- - if (val == "save_as") {- if (MenuTab.save_as ()) {- finished ();- }- }- - if (val == "discard") {- // discard it- finished ();- }- - if (val == "cancel") {- MainWindow.get_tab_bar ().close_display (this);- } - - });- }-- public override string get_name () {- return "Save?";- }-- public override bool is_html_canvas () {- return true;- }-- public override string get_html () {- Font f = Supplement.get_current_font ();- string fn = f.get_file_name ();- - if (fn == "") {- fn = f.get_name () + ".ffi";- }- - return """-<html>-<head>- <script type="text/javascript" src="supplement.js"></script>- <style type="text/css">@import url("style.css");</style>-</head>-<body>- - <div style="width:300px; margin: 50px auto 0 auto;">- <div class="heading"><h2>""" + _("Save?") + """</h2></div>- - <p>""" + fn + """</p>- - <form>- <input class="button" type="button" value=""" + "\"" + _("Save") + "\"" + """ onclick="call ('save_dialog:save');"/>- <input class="button" type="button" value=""" + "\"" + _("Save as") + "\"" + """ onclick="call ('save_dialog:save_as');"/>- <input class="button" type="button" value=""" + "\"" + _("Discard") + "\"" + """ onclick="call ('save_dialog:discard');"/>- <input class="button" type="button" value=""" + "\"" + _("Cancel") + "\"" + """ onclick="call ('save_dialog:cancel');"/>- <br />- </form>- </div>-</body>-</html>""";-- // FIXME:- // tell xgettext to extract this:- _("Save?");- }-}- -}deleted file mode 100644@@ -1,141 +0,0 @@-/*- 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 Scrollbar : GLib.Object {- Allocation alloc;-- double pos = 0;- double height = 0.1;- - double y = 0;- double h = 0;- - double y_begin = 0;- double y_last = 0;- bool move = false;- bool visible = true;-- public signal void signal_scroll (double delta, double delta_last, double absolute);-- public Scrollbar () {- }- - public void set_handle_size (double s) {- height = (s < 0.01) ? 0.01 : s;- - if (height > 1) {- height = 1;- }- - h = height * alloc.height;- }-- public void set_handle_position (double p) {- pos = (p > 0) ? p : 0;- y = pos * alloc.height;- }- - public void button_press (uint button, double x, double y) {- move = true;- y_begin = y;- y_last = y;- }- - public void button_release (int button, double ex, double ey) {- move = false;- }- - public void motion_notify (double ex, double ey) {- double d = 0;- double dl = 0;- double m;- - h = height * alloc.height;- m = alloc.height;- - if (move && 0 < ey < m) {- d = ey - y_begin;- dl = ey - y_last;- y += dl;- y_last = ey;- }-- if (y < 0) {- y = 0;- }-- if (y > alloc.height - h) {- y = alloc.height - h;- d = 0;- dl = 0;- }- - if (move) {- pos = y / alloc.height;- }- - if (move) {- signal_scroll (d / (m - h), dl / (m -h), y / (m - h));- pos = y / m;- }-- }- - public void draw (Context cr, Allocation alloc) {- double ax, ay, aw, ah;-- this.alloc = alloc;-- ax = alloc.width - 10;- ay = 0;- aw = alloc.width;- ah = alloc.height;- - if (!visible) {- cr.save ();- cr.rectangle (ax, ay, aw, ah);- cr.set_line_width (0);- cr.set_source_rgba (194/255.0, 194/255.0, 194/255.0, 1);- cr.fill_preserve ();- cr.stroke ();- cr.restore ();- return;- }- - cr.save ();- cr.rectangle (ax, ay, aw, ah);- cr.set_line_width (0);- cr.set_source_rgba (183/255.0, 200/255.0, 223/255.0, 1);- cr.fill_preserve ();- cr.stroke ();- cr.restore (); - - cr.save ();- cr.rectangle (ax, pos * alloc.height, alloc.width, ah * height);- cr.set_line_width (0);- cr.set_source_rgba (133/255.0, 143/255.0, 174/255.0, 1);- cr.fill_preserve ();- cr.stroke ();- cr.restore (); - }-}--}deleted file mode 100644@@ -1,113 +0,0 @@-/*- 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/>.-*/--namespace Supplement {--// TODO contrast--class ShrinkTool : Tool {-- public ShrinkTool (string n) {- base (n, "shrink path");-- select_action.connect((self) => {- Glyph g = MainWindow.get_current_glyph ();- - foreach (Path p in g.path_list) {- shrink_copy (p, 0.25);- }- - MainWindow.get_glyph_canvas ().redraw ();- });-- select_action.connect((self) => {- });- - press_action.connect((self, b, x, y) => {- });-- move_action.connect((self, x, y) => {- });- }- - public static int path_index (Path p) {- Glyph g = MainWindow.get_current_glyph ();- int i = 0;- - foreach (Path pt in g.path_list) {- if (p == pt) {- return i;- }- i++;- }- - return -1;- }- - public static void shrink_copy (Path px, double step) { - Path p = px.copy ();- shrink (px, step);- p.set_editable (true);- } - - public static void shrink (Path p, double step) { - if (p.points.length () < 3) return;-- p.create_list ();- - EditPoint ep;-- p.create_list ();-- foreach (EditPoint pep in p.points) {- unowned List<EditPoint>? lep = pep.get_next ();- - if (unlikely (lep == null)) {- warning ("Bad list.");- stderr.printf (@"No links in path $(path_index (p)). Length: $(p.points.length ())\n");- p.print_boundries ();- continue;- }- - ep = ((!)lep).data;- move_point (p, pep, ep, step);- }- - move_point (p, p.points.last ().data, p.points.first ().data, step);- - p.set_editable (true);- }- - static void move_point (Path p, EditPoint pep, EditPoint ep, double step) { - double istep = 1 - step;- - double mx = ep.get_right_handle ().x () / 2 + ep.get_left_handle ().x () / 2;- double my = ep.get_right_handle ().y () / 2 + ep.get_left_handle ().y () / 2;- - ep.x = mx * step + ep.x * istep;- ep.y = my * step + ep.y * istep;-- }- - public override bool test () {- test_select_action ();- return true;- }--}- -}deleted file mode 100644@@ -1,228 +0,0 @@-/*- 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 {--public class SpinButton : Tool {-- public signal void new_value_action (SpinButton selected);- - public int8 deka = 2;- public int8 deci = 0;- public int8 centi = 0;- public int8 milli = 0;- - bool value_from_motion = false;- double begin_x = 0;- double begin_y = 0;- double begin_value = 0;- - double max = 9.999;- double min = 0;- - public SpinButton (string? name = null, string tip = "", unichar key = '\0', uint modifier_flag = 0) {- base (null , tip, key, modifier_flag);- - if (name != null) {- base.name = (!) name;- }- - set_icon ("spin_button");- - panel_press_action.connect ((selected, button, tx, ty) => {- double py = Math.fabs (y - ty);- int n;- - if (is_selected ()) {- n = (button == 3) ? 10 : 1;- - for (int i = 0; i < n; i++) {- if (py < 28) increase ();- if (py > 35) decrease ();- }- }- - value_from_motion = true;- begin_x = tx;- begin_y = ty;- - begin_value = double.parse (get_display_value ());- });-- panel_move_action.connect ((selected, button, tx, ty) => {- double d;- if (value_from_motion) {- d = (begin_y - ty) / 200;- d = (d < 0) ? -Math.pow (d, 2) : Math.pow (d, 2);- set_value_round (begin_value + d);- }- });-- panel_release_action.connect ((selected, button, tx, ty) => {- value_from_motion = false;- });- }- - public void set_max (double max) {- this.max = max;- }-- public void set_min (double min) {- this.min = min;- }- - public void increase () { - if (deka == 9 && deci == 9 && centi == 9 && milli == 9) {- return;- }- - milli++;-- if (milli == 10) {- centi++;- milli = 0;- }- - if (centi == 10) {- deci++;- centi = 0;- }-- if (deci == 10) {- deka++;- deci = 0;- }- - if (get_value () > max) {- set_value_round (max, false);- }-- new_value_action (this);- }-- public void decrease () {- if (deka == 0 && deci == 0 && centi == 0) {- return;- }- - milli--;- - if (milli == -1) {- centi--;- milli = 9;- }- - if (centi == -1) {- deci--;- centi = 9;- }-- if (deci == -1) {- deka--;- deci = 9;- }- - if (get_value () < 0.05) { // lower limit- set_value_round (0.05);- }-- if (get_value () < min) {- set_value_round (min, false);- }- - new_value_action (this);- }-- public void set_value (string new_value, bool check_boundries = true, bool emit_signal = true) {- string v = new_value;- - while (!(v.char_count () >= 5)) {- if (v.index_of (".") == -1) {- v += ".";- } else {- v += "0";- }- - return;- }- - deka = (int8) int.parse (v.substring (0, 1));- deci = (int8) int.parse (v.substring (2, 1));- centi = (int8) int.parse (v.substring (3, 1));- milli = (int8) int.parse (v.substring (4, 1));- - if (emit_signal) {- new_value_action (this);- }- - if (check_boundries && get_value () > max) {- set_value_round (max, false);- }-- if (check_boundries && get_value () < min) {- set_value_round (min, false);- }- }-- public void set_value_round (double v, bool check_boundries = true, bool emit_signal = true) {- int8 m;- - v += 0.005;- - if (v > 10) v = 9.999;- if (v < 0.001) v = 0.001;- - m = milli; // ignore milli value- set_value (@"$v", check_boundries, emit_signal);- milli = m;- }- - public double get_value () {- return deka + (deci / 10.0) + (centi / 100.0) + (milli / 1000.0);- }-- public string get_display_value () {- return @"$deka.$deci$centi$milli";- }-- public override void draw (Context cr) {- double xt = x + 3 + w;- double yt = y + h + 17;-- double text_x = -4.5;- double text_y = 11;-- base.draw (cr);- - cr.save ();- - cr.set_source_rgba (99/255.0, 99/255.0, 99/255.0, 1);- - cr.set_font_size (10);- cr.select_font_face ("Cantarell", FontSlant.NORMAL, FontWeight.NORMAL);-- cr.move_to (xt + text_x, yt + text_y);- - cr.show_text (get_display_value ());- - cr.restore ();- }-}--}deleted file mode 100644@@ -1,223 +0,0 @@-/* - 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 Supplement; -using Birdfont; - -const string GETTEXT_PACKAGE = "birdfont"; - -namespace Supplement { - -public class Supplement { - public static Argument args; - internal static bool experimental = false; - internal static bool show_coordinates = false; - internal static bool fatal_wanings = false; - public static bool win32 = false; - internal static string exec_path = ""; - - internal static Font current_font; - public static Glyph current_glyph; - - public void init (string[] arg) { - int err_arg; - int i; - File font_file; - IdleSource idle; - string exec_path; - - stdout.printf ("birdfont version %s\n", VERSION); - stdout.printf ("built on %s\n", BUILD_TIMESTAMP); - - init_gettext (); - - args = new Argument.command_line (arg); - - if (args.has_argument ("--help")) { - args.print_help (); - Process.exit (0); - } - - err_arg = args.validate (); - if (err_arg != 0) { - stdout.printf (@"Unknown parameter $(arg [err_arg])\n\n"); - args.print_help (); - Process.exit (0); - } - - current_font = new Font (); - current_glyph = new Glyph (""); - - experimental = args.has_argument ("--test"); - show_coordinates = args.has_argument ("--show-coordinates"); - fatal_wanings = args.has_argument ("--fatal-warning"); - win32 = (arg[0].index_of (".exe") > -1) || arg[0] == "wine"; - exec_path = ""; - - if (win32) { - // wine hack to get "." folder in win32 environment - i = arg[0].last_index_of ("\\"); - - if (i != -1) { - exec_path = arg[0]; - exec_path = exec_path.substring (0, i); - exec_path = wine_to_unix_path (exec_path); - } - } else { - exec_path = "./"; - } - - Preferences preferences = new Preferences (); - preferences.load (); - - if (args.get_file () != "") { - font_file = File.new_for_path (args.get_file ()); - - if (!font_file.query_exists ()) { - stderr.printf (@"File $(args.get_file ()) not found."); - Process.exit (-1); - } - } - - if (fatal_wanings) { - LogLevelFlags levels = LogLevelFlags.LEVEL_ERROR | LogLevelFlags.LEVEL_CRITICAL | LogLevelFlags.LEVEL_WARNING; - Log.set_handler (null, levels, fatal_warning); - } - - preferences.set_last_file (get_current_font ().get_path ()); - } - - static void init_gettext () { - File f; - - Intl.setlocale (LocaleCategory.MESSAGES, ""); - Intl.bind_textdomain_codeset (GETTEXT_PACKAGE, "utf-8"); - - f = File.new_for_path ("./build/locale/sv/LC_MESSAGES/birdfont.mo"); - if (f.query_exists ()) { - Intl.bindtextdomain (GETTEXT_PACKAGE, "./build/locale"); - return; - } - - f = File.new_for_path ("./locale/sv/LC_MESSAGES/birdfont.mo"); - if (f.query_exists ()) { - Intl.bindtextdomain (GETTEXT_PACKAGE, "./locale"); - return; - } - - f = File.new_for_path ("../locale/sv/LC_MESSAGES/birdfont.mo"); - if (f.query_exists ()) { - Intl.bindtextdomain (GETTEXT_PACKAGE, "../locale"); - return; - } - - f = File.new_for_path ("/usr/share/locale/sv/LC_MESSAGES/birdfont.mo"); - if (f.query_exists ()) { - Intl.bindtextdomain (GETTEXT_PACKAGE, "/usr/local/share/locale"); - return; - } - - f = File.new_for_path ("/usr/share/locale/sv/LC_MESSAGES/birdfont.mo"); - if (f.query_exists ()) { - Intl.bindtextdomain (GETTEXT_PACKAGE, "/usr/share/locale"); - return; - } - - warning ("translations not found"); - } - - public static Font get_current_font () { - return current_font; - } - - internal static void fatal_warning (string? log_domain, LogLevelFlags log_levels, string message) { - bool fatal = true; - - if (log_domain != null) { - stderr.printf ("%s: \n", (!) log_domain); - } - - stderr.printf ("\n%s\n\n", message); - assert (!fatal); - } - - internal static void new_font () { - current_font = new Font (); - } - - public static File get_preview_directory () { - File settings = get_settings_directory (); - File backup = settings.get_child ("preview"); - - if (!backup.query_exists ()) { - DirUtils.create ((!) backup.get_path (), 0xFFFFFF); - } - - return backup; - } - - internal static File get_thumbnail_directory () { - File thumbnails = get_settings_directory ().get_child ("thumbnails"); - - if (!thumbnails.query_exists ()) { - DirUtils.create ((!) thumbnails.get_path (), 0xFFFFFF); - } - - return thumbnails; - } - - internal static File get_settings_directory () { - File home = File.new_for_path (Environment.get_home_dir ()); - File settings = home.get_child (".birdfont"); - - if (!settings.query_exists ()) { - DirUtils.create ((!) settings.get_path (), 0xFFFFFF); - } - - return settings; - } - - internal static File get_backup_directory () { - File settings = get_settings_directory (); - File backup = settings.get_child ("backup"); - - if (!backup.query_exists ()) { - DirUtils.create ((!) backup.get_path (), 0xFFFFFF); - } - - return backup; - } - - internal static bool has_argument (string param) { - if (is_null (args)) { - warning ("args is null"); - return false; - } - - return args.has_argument (param); - } - - internal static string? get_argument (string param) { - return args.get_argument (param); - } -} - -internal bool is_null (void* n) { - return n == null; -} - -} deleted file mode 100644@@ -1,345 +0,0 @@-/*- 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 Svg {-- /** Export to svg glyph data. */- public static string to_svg_glyph (Glyph g, double scale = 1) { - StringBuilder svg = new StringBuilder ();-- foreach (Path pl in g.path_list) {- write_path_as_glyph (pl, svg, g, scale);- }- - return svg.str;- }-- /** Export to regular svg path data. */- public static string to_svg_path (Path pl, Glyph g, double scale = 1) { - StringBuilder svg = new StringBuilder ();- pl.create_list ();- write_path (pl, svg, g, false, scale);- return svg.str;- }-- private static void write_path_as_glyph (Path pl, StringBuilder svg, Glyph g, double scale = 1) {- write_path (pl, svg, g, true, scale);- }-- private static void write_path (Path pl, StringBuilder svg, Glyph g, bool do_glyph, double scale = 1) {- int i = 0;- EditPoint? n = null;- EditPoint m;- - if (pl.points.length () <= 2) {- return;- }- - pl.create_list ();- - foreach (var e in pl.points) {- if (i == 0) {- add_abs_start (e, svg, g, do_glyph, scale);- i++;- n = e;- continue;- }- - m = (!) n;-- add_abs_next (m, e, svg, g, do_glyph, scale);- - n = e;- i++;- }-- m = pl.points.first ().data;- - add_abs_next ((!) n, m, svg, g, do_glyph, scale);-- close_path (svg);- }-- private static void add_abs_next (EditPoint start, EditPoint end, StringBuilder svg, Glyph g, bool do_glyph, double scale = 1) {- if (start.right_handle.type == PointType.LINE && end.left_handle.type == PointType.LINE) {- add_abs_line_to (end, start, svg, g, do_glyph, scale);- } else if (end.get_left_handle ().type == PointType.NONE) {- add_quadratic_abs_path (end, start, svg, g, do_glyph, scale);- } else {- add_cubic_abs_path (end, start, svg, g, do_glyph, scale);- }- }-- private static void add_abs_start (EditPoint ep, StringBuilder svg, Glyph g, bool do_glyph, double scale = 1) { - double left = g.left_limit;- double baseline = Supplement.get_current_font ().base_line;- - svg.append_printf ("M");-- svg.append_printf ("%s ", round ((ep.x - left) * scale));- svg.append_printf ("%s ", round ((ep.y + baseline) * scale));- }- - private static void close_path (StringBuilder svg) {- svg.append ("z");- } - - private static void add_abs_line_to (EditPoint start, EditPoint stop, StringBuilder svg, Glyph g, bool to_glyph, double scale = 1) {- double baseline = Supplement.get_current_font ().base_line;- double left = g.left_limit;- - double xa, ya, xb, yb;- - Path.get_line_points (start, stop, out xa, out ya, out xb, out yb);-- double height = Supplement.get_current_font ().get_height (); // no probably not-- double center_x = Glyph.xc ();- double center_y = Glyph.yc ();- - if (!to_glyph) {- svg.append ("L");- - svg.append_printf ("%s ", round ((xa - center_x - left) * scale));- svg.append_printf ("%s ", round ((ya - center_y - baseline + height) * scale)); -- svg.append ("L");- - svg.append_printf ("%s ", round ((xb - center_x - left) * scale));- svg.append_printf ("%s ", round ((yb - center_y - baseline + height) * scale)); -- } else {- svg.append ("L");- - svg.append_printf ("%s ", round ((xa - center_x - left) * scale));- svg.append_printf ("%s ", round ((-ya + center_y + baseline) * scale));-- svg.append ("L");- - svg.append_printf ("%s ", round ((xb - center_x - left) * scale));- svg.append_printf ("%s ", round ((-yb + center_y + baseline) * scale)); - }- }- - private static void add_quadratic_abs_path (EditPoint start, EditPoint end, StringBuilder svg, Glyph g, bool to_glyph, double scale = 1) {- double left = g.left_limit;- double baseline = Supplement.get_current_font ().base_line;- double height = Supplement.get_current_font ().get_height (); // no probably not- - double xa, ya, xb, yb, xc, yc, xd, yd;- - Path.get_bezier_points (start, end, out xa, out ya, out xb, out yb, out xc, out yc, out xd, out yd);-- double center_x = Glyph.xc ();- double center_y = Glyph.yc ();- - // cubic path- if (!to_glyph) {- svg.append_printf ("Q");-- svg.append_printf ("%s ", round ((xb - center_x - left) * scale));- svg.append_printf ("%s ", round ((yb - center_y - baseline + height) * scale));- - svg.append_printf ("%s ", round ((xc - center_x - left) * scale));- svg.append_printf ("%s ", round ((yc - center_y - baseline + height) * scale));-- } else { - svg.append_printf ("Q");-- svg.append_printf ("%s ", round ((xb - center_x - left) * scale));- svg.append_printf ("%s ", round ((-yb + center_y + baseline) * scale));- - svg.append_printf ("%s ", round ((xc - center_x - left) * scale));- svg.append_printf ("%s ", round ((-yc + center_y + baseline) * scale)); - }- }- - private static void add_cubic_abs_path (EditPoint start, EditPoint end, StringBuilder svg, Glyph g, bool to_glyph, double scale = 1) {- double left = g.left_limit;- double baseline = Supplement.get_current_font ().base_line;- double height = Supplement.get_current_font ().get_height (); // no probably not- - double xa, ya, xb, yb, xc, yc, xd, yd;- - Path.get_bezier_points (start, end, out xa, out ya, out xb, out yb, out xc, out yc, out xd, out yd);-- double center_x = Glyph.xc ();- double center_y = Glyph.yc ();- - // cubic path- if (!to_glyph) {- svg.append_printf ("C");-- svg.append_printf ("%s ", round ((xb - center_x - left) * scale));- svg.append_printf ("%s ", round ((yb - center_y - baseline + height) * scale));- - svg.append_printf ("%s ", round ((xc - center_x - left) * scale));- svg.append_printf ("%s ", round ((yc - center_y - baseline + height) * scale));- - svg.append_printf ("%s ", round ((xd - center_x - left) * scale));- svg.append_printf ("%s ", round ((yd - center_y - baseline + height) * scale)); -- } else { - svg.append_printf ("C");-- svg.append_printf ("%s ", round ((xb - center_x - left) * scale));- svg.append_printf ("%s ", round ((-yb + center_y + baseline) * scale));- - svg.append_printf ("%s ", round ((xc - center_x - left) * scale));- svg.append_printf ("%s ", round ((-yc + center_y + baseline) * scale)); - - svg.append_printf ("%s ", round ((xd - center_x - left) * scale));- svg.append_printf ("%s ", round ((-yd + center_y + baseline) * scale)); - }- }- - /** Draw path from svg font data. */- public static void draw_svg_path (Context cr, string svg, double x, double y, double scale = 1) {- double x1, x2, x3;- double y1, y2, y3;-- string[] d = svg.split (" ");- - x /= scale;- y /= scale;-- if (d.length == 0) {- return;- }- - cr.save ();-- cr.set_line_width (1);- cr.set_source_rgba (0, 0, 0, 1);- - if (svg == "") {- return;- }- - for (int i = 0; i < d.length; i++) {- - // trim off leading white space - while (d[i].index_of (" ") == 0) { - d[i] = d[i].substring (1);- }- - if (d[i].index_of ("L") == 0) {- x1 = double.parse (d[i].substring (1)) + x;- y1 = -double.parse (d[i+1]) + y;- - x1 *= scale;- y1 *= scale;- - cr.line_to (x1, y1);- continue;- }-- if (d[i].index_of ("Q") == 0) {- x1 = double.parse (d[i].substring (1)) + x;- y1 = -double.parse (d[i+1]) + y;-- x2 = double.parse (d[i+2]) + x;- y2 = -double.parse (d[i+3]) + y;-- x1 *= scale;- y1 *= scale;-- x2 *= scale;- y2 *= scale;- - cr.curve_to (x1, y1, x2, y2, x2, y2);- continue;- }- - if (d[i].index_of ("C") == 0) {- x1 = double.parse (d[i].substring (1)) + x;- y1 = -double.parse (d[i+1]) + y;-- x2 = double.parse (d[i+2]) + x;- y2 = -double.parse (d[i+3]) + y;-- x3 = double.parse (d[i+4]) + x;- y3 = -double.parse (d[i+5]) + y;-- x1 *= scale;- y1 *= scale;-- x2 *= scale;- y2 *= scale;- - x3 *= scale;- y3 *= scale;- - cr.curve_to (x1, y1, x2, y2, x3, y3);- continue;- }-- if (d[i].index_of ("M") == 0) {- x1 = double.parse (d[i].substring (1)) + x;- y1 = -double.parse (d[i+1]) + y;- - x1 *= scale;- y1 *= scale;- - cr.move_to (x1, y1);- continue;- }- - if (d[i].index_of ("zM") == 0) {- cr.close_path ();- - x1 = double.parse (d[i].substring (2)) + x;- y1 = -double.parse (d[i+1]) + y;- - x1 *= scale;- y1 *= scale;- - cr.move_to (x1, y1);- continue;- }-- if (d[i].index_of ("z") == 0) {- cr.close_path ();- continue;- }- - }- - cr.fill ();- cr.restore ();- }--}--internal static string round (double p) {- string v = p.to_string ();- char[] c = new char [501];- - v = p.format (c, "%3.15f");- - if (v.index_of ("e") != -1) { - return "0.0";- }- - return v;-}- -}deleted file mode 100644@@ -1,132 +0,0 @@-/*- 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/>.-*/--namespace Supplement {--class SvgFontFormatWriter : Object {-- DataOutputStream os;-- public SvgFontFormatWriter () {- }-- public void open (File file) throws Error {- if (file.query_exists ()) {- throw new FileError.EXIST ("SvgFontFormatWriter: file exists.");- }- - os = new DataOutputStream (file.create(FileCreateFlags.REPLACE_DESTINATION));- }-- public void close () throws Error {- os.close ();- }- - public void write_font_file (Font font) throws Error {- string font_name = font.get_name ();- - int units_per_em = 100;- - int ascent = 80;- int descent = -20;- - StringBuilder b;- - Glyph? g;- Glyph glyph;- unichar indice = 0;- - string uni;- - put ("""<?xml version="1.0" standalone="no"?>""");- put ("""<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >""");- put ("""<svg xmlns="http://www.w3.org/2000/svg">""");-- // (metadata goes here)-- put ("<defs>");-- put (@"<font id=\"$font_name\" horiz-adv-x=\"250\" >");- put (@"<font-face units-per-em=\"$units_per_em\" ascent=\"$ascent\" descent=\"$descent\" />");-- // (missing-glyph goes here)-- while (true) {- g = font.get_glyph_indice (indice++);- - if (g == null) {- break;- }- - glyph = (!) g;- - b = new StringBuilder ();- b.append_unichar (glyph.get_unichar ());-- if (glyph.get_unichar () >= ' ' && b.str.validate ()) {- if (b.str == "\"" || b.str == "&" || b.str == "<") {- uni = Font.to_hex_code (glyph.get_unichar ());- put (@"<glyph unicode=\"&#x$(uni);\" horiz-adv-x=\"$(glyph.get_width ())\" d=\"$(glyph.get_svg_data ())\" />"); - } else {- put (@"<glyph unicode=\"$(b.str)\" horiz-adv-x=\"$(glyph.get_width ())\" d=\"$(glyph.get_svg_data ())\" />");- }- }- }-- while (true) {- g = font.get_glyph_indice (indice++);- - if (g == null) {- break;- }- - glyph = (!) g;- - foreach (Kerning k in glyph.kerning) {- string l, r;- Font f = Supplement.get_current_font ();- Glyph? gr = f.get_glyph (k.glyph_right);- Glyph glyph_right;- - if (gr == null) {- warning ("kerning glyph that does not exist.");- }- - glyph_right = (!) gr;- - l = Font.to_hex_code (glyph.unichar_code);- r = Font.to_hex_code (glyph_right.unichar_code);- - os.put_string (@"<hkern u1=\"&#x$l;\" u2=\"&#x$r;\" k=\"$(k.val)\"/>\n");- }- } -- put ("</font>");- put ("</defs>");- put ("</svg>");- }-- /** Write a new line */- private void put (string line) throws Error {- os.put_string (line);- os.put_string ("\n");- }--}---}deleted file mode 100644@@ -1,49 +0,0 @@-/*- 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/>.-*/--namespace Supplement {--public class Tab : GLib.Object {-- bool always_open;- double width; - FontDisplay display;-- public Tab (FontDisplay glyph, double tab_width, bool always_open) {- width = tab_width;- this.display = glyph;- this.always_open = always_open;- }-- public bool has_close_button () {- return !always_open;- }-- public FontDisplay get_display () {- return display;- }- - public string get_label () {- return display.get_name ();- }- - public double get_width () {- return width;- }-}--}deleted file mode 100644@@ -1,583 +0,0 @@-/*- 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 {--public class TabBar : GLib.Object {- - int width = 0;- int height = 0;- - public List<Tab> tabs = new List<Tab> ();-- static const int NO_TAB = -1;- static const int NEXT_TAB = -2;- static const int PREVIOUS_TAB = -3;-- int first_tab = 0;- int selected = 0;- int over = NO_TAB;- int over_close = NO_TAB;- - public signal void signal_tab_selected (Tab selected_tab);-- Tab? previous_tab = null;- Tab? current_tab = null;-- ImageSurface? tab1_left = null;- ImageSurface? tab1_right = null;-- ImageSurface? tab2_left = null;- ImageSurface? tab2_right = null;-- ImageSurface? tab3_left = null;- ImageSurface? tab3_right = null;- - ImageSurface? bar_background = null;- ImageSurface next_tab;- ImageSurface to_previous_tab;- - public TabBar () {- tab1_left = Icons.get_icon ("tab1_left.png");- tab1_right = Icons.get_icon ("tab1_right.png");-- tab2_left = Icons.get_icon ("tab2_left.png");- tab2_right = Icons.get_icon ("tab2_right.png");-- tab3_left = Icons.get_icon ("tab3_left.png");- tab3_right = Icons.get_icon ("tab3_right.png");- - bar_background = Icons.get_icon ("tabbar_background.png");- - next_tab = (!) Icons.get_icon ("next_tab.png");- to_previous_tab = (!) Icons.get_icon ("previous_tab.png");- - return_if_fail (!is_null (next_tab));- }- - public void motion (double x, double y) {- TooltipArea status = MainWindow.get_tool_tip ();- is_over_close (x, y, out over, out over_close);- }- - private bool is_over_next (double x) {- return x > width - 19 && has_scroll ();- }-- private bool is_over_previous (double x) {- return x < 19 && has_scroll ();- }-- private void is_over_close (double x, double y, out int over, out int over_close) {- int i = 0;- double offset = 19;- - if (x < 19 && has_scroll ()) {- over_close = NO_TAB;- over = PREVIOUS_TAB;- return;- }- - foreach (Tab t in tabs) {- - if (i < first_tab) {- i++;- continue;- }- - if (offset + t.get_width () + 3 > width && has_scroll ()) {- over_close = NO_TAB;- over = NEXT_TAB;- return;- }- - if (offset < x < offset + t.get_width ()) {- over = i;- - if (8 < y < 20 && x > offset + t.get_width () - 16) {- over_close = i;- } else {- over_close = NO_TAB;- }- - return;- }- - offset += t.get_width () + 3;- i++;- }-- over_close = NO_TAB; - over = NO_TAB;- } - - - /** Select tab for a glyph by charcode or name.- * @return true if the tab was found- */- public bool select_char (string s) {- int i = 0;- foreach (Tab t in tabs) {- if (t.get_label () == s) {- select_tab (i);- return true;- }- i++;- }- - return false;- }-- public bool select_tab_name (string s) {- return select_char (s);- }-- public void select_overview () {- select_tab_name ("Overview");- }-- private void select_previous_tab () {- Tab t;- bool open;- - if (previous_tab == null) {- return;- }- - t = (!) previous_tab;- open = selected_open_tab (t);- - if (!open) {- select_tab ((int) tabs.length () - 1);- }- }- - public void close_display (FontDisplay f) {- int i = -1;- foreach (var t in tabs) {- ++i;- - if (t.get_display () == f) {- close_tab (i) ;- return;- }- }- - return_if_fail (i != -1);- } -- public bool close_tab (int index, bool background_tab = false) { - unowned List<Tab?>? lt;- Tab t;- - if (!(0 <= index < tabs.length ())) {- return false;- }- - lt = tabs.nth(index);- - if (lt == null || ((!) lt).data == null) {- return false;- }-- if (first_tab > 0) {- first_tab--;- }-- t = (!) ((!) lt).data;-- if (t.has_close_button ()) {- tabs.delete_link (tabs.nth(index));- - if (!background_tab) {- select_previous_tab ();- }- - return true;- }- - select_tab (index);- return false;- }- - public bool close_by_name (string name, bool background_tab = false) {- int i = 0;- - foreach (var t in tabs) {- if (t.get_label () == name) {- return close_tab (i, background_tab);- }- - i++;- }- - return false;- }- - public void close_background_tab_by_name (string name) {- close_by_name (name, true);- }- - /** Select a tab and return true if it is open. */- public bool selected_open_tab (Tab t) {- int i = 0;- foreach (var n in tabs) {- if (n == t) {- select_tab (i);- return true;- }- - i++;- }- - return false;- }-- public Tab? get_tab (string name) {- foreach (var n in tabs) {- if (n.get_label () == name) {- return n;- }- }- - return null;- }-- public bool selected_open_tab_by_name (string t) {- int i = 0;- foreach (var n in tabs) {- if (n.get_label () == t) {- select_tab (i);- return true;- }- - i++;- }- - return false;- }- - public Tab get_selected_tab () {- return tabs.nth (get_selected ()).data;- }- - public uint get_length () {- return tabs.length ();- }-- public int get_selected () {- return selected;- }- - public void select_tab (int index) {- Tab t;-- if (index == NEXT_TAB) {- selected++;- - if (selected >= tabs.length ()) {- selected = (int) tabs.length () - 1;- }- - scroll_to_tab (selected);- return;- }- - if (index == PREVIOUS_TAB) {-- if (selected > 0) {- selected--;- }- - scroll_to_tab (selected);- return;- }- - if (!(0 <= index < tabs.length ())) {- return;- }-- selected = index;- - unowned List<Tab?>? lt = tabs.nth(index);- - return_if_fail(lt != null);- t = (!) ((!) lt).data;- - previous_tab = current_tab;- current_tab = t;-- scroll_to_tab (selected);- }- - private bool has_scroll () {- int i = 0;- double offset = 19;- - if (first_tab > 0) {- return true;- }- - foreach (Tab t in tabs) { - if (i < first_tab) {- i++;- continue;- }- - if (offset + t.get_width () + 3 > width - 19) {- return true;- }-- offset += t.get_width () + 3;- i++;- }- - return false; - }- - private void signal_selected (int index) {- unowned List<Tab?>? lt;- Tab t;- - lt = tabs.nth(index);- return_if_fail(lt != null); - t = (!) ((!) lt).data;- - signal_tab_selected (t); - }- - private void scroll_to_tab (int index) {- double offset = 19;- int i = 0;- - if (index < first_tab) {- first_tab = index;- signal_selected (index);- return;- }- - foreach (Tab t in tabs) {- - if (i < first_tab) {- i++;- continue;- }- - // out of view- if (offset + t.get_width () + 3 > width - 19) {- first_tab++;- scroll_to_tab (index);- return;- }-- // in view- if (i == index) {- signal_selected (index);- return;- }-- offset += t.get_width () + 3;- i++;- }- - warning ("");- }- - public void select_tab_click (double x, double y, int width, int height) {- int over, close;- - this.width = width;- this.height = height;- - is_over_close (x, y, out over, out close);-- if (over_close >= 0 && over == selected) {- close_tab (over_close);- } else {- select_tab (over);- }- }- - public void add_tab (FontDisplay display_item, double tab_width = -1, bool always_open = false) {- int s = (tabs.length () == 0) ? 0 : selected + 1;- - if (tab_width < 0) {- //cr.text_extents (display_item.get_name (), out te); // this is not a good estimation, pango might solve it- //tab_width = te.width + 30;- - tab_width = 9 * display_item.get_name ().char_count ();- tab_width += 30;- }- - tabs.insert (new Tab (display_item, tab_width, always_open), s);- select_tab (s);- }- - /** Returns true if the new item was added to the bar. */- public bool add_unique_tab (FontDisplay display_item, double tab_width = -1, bool always_open = false) {- bool i = select_tab_name (display_item.get_name ());-- if (!i) {- add_tab (display_item, tab_width, always_open);- return true;- }- - return false;- }- - public void draw (Context cr, int width, int height) {- this.width = width;- this.height = height;- - cr.save ();- cr.rectangle (0, 0, width, height);- cr.set_line_width (0);- cr.set_source_rgba (183/255.0, 200/255.0, 223/255.0, 1);- cr.fill_preserve ();- cr.stroke ();- cr.restore ();-- for (int j = 0; j < width; j++) {- cr.set_source_surface ((!) bar_background, j, 0);- cr.paint ();- }-- if (has_scroll ()) {- // left arrow- cr.set_source_surface (to_previous_tab, 3, (height - to_previous_tab.get_height ()) / 2.0);- cr.paint ();-- // right arrow- cr.set_source_surface (next_tab, width - 19, (height - next_tab.get_height ()) / 2.0);- cr.paint ();- }- - draw_tabs (cr);- - }- - // this can be removed when tabs are not referenced by name- string translate (string s) {- switch (s) {- case "Overview":- return _("Overview");- break;- case "Kerning":- return _("Kerning");- break;- case "Menu":- return _("Menu");- break;- default:- return s;- break;- }- }- - private void draw_tabs (Context cr) {- double close_opacity;- double offset = 19;- int i = 0;- - foreach (Tab t in tabs) {- - if (i < first_tab) {- i++;- continue;- }-- cr.save ();- cr.translate (offset, 0);- - if (offset + t.get_width () + next_tab.get_width () + 3 > width) {- break;- }- - // background- if (i == selected) {- for (int j = 0; j < t.get_width (); j++) {- cr.set_source_surface ((!) tab3_right, j, 2);- cr.paint ();- } - } else if (i == over) {- for (int j = 0; j < t.get_width (); j++) {- cr.set_source_surface ((!) tab2_right, j, 2);- cr.paint ();- } - } else {- for (int j = 0; j < t.get_width (); j++) {- cr.set_source_surface ((!) tab1_right, j, 2);- cr.paint ();- }- }- - // close- if (t.has_close_button ()) {- cr.set_line_width (1);- - close_opacity = (over_close == i) ? 1 : 0.2; - cr.set_source_rgba (0, 0, 0, close_opacity);- - cr.move_to (t.get_width () - 5, 11);- cr.line_to (t.get_width () - 10, 16);-- cr.move_to (t.get_width () - 10, 11);- cr.line_to (t.get_width () - 5, 16);- - cr.stroke (); - }-- cr.set_source_rgba (0, 0, 0, 1);- cr.set_font_size (14);- cr.move_to (8, 18);- cr.show_text (translate (t.get_label ()));- cr.stroke ();- - // edges- if (tab1_left != null && tab1_right != null && tab2_left != null && tab2_right != null && tab3_left != null && tab3_right != null) {- if (i == selected) {- cr.set_source_surface ((!) tab3_left, 0, 2);- cr.paint ();- - cr.set_source_surface ((!) tab3_right, t.get_width () - 2, 2);- cr.paint ();- } else if (i == over) {- cr.set_source_surface ((!) tab2_left, 0, 2);- cr.paint ();-- cr.set_source_surface ((!) tab2_right, t.get_width () - 2, 2);- cr.paint ();- } else {- cr.set_source_surface ((!) tab1_left, 0, 2);- cr.paint ();-- cr.set_source_surface ((!) tab1_right, t.get_width () - 2, 2);- cr.paint ();- }- }-- cr.restore ();- - offset += t.get_width () + 3;- i++;- }- }-}--}deleted file mode 100644@@ -1,527 +0,0 @@-/*- 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/>.-*/--namespace Supplement {--/** All the things we want to test listed is here. */-class TestCases {- - public List<Test> test_cases;-- public TestCases () {- add (test_overview, "Overview");- add (test_data_reader, "Font data reader");- add (test_argument, "Argument list");- add (test_glyph_ranges, "Glyph ranges");- add (test_glyph_table, "Glyph table");- add (test_active_edit_point, "Active edit point");- add (test_hex, "Unicode hex values");- add (test_reverse_path, "Reverse path");- add (test_reverse_random_paths, "Reverse random paths");- add (test_coordinates, "Coordinates");- add (test_drawing, "Pen tool");- add (test_delete_points, "Delete edit points");- add (test_save_backup, "Save backup");- add (test_convert_to_quadratic_bezier_path, "Convert to quadratic path");- add (test_notdef, "Notdef");- add (test_merge, "Merge");- add (test_over_path, "Over path");- add (test_preview, "Preview");- add (test_export, "Export");- add (test_background_coordinates, "Background coordinates");- }-- public static void test_background_coordinates () {- GlyphBackgroundImage bg = new GlyphBackgroundImage ("");- - bg.set_position (100, 100);- bg.set_img_offset (bg.img_offset_x, bg.img_offset_y);- warn_if_fail (bg.img_x == 100 && bg.img_y == 100);-- bg.set_img_offset (100, 100);- bg.set_position (bg.img_x, bg.img_y);- warn_if_fail (bg.img_offset_x == 100 && bg.img_offset_y == 100);- }-- public static void test_export () {- Glyph g;- Path p = new Path ();- - Tool.test_open_next_glyph ();- g = MainWindow.get_current_glyph ();- - p.add (-10, -10);- p.add (50, 50);- p.add (10, -10);- - p.close ();- g.add_path (p); -- MenuTab.preview ();-- // TODO: run this many times on big font- for (int i = 0; i < 10; i++) {- ExportTool.export_all ();- Tool.yield ();- } - }--- public static void test_preview () {- Glyph g;- Path p = new Path ();- - Tool.test_open_next_glyph ();- g = MainWindow.get_current_glyph ();- - p.add (-10, 10);- p.add (10, 10);- p.add (10, -10);- p.add (-10, -10);- p.close ();- g.add_path (p); -- MenuTab.preview ();-- // TODO: run this many times on big font- for (int i = 0; i < 100; i++) {- MainWindow.get_tab_bar ().select_tab_name ("Menu");- Tool.yield ();- - MainWindow.get_tab_bar ().select_tab_name ("Preview");- Tool.yield ();- }- - }-- public static void test_over_path () {- Glyph g;- Path p = new Path ();- Tool pen_tool;- - pen_tool = MainWindow.get_toolbox ().get_tool ("pen_tool");- pen_tool.test_select_action ();- Tool.test_open_next_glyph ();- - g = MainWindow.get_current_glyph ();-- pen_tool.test_click_action (1, 10, 10);- pen_tool.test_click_action (1, 10, 10);- pen_tool.test_click_action (1, 100, 10);- pen_tool.test_click_action (1, 100, 100);- pen_tool.test_click_action (1, 10, 100);- pen_tool.test_click_action (3, 0, 0);-- warn_if_fail (g.active_paths.length () == 0);-- g.select_path (50, 50);-- warn_if_fail (g.active_paths.length () == 1);- - p.add (-10, 10);- p.add (10, 10);- p.add (10, -10);- p.add (-10, -10);- p.update_region_boundries ();- g.add_path (p);- g.close_path ();-- if (!p.is_over_coordinate (0, 0)) {- warning ("Coordinate 0, 0 is not in path.");- }- - if (!p.is_over_coordinate (-10, 10)) {- warning ("Corner corrdinate -10, 10 is not in path.");- }- - warn_if_fail (!p.is_over_coordinate (-20, -20));- - for (double x = -10; x <= 10; x += 0.1) {- for (double y = 10; y <= 10; y += 0.1) {- warn_if_fail (p.is_over_coordinate (x, y));- } - } - - }-- public static void test_merge () {- Glyph g;- Path p = new Path ();- Path p2 = new Path ();- - Tool.test_open_next_glyph ();- g = MainWindow.get_current_glyph ();- - p.add (-10, 10);- p.add (10, 10);- p.add (10, -10);- p.add (-10, -10);- p.close ();- g.add_path (p);-- p2.add (10, 10);- p2.add (10, -10); - p2.add (20, -10);- p2.add (20, 10);- p2.reverse ();- p2.close ();- g.add_path (p2);- - g.add_active_path (p);- g.add_active_path (p);- - g.add_active_path (p);- g.merge_all ();- }-- public static void test_notdef () {- Font f = Supplement.get_current_font ();- Glyph n = f.get_not_def_character ();- Glyph g;- Path pn;- - f.add_glyph (n);- - Tool.test_open_next_glyph ();- g = MainWindow.get_current_glyph ();- foreach (Path p in n.path_list) {- pn = p.copy ().get_quadratic_points ();- g.path_list.append (pn);- pn.move (50, 0);- g.path_list.append (p.copy ());- }- }-- public static void test_convert_to_quadratic_bezier_path () {- Glyph g;- Path gqp;- Path gqp_points;- - Path p = new Path ();- Path p1 = new Path ();- List<Path> qpl = new List<Path> (); - - EditPoint e0, e1, e2, e3;- - g = MainWindow.get_current_glyph ();- - // split_all_cubic_in_half- foreach (Path gp in g.path_list) {- gqp_points = gp.copy ();- gqp_points.split_cubic_in_parts (gqp_points);- gqp_points.move (50, 0);- qpl.append (gqp_points);- }- - // convert to quadratic points- foreach (Path gp in g.path_list) {- gqp = gp.get_quadratic_points ();- gqp.move (100, 0);- qpl.append (gqp);- }-- foreach (Path gp in qpl) {- g.add_path (gp);- }-- Tool.test_open_next_glyph ();- - g = MainWindow.get_current_glyph ();- - p.add (-10, 10);- p.add (10, 10);- p.add (10, -10);- p.add (-10, -10);- p.close ();- g.add_path (p);- g.add_path (p1.get_quadratic_points ());-- e0 = new EditPoint (20, 40);- e1 = new EditPoint (40, 40);- e2 = new EditPoint (40, 20);- e3 = new EditPoint (20, 20);-- p1.add_point (e0);- p1.add_point (e1);- p1.add_point (e2);- p1.add_point (e3);- p1.close ();-- e0.set_tie_handle (true);- e1.set_tie_handle (true);- e2.set_tie_handle (true);- e3.set_tie_handle (true);-- e0.process_tied_handle ();- e1.process_tied_handle ();- e2.process_tied_handle ();- e3.process_tied_handle ();-- g.add_path (p1);- g.add_path (p1.get_quadratic_points ());- }-- public static void test_overview () {- OverView o = MainWindow.get_overview ();-- warn_if_fail (o.selected_char_is_visible ());- - for (int i = 0; i < 10; i++) {- o.key_down ();- warn_if_fail (o.selected_char_is_visible ());- }-- for (int i = 0; i < 15; i++) {- o.key_up ();- warn_if_fail (o.selected_char_is_visible ());- }-- for (int i = 0; i < 6; i++) {- o.key_down ();- warn_if_fail (o.selected_char_is_visible ());- }-- for (int i = 0; i < 3; i++) {- o.key_down ();- warn_if_fail (o.selected_char_is_visible ());- }- - for (int i = 0; i < 2000; i++) {- o.scroll_adjustment (5);- }-- for (int i = 0; i < 2000; i++) {- o.scroll_adjustment (-5);- }- }-- public static void test_data_reader () {- FontData fd = new FontData ();- uint len;-- try {- fd.add (7);- fd.add_ulong (0x5F0F3CF5);- fd.add_ulong (9);- - warn_if_fail (fd.table_data[0] == 7);- warn_if_fail (fd.read () == 7);- warn_if_fail (fd.read_ulong () == 0x5F0F3CF5);- warn_if_fail (fd.read_ulong () == 9);- - fd = new FontData ();- for (int16 i = 0; i < 2048; i++) {- fd.add_short (i);- }- - fd.seek (2 * 80);- warn_if_fail (fd.read_short () == 80);- - fd.seek (100);- fd.add_short (7);- fd.seek (100);- warn_if_fail (fd.read_short () == 7);- - fd.seek_end ();- len = fd.length ();- fd.add (0);- warn_if_fail (len + 1 == fd.length ());- } catch (GLib.Error e) {- warning (e.message);- }- }-- public static void test_argument () {- Argument arg = new Argument ("supplement -t \"Argument list\" --unknown -unknown --help -s");- - return_if_fail (arg.has_argument ("--test"));- return_if_fail ((!) arg.get_argument ("--test") == "\"Argument list\"" );- return_if_fail (arg.has_argument ("--unknown"));- return_if_fail (arg.has_argument ("--help"));- return_if_fail (arg.has_argument ("--slow"));- return_if_fail (arg.validate () != 0);-- arg = new Argument ("supplement --test \"Argument list\"");- return_if_fail ((!) arg.get_argument ("--test") == "\"Argument list\"" );- return_if_fail (!arg.has_argument ("--help"));- return_if_fail (!arg.has_argument ("--slow"));- return_if_fail (arg.validate () == 0);- - }-- public static void test_glyph_ranges () {- GlyphRange gr = new GlyphRange ();- - gr.add_range ('b', 'c');- gr.add_single ('d');- gr.add_range ('e', 'h');- gr.add_range ('k', 'm');- gr.add_range ('o', 'u');- gr.add_range ('a', 'd');- gr.add_range ('f', 'z');- gr.add_range ('b', 'd');- - return_if_fail (gr.length () == 'z' - 'a' + 1);- return_if_fail (gr.get_ranges ().length () == 1);- return_if_fail (gr.get_ranges ().first ().data.length () == 'z' - 'a' + 1);- - for (unichar i = 'a'; i <= 'z'; i++) {- uint index = i - 'a';- string c = gr.get_char (index);- StringBuilder s = new StringBuilder ();- s.append_unichar (i);- - if (c != s.str) {- warning (@"wrong glyph in glyph range got \"$c\" expected \"$(s.str)\" for index $(index).");- }- }- - gr = new GlyphRange ();- gr.add_single ('a');- gr.add_range ('c', 'e');- gr.add_single ('◊');- return_if_fail (gr.get_char (0) == "a");- return_if_fail (gr.get_char (1) == "c");- return_if_fail (gr.get_char (2) == "d");- return_if_fail (gr.get_char (3) == "e");- return_if_fail (gr.get_char (4) == "◊");- }-- public static void test_glyph_table () {- GlyphTable table = new GlyphTable ();- List<GlyphCollection> gc = new List<GlyphCollection> ();- List<unowned GlyphCollection> gc_copy;- GlyphCollection g;- - return_if_fail (table.length () == 0);- return_if_fail (table.get ("Some glyph") == null);- - // generate test data- for (uint i = 0; i < 1000; i++) {- gc.append (new GlyphCollection (new Glyph (@"TEST $i", i + 'a')));- }-- // insert in random order- gc_copy = gc.copy ();- for (uint i = 0; gc_copy.length () > 0; i++) {- int t = (int) ((gc_copy.length () - 1) * Random.next_double ());- g = gc_copy.nth (t).data;- - if (!table.insert (g)) {- warning (@"Failed to insert $(g.get_name ())");- return;- }- - gc_copy.remove_all (g);- - if (!table.validate_index ()) {- table.print_all ();- warning ("index is invalid");- return;- }- }- - return_if_fail (table.length () == gc.length ());- - // validate table- for (uint i = 0; i > 1000; i++) {- g = (!) table.get (gc.nth (i).data.get_name ());- return_if_fail (gc.nth (i).data == g);- }- - // search - for (int i = 0; i < 2000; i++) {- int t = (int) (999 * Random.next_double ());- if (table.get (@"TEST $t") == null) {- table.print_all ();- warning (@"Did't find TEST $t in glyph table.");- return;- }- }-- // remove- table.remove ("TEST 0");- table.remove ("TEST 53");- return_if_fail (table.get ("TEST 0") == null);- return_if_fail (table.get ("TEST 53") == null);- - // search - return_if_fail (table.get ("TEST 52") != null);- return_if_fail (table.get ("TEST 54") != null);- }-- public static void test_delete_points () {- PenTool tool = (PenTool) MainWindow.get_toolbox ().get_tool ("pen_tool");- tool.test_delete_points ();- }-- public static void test_active_edit_point () {- PenTool tool = (PenTool) MainWindow.get_toolbox ().get_tool ("pen_tool");- tool.test_active_edit_point ();- }- - public static void test_save_backup () {- // TODO draw various things and assert that they are restored correctly- Supplement.get_current_font ().save_backup ();- Supplement.get_current_font ().restore_backup ();- }-- public static void test_hex () {- test_hex_conv ('H', "U+48", 72);- test_hex_conv ('1', "U+31", 49);- test_hex_conv ('å', "U+e5", 229);- test_hex_conv ('◊', "U+25ca", 9674);- }-- private static void test_hex_conv (unichar h, string sr, int r) {- string s = Font.to_hex (h);- unichar t = Font.to_unichar (sr);- - if (s != sr) warning (@"($s != \"$sr\")");- if ((int)t != r || t != h) warning (@"$((int)t) != $r || $t != '$h'");- }-- public static void test_coordinates () {- PenTool tool = (PenTool) MainWindow.get_toolbox ().get_tool ("pen_tool");- tool.test_coordinates (); - }-- public static void test_reverse_random_paths () {- PenTool tool = (PenTool) MainWindow.get_toolbox ().get_tool ("pen_tool");- tool.test_reverse_random_triangles (); - }-- public static void test_reverse_path () {- PenTool tool = (PenTool) MainWindow.get_toolbox ().get_tool ("pen_tool");- tool.test_reverse_path ();- }-- public static void test_drawing () {- Tool tool = MainWindow.get_toolbox ().get_tool ("pen_tool");- tool.test ();- }- - private void add (Callback callback, string name) {- test_cases.append (new Test (callback, name));- }- - public unowned List<Test> get_test_functions () {- return test_cases;- }-} - -}deleted file mode 100644@@ -1,262 +0,0 @@-/* - 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/>. -*/ - -namespace Supplement { - -class Test : Object { - public Callback callback; - public string name; - - public Test (Callback callback, string name) { - this.callback = callback; - this.name = name; - } -} - -/** Class for executing tests cases. */ -class TestSupplement : GLib.Object { - - const int NOT_STARTED = 0; - const int RUNNING = 1; - const int PAUSED = 2; - const int DONE = 3; - - static int state = NOT_STARTED; - - TestCases tests; - - unowned List<Test> test_cases; - unowned List<Test?>? current_case = null; - - unowned List<Test> passed; - unowned List<Test> failed; - unowned List<Test> skipped; - - static TestSupplement? singleton = null; - - bool has_failed = false; - bool has_skipped = false; - - static bool slow_test = false; - - string test_cases_to_run; // name of specific test case or all to run all test cases - - public TestSupplement () { - assert (singleton == null); - tests = new TestCases (); - test_cases = tests.get_test_functions (); - current_case = test_cases.first (); - test_cases_to_run = "All"; - - from_command_line (); - } - - public static bool is_slow_test () { - return slow_test; - } - - public static void set_slow_test (bool s) { - slow_test = s; - } - - public static TestSupplement get_singleton () { - if (singleton == null) { - singleton = new TestSupplement (); - } - - return (!) singleton; - } - - private bool has_test_case (string s) { - foreach (var t in test_cases) { - if (t.name == s) return true; - } - - if (s == "" || s == "All") { - print ("No specific tescase given run all test cases.\n"); - return true; - } - - return false; - } - - /** Run only test specified on the command line. */ - private void from_command_line () { - string? stn = Supplement.get_argument ("--test"); - - if (stn != null) { - string st = (!) stn; - - if (!has_test_case (st)) { - stderr.printf (@"Test case \"$st\" does not exist.\n"); - stderr.printf ("\nAvaliable test cases:\n"); - - foreach (var t in test_cases) { - stderr.printf (t.name); - stderr.printf ("\n"); - } - - Process.exit(1); - } - - if (st == "All" || st == "") { - return; - } else { - stderr.printf (@"Run only test case \"$st\" \n"); - } - - test_cases_to_run = st; - } - } - - public static void log (string? log_domain, LogLevelFlags log_levels, string message) { - Test t = (!)((!)get_singleton ().current_case).data; - - if (log_domain != null) { - stderr.printf ("%s: \n", (!) log_domain); - } - - stderr.printf ("Testcase \"%s\" failed because:\n", t.name); - stderr.printf ("%s\n\n", message); - - get_singleton ().has_failed = true; - - assert (!Supplement.fatal_wanings); - } - - public static void @continue () { - int s = AtomicInt.get (ref state); - if (s == DONE) { - singleton = null; - } - - TestSupplement t = get_singleton (); - - LogLevelFlags levels = LogLevelFlags.LEVEL_ERROR | LogLevelFlags.LEVEL_CRITICAL | LogLevelFlags.LEVEL_WARNING; - Log.set_handler (null, levels, log); - - AtomicInt.set (ref state, RUNNING); - t.run_all_tests (); - } - - public static bool is_running () { - int r = AtomicInt.get (ref state); - return (r == RUNNING); - } - - public static void pause () { - AtomicInt.set (ref state, PAUSED); - Log.set_handler (null, LogLevelFlags.LEVEL_MASK, Log.default_handler); - } - - private static void pad (int t) { - for (int i = 0; i < t; i++) stdout.printf (" "); - } - - public void print_result () { - stdout.printf ("\n"); - stdout.printf ("Testcase results:\n"); - - foreach (var t in skipped) { - stdout.printf ("%s", t.name); - pad (40 - t.name.char_count()); - stdout.printf ("Skipped\n"); - } - - if (skipped.length () > 0) { - stdout.printf ("\n"); - } - - foreach (var t in passed) { - stdout.printf ("%s", t.name); - pad (40 - t.name.char_count()); - stdout.printf ("Passed\n"); - } - - foreach (var t in failed) { - stdout.printf ("%s", t.name); - pad (40 - t.name.char_count()); - stdout.printf ("Failed\n"); - } - - stdout.printf ("\n"); - - stdout.printf ("Total %u test cases executed, %u passed and %u failed.\n", (passed.length () + failed.length ()), passed.length (), failed.length ()); - } - - /** Run tests in main loop. */ - public void run_all_tests () { - var idle = new TimeoutSource (20); - - idle.set_callback(() => { - int s = AtomicInt.get (ref state); - - if (s != RUNNING || current_case == null) { - return false; - } - - Test test = (!)((!) current_case).data; - - has_failed = false; - has_skipped = false; - - if (test_cases_to_run != "All" && test_cases_to_run != test.name) { - has_skipped = true; - } else { - test.callback (); - } - - if (has_failed) { - failed.append ((!) test); - - if (Supplement.has_argument ("--exit")) { - print_result (); - Process.exit (1); - } - - } else if (has_skipped) { - skipped.append ((!) test); - } else { - passed.append ((!) test); - } - - if (unlikely (current_case == test_cases.last ())) { - stdout.printf ("Finished running test suite.\n"); - - AtomicInt.set (ref state, DONE); - Log.set_handler (null, LogLevelFlags.LEVEL_MASK, Log.default_handler); - - print_result (); - - if (Supplement.has_argument ("--exit")) { - print_result (); - Process.exit ((failed.length () == 0) ? 0 : 1); - } - return false; - } - - current_case = ((!) current_case).next; - - return true; - }); - - idle.attach (null); - } - -} - -} deleted file mode 100644@@ -1,353 +0,0 @@-/*- 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 {--public class Tool : GLib.Object {- - public double x = 0;- public double y = 0;- public double w = 0;- public double h = 0;-- protected bool active = false;- protected bool selected = false;- - double r_default_selected = 131/255.0;- double g_default_selected = 179/255.0;- double b_default_selected = 231/255.0;- double a_default_selected = 1;- - double r_default = 185/255.0;- double g_default = 207/255.0;- double b_default = 231/255.0;- double a_default = 1;- - double r;- double g;- double b;- double a;- - ImageSurface? icon = null;- - public signal void select_action (Tool selected);- public signal void deselect_action (Tool selected);- - public signal void press_action (Tool selected, int button, int x, int y);- public signal void double_click_action (Tool selected, int button, int x, int y);- public signal void move_action (Tool selected, int x, int y);- public signal void release_action (Tool selected, int button, int x, int y);- - public signal void key_press_action (Tool selected, uint32 keyval);- public signal void key_release_action (Tool selected, uint32 keyval);- - public signal void panel_press_action (Tool selected, uint button, double x, double y);- public signal void panel_release_action (Tool selected, uint button, double x, double y);- public signal void panel_move_action (Tool selected, double x, double y);- - public signal void draw_action (Tool selected, Context cr, Glyph glyph);- - protected string name = "";- - static int next_id = 1;- - int id;- - public bool new_selection = false;- - bool show_bg = true;- - string tip = "";-- // keyboard bindings- public uint modifier_flag;- public unichar key;- - private static ImageSurface? selected_button = null;- private static ImageSurface? active_selected_button = null;- private static ImageSurface? deselected_button = null;- private static ImageSurface? active_deselected_button = null;- - /** Create tool with a certain name and load icon "name".png */- public Tool (string? name = null, string tip = "", unichar key = '\0', uint modifier_flag = 0) {- this.tip = tip;- - if (selected_button == null) {- selected_button = Icons.get_icon ("tool_button_selected.png");- active_selected_button = Icons.get_icon ("tool_button_selected_active.png");- deselected_button = Icons.get_icon ("tool_button_deselected.png");- active_deselected_button = Icons.get_icon ("tool_button_deselected_active.png");- }- - if (name != null) {- set_icon ((!) name);- this.name = (!) name;- }- - this.key = key;- this.modifier_flag = modifier_flag;- - id = next_id;- next_id++;- - panel_press_action.connect ((self, button, x, y) => {- MainWindow.get_tool_tip ().set_text_from_tool ();- });- - r = r_default;- g = g_default;- b = b_default;- a = a_default;- }- - public void set_icon (string name) {- StringBuilder n = new StringBuilder ();- n.append ((!) name);- n.append (".png");- icon = Icons.get_icon (n.str);- }- - public bool is_active () {- return active;- }- - public void set_show_background (bool bg) {- show_bg = bg;- }- - public int get_id () {- return id;- }-- public string get_name () {- return name;- }-- public bool is_selected () {- return selected;- }- - public virtual void set_properties (double tx, double ty, double tw, double th) {- x = tx;- y = ty;- w = tw;- h = th;- }-- public string get_tip () {- return tip;- }-- public bool is_over (double xp, double yp) { - return (x <= xp <= x + w + 12 + 15 && y + 15 <= yp <= y + w + 12 + 30); - }- - public bool set_selected (bool a) {- TooltipArea? tpa = null; - tpa = MainWindow.get_tool_tip ();- - new_selection = true;- selected = a;- set_active (a);- - if (!a) {- deselect_action (this);- } else {- ((!)tpa).update_text ();- }- - return true;- }- - public bool set_active (bool ac) {- bool ret = (active != ac);-- if (selected) {- if (ac) {- r = r_default_selected;- g = g_default_selected;- b = b_default_selected;- a = a_default_selected * 0.5;- } else {- r = r_default_selected;- g = g_default_selected;- b = b_default_selected;- a = a_default_selected;- } - } else {- if (ac) {- r = r_default;- g = g_default;- b = b_default;- a = a_default * 0.2;- } else {- r = r_default;- g = g_default;- b = b_default;- a = a_default;- }- }-- active = ac;- return ret;- }- - public virtual void draw (Context cr) {- double xt = x + 3 + w;- double yt = y + h + 17;-- double bgx, bgy;- - bgx = xt - 6;- bgy = yt - 7;-- cr.save ();-- // Button in four states- if (selected && selected_button != null) {- cr.set_source_surface ((!) selected_button, bgx, bgy);- cr.paint ();- }-- if (selected && active && active_selected_button != null) {- cr.set_source_surface ((!) active_selected_button, bgx, bgy);- cr.paint ();- }-- if (!selected && deselected_button != null) {- cr.set_source_surface ((!) deselected_button, bgx, bgy);- cr.paint ();- }-- if (!selected && active && active_deselected_button != null) {- cr.set_source_surface ((!) active_deselected_button, bgx, bgy);- cr.paint ();- }- - if (icon != null) {- ImageSurface i = (!) icon;- - if (likely (i.status () == Cairo.Status.SUCCESS)) {- cr.set_source_surface (i, xt + (15 - i.get_width () / 2) - 5.7, yt + (15 - i.get_height ()) / 2.0);- cr.paint ();- } else {- warning (@"Falied to load icon for $name");- }- }- - cr.restore ();- }-- /** Run a test case for this tool. */- public virtual bool test () {- stderr.printf (@"$(get_name ()) does not have a test case, implement one by overriding test method in Tool base class.");- return false;- }- - /** Help function to test button press actions. */- public void test_click_action (int b, int x, int y) {- Tool.yield ();- press_action (this, b, x, y);- - Tool.yield ();- release_action (this, b, x, y);- }-- /** Help function to test select action for this tool. */- public void test_select_action () {- Tool.yield ();- MainWindow.get_toolbox ().select_tool (this);- }-- public void test_move_action (int x, int y) {- Tool.yield ();- move_action (this, x, y);- }-- public void test_press_action (int b, int x, int y) {- Tool.yield ();- press_action (this, b, x, y);- }-- public void test_release_action (int b, int x, int y) {- Tool.yield ();- release_action (this, b, x, y);- }-- public static void test_open_next_glyph () {- Tool.yield ();- OverView o = MainWindow.get_overview ();- - MainWindow.get_tab_bar ().select_overview ();- MainWindow.get_toolbox ().select_tool_by_name ("utf_8");- - o.select_next_glyph ();- Tool.yield ();- - o.open_current_glyph ();- Tool.yield ();- }- - /** Run pending events in main loop before continue. */- public static void @yield () {- int t = 0;- var time = new TimeoutSource(500);- bool timeout;-- if (TestSupplement.is_slow_test ()) {- timeout = false;- - time.set_callback(() => {- timeout = true;- return false;- });-- time.attach(null); - } else {- timeout = true;- }- - unowned MainContext c = MainContext.default ();- bool a = c.acquire ();- - while (unlikely (!a)) {- warning ("Failed to acquire main loop.\n");- return;- }-- while (c.pending () || TestSupplement.is_slow_test ()) {- c.iteration (true);- t++;-- if (!c.pending () && TestSupplement.is_slow_test ()) {- if (timeout) break;- }- - if (unlikely (t > 100000)) {- assert (false);- break;- }- }- - c.release ();- }- -}--}deleted file mode 100644@@ -1,868 +0,0 @@-/*- 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;-using Math;--namespace Supplement {--public class Toolbox : GLib.Object {- GlyphCanvas glyph_canvas;- - bool expanded = true;- - ToolboxExpander toolbox_expander;- - public List<Expander> expanders;- - Tool current_tool = new Tool ("no_icon");- - Expander draw_tools;- Expander grid_expander;- - BackgroundTool move_background;- CutBackgroundTool cut_background;- - public Tool press_tool = new Tool (null); // activate the pressed button on release- - public SpinButton background_scale = new SpinButton ();- public SpinButton precision;- - public signal void redraw (int x, int y, int w, int h);- - public int allocation_width = 0;- public int allocation_height = 0;- - ImageSurface? toolbox_background = null;- - public Toolbox (GlyphCanvas main_glyph_canvas) {- glyph_canvas = main_glyph_canvas;-- toolbox_background = Icons.get_icon ("toolbox_background.png");-- toolbox_expander = new ToolboxExpander ();- - add_expander (toolbox_expander);- - draw_tools = new Expander ();- - Expander path_tool_modifiers = new Expander ();- Expander draw_tool_modifiers = new Expander ();- Expander characterset_tools = new Expander ();- Expander test_tools = new Expander ();- Expander guideline_tools = new Expander ();- Expander view_tools = new Expander ();- Expander grid = new Expander ();- Expander background_tools = new Expander ();- Expander trace = new Expander ();- - grid_expander = grid;-- // Draw tools- PenTool pen_tool = new PenTool ("pen_tool");- draw_tools.add_tool (pen_tool);- - ZoomTool zoom_tool = new ZoomTool ("zoom_tool");- draw_tools.add_tool (zoom_tool);-- CutTool cut_tool = new CutTool ("cut");- draw_tools.add_tool (cut_tool); -- Tool move_tool = new MoveTool ("move");- draw_tools.add_tool (move_tool);-- Tool shrink_tool = new ShrinkTool ("shrink");- // Fixa: draw_tools.add_tool (shrink_tool); - - // Draw tool modifiers- // DELETE tools:- - Tool new_point = new Tool ("new_point", _("Right click to add new point, left click to move points") + " " + _("and double click to add new point on path"), 'a');- new_point.select_action.connect ((self) => {- select_draw_tool ();- });- - // draw_tool_modifiers.add_tool (new_point);-- Tool insert_point_on_path = new Tool ("insert_point_on_path", _("Add new point on path"), 'n');- insert_point_on_path.select_action.connect ((self) => {- select_draw_tool ();- pen_tool.begin_from_new_point_on_path ();- });- // draw_tool_modifiers.add_tool (insert_point_on_path);- - if (Supplement.experimental) {- Tool new_point_on_path = new Tool ("new_point_on_path", _("Begin new line from point on path"), 'n');- new_point_on_path.select_action.connect ((self) => {- select_draw_tool ();- pen_tool.begin_from_new_point_on_path ();- });- draw_tool_modifiers.add_tool (new_point_on_path); - }-- Tool tie_editpoint_tool = new Tool ("tie_point", _("Tie curve handles for selected edit point"), 'w');- tie_editpoint_tool.select_action.connect ((self) => {- bool tie;- - select_draw_tool ();- - foreach (EditPoint ep in pen_tool.selected_points) {- tie = !ep.tie_handles;-- if (tie) {- ep.process_tied_handle ();- }-- ep.set_tie_handle (tie);- - MainWindow.get_current_glyph ().update_view (); - }- });- draw_tool_modifiers.add_tool (tie_editpoint_tool); -- Tool tie_x_or_y = new Tool ("tie_x_or_y", _("Tie coordinates to previous edit point"), 'q');- tie_x_or_y.select_action.connect((self) => {- select_draw_tool ();- pen_tool.tie_x_or_y_coordinates = !pen_tool.tie_x_or_y_coordinates;- });- // Fixa: draw_tool_modifiers.add_tool (tie_x_or_y); -- Tool erase_tool = new Tool ("erase_tool", _("Delete edit points"));- erase_tool.select_action.connect((self) => {- select_draw_tool ();- });- // draw_tool_modifiers.add_tool (erase_tool); -- // adjust precision- string precision_value = Preferences.get ("precision");- - precision = new SpinButton ("precision", _("Set precision"));- - if (precision_value != "") {- precision.set_value (precision_value);- } else {- precision.set_value_round (1);- }- - precision.new_value_action.connect ((self) => {- select_tool (precision);- - Preferences.set ("precision", self.get_display_value ());- redraw ((int) precision.x, (int) precision.y, 70, 70);- });-- precision.select_action.connect((self) => {- pen_tool.set_precision (((SpinButton)self).get_value ());- });- - precision.set_min (0.001);- precision.set_max (1);- - draw_tool_modifiers.add_tool (precision);- - // path tools- Tool union_paths_tool = new MergeTool ("union_paths");- path_tool_modifiers.add_tool (union_paths_tool);- - Tool reverse_path_tool = new Tool ("reverse_path", _("Create counter from outline"), 'r');- reverse_path_tool.select_action.connect ((self) => {- Glyph g = MainWindow.get_current_glyph ();- - foreach (Path p in g.active_paths) {- p.reverse ();- }- - g.redraw_area (0, 0, g.allocation.width, g.allocation.height);- });- path_tool_modifiers.add_tool (reverse_path_tool);-- Tool move_layer = new Tool ("move_layer", _("Move to path to the bottom layer"), 'd');- move_layer.select_action.connect ((self) => {- Glyph g = MainWindow.get_current_glyph ();-- foreach (Path p in g.active_paths) {- g.path_list.remove (p);- g.path_list.prepend (p);- }- });- path_tool_modifiers.add_tool (move_layer);- - // Character set tools- Tool full_unicode = new Tool ("utf_8", _("Show full unicode characters set"), 'f', CTRL);- full_unicode.select_action.connect ((self) => {- MainWindow.get_tab_bar ().add_unique_tab (new OverView (), 100, false); - OverView o = MainWindow.get_overview ();- GlyphRange gr = new GlyphRange ();- gr.use_full_unicode_range ();- o.set_glyph_range (gr);- MainWindow.get_tab_bar ().select_tab_name ("Overview");- });- characterset_tools.add_tool (full_unicode);-- Tool custom_character_set = new Tool ("custom_character_set", _("Show default characters set"), 'r', CTRL);- custom_character_set.select_action.connect ((self) => {- MainWindow.get_tab_bar ().add_unique_tab (new OverView (), 100, false);- OverView o = MainWindow.get_overview ();- GlyphRange gr = new GlyphRange ();- gr.use_default_range ();- o.set_glyph_range (gr);- MainWindow.get_tab_bar ().select_tab_name ("Overview");- });- characterset_tools.add_tool (custom_character_set);-- Tool avalilable_characters = new Tool ("available_characters", _("Show all characters in font"), 'd', CTRL);- avalilable_characters.select_action.connect ((self) => {- MainWindow.get_tab_bar ().add_unique_tab (new OverView (), 100, false);- OverView o = MainWindow.get_overview ();- o.display_all_available_glyphs ();- MainWindow.get_tab_bar ().select_tab_name ("Overview");- });- characterset_tools.add_tool (avalilable_characters);-- Tool delete_glyph = new Tool ("delete_selected_glyph", _("Delete selected glyph"));- delete_glyph.select_action.connect ((self) => {- OverView o = MainWindow.get_overview ();- - if (MainWindow.get_current_display () is OverView) {- o.delete_selected_glyph ();- }- - MainWindow.get_tab_bar ().select_tab_name ("Overview");- });- characterset_tools.add_tool (delete_glyph);- - if (Supplement.has_argument ("--test")) {- Tool test_case = new Tool ("test_case");- test_case.select_action.connect((self) => {- if (self.is_selected ()) {- if (TestSupplement.is_running ()) {- TestSupplement.pause ();- } else {- TestSupplement.continue ();- }- }- });- test_tools.add_tool (test_case);-- Tool slow_test = new Tool ("slow_test");- slow_test.select_action.connect((self) => {- bool s = TestSupplement.is_slow_test ();- TestSupplement.set_slow_test (!s);- s = TestSupplement.is_slow_test ();- self.set_selected (s);- });- - test_tools.add_tool (slow_test);- - // Run from commad line- string? st = Supplement.get_argument ("--test");- if (st != null && ((!)st).char_count () > 0) {- IdleSource idle = new IdleSource ();-- idle.set_callback (() => { - select_tool (test_case);- return false;- });- - idle.attach (null);- }- - if (Supplement.has_argument ("--slow")) {- select_tool (slow_test);- }- - }- - // help lines, grid and other guidlines- Tool help_lines = new Tool ("help_lines", "Show help lines", 'l');- help_lines.select_action.connect ((self) => {- bool h;- h = glyph_canvas.get_current_glyph ().get_show_help_lines ();- glyph_canvas.get_current_glyph ().set_show_help_lines (!h);- self.set_selected (!h);- glyph_canvas.get_current_glyph ().redraw_help_lines ();- }); -- guideline_tools.add_tool (help_lines);-- Tool xheight_help_lines = new Tool ("show_xheight_helplines", _("Show help lines for x-height and baseline"), 'x');- xheight_help_lines.select_action.connect ((self) => {- Glyph g = MainWindow.get_current_glyph ();- bool v = !g.get_xheight_lines_visible ();- g.set_xheight_lines_visible (v);- self.set_selected (v);- MainWindow.get_glyph_canvas ().redraw ();- - if (v && !help_lines.is_selected ()) {- select_tool (help_lines);- }- - });- guideline_tools.add_tool (xheight_help_lines);-- Tool background_help_lines = new Tool ("background_help_lines", _("Show help lines at top and bottom margin"), 't');- background_help_lines.select_action.connect ((self) => {- Glyph g = MainWindow.get_current_glyph ();- bool v = !g.get_margin_lines_visible ();- g.set_margin_lines_visible (v);- self.set_selected (v);- MainWindow.get_glyph_canvas ().redraw ();- - if (v && !help_lines.is_selected ()) {- select_tool (help_lines);- }- });- guideline_tools.add_tool (background_help_lines);-- Tool new_grid = new GridTool ("new_grid");- guideline_tools.add_tool (new_grid);-- // Zoom tools - Tool zoom_in = new Tool ("zoom_in", _("Zoom in"), '+', CTRL);- zoom_in.select_action.connect ((self) => {- zoom_tool.store_current_view ();- glyph_canvas.get_current_display ().zoom_in ();- });- view_tools.add_tool (zoom_in);-- Tool zoom_out = new Tool ("zoom_out", _("Zoom out"), '-', CTRL);- zoom_out.select_action.connect ((self) => {- zoom_tool.store_current_view ();- glyph_canvas.get_current_display ().zoom_out ();- });- view_tools.add_tool (zoom_out);-- Tool reset_zoom = new Tool ("zoom_1_1", _("Zoom to scale 1:1"), '0', CTRL);- reset_zoom.select_action.connect ((self) => {- zoom_tool.store_current_view ();- glyph_canvas.get_current_display ().reset_zoom ();- glyph_canvas.redraw_area(0, 0, glyph_canvas.allocation.width, glyph_canvas.allocation.height);- });- view_tools.add_tool (reset_zoom);-- Tool full_glyph = new Tool ("full_glyph", _("Show full glyph"));- full_glyph.select_action.connect((self) => {- zoom_tool.store_current_view ();- zoom_tool.zoom_full_glyph ();- });- view_tools.add_tool (full_glyph);-- Tool zoom_boundries = new Tool ("zoom_boundries", _("Zoom in at region boundries"), 'v');- zoom_boundries.select_action.connect((self) => {- zoom_tool.store_current_view ();- glyph_canvas.get_current_display ().zoom_max ();- });- view_tools.add_tool (zoom_boundries);-- Tool zoom_bg = new Tool ("zoom_background_image", _("Zoom in background image"), 'b');- zoom_bg.select_action.connect((self) => {- if (MainWindow.get_current_glyph ().get_background_image () != null) {- zoom_tool.store_current_view (); - glyph_canvas.get_current_display ().reset_zoom ();- - zoom_tool.zoom_full_background_image ();- - glyph_canvas.redraw_area(0, 0, glyph_canvas.allocation.width, glyph_canvas.allocation.height);- }- });- view_tools.add_tool (zoom_bg);-- Tool zoom_prev = new Tool ("prev", _("Previous view"), 'j', CTRL);- zoom_prev.select_action.connect((self) => {- zoom_tool.previous_view ();- });- view_tools.add_tool (zoom_prev);-- Tool zoom_next = new Tool ("next", _("Next view"), 'l', CTRL);- zoom_next.select_action.connect((self) => {- zoom_tool.next_view ();- });- view_tools.add_tool (zoom_next);- - // background tools- background_scale = new SpinButton ("scale_background", _("Set size for background image"));- background_scale.set_value_round (1);- - background_scale.new_value_action.connect((self) => {- background_scale.select_action (self);- });- - background_scale.select_action.connect((self) => {- SpinButton sb = (SpinButton) self;- Glyph g = MainWindow.get_current_glyph ();- GlyphBackgroundImage? img = g.get_background_image ();- double s = sb.get_value ();- - if (img != null) {- ((!)img).set_img_scale (s, s);- }- - MainWindow.get_glyph_canvas ().redraw ();- });- - background_tools.add_tool (background_scale); -- BackgroundTool move_background = new BackgroundTool ("move_background"); - background_tools.add_tool (move_background);- this.move_background = move_background;- - CutBackgroundTool cut_background = new CutBackgroundTool ("cut_background");- background_tools.add_tool (cut_background);- this.cut_background = cut_background;- - Tool show_bg = new Tool ("show_background", _("Show/hide background image"));- show_bg.select_action.connect ((self) => {- Glyph g = MainWindow.get_current_glyph ();- g.set_background_visible (!g.get_background_visible ());- MainWindow.get_glyph_canvas ().redraw ();- });- background_tools.add_tool (show_bg);-- Tool bg_selection = new Tool ("insert_background", _("Insert a new background image"));- bg_selection.select_action.connect((self) => {- Glyph? g = null;- FontDisplay fd = MainWindow.get_current_display ();- TooltipArea tp = MainWindow.get_tool_tip ();- tp.show_text (_("Creating thumbnails"));- - bg_selection.yield ();- - if (fd is Glyph) {- g = (Glyph) fd;- } else {- return;- }- - BackgroundSelection bgs = new BackgroundSelection ();- MainWindow.get_tab_bar ().add_unique_tab (bgs , 120, false);- - tp.show_text ("");- });- - bg_selection.set_show_background (true);- background_tools.add_tool (bg_selection);- - SpinButton background_contrast = new SpinButton ("background_contrast", _("Set contrast for background image"));- background_contrast.set_value_round (1);-- background_contrast.new_value_action.connect ((self) => {- background_contrast.select_action (self);- });- - background_contrast.select_action.connect ((self) => { - Glyph g = MainWindow.get_current_glyph ();- GlyphBackgroundImage? bg = g.get_background_image ();- GlyphBackgroundImage b;- - if (bg != null) {- b = (!) bg;- b.set_contrast (background_contrast.get_value ());- }- });- - // Fixa: background_tools.add_tool (background_contrast);-- SpinButton background_threshold = new SpinButton ("background_threshold", _("Set threshold"));- background_threshold.set_value_round (1);-- background_threshold.new_value_action.connect ((self) => {- background_threshold.select_action (self);- });- - background_threshold.select_action.connect ((self) => { - Glyph g = MainWindow.get_current_glyph ();- GlyphBackgroundImage? bg = g.get_background_image ();- GlyphBackgroundImage b;- - if (bg != null) {- b = (!) bg;- b.set_threshold (background_threshold.get_value ());- }- - });- trace.add_tool (background_threshold);- - draw_tools.set_open (true);- draw_tool_modifiers.set_open (true);- path_tool_modifiers.set_open (true);- view_tools.set_open (true);- grid.set_open (true);- characterset_tools.set_open (true);- test_tools.set_open (true);- guideline_tools.set_open (true);- background_tools.set_open (true);- trace.set_open (true);- - add_expander (draw_tools);- add_expander (draw_tool_modifiers);- add_expander (path_tool_modifiers); - - add_expander (characterset_tools);- add_expander (guideline_tools);- add_expander (grid);- add_expander (view_tools);- add_expander (background_tools);- - // Fixa: add_expander (trace);- - add_expander (test_tools);- - draw_tools.set_persistent (true);- draw_tools.set_unique (true);- - draw_tool_modifiers.set_persistent (true);- draw_tool_modifiers.set_unique (true);-- path_tool_modifiers.set_persistent (false);- path_tool_modifiers.set_unique (false);-- characterset_tools.set_persistent (true);- characterset_tools.set_unique (true);- - test_tools.set_persistent (true);- - guideline_tools.set_persistent (true);- guideline_tools.set_unique (false);- - grid.set_persistent (true);- grid.set_unique (true);- grid.set_open (false);- - background_tools.set_persistent (true);- background_tools.set_unique (true);- - trace.set_persistent (false);- trace.set_unique (false);-- update_expanders ();- reset_active_tool ();-- // Default selection- var idle = new IdleSource();- idle.set_callback (() => {- new_point.set_selected (true);- pen_tool.set_selected (true);- select_draw_tool (); - - if (glyph_canvas.get_current_glyph ().get_show_help_lines ()) {- help_lines.set_selected (true);- help_lines.set_active (false);- }-- add_new_grid ();- add_new_grid ();- - move (0, 0);- - return false;- });- - idle.attach (null);- }- - public void key_press (uint keyval) {- print ("key_press\n");- foreach (var exp in MainWindow.get_toolbox ().expanders) {- foreach (Tool t in exp.tool) {- t.set_active (false);- - if (t.key == keyval && t.modifier_flag == NONE) {- MainWindow.get_toolbox ().select_tool (t);- }- }- }- }- - public void press (uint button, double x, double y) {- foreach (var exp in expanders) {- foreach (Tool t in exp.tool) {- if (t.is_over (x, y)) {- t.panel_press_action (t, button, x, y);- press_tool = t;- }- }- }- }- - public void release (uint button, double x, double y) {- foreach (var exp in expanders) {- if (exp.is_over (x, y)) {- exp.set_open (! exp.is_open ()); - update_expanders (); - redraw ((int) exp.x - 10, (int) exp.y - 10, allocation_width, (int) (allocation_height - exp.y + 10));- }- - if (exp.is_open ()) {- foreach (Tool t in exp.tool) {- bool active = t.is_over (x, y);- - if (active) {- if (press_tool == t) {- select_tool (t);- }- }- - t.panel_release_action (t, button, x, y);- }- }- }- }- - private void select_draw_tool () {- select_tool_by_name ("pen_tool");- }- - public void move (double x, double y) {- bool update;- bool a;- foreach (var exp in expanders) {- a = exp.is_over (x, y);- update = exp.set_active (a);- - if (update) {- MainWindow.get_toolbox ().redraw ((int) exp.x - 10, (int) exp.y - 10, (int) (exp.x + exp.w + 10), (int) (exp.y + exp.h + 10));- }- - if (exp.is_open ()) {- foreach (Tool t in exp.tool) {- bool active = t.is_over (x, y);- TooltipArea? tpa = null;- - update = t.set_active (active);- tpa = MainWindow.get_tool_tip ();- - if (active && tpa != null) {- ((!)tpa).update_text ();- }- - if (update) {- MainWindow.get_toolbox ().redraw (0, 0, allocation_width, allocation_height);- }- - t.panel_move_action (t, x, y);- }- }- }- }- - public void remove_all_grid_buttons () {- while (grid_expander.tool.length () > 0) {- grid_expander.tool.remove_link (grid_expander.tool.last ());- }- - while (GridTool.sizes.length () > 0) {- GridTool.sizes.remove_link (GridTool.sizes.first ());- }- - grid_expander.set_open (true);- update_expanders ();- redraw (0, 0, allocation_width, allocation_height); - }- - public void parse_grid (string spin_button_value) {- SpinButton sb = add_new_grid ();- sb.set_value (spin_button_value);- select_tool (sb);- }-- public SpinButton add_new_grid () {- SpinButton grid_width = new SpinButton ("grid_width", _("Set size for grid"));- - grid_width.new_value_action.connect((self) => {- grid_width.select_action (grid_width);- });-- grid_width.select_action.connect((self) => {- SpinButton sb = (SpinButton) self;- GridTool.set_grid_width (sb.get_value ());- MainWindow.get_glyph_canvas ().redraw ();- });- - grid_expander.add_tool (grid_width);-- GridTool.sizes.append (grid_width);-- grid_expander.set_open (true);- update_expanders ();- - redraw (0, 0, allocation_width, allocation_height);- - select_tool (grid_width);- grid_width.set_active (false);- - return grid_width;- }-- public void remove_current_grid () {- Tool grid_width;- - foreach (Tool t in grid_expander.tool) {- if (t.is_selected () && t is SpinButton) {- GridTool.sizes.remove ((SpinButton)t);- grid_expander.tool.remove (t);- break;- }- }- - if (grid_expander.tool.length () > 0) {- grid_width = grid_expander.tool.last ().data;- select_tool (grid_width);- grid_width.set_active (false);- }- - update_expanders ();- redraw (0, 0, allocation_width, allocation_height);- }-- public void reset_active_tool () {- foreach (var exp in expanders) {- foreach (Tool t in exp.tool) {- t.set_active (false);- }- }- }-- public Tool? get_active_tool () {- foreach (var exp in expanders) {- foreach (Tool t in exp.tool) {- if (t.is_active ()) {- return t;- }- }- }- - return null;- }-- public Tool get_current_tool () {- return current_tool;- }- - public void select_tool (Tool tool) {- foreach (var exp in expanders) {- - foreach (Tool t in exp.tool) {- if (tool.get_id () == t.get_id ()) {- exp.set_open (true);- - bool update;- - update = tool.set_selected (true);- update = tool.set_active (true);- - tool.select_action (tool); // execute command- - if (update) { - redraw ((int) exp.x - 10, (int) exp.y - 10, allocation_width, (int) (allocation_height - exp.y + 10));- }- - if (exp == draw_tools || t == move_background || t == cut_background) {- current_tool = tool;- }- }- }- - }- }- - public Tool get_tool (string name) {- foreach (var e in expanders) {- foreach (var t in e.tool) {- if (t.get_name () == name) {- return t;- }- }- }-- if (name == cut_background.get_name ()) {- return cut_background;- }- - warning ("No tool found for name \"%s\".\n", name);- - return new Tool ("no_icon");- }- - public static void select_tool_by_name (string name) {- Toolbox b = MainWindow.get_toolbox ();- b.select_tool (b.get_tool (name));- }- - private void update_expanders () {- Expander? p = null; - Expander pp;- foreach (var e in expanders) {- if (p != null) {- pp = (!) p;- e.set_offset (pp.y + pp.margin + 12);- }- - p = e;- }- }- - private void add_expander (Expander e) {- expanders.append (e);- }- - private void draw_expanders (int w, int h, Context cr) {- foreach (Expander e in expanders) {- e.draw (w, h, cr);- if (e.is_open ()) {- e.draw_content (w, h, cr);- }- }- }- - public void draw (int w, int h, Context cr) { - cr.save ();- - cr.rectangle(0, 0, w, h);- cr.set_line_width(0);- cr.set_source_rgba(255/255.0, 255/255.0, 255/255.0, 1);- cr.fill();-- for (int i = 0; i < w; i++) {- cr.set_source_surface ((!) toolbox_background, i, 0);- cr.paint ();- }-- cr.rectangle(0, 0, 1, h);- cr.set_line_width(0);- cr.set_source_rgba(0/255.0, 0/255.0, 0/255.0, 1);- cr.fill();- - draw_expanders (w, h, cr);- - cr.restore ();- }-}--class ToolboxExpander : Expander {- - public ToolboxExpander () {- }- - public override bool set_open (bool o) {- bool r = (open != o);- rotation = (o) ? 0 : Math.PI;- open = o;- return r;- }-}--}deleted file mode 100644@@ -1,122 +0,0 @@-/*- 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;-using Gdk;-using Gtk;--namespace Supplement {--public class TooltipArea : GLib.Object {-- string tool_tip;- public signal void redraw ();-- public TooltipArea () {- set_text_from_tool ();- }-- public void update_text () {- set_text_from_current_tool ();- redraw ();- }- - public void show_text (string text) {- tool_tip = text;- redraw ();- }- - public void set_text_from_tool () {- set_text_from_current_tool ();- redraw ();- }- - private void set_text_from_current_tool () {- Toolbox? tb = MainWindow.get_toolbox ();- Tool? t;- Tool tool;- StringBuilder sb;- - if (tb == null) {- return;- }- - t = ((!)tb).get_active_tool ();- - if (t == null) {- return;- }- - tool = (!) t;- - if (tool.key != '\0') {- sb = new StringBuilder ();-- sb.append ("(");- - if (tool.modifier_flag == CTRL) {- sb.append ("Ctrl+");- } else if (tool.modifier_flag == SHIFT) {- sb.append ("Shift+");- } -- sb.append_unichar (tool.key);- sb.append (") ");- sb.append (tool.get_tip ());- - show_text (sb.str);- } else {- show_text (tool.get_tip ());- }- - }- - public void draw (Context cr, Allocation alloc) {- cr.save ();- cr.rectangle (0, 0, alloc.width, alloc.height);- cr.set_line_width (0);- cr.set_source_rgba (183/255.0, 200/255.0, 223/255.0, 1);- cr.fill_preserve ();- cr.stroke ();- cr.restore ();-- cr.save ();- cr.rectangle (0, 0, alloc.width, 1);- cr.set_line_width (0);- cr.set_source_rgba (107/255.0, 127/255.0, 168/255.0, 1);- cr.fill_preserve ();- cr.stroke ();- cr.restore ();- - cr.save ();- cr.rectangle (0, 1, alloc.width, 1);- cr.set_line_width (0);- cr.set_source_rgba (145/255.0, 160/255.0, 190/255.0, 1);- cr.fill_preserve ();- cr.stroke ();- cr.restore ();- - cr.save ();- cr.set_font_size (14);- cr.move_to (5, 15);- cr.show_text (tool_tip);- cr.restore ();-- }-}--}deleted file mode 100644@@ -1,49 +0,0 @@-/*- 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/>.-*/--namespace Supplement {- -public class UniRange : GLib.Object {- - public unichar start;- public unichar stop;- - public UniRange (unichar start, unichar stop) {- this.start = start;- this.stop = stop;- }- - public unichar length () {- return stop - start + 1;- }-- public unichar get_char (unichar index) {- unichar result = start + index;- - if (unlikely (index < 0)) {- warning ("Index is negative in UniRange.");- }- - if (unlikely (!(start <= result <= stop))) {- warning ("Index out of range in UniRange (%u <= %u <= %u) (index: %u)\n", start, result, stop, index);- }- - return result;- }-}--}deleted file mode 100644@@ -1,52 +0,0 @@-/*- 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/>.-*/--namespace Supplement {--/** User interface action for unit testing and undo command.- */-interface UserActions {- public abstract void do_action ();- public abstract void undo ();- public abstract void reundo ();- public abstract void test ();-}--interface UserClickActions {- public abstract void do_click (int button, int x, int y);-}--class NewPointAction : UserActions, UserClickActions {-- public void do_action () {- }-- public void undo () {- }- - public void reundo () {- }- - public void do_click (int button, int x, int y) {- }- - public void test () {- }- -}--}deleted file mode 100644@@ -1,113 +0,0 @@-/*- 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;-using Math;--namespace Supplement {--public class VersionList : DropMenu {- int versions = 1;- int current_version = 0;- public List<Glyph> glyphs = new List<Glyph> ();- public static bool suppress_signal = false;-- public VersionList (Glyph? g = null) {- base ("version");- set_direction (MenuDirection.POP_UP);- - MenuAction ma = add_item (_("New version"));- ma.action = (self) => {- return_if_fail (self.parent != null);- return_if_fail (glyphs.length () > 0);- - Supplement.get_current_font ().touch ();- - add_new_version ();- current_version = (int) glyphs.length () - 1;- };- - if (g != null) {- add_glyph ((!) g);- }- }- - public static void set_suppress_signal (bool s) {- suppress_signal = s;- }- - public Glyph get_current () {- if (unlikely (current_version >= glyphs.length ())) {- warning (@"current_version >= glyphs.length ($current_version >= $(glyphs.length ()))");- return new Glyph ("");- }- - return glyphs.nth (current_version).data;- }-- public void add_new_version () {- Glyph g = get_current ();- Glyph new_version = g.copy ();- add_glyph (new_version);- }- - private void set_selected_item (MenuAction ma) {- int i = ma.index;- - return_if_fail (0 <= i < glyphs.length ());-- current_version = i;- - return_if_fail (ma.parent != null);- - ((!)ma.parent).deselect_all ();- ma.set_selected (true); - - if (!is_null (MainWindow.get_tab_bar ())) {- MainWindow.get_tab_bar ().close_by_name (get_current ().name);- } - }- - public void add_glyph (Glyph new_version, bool selected = true) {- MenuAction ma;- - versions++;- glyphs.append (new_version);-- ma = add_item (_("Version") + @" $(versions - 1)");- ma.index = (int) glyphs.length () - 1;- - ma.action = (self) => {- Font font;- - if (suppress_signal) {- return;- }- - font = Supplement.get_current_font ();- set_selected_item (self);- font.touch ();- };-- if (selected && !suppress_signal) {- set_selected_item (ma);- }- }--}--}deleted file mode 100644@@ -1,157 +0,0 @@-/*- 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 Gtk;--namespace Supplement {--class ZoomTool : Tool {-- int zoom_area_begin_x = -1;- int zoom_area_begin_y = -1;- - int view_index = 0;- List<Tab> views = new List<Tab> ();- - public ZoomTool (string n) {- base (n, "Zoom");-- select_action.connect((self) => {- });-- select_action.connect((self) => {- });- - press_action.connect((self, b, x, y) => {- if (b == 1) {- zoom_area_begin_x = x;- zoom_area_begin_y = y;- }- });-- move_action.connect((self, x, y) => {- if (zoom_area_begin_x > 0) {- Glyph g = MainWindow.get_current_glyph ();- g.show_zoom_area (zoom_area_begin_x, zoom_area_begin_y, x, y);- }- });- - release_action.connect((self, b, x, y) => {- Glyph g;- - if (b == 1) {- store_current_view ();- - g = MainWindow.get_current_glyph ();- - if (zoom_area_begin_x == x && zoom_area_begin_y == y) { // zero width center this point but don't zoom in- g.set_center (x, y);- } else { - g.set_zoom_from_area ();- }- - zoom_area_begin_x = -1;- zoom_area_begin_y = -1;- }- });- }-- public void zoom_full_background_image () {- GlyphBackgroundImage bg;- Glyph g = MainWindow.get_current_glyph ();- int x, y;- - if (g.get_background_image () == null) {- return;- }- - bg = (!) g.get_background_image ();- - x = (int) (bg.img_offset_x);- y = (int) (bg.img_offset_y);- - g.set_zoom_area (x, y, (int) (x + bg.size_margin * bg.img_scale_x), (int) (y + bg.size_margin * bg.img_scale_y));- g.set_zoom_from_area ();- }-- public void zoom_full_glyph () {- store_current_view ();- - MainWindow.get_current_display ().zoom_min ();- }- - public override bool test () {- test_select_action ();- return true;- }- - /** Add an item to zoom view list. */- public void store_current_view () { - if (views.length () - 1 > view_index) {- unowned List<Tab> i = views.nth (view_index + 1);- while (i != i.last ()) {- i.delete_link (i.next);- }- }- - views.append (MainWindow.get_current_tab ());- view_index = (int)views.length () - 1;- MainWindow.get_current_display ().store_current_view ();- }-- /** Redo last zoom.*/- public void next_view () {- if (view_index + 1 >= (int) views.length ()) {- return;- }- - view_index++;- - MainWindow.select_tab (views.nth (view_index).data);- MainWindow.get_current_display ().next_view ();- MainWindow.get_glyph_canvas ().redraw ();- }- - /** Undo last zoom. */- public void previous_view () {- if (view_index == 0) {- return;- }- - view_index--;- - MainWindow.select_tab (views.nth (view_index).data);- MainWindow.get_current_display ().restore_last_view ();- MainWindow.get_glyph_canvas ().redraw ();- }-}--class ZoomView : GLib.Object {- public double x;- public double y;- public double zoom;- public Allocation allocation;- - public ZoomView (double x, double y, double zoom, Allocation allocation) {- this.x = x;- this.y = y;- this.zoom = zoom;- this.allocation = allocation;- }-}- -}deleted file mode 100644@@ -1,139 +0,0 @@-BIRDFONT = [- 'Allocation.vala',- 'Argument.vala',- 'Font.vala',- 'BackgroundTool.vala',- 'BackgroundSelection.vala',- 'Birdfont.vala',- 'EotWriter.vala',- 'Expander.vala',- 'ExportTool.vala',- 'FontDisplay.vala',- 'CharDatabase.vala',- 'Config.vala',- 'Context.vala',- 'CutBackgroundTool.vala',- 'CutTool.vala',- 'ClipTool.vala',- 'DropMenu.vala',- 'EditPoint.vala',- 'EditPointHandle.vala',- 'Glyph.vala',- 'GlyphBackgroundImage.vala',- 'GlyphCanvas.vala',- 'GlyphCollection.vala',- 'GlyphRange.vala',- 'GlyphTable.vala',- 'GridTool.vala',- 'Icons.vala',- 'Kerning.vala', - 'KeyBindings.vala', - 'Line.vala',- 'MainWindow.vala',- 'MenuAction.vala',- 'MenuTab.vala',- 'MergeTool.vala',- 'MoveTool.vala',- 'NativeWindow.vala',- 'Intersection.vala',- 'IntersectionList.vala',- 'IOThread.vala',- 'OpenFontFormatWriter.vala',- 'OpenFontFormatReader.vala',- 'OverView.vala',- 'Path.vala',- 'PenTool.vala',- 'Preview.vala',- 'ResizeTool.vala',- 'SaveDialog.vala',- 'Scrollbar.vala',- 'ShrinkTool.vala',- 'SpinButton.vala',- 'Supplement.vala',- 'Svg.vala',- 'SvgFontFormatWriter.vala',- 'Preferences.vala',- 'Tab.vala',- 'TabBar.vala',- 'TestCases.vala', - 'TestSupplement.vala', - 'Tool.vala',- 'Toolbox.vala',- 'TooltipArea.vala',- 'UniRange.vala',- 'VersionList.vala',- 'ZoomTool.vala',-]--PARAM = [- '-X -D\'GETTEXT_PACKAGE="birdfont"\' '- '--thread',- '--disable-warnings',- '--enable-experimental',- '--enable-experimental-non-null',- '--vapidir=./',-]--def build (bld):-- bld.shlib (- target = 'birdfont',- param = PARAM,- packages = 'libxml-2.0',- uselib = 'CAIRO GLIB GIO PIXBUF XML',- source = BIRDFONT,- name = "libbirdfont",- )-- bld (- rule = "mv src/birdfont.h ./",- depends = "libbirdfont",- name = "birdfont_h"- )- - bld (- rule = 'valac -C ./src/birdfont.vapi ../src/Main.vala ../src/GtkWindow.vala -X ../build/src/libbirdfont.so -X ./birdfont.h --pkg gtk+-2.0 --pkg libxml-2.0 --pkg gdk-2.0 --pkg webkit-1.0',- name = 'main_c',- source = ['../src/Main.vala', '../src/GtkWindow.vala'],- target = '../Main.c',- depends = 'libbirdfont'- )-- bld (- rule = "valac -C ./src/birdfont.vapi ../src/BirdfontExport.vala -X ../build/src/libbirdfont.so -X ./birdfont.h --pkg gtk+-2.0 --pkg libxml-2.0 --pkg gdk-2.0 --pkg webkit-1.0",- depends = 'libbirdfont',- target = '../BirdfontExport.c',- name = 'birdfont_export'- )-- bld (- rule = """gcc ../build/Main.c ../build/GtkWindow.c \- -D 'GETTEXT_PACKAGE="birdfont"' \- -I ../build/ -I ../build/src -L ../build/src -l birdfont \- -o ${TGT} \- $(pkg-config --cflags --libs glib-2.0) \- $(pkg-config --cflags --libs libxml-2.0) \- $(pkg-config --cflags --libs gio-2.0) \- $(pkg-config --cflags --libs libsoup-2.4) \- $(pkg-config --cflags --libs gtk+-2.0) \- $(pkg-config --cflags --libs webkit-1.0)""",- source = ['../Main.c'],- target = 'birdfont',- depends = 'main_c'- )-- bld (- rule = 'gcc ../build/BirdfontExport.c -I../build/ -L ../build/src -l birdfont -o ${TGT} \- $(pkg-config --cflags --libs glib-2.0) \- $(pkg-config --cflags --libs libxml-2.0) \- $(pkg-config --cflags --libs gio-2.0) \- $(pkg-config --cflags --libs libsoup-2.4) \- $(pkg-config --cflags --libs gtk+-2.0) \- $(pkg-config --cflags --libs webkit-1.0)',- source = ['../BirdfontExport.c'],- target = 'birdfont-export',- depends = 'birdfont_export'- )-- bld.install_files('${PREFIX}/bin/', ['../build/src/birdfont'], chmod=755)- bld.install_files('${PREFIX}/bin/', ['../build/src/birdfont-export'], chmod=755)@@ -53,8 +53,10 @@ def build(bld): bld.add_pre_fun(pre) bld.add_post_fun(post) - - bld.recurse('src') + + bld.recurse('libbirdfont') + bld.recurse('birdfont') + bld.recurse('birdfont-export') start_dir = bld.path.find_dir('./') bld.install_files('${PREFIX}/share/birdfont/', start_dir.ant_glob('layout/*'), cwd=start_dir, relative_trick=True) @@ -142,14 +144,14 @@ def compile_translations (bld): ) bld.install_files( - '${PREFIX}/locale/" + loc + "/LC_MESSAGES/', + '${PREFIX}/share/locale/' + loc + '/LC_MESSAGES/', ['build/locale/' + loc + '/LC_MESSAGES/birdfont.mo'] ) def write_config (cfg): print ("Writing Config.vala") - f = open('./src/Config.vala', 'w+') + f = open('./libbirdfont/Config.vala', 'w+') f.write("// Don't edit this file – it's generated by wscript\n") f.write("namespace Supplement {\n")