The Birdfont Source Code


All Repositories / birdfont.git / commitdiff – RSS feed

Merge branch 'master' of github.com:johanmattssonm/birdfont

These changes was commited to the Birdfont repository Wed, 20 Jan 2016 21:48:19 +0000.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git
[Wed, 20 Jan 2016 21:48:19 +0000]

Updated Files

README.md
birdfont/GtkWindow.vala
birdfont/Main.vala
birdui/BoxLayout.vala
birdui/Component.vala
birdui/GtkWidget.vala
birdui/GtkWindow.vala
birdui/HBox.vala
birdui/Overflow.vala
birdui/Style.vala
birdui/SvgComponent.vala
birdui/VBox.vala
build.py
configure
dodo.py
install.py
libbirdfont/BackgroundTab.vala
libbirdfont/BezierPoints.vala
libbirdfont/BirdFont.vala
libbirdfont/BirdFontFile.vala
libbirdfont/CanvasSettings.vala
libbirdfont/ClipTool.vala
libbirdfont/Color.vala
libbirdfont/ColorPicker.vala
libbirdfont/DrawingTools.vala
libbirdfont/EmbeddedSvg.vala
libbirdfont/Expander.vala
libbirdfont/ExportTool.vala
libbirdfont/Glyph.vala
libbirdfont/GlyphSequence.vala
libbirdfont/Gradient.vala
libbirdfont/Help.vala
libbirdfont/KerningDisplay.vala
libbirdfont/LabelTool.vala
libbirdfont/Layer.vala
libbirdfont/LayerLabel.vala
libbirdfont/LayerUtils.vala
libbirdfont/LicenseDialog.vala
libbirdfont/MainWindow.vala
libbirdfont/Menu.vala
libbirdfont/MenuItem.vala
libbirdfont/MenuTab.vala
libbirdfont/MoveTool.vala
libbirdfont/OpenFontFormat/FontData.vala
libbirdfont/OpenFontFormat/GlyfData.vala
libbirdfont/OpenFontFormat/GlyfTable.vala
libbirdfont/OpenFontFormat/OpenFontFormatReader.vala
libbirdfont/OpenFontFormat/OpenFontFormatWriter.vala
libbirdfont/OpenFontFormat/OtfInputStream.vala
libbirdfont/OpenFontFormat/OtfTable.vala
libbirdfont/OpenFontFormat/SvgTable.vala
libbirdfont/OrientationTool.vala
libbirdfont/OtfFeatureTable.vala
libbirdfont/OverView.vala
libbirdfont/OverViewItem.vala
libbirdfont/OverviewTools.vala
libbirdfont/Path.vala
libbirdfont/PathList.vala
libbirdfont/PathObject.vala
libbirdfont/PenTool.vala
libbirdfont/QuestionDialog.vala
libbirdfont/Renderer/CachedFont.vala
libbirdfont/Renderer/Drawing.vala
libbirdfont/Renderer/FallbackFont.vala
libbirdfont/Renderer/FontCache.vala
libbirdfont/Renderer/LineTextArea.vala
libbirdfont/Renderer/Text.vala
libbirdfont/Renderer/TextArea.vala
libbirdfont/Renderer/fontconfig.c
libbirdfont/ResizeTool.vala
libbirdfont/SaveCallback.vala
libbirdfont/SearchPaths.vala
libbirdfont/SettingsTab.vala
libbirdfont/Stop.vala
libbirdfont/StrokeTool.vala
libbirdfont/Svg.vala
libbirdfont/SvgArc.vala
libbirdfont/SvgFontFormatWriter.vala
libbirdfont/SvgParser.vala
libbirdfont/SvgStyle.vala
libbirdfont/TabContent.vala
libbirdfont/Test.vala
libbirdfont/TestCases.vala
libbirdfont/TextRendering/CachedFont.vala
libbirdfont/TextRendering/Drawing.vala
libbirdfont/TextRendering/FallbackFont.vala
libbirdfont/TextRendering/FontCache.vala
libbirdfont/TextRendering/LineTextArea.vala
libbirdfont/TextRendering/Text.vala
libbirdfont/TextRendering/TextArea.vala
libbirdfont/TextRendering/fontconfig.c
libbirdfont/Theme.vala
libbirdfont/Tool.vala
libbirdfont/ToolItem.vala
libbirdfont/TrackTool.vala
libsvgbird/AttributePattern.vala
libsvgbird/BezierPoints.vala
libsvgbird/Circle.vala
libsvgbird/ClipPath.vala
libsvgbird/Color.vala
libsvgbird/Defs.vala
libsvgbird/Doubles.vala
libsvgbird/Ellipse.vala
libsvgbird/EmptyObject.vala
libsvgbird/Gradient.vala
libsvgbird/Layer.vala
libsvgbird/Line.vala
libsvgbird/LineCap.vala
libsvgbird/Object.vala
libsvgbird/ObjectGroup.vala
libsvgbird/PointValue.vala
libsvgbird/Points.vala
libsvgbird/Polygon.vala
libsvgbird/Polyline.vala
libsvgbird/Rectangle.vala
libsvgbird/Selector.vala
libsvgbird/SelectorPattern.vala
libsvgbird/SelectorTag.vala
libsvgbird/Stop.vala
libsvgbird/StyleSheet.vala
libsvgbird/SvgArc.vala
libsvgbird/SvgDrawing.vala
libsvgbird/SvgFile.vala
libsvgbird/SvgPath.vala
libsvgbird/SvgStyle.vala
libsvgbird/SvgTransform.vala
libsvgbird/SvgTransforms.vala
libsvgbird/point_value.h
po/birdfont.pot
po/cs.po
po/de.po
po/el.po
po/es.po
po/fi.po
po/fr.po
po/he.po
po/id.po
po/it.po
po/nb.po
po/nl.po
po/oc.po
po/pl.po
po/pt.po
po/pt_BR.po
po/ru.po
po/sk.po
po/sr.po
po/sv.po
po/tr.po
po/uk.po
resources/dark.theme
resources/icons.bf
scripts/builder.py
scripts/version.py
--- a/README.md +++ b/README.md @@ -27,7 +27,6 @@ libwebkitgtk-3.0-dev libnotify-dev libsqlite3-dev - librsvg2-dev libxmlbird-dev XML Bird is available from [birdfont.org][xmlbird].
--- a/birdfont/GtkWindow.vala +++ b/birdfont/GtkWindow.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2012s 2013 2014 Johan Mattsson + Copyright (C) 2012 2013 2014 2015 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 @@ -51,8 +51,6 @@ Entry text_entry; Box text_box; Gtk.Button submit_text_button; - - Gtk.Window tooltip_window = new Gtk.Window (); ToolboxCanvas toolbox; @@ -112,12 +110,7 @@ uri = Preview.get_uri (); html = Preview.get_html_with_absolute_paths (); - try { - html_canvas.load_html_string (html, uri); - } catch (Error e) { - warning (e.message); - warning ("Failed to load html into canvas."); - } + html_canvas.load_html_string (html, uri); // show the webview when loading has finished html_box.set_visible (true); @@ -488,8 +481,8 @@ } catch (GLib.Error e) { warning (e.message); } - } - + } + public void run_non_blocking_background_thread (Task t) { unowned Thread<void*> bg;
--- a/birdfont/Main.vala +++ b/birdfont/Main.vala @@ -32,7 +32,7 @@ window.set_native (native_window); native_window.init (); - birdfont.load_font_from_command_line (); + BirdFont.BirdFont.load_font_from_command_line (); Gtk.main (); return 0;
diff --git birdui/BoxLayout.vala(new)
--- /dev/null +++ b/birdui/BoxLayout.vala @@ -1,1 +1,216 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Gee; + using SvgBird; + using B; + using Cairo; + + namespace Bird { + + public enum BoxOrientation { + HORIZONTAL, + VERTICAL + } + + class BoxLayout : Component { + + public BoxOrientation orientation { get; private set; } + + public BoxLayout (XmlElement layout_tag, BoxOrientation orientation, Defs defs) { + base (layout_tag, defs); + this.orientation = orientation; + } + + public override void draw (Context cairo) { + cairo.save (); + cairo.translate (padded_x, padded_y); + clip (cairo); + + foreach (Component component in components) { + component.draw (cairo); + } + + cairo.restore (); + } + + bool is_width_remainder (Component component) { + return component.style.property_equals ("width", "remainder"); + } + + bool is_height_remainder (Component component) { + return component.style.property_equals ("height", "remainder"); + } + + /** The smallest size this layout can be. */ + public override void get_min_size (out double min_width, out double min_height) { + min_width = 0; + min_height = 0; + + if (orientation == BoxOrientation.HORIZONTAL) { + foreach (Component component in components) { + double w, h; + + component.get_min_size (out w, out h); + min_width += w; + + if (h > min_height) { + min_height = h; + } + } + } else { + foreach (Component component in components) { + double w, h; + + component.get_min_size (out w, out h); + min_height += h; + + if (w > min_width) { + min_width = w; + } + } + } + + min_width += get_padding_left (); + min_width += get_padding_right (); + min_height += get_padding_top (); + min_height += get_padding_bottom (); + + limit_size (ref min_width, ref min_height); + } + + public override void layout (double parent_width, double parent_height) { + double w, h; + + get_min_size (out w, out h); + int remainders = count_remainders (); + + if (!is_width_remainder (this)) { + width = w; + } + + if (!is_height_remainder (this)) { + height = h; + } + + if (remainders > 0) { + double remainder_size; + + if (orientation == BoxOrientation.HORIZONTAL) { + remainder_size = (parent_width - w) / remainders; + layout_variable_size (remainder_size, parent_height); + } else { + remainder_size = (parent_height - h) / remainders; + layout_variable_size (remainder_size, parent_width); + } + } + + if (orientation == BoxOrientation.HORIZONTAL) { + foreach (Component component in components) { + component.layout (parent_width, h); + } + } else { + foreach (Component component in components) { + component.layout (w, parent_height); + } + } + + layout_positions (); + } + + int count_remainders () { + int remainders = 0; + + if (orientation == BoxOrientation.HORIZONTAL) { + foreach (Component component in components) { + if (is_width_remainder (component)) { + remainders++; + } + } + } else { + foreach (Component component in components) { + if (is_height_remainder (component)) { + remainders++; + } + } + } + + return remainders; + } + + void layout_variable_size (double remainder_size, double fixed_size) { + double w, h; + + w = 0; + h = 0; + foreach (Component component in components) { + bool width_remainder = is_width_remainder (component); + bool height_remainder = is_height_remainder (component); + + if (width_remainder && orientation == BoxOrientation.HORIZONTAL) { + w = remainder_size; + w -= component.get_padding_left (); + w -= component.get_padding_right (); + h = fixed_size; + } + + if (height_remainder && orientation == BoxOrientation.VERTICAL) { + w = fixed_size; + h = remainder_size; + h -= component.get_padding_top (); + h -= component.get_padding_bottom (); + } + + if (width_remainder || height_remainder) { + double min_width, min_height; + component.get_min_size (out min_width, out min_height); + + if (w < min_width) { + w = min_width; + } + + if (h < min_height) { + h = min_height; + } + + component.limit_size (ref w, ref h); + component.width = w; + component.height = h; + } + } + } + + void layout_positions () { + double child_x = 0; + double child_y = 0; + + foreach (Component component in components) { + component.x = child_x + component.get_padding_left (); + component.y = child_y + component.get_padding_top (); + + if (orientation == BoxOrientation.HORIZONTAL) { + child_x = component.x + component.padded_width + get_padding_right (); + } else { + child_y = component.y + component.padded_height + get_padding_bottom (); + } + } + } + + public override string to_string () { + return "BoxLayout"; + } + } + + }
diff --git birdui/Component.vala(new)
--- /dev/null +++ b/birdui/Component.vala @@ -1,1 +1,410 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using B; + using SvgBird; + using Gee; + using Cairo; + + namespace Bird { + + public abstract class Component : GLib.Object { + public double width { get; protected set; } + public double height { get; protected set; } + + public double padded_width { + get { + return width + get_padding_top () + get_padding_bottom (); + } + } + + public double padded_height { + get { + return height + get_padding_left () + get_padding_right (); + } + } + + public double padded_x { + get { + return x + get_padding_top (); + } + } + + public double padded_y { + get { + return y + get_padding_left (); + } + } + + /** Vertical placement for this component relative to the parent container. */ + public double x { get; protected set; } + + /** Horizontal placement for this component relative to the parent container. */ + public double y { get; protected set; } + + /** The parts this component is made of. */ + protected ArrayList<Component> components = new ArrayList<Component> (); + + XmlElement component_tag; + + /** Style sheet and other SVG definitions. */ + Defs defs = new Defs (); + protected SvgStyle style = new SvgStyle (); + + string? css_class = null; + string? id = null; + + Overflow overflow = Overflow.VISIBLE; + + public Component.load (string file_name, double width, double height) { + this.width = width; + this.height = height; + load_file (file_name, null); + layout (width, height); + } + + /** Parse component ui file but don't do layout. */ + internal Component.load_component (string file_name, Component parent) { + load_file (file_name, parent); + } + + public Component (XmlElement component_tag, Defs defs) { + this.defs = defs; + this.component_tag = component_tag; + set_identification (); + parse (component_tag); + } + + public Component.embedded (XmlElement component_tag, Defs defs) { + this.defs = defs; + this.component_tag = component_tag; + set_identification (); + } + + void set_identification () { + foreach (Attribute attribute in component_tag.get_attributes ()) { + string attribute_name = attribute.get_name (); + + if (attribute_name == "id") { + id = attribute.get_content (); + } else if (attribute_name == "class") { + css_class = attribute.get_content (); + } + } + } + + private void inherit_styles_sheet (Defs defs) { + this.defs = defs; + } + + protected void parse_style (XmlElement style_tag) { + defs = defs.shallow_copy (); + StyleSheet style_sheet = StyleSheet.parse (defs, style_tag); + defs.style_sheet.merge (style_sheet); + } + + protected void parse_svg (XmlElement svg_tag) { + foreach (Attribute attribute in svg_tag.get_attributes ()) { + string attribute_name = attribute.get_name (); + + if (attribute_name == "file") { + string file_name = attribute.get_content (); + SvgComponent svg = new SvgComponent (svg_tag, defs, file_name); + add_component (svg); + } + } + } + + protected void add_component (Component component) { + components.add (component); + component.set_style_properties (this); + } + + protected void set_style_properties (Component parent) { + inherit_styles_sheet (parent.defs); + style = SvgStyle.parse (parent.defs, parent.style, component_tag); + set_overflow_property_from_css (); + set_identification (); + } + + protected void set_overflow_property_from_css () { + string? css_overflow = style.get_css_property ("overflow"); + + if (css_overflow != null) { + string overflow_property = (!) css_overflow; + + if (overflow_property == "hidden") { + overflow = Overflow.HIDDEN; + } else if (overflow_property == "visible") { + overflow = Overflow.VISIBLE; + } + } + } + + protected void parse_layout (XmlElement layout_tag) { + string type = ""; + + foreach (Attribute attribute in layout_tag.get_attributes ()) { + string attribute_name = attribute.get_name (); + + if (attribute_name == "type") { + type = attribute.get_content (); + } + } + + if (type == "hbox") { + HBox hbox = new HBox (layout_tag, defs); + add_component (hbox); + } else if (type == "vbox") { + VBox vbox = new VBox (layout_tag, defs); + add_component (vbox); + } else { + warning (type + " layout of type is supported in this verison."); + } + } + + protected void parse_component (XmlElement component_tag) { + foreach (Attribute attribute in component_tag.get_attributes ()) { + string attribute_name = attribute.get_name (); + + if (attribute_name == "file") { + string file_name = attribute.get_content (); + UI ui = new UI.load_component (file_name, this); + + foreach (Component component in ui.components) { + add_component (component); + + SvgStyle copied_style; + copied_style = SvgStyle.parse (defs, style, component_tag); + copied_style.apply (component.style); + component.style = copied_style; + } + } + } + } + + protected void parse (XmlElement component_tag) { + foreach (XmlElement tag in component_tag) { + string tag_name = tag.get_name (); + + if (tag_name == "ui") { + parse (tag); + } else if (tag_name == "layout") { + parse_layout (tag); + } else if (tag_name == "component") { + parse_component (tag); + } else if (tag_name == "svg") { + parse_svg (tag); + } else if (tag_name == "style") { + parse_style (tag); + } else { + unused_tag (tag_name); + } + } + } + + internal void unused_attribute (string attribute) { + warning ("The attribute " + attribute + " is not known in this version."); + } + + internal void unused_tag (string tag_name) { + warning ("The tag " + tag_name + " is not known in this version."); + } + + public void load_file (string file_name, Component? parent) { + if (file_name.has_suffix (".ui")) { + load_layout (file_name, parent); + } else if (file_name.has_suffix (".svg")) { + SvgComponent svg = new SvgComponent.for_file (file_name); + add_component (svg); + } else { + warning (file_name + " is not a ui file or svg file."); + } + } + + public void load_layout (string file_name, Component? parent) { + string? path = find_file (file_name); + + if (path == null) { + warning (file_name + " not found."); + return; + } + + string xml_data; + File layout_file = File.new_for_path ((!) path); + + try { + FileUtils.get_contents((!) layout_file.get_path (), out xml_data); + } catch (GLib.Error error) { + warning (error.message); + return; + } + + XmlTree xml_parser = new XmlTree (xml_data); + XmlElement tag = xml_parser.get_root (); + + if (parent == null) { + component_tag = tag; + defs = new Defs (); + } else { + Component p = (!) parent; + component_tag = p.component_tag; + defs = p.defs; + } + + parse (tag); + } + + public static string find_file (string file_name) { + File file = File.new_for_path ("birdui/" + file_name); + + if (file.query_exists ()) { + return (!) file.get_path (); + } + + return file_name; + } + + public virtual void get_min_size (out double min_width, out double min_height) { + min_width = width; + min_height = height; + + if (unlikely (components.size > 1)) { + warning ("A component has several parts but no layout has been set."); + } + + foreach (Component component in components) { + component.get_min_size (out min_width, out min_height); + } + + min_width += get_padding_left (); + min_width += get_padding_right (); + min_height += get_padding_top (); + min_height += get_padding_bottom (); + } + + public virtual void layout (double parent_width, double parent_height) { + warning ("A component without a layout was added to the view."); + + double w, h; + get_min_size (out w, out h); + + width = w; + height = h; + + foreach (Component component in components) { + component.layout (parent_width, parent_height); + } + } + + public void clip (Context cairo) { + if (overflow == Overflow.HIDDEN) { + cairo.rectangle (0, 0, width, height); + cairo.clip (); + } + } + + public abstract void draw (Context cairo); + + public virtual void motion_notify_event (double x, double y) { + } + + public virtual void button_press_event (uint button, double x, double y) { + } + + public virtual string to_string () { + return @"$(component_tag.get_name ())"; + } + + public void print_tree () { + print_tree_level (0); + } + + protected void print_tree_level (int indent) { + for (int i = 0; i < indent; i++) { + print ("\t"); + } + + print (@"$(to_string ()) width: $padded_width, height: $padded_height x $x y $y $(style)\n"); + + foreach (Component component in components) { + component.print_tree_level (indent + 1); + } + } + + protected double get_padding_bottom () { + return SvgFile.parse_number (style.get_css_property ("padding-bottom")); + } + + protected double get_padding_top () { + return SvgFile.parse_number (style.get_css_property ("padding-top")); + } + + protected double get_padding_left () { + return SvgFile.parse_number (style.get_css_property ("padding-left")); + } + + protected double get_padding_right () { + return SvgFile.parse_number (style.get_css_property ("padding-right")); + } + + protected void limit_size (ref double width, ref double height) { + string? min_width = style.get_css_property ("min-width"); + if (min_width != null) { + double w = SvgFile.parse_number (min_width); + if (width < w) { + width = w; + } + } + + string? min_height = style.get_css_property ("min-height"); + if (min_height != null) { + double h = SvgFile.parse_number (min_height); + + if (height < h) { + height = h; + } + } + + string? max_width = style.get_css_property ("max-width"); + if (max_width != null) { + double w = SvgFile.parse_number (max_width); + + if (width > w) { + width = w; + } + } + + string? max_height = style.get_css_property ("max-height"); + if (max_height != null) { + double h = SvgFile.parse_number (max_height); + + if (height > h) { + height = h; + } + } + + if (width < 0) { + width = 0; + } + + if (height < 0) { + height = 0; + } + } + } + + } +
diff --git birdui/GtkWidget.vala(new)
--- /dev/null +++ b/birdui/GtkWidget.vala @@ -1,1 +1,60 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Gtk; + using Gdk; + using Cairo; + using Math; + + namespace Bird { + + public class Widget : DrawingArea { + + UI component; + + public Widget (UI main_component) { + component = main_component; + + add_events (EventMask.BUTTON_PRESS_MASK + | EventMask.POINTER_MOTION_MASK + | EventMask.LEAVE_NOTIFY_MASK); + + motion_notify_event.connect ((event)=> { + component.motion_notify_event (event.x, event.y); + return true; + }); + + button_press_event.connect ((event)=> { + component.button_press_event (event.button, event.x, event.y); + return true; + }); + + draw.connect ((event) => { + Context cairo_context = cairo_create (get_window ()); + component.draw (cairo_context); + return true; + }); + + int width = (int) rint (component.padded_width); + int height = (int) rint (component.padded_height); + set_size_request (width, height); + + size_allocate.connect((allocation) => { + component.resize (allocation.width, allocation.height); + }); + } + } + + }
diff --git birdui/GtkWindow.vala(new)
--- /dev/null +++ b/birdui/GtkWindow.vala @@ -1,1 +1,40 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Bird; + using Gtk; + + int main (string[] args) { + Gtk.init (ref args); + + Window window = new Window (); + window.set_title ("UI Bird"); + window.destroy.connect (Gtk.main_quit); + + UI layout = new UI ("test.ui", 700, 200); + + layout.print_tree (); + + Bird.Widget primary_layout = new Bird.Widget (layout); + + Box vbox = new Box (Orientation.VERTICAL, 0); + vbox.pack_start(primary_layout, true, true, 0); + window.add (vbox); + + window.show_all (); + + Gtk.main (); + return 0; + }
diff --git birdui/HBox.vala(new)
--- /dev/null +++ b/birdui/HBox.vala @@ -1,1 +1,32 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Gee; + using B; + using SvgBird; + + namespace Bird { + + class HBox : BoxLayout { + public HBox (XmlElement layout, Defs defs) { + base (layout, BoxOrientation.HORIZONTAL, defs); + } + + public override string to_string () { + return "HBox"; + } + } + + }
diff --git birdui/Overflow.vala(new)
--- /dev/null +++ b/birdui/Overflow.vala @@ -1,1 +1,23 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + namespace Bird { + + public enum Overflow { + HIDDEN, + VISIBLE + } + + }
diff --git birdui/Style.vala(new)
--- /dev/null +++ b/birdui/Style.vala @@ -1,1 +1,35 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using B; + using SvgBird; + using Gee; + using Cairo; + + namespace Bird { + + public class Style : GLib.Object { + public SvgStyle svg_style; + + public Style () { + svg_style = new SvgStyle (); + } + + public Style.for_svg (SvgStyle svg_style) { + this.svg_style = svg_style; + } + } + + }
diff --git birdui/SvgComponent.vala(new)
--- /dev/null +++ b/birdui/SvgComponent.vala @@ -1,1 +1,103 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using B; + using SvgBird; + using Gee; + using Cairo; + + namespace Bird { + + class SvgComponent : Component { + string? path = null; + string file_name = ""; + SvgDrawing? drawing = null; + + public SvgComponent (XmlElement svg_component_tag, Defs defs, string svg_file) { + base.embedded (svg_component_tag, defs); + load_svg (svg_file); + } + + public SvgComponent.for_file (string svg_file) { + base.embedded (new XmlElement.empty (), new Defs ()); + load_svg (svg_file); + } + + public override void get_min_size (out double min_width, out double min_height) { + min_width = 0; + min_height = 0; + + if (drawing != null) { + SvgDrawing svg = (!) drawing; + min_width = svg.width + get_padding_left () + get_padding_right (); + min_height = svg.height + get_padding_bottom () + get_padding_top (); + } + } + + public override void layout (double parent_width, double parent_height) { + if (unlikely (components.size > 0)) { + warning ("SVG files can not have subviews."); + } + + if (drawing != null) { + SvgDrawing svg = (!) drawing; + width = svg.width; + height = svg.height; + } + } + + public override string to_string () { + return "Svg: " + file_name; + } + + private void load_svg (string file_name) { + this.file_name = file_name; + path = find_file (file_name); + + if (path == null) { + warning (file_name + " not found."); + return; + } + + string xml_data; + File svg_file = File.new_for_path ((!) path); + try { + FileUtils.get_contents((!) svg_file.get_path (), out xml_data); + } catch (GLib.Error error) { + warning (error.message); + return; + } + + SvgFile svg_parser = new SvgFile (); + drawing = svg_parser.parse_svg_data (xml_data); + } + + public override void draw (Context cairo) { + cairo.save (); + cairo.translate (padded_x, padded_y); + clip (cairo); + + if (drawing != null) { + SvgDrawing svg = (!) drawing; + svg.draw (cairo); + } + + cairo.restore (); + } + + } + + } +
diff --git birdui/VBox.vala(new)
--- /dev/null +++ b/birdui/VBox.vala @@ -1,1 +1,33 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Gee; + using B; + using SvgBird; + + namespace Bird { + + class VBox : BoxLayout { + public VBox (XmlElement layout, Defs defs) { + base (layout, BoxOrientation.VERTICAL, defs); + } + + public override string to_string () { + return "VBox"; + } + + } + + }
diff --git a/build.py b/build.py
--- a/build.py +++ b/build.py @@ -10,19 +10,25 @@ if platform == 'msys': process_tasks(dodo.make_libbirdgems('libbirdgems.dll', [])) - process_tasks(dodo.make_libbirdfont('libbirdfont.dll', ['libbirdgems.dll'])) - process_tasks(dodo.make_libbirdfont('libbirdfont.dll', ['libbirdgems.dll'])) - process_tasks(dodo.make_birdfont_test('birdfont-test.exe', ['libbirdgems.so', 'libbirdfont.so'])) + process_tasks(dodo.make_libbirdgems('libsvgbird.dll', [])) + process_tasks(dodo.make_libbirdfont('libbirdfont.dll', ['libbirdgems.dll', 'libsvgbird.dll'])) + process_tasks(dodo.make_libbirdfont('libbirdfont.dll', ['libbirdgems.dll', 'libsvgbird.dll'])) + process_tasks(dodo.make_birdfont_test('birdfont-test.exe', + ['libbirdgems.dll', 'libbirdfont.dll', 'libsvgbird.dll'])) elif platform == 'darwin': gems = "libbirdgems." + str(version.LIBBIRDGEMS_SO_VERSION) + '.dylib' bird = "libbirdfont." + str(version.SO_VERSION) + '.dylib'; + svg = "libsvgbird." + str(version.LIBSVGBIRD_SO_VERSION) + '.dylib'; + process_tasks(dodo.make_libsvgbird(svg, [])) process_tasks(dodo.make_libbirdgems(gems, [])) process_tasks(dodo.make_libbirdfont(bird, [gems])) process_tasks(dodo.task_man()) else: + process_tasks(dodo.task_libsvgbird()) process_tasks(dodo.task_libbirdgems()) process_tasks(dodo.task_libbirdfont()) - process_tasks(dodo.make_birdfont_test('birdfont-test', ['libbirdgems.so', 'libbirdfont.so'])) + process_tasks(dodo.make_birdfont_test('birdfont-test', + ['libsvgbird.so', 'libbirdgems.so', 'libbirdfont.so'])) if config.GTK: process_tasks(dodo.task_birdfont())
--- a/configure +++ b/configure @@ -11,7 +11,8 @@ from scripts.run import run TARGETS = ['libbirdfont', - 'libbirdgems', + 'libbirdgems', + 'libsvgbird', 'birdfont', 'birdfont-autotrace', 'birdfont-export', @@ -49,12 +50,34 @@ version = [int(n) for n in v.split ('.')] return [a,b,c] <= version - def test_library_version (lib): + def test_library_version (lib, required=True, version=None): print ('Looking for library: ' + lib + '\t\t') process = subprocess.Popen ('pkg-config --modversion ' + lib, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + v = process.stdout.readline().decode('utf-8') process.communicate()[0] - return process.returncode == 0 - + + if not process.returncode == 0: + if required: + print (FAIL + lib + ' not found' + ENDC) + exit (1) + else: + return False + + if version == None: + return True + + installed_version = v.split ('.'); + library_version = version.split ('.'); + + if installed_version < library_version: + if required: + print (FAIL + lib + ' version >= ' + version + ' not found.' + ENDC) + exit (1) + else: + return False + + return True + def has_posixvala (): posixvala = test_library_version ('posixvala') if not posixvala: @@ -82,7 +105,6 @@ 'libsoup-2.4', 'libnotify', 'sqlite3', - 'xmlbird' ] else: libs = [ @@ -90,25 +112,23 @@ 'glib-2.0', 'sqlite3', 'fontconfig', - 'xmlbird' ] + + test_library_version ('xmlbird', True, '1.2.0') for lib in libs: - if not test_library_version (lib): - print (FAIL + 'Can not find ' + lib + ENDC) - exit (1) + test_library_version (lib) if libbgee == 'Any': - if test_library_version ('gee-0.8'): + if test_library_version ('gee-0.8', False): gee = 'gee-0.8' - elif test_library_version ('gee-1.0'): + elif test_library_version ('gee-1.0', False): gee = 'gee-1.0' else: print (FAIL + 'Can not find libgee (version 0.8 or version 1.0).' + ENDC) exit (1) else: if not test_library_version (libbgee): - print (FAIL + 'Can not find lib gee.' + ENDC) exit (1) gee = libbgee; @@ -187,13 +207,13 @@ configfile.write_config(options.prefix) configfile.write_compile_parameters(options.prefix, - options.dest, - options.cc, - gee, - options.valac, - options.nonnull, - valacflags, - cflags, - ldflags, - options.gtk) + options.dest, + options.cc, + gee, + options.valac, + options.nonnull, + valacflags, + cflags, + ldflags, + options.gtk)
diff --git a/dodo.py b/dodo.py
--- a/dodo.py +++ b/dodo.py @@ -1,5 +1,5 @@ """ - Copyright (C) 2012 2013 2014 2015 Eduardo Naufel Schettino and Johan Mattsson + Copyright (C) 2012 - 2016 Eduardo Naufel Schettino and 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 @@ -46,6 +46,13 @@ LIBBIRDGEMS_SO_VERSION='${LIBbirdgems_VERSION}' else: LIBBIRDGEMS_SO_VERSION=version.LIBBIRDGEMS_SO_VERSION + + if "kfreebsd" in sys.platform: + LIBSVGBIRD_SO_VERSION=version.LIBSVGBIRD_SO_VERSION + elif "openbsd" in sys.platform: + LIBSVGBIRD_SO_VERSION='${LIBbirdgems_VERSION}' + else: + LIBSVGBIRD_SO_VERSION=version.LIBSVGBIRD_SO_VERSION if "kfreebsd" in sys.platform: SO_VERSION=version.SO_VERSION @@ -78,14 +85,15 @@ --pkg webkitgtk-3.0 \ --pkg libnotify \ --pkg xmlbird \ - --pkg librsvg-2.0 \ - --pkg libbirdfont + --pkg libbirdfont \ + --pkg libsvgbird \ """ cc_command = config.CC + " " + config.CFLAGS.get("birdfont", "") + """ \ -c C_SOURCE \ -D 'GETTEXT_PACKAGE="birdfont"' \ -I./build/libbirdfont \ + -I./build/libsvgbird \ $(pkg-config --cflags sqlite3) \ $(pkg-config --cflags """ + config.GEE + """) \ $(pkg-config --cflags gio-2.0) \ @@ -94,7 +102,6 @@ $(pkg-config --cflags gdk-pixbuf-2.0) \ $(pkg-config --cflags webkitgtk-3.0) \ $(pkg-config --cflags libnotify) \ - $(pkg-config --cflags librsvg-2.0) \ -o OBJECT_FILE""" linker_command = config.CC + " " + config.LDFLAGS.get("birdfont", "") + """ \ @@ -109,8 +116,7 @@ $(pkg-config --libs webkitgtk-3.0) \ $(pkg-config --libs xmlbird) \ $(pkg-config --libs libnotify) \ - $(pkg-config --libs librsvg-2.0) \ - -L./build -L./build/bin -l birdgems\ + -L./build -L./build/bin -l birdgems -l svgbird \ -o build/bin/""" + target_binary birdfont = Builder('birdfont', @@ -124,7 +130,7 @@ yield birdfont.build() def task_birdfont(): - yield make_birdfont('birdfont', ['libbirdgems.so', 'libbirdfont.so']) + yield make_birdfont('birdfont', ['libbirdgems.so', 'libbirdfont.so', 'libsvgbird.so']) def make_birdfont_export(target_binary, deps): valac_command = config.VALAC + """ \ @@ -139,13 +145,16 @@ --pkg gio-2.0 \ --pkg cairo \ --pkg xmlbird \ - --pkg libbirdfont + --pkg libsvgbird \ + --pkg libbirdfont \ + --pkg libsvgbird \ """ cc_command = config.CC + " " + config.CFLAGS.get("birdfont-export", "") + """ \ -c C_SOURCE \ -D 'GETTEXT_PACKAGE="birdfont"' \ -I./build/libbirdfont \ + -I./build/libsvgbird \ $(pkg-config --cflags sqlite3) \ $(pkg-config --cflags """ + config.GEE + """) \ $(pkg-config --cflags gio-2.0) \ @@ -163,8 +172,7 @@ $(pkg-config --libs cairo) \ $(pkg-config --libs glib-2.0) \ $(pkg-config --libs xmlbird) \ - $(pkg-config --libs librsvg-2.0) \ - -L./build -L./build/bin -l birdgems\ + -L./build -L./build/bin -l birdgems -l svgbird \ -o ./build/bin/""" + target_binary birdfont_export = Builder('birdfont-export', @@ -178,7 +186,7 @@ yield birdfont_export.build() def task_birdfont_export(): - yield make_birdfont_export('birdfont-export', ['libbirdgems.so', 'libbirdfont.so']) + yield make_birdfont_export('birdfont-export', ['libbirdgems.so', 'libbirdfont.so', 'libsvgbird.so']) def make_birdfont_import(target_binary, deps): valac_command = config.VALAC + """\ @@ -194,12 +202,14 @@ --pkg cairo \ --pkg xmlbird \ --pkg libbirdfont \ + --pkg libsvgbird \ """ cc_command = config.CC + " " + config.CFLAGS.get("birdfont-import", "") + """ \ -c C_SOURCE \ -D 'GETTEXT_PACKAGE="birdfont"' \ -I./build/libbirdfont \ + -I./build/libsvgbird \ $(pkg-config --cflags sqlite3) \ $(pkg-config --cflags """ + config.GEE + """) \ $(pkg-config --cflags gio-2.0) \ @@ -217,8 +227,7 @@ $(pkg-config --libs cairo) \ $(pkg-config --libs glib-2.0) \ $(pkg-config --libs xmlbird) \ - $(pkg-config --libs librsvg-2.0) \ - -L./build -L./build/bin -l birdgems\ + -L./build -L./build/bin -l birdgems -l svgbird \ -o ./build/bin/""" + target_binary birdfont_import = Builder('birdfont-import', @@ -232,7 +241,7 @@ yield birdfont_import.build() def task_birdfont_import(): - yield make_birdfont_import('birdfont-import', ['libbirdgems.so', 'libbirdfont.so']) + yield make_birdfont_import('birdfont-import', ['libbirdgems.so', 'libbirdfont.so', 'libsvgbird.so']) def make_birdfont_autotrace(target_binary, deps): valac_command = config.VALAC + """\ @@ -248,12 +257,14 @@ --pkg cairo \ --pkg xmlbird \ --pkg libbirdfont \ + --pkg libsvgbird \ """ cc_command = config.CC + " " + config.CFLAGS.get("birdfont-autotrace", "") + """ \ -c C_SOURCE \ -D 'GETTEXT_PACKAGE="birdfont"' \ -I./build/libbirdfont \ + -I./build/libsvgbird \ $(pkg-config --cflags sqlite3) \ $(pkg-config --cflags """ + config.GEE + """) \ $(pkg-config --cflags gio-2.0) \ @@ -264,6 +275,7 @@ linker_command = config.CC + " " + config.LDFLAGS.get("birdfont-autotrace", "") + """ \ build/birdfont-autotrace/*.o \ -I./build/libbirdfont \ + -I./build/libsvgbird \ -Lbuild/bin/ -lbirdfont \ -lm \ $(pkg-config --libs sqlite3) \ @@ -272,8 +284,7 @@ $(pkg-config --libs cairo) \ $(pkg-config --libs glib-2.0) \ $(pkg-config --libs xmlbird) \ - $(pkg-config --libs librsvg-2.0) \ - -L./build -L./build/bin -l birdgems\ + -L./build -L./build/bin -l birdgems -l svgbird\ -o ./build/bin/""" + target_binary birdfont_autotrace = Builder('birdfont-autotrace', @@ -287,7 +298,7 @@ yield birdfont_autotrace.build() def task_birdfont_autotrace(): - yield make_birdfont_autotrace('birdfont-autotrace', ['libbirdgems.so', 'libbirdfont.so']) + yield make_birdfont_autotrace('birdfont-autotrace', ['libbirdgems.so', 'libbirdfont.so', 'libsvgbird.so']) def make_libbirdfont(target_binary, deps): valac_command = config.VALAC + """\ @@ -301,15 +312,16 @@ -H build/libbirdfont/birdfont.h \ libbirdfont/*.vala \ libbirdfont/OpenFontFormat/*.vala \ - libbirdfont/Renderer/*.vala \ + libbirdfont/TextRendering/*.vala \ + --pkg posix \ --pkg """ + config.GEE + """ \ --pkg gio-2.0 \ --pkg cairo \ --pkg xmlbird \ --pkg libbirdgems \ + --pkg libsvgbird \ --pkg sqlite3 \ --pkg gdk-pixbuf-2.0 \ - --pkg librsvg-2.0 \ """ cc_command = config.CC + " " + config.CFLAGS.get("libbirdfont", "") + """ \ @@ -318,6 +330,7 @@ -D 'GETTEXT_PACKAGE="birdfont"' \ -I ./build/libbirdfont \ -I ./build/libbirdgems \ + -I ./build/libsvgbird \ $(pkg-config --cflags sqlite3) \ $(pkg-config --cflags fontconfig) \ $(pkg-config --cflags """ + config.GEE + """) \ @@ -325,7 +338,6 @@ $(pkg-config --cflags cairo) \ $(pkg-config --cflags glib-2.0) \ $(pkg-config --cflags xmlbird) \ - $(pkg-config --cflags librsvg-2.0) \ -o OBJECT_FILE""" linker_command = config.CC + " " + config.LDFLAGS.get("libbirdfont", "") + """ \ @@ -340,7 +352,7 @@ $(pkg-config --libs cairo) \ $(pkg-config --libs glib-2.0) \ $(pkg-config --libs xmlbird) \ - -L./build -L./build/bin -l birdgems\ + -L./build -L./build/bin -l birdgems -l svgbird \ -o ./build/bin/""" + target_binary libbirdfont = Builder('libbirdfont', @@ -354,7 +366,64 @@ yield libbirdfont.build() def task_libbirdfont(): - yield make_libbirdfont('libbirdfont.so.' + SO_VERSION, ['libbirdgems.so']) + yield make_libbirdfont('libbirdfont.so.' + SO_VERSION, ['libbirdgems.so', 'libsvgbird.so']) + + def make_libsvgbird(target_binary, deps): + valac_command = config.VALAC + """\ + -C \ + --vapidir=./ \ + --basedir build/libsvgbird/ \ + """ + config.NON_NULL + """ \ + """ + config.VALACFLAGS.get("libsvgbird", "") + """ \ + --enable-experimental \ + --library libsvgbird \ + -H build/libsvgbird/svgbird.h \ + libsvgbird/*.vala \ + --pkg posix \ + --pkg """ + config.GEE + """ \ + --pkg gio-2.0 \ + --pkg cairo \ + --pkg xmlbird \ + """ + + cc_command = config.CC + " " + config.CFLAGS.get("libsvgbird", "") + """ \ + -c C_SOURCE \ + -fPIC \ + -I ./build/libsvgbird \ + $(pkg-config --cflags """ + config.GEE + """) \ + $(pkg-config --cflags gio-2.0) \ + $(pkg-config --cflags cairo) \ + $(pkg-config --cflags glib-2.0) \ + $(pkg-config --cflags xmlbird) \ + -o OBJECT_FILE""" + + linker_command = config.CC + " " + config.LDFLAGS.get("libsvgbird", "") + """ \ + -shared \ + """ + soname(target_binary) + """ \ + build/libsvgbird/*.o \ + $(pkg-config --libs """ + config.GEE + """) \ + $(pkg-config --libs gio-2.0) \ + $(pkg-config --libs cairo) \ + $(pkg-config --libs glib-2.0) \ + $(pkg-config --libs xmlbird) \ + -L./build -L./build/bin \ + -o ./build/bin/""" + target_binary + + link_name = 'libsvgbird.so' + source_directory = 'libsvgbird' + + libsvgbird = Builder(source_directory, + valac_command, + cc_command, + linker_command, + target_binary, + link_name, + deps) + + yield libsvgbird.build() + + def task_libsvgbird(): + yield make_libsvgbird('libsvgbird.so.' + LIBSVGBIRD_SO_VERSION, []) def make_libbirdgems(target_binary, deps): valac_command = config.VALAC + """\ @@ -450,12 +519,14 @@ --pkg cairo \ --pkg xmlbird \ --pkg libbirdfont \ + --pkg libsvgbird \ """ cc_command = config.CC + " " + config.CFLAGS.get("birdfont-test", "") + """ \ -c C_SOURCE \ -D 'GETTEXT_PACKAGE="birdfont"' \ -I./build/libbirdfont \ + -I./build/libsvgbird \ $(pkg-config --cflags sqlite3) \ $(pkg-config --cflags """ + config.GEE + """) \ $(pkg-config --cflags gio-2.0) \ @@ -472,8 +543,7 @@ $(pkg-config --libs cairo) \ $(pkg-config --libs glib-2.0) \ $(pkg-config --libs xmlbird) \ - $(pkg-config --libs librsvg-2.0) \ - -L./build -L./build/bin -l birdgems\ + -L./build -L./build/bin -l birdgems -l svgbird\ -o build/bin/""" + target_binary test = Builder('birdfont-test', @@ -489,5 +559,56 @@ def task_birdfont_test(): yield make_birdfont_test('birdfont-test', ['libbirdgems.so', 'libbirdfont.so']) + def make_birdui(target_binary, deps): + valac_command = config.VALAC + """\ + -C \ + --pkg posix \ + --pkg """ + config.GEE + """ \ + --pkg gtk+-3.0 \ + --pkg libsvgbird \ + --pkg xmlbird \ + --vapidir=./ \ + --basedir=build/birdui/ \ + """ + config.NON_NULL + """ \ + --enable-experimental \ + birdui/*.vala \ + """ + + cc_command = config.CC + """ \ + -fPIC \ + $(pkg-config --cflags gtk+-3.0) \ + $(pkg-config --cflags glib-2.0) \ + $(pkg-config --cflags xmlbird) \ + $(pkg-config --cflags """ + config.GEE + """) \ + -g \ + -I ./build/libsvgbird \ + -c C_SOURCE \ + -o OBJECT_FILE \ + """ + + linker_command = config.CC + """ \ + """ + soname(target_binary) + """ \ + -fPIC \ + -g \ + build/birdui/*.o \ + $(pkg-config --libs gtk+-3.0) \ + $(pkg-config --libs glib-2.0) \ + $(pkg-config --libs gobject-2.0) \ + $(pkg-config --libs xmlbird) \ + $(pkg-config --libs """ + config.GEE + """) \ + -L ./build/bin -l m -l svgbird \ + -o build/bin/""" + target_binary + + libbirdgems = Builder('birdui', + valac_command, + cc_command, + linker_command, + target_binary, + None, + deps) + + yield libbirdgems.build() + def task_birdui(): + yield make_birdui('birdui', [])
--- a/install.py +++ b/install.py @@ -130,7 +130,7 @@ if platform.dist()[0] == 'Ubuntu' or platform.dist()[0] == 'Debian': process = subprocess.Popen(['dpkg-architecture', '-qDEB_HOST_MULTIARCH'], stdout=subprocess.PIPE) out, err = process.communicate() - libdir = '/lib/' + out.rstrip ('\n') + libdir = '/lib/' + out.decode('UTF-8').rstrip ('\n') else: p = platform.machine() if p == 'i386' or p == 's390' or p == 'ppc' or p == 'armv7hl':
--- a/libbirdfont/BackgroundTab.vala +++ b/libbirdfont/BackgroundTab.vala @@ -14,6 +14,7 @@ using Math; using Cairo; + using SvgBird; namespace BirdFont {
diff --git libbirdfont/BezierPoints.vala(deleted)
--- a/libbirdfont/BezierPoints.vala +++ /dev/null @@ -1,34 +1,1 @@ - /* - Copyright (C) 2014 Johan Mattsson - - This library is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as - published by the Free Software Foundation; either version 3 of the - License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - */ - - namespace BirdFont { - - /** Bezier point container for the SVG parser. */ - public class BezierPoints { - public unichar type = '\0'; - public unichar svg_type = '\0'; - public double x0 = 0; - public double y0 = 0; - public double x1 = 0; - public double y1 = 0; - public double x2 = 0; - public double y2 = 0; - - public string to_string () { - return @"$((!)type.to_string ()) $x0,$y0 $x1,$y1 $x2,$y2 SVG:$((!)svg_type.to_string ())"; - } - } - - }
--- a/libbirdfont/BirdFont.vala +++ b/libbirdfont/BirdFont.vala @@ -90,7 +90,6 @@ public static Font current_font; public static GlyphCollection current_glyph_collection; - public static Drawing? drawing = null; public static string? settings_subdirectory = null; @@ -166,6 +165,8 @@ Process.exit (0); } #endif + + CanvasSettings.init (); Preferences.load (); // always load default theme when names in theme does change
--- a/libbirdfont/BirdFontFile.vala +++ b/libbirdfont/BirdFontFile.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2013 2014 2015 Johan Mattsson + Copyright (C) 2013 2014 2015 2016 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -11,7 +11,9 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. */ + using B; + using SvgBird; namespace BirdFont { @@ -432,52 +434,47 @@ public void write_glyph (Glyph g, DataOutputStream os) throws GLib.Error { os.put_string (@"\t<glyph id=\"$(g.version_id)\" left=\"$(double_to_string (g.left_limit))\" right=\"$(double_to_string (g.right_limit))\">\n"); - foreach (Layer layer in g.layers.subgroups) { + foreach (Layer layer in g.layers.get_sublayers ()) { write_layer (layer, os); } - - write_embedded_svg (g, os); write_glyph_background (g, os); os.put_string ("\t</glyph>\n"); } - void write_embedded_svg (Glyph g, DataOutputStream os) throws GLib.Error { + void write_embedded_svg (EmbeddedSvg svg, DataOutputStream os) throws GLib.Error { + XmlParser xml = new XmlParser ((!) svg.svg_data); - if (g.color_svg_data != null) { - XmlParser xml = new XmlParser ((!) g.color_svg_data); + if (xml.validate ()) { + os.put_string (@"<embedded "); + os.put_string (@"type=\"svg\" "); + os.put_string (@"x=\"$(round (svg.x))\""); + os.put_string (@"y=\"$(round (svg.y))\""); + os.put_string (@">\n"); - if (xml.validate ()) { - os.put_string (@"<embedded "); - os.put_string (@"type=\"svg\" "); - os.put_string (@"x=\"$(round (g.svg_x))\""); - os.put_string (@"y=\"$(round (g.svg_y))\""); - os.put_string (@">\n"); - - Tag tag = xml.get_root_tag (); - - os.put_string ("<"); - os.put_string (tag.get_name ()); - - os.put_string (" "); - write_tag_attributes (os, tag); - - string content = tag.get_content (); - - if (content == "") { - os.put_string (" /"); - } - - os.put_string (">"); - - os.put_string (content); - - os.put_string ("</"); - os.put_string (tag.get_name ()); - os.put_string (">\n"); - - os.put_string ("</embedded>"); + Tag tag = xml.get_root_tag (); + + os.put_string ("<"); + os.put_string (tag.get_name ()); + + os.put_string (" "); + write_tag_attributes (os, tag); + + string content = tag.get_content (); + + if (content == "") { + os.put_string (" /"); } + + os.put_string (">"); + + os.put_string (content); + + os.put_string ("</"); + os.put_string (tag.get_name ()); + os.put_string (">\n"); + + os.put_string ("</embedded>\n"); } } @@ -507,37 +504,48 @@ } void write_layer (Layer layer, DataOutputStream os) throws GLib.Error { - string data; - - // FIXME: name etc. os.put_string (@"\t\t<layer name= \"$(layer.name)\" visible=\"$(layer.visible)\">\n"); - foreach (Path p in layer.get_all_paths ().paths) { - data = get_point_data (p); - if (data != "") { - os.put_string (@"\t\t\t<path "); - - if (p.stroke != 0) { - os.put_string (@"stroke=\"$(double_to_string (p.stroke))\" "); - } - - if (p.line_cap != LineCap.BUTT) { - if (p.line_cap == LineCap.ROUND) { - os.put_string (@"cap=\"round\" "); - } else if (p.line_cap == LineCap.SQUARE) { - os.put_string (@"cap=\"square\" "); - } - } - - if (p.skew != 0) { - os.put_string (@"skew=\"$(double_to_string (p.skew))\" "); - } - - os.put_string (@"data=\"$(data)\" />\n"); + foreach (SvgBird.Object o in layer.objects.objects) { + + if (o is EmbeddedSvg) { + write_embedded_svg ((EmbeddedSvg) o, os); } + + if (o is PathObject) { + Path p = ((PathObject) o).get_path (); + write_path_object (p, os); + } } os.put_string ("\t\t</layer>\n"); + } + + void write_path_object (Path p, DataOutputStream os) throws GLib.Error { + string data; + + data = get_point_data (p); + if (data != "") { + os.put_string (@"\t\t\t<path "); + + if (p.stroke != 0) { + os.put_string (@"stroke=\"$(double_to_string (p.stroke))\" "); + } + + if (p.line_cap != LineCap.BUTT) { + if (p.line_cap == LineCap.ROUND) { + os.put_string (@"cap=\"round\" "); + } else if (p.line_cap == LineCap.SQUARE) { + os.put_string (@"cap=\"square\" "); + } + } + + if (p.skew != 0) { + os.put_string (@"skew=\"$(double_to_string (p.skew))\" "); + } + + os.put_string (@"data=\"$(data)\" />\n"); + } } public static string double_to_string (double n) { @@ -1447,11 +1455,7 @@ foreach (Tag t in tag) { if (t.get_name () == "background") { parse_background_scale (glyph, t); - } - - if (t.get_name () == "embedded") { - parse_embedded_svg (glyph, t); - } + } } foreach (Path p in glyph.get_all_paths ()) { @@ -1464,7 +1468,7 @@ master.insert_glyph (glyph, selected || selected_id == id); } - void parse_embedded_svg (Glyph glyph, Tag tag) { + void parse_embedded_svg (Layer layer, Tag tag) { string type = ""; double x = 0; double y = 0; @@ -1484,9 +1488,10 @@ } if (type == "svg") { - glyph.svg_x = x; - glyph.svg_y = y; - glyph.color_svg_data = tag.get_content (); + EmbeddedSvg svg = SvgParser.parse_embedded_svg_data (tag.get_content ()); + svg.x = x; + svg.y = y; + layer.add_object (svg); } } @@ -1507,7 +1512,11 @@ foreach (Tag t in tag) { if (t.get_name () == "path") { path = parse_path (t); - layer.add_path (path); + LayerUtils.add_path (layer, path); + } + + if (t.get_name () == "embedded") { + parse_embedded_svg (layer, t); } } @@ -1542,7 +1551,7 @@ } } - return path; + return path; } private static void line (Path path, string px, string py) { @@ -1948,7 +1957,8 @@ ligatures = font.get_ligatures (); ligatures.add_ligature (sequence, ligature); } + } }
--- /dev/null +++ b/libbirdfont/CanvasSettings.vala @@ -1,1 +1,93 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using B; + using Cairo; + + namespace BirdFont { + + public class CanvasSettings { + + public static double stroke_width { + get { + settings_mutex.lock (); + double r = stroke_width_setting; + + if (unlikely (stroke_width_setting < 1)) { + string width = Preferences.get ("stroke_width"); + if (width != "") { + stroke_width_setting = double.parse (width); + } + } + + if (stroke_width_setting < 1) { + stroke_width_setting = 1; + } + + settings_mutex.unlock (); + return r; + } + + set { + settings_mutex.lock (); + stroke_width_setting = value; + settings_mutex.unlock (); + } + } + + public static bool show_all_line_handles { + get { + settings_mutex.lock (); + bool r = show_all_line_handles_setting; + settings_mutex.unlock (); + return r; + } + + set { + settings_mutex.lock (); + show_all_line_handles_setting = value; + settings_mutex.unlock (); + } + } + + public static bool fill_open_path { + get { + settings_mutex.lock (); + bool r = fill_open_path_setting; + settings_mutex.unlock (); + return r; + } + + set { + settings_mutex.lock (); + fill_open_path_setting = value; + settings_mutex.unlock (); + } + } + + static Mutex settings_mutex; + + /** The stroke of an outline when the path is not filled. */ + static double stroke_width_setting = 0; + static bool show_all_line_handles_setting = true; + static bool fill_open_path_setting = false; + + public static void init () { + settings_mutex = new Mutex (); + } + + } + + }
--- a/libbirdfont/ClipTool.vala +++ b/libbirdfont/ClipTool.vala @@ -11,6 +11,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. */ + + using SvgBird; namespace BirdFont { @@ -95,7 +97,7 @@ dx = g.motion_x - x - w / 2.0; dy = g.motion_y - y + h / 2.0; - foreach (Path path in g.active_paths) { + foreach (SvgBird.Object path in g.active_paths) { path.move (dx, dy); } } else if (fd is KerningDisplay) { @@ -260,7 +262,7 @@ } } } else if (glyph.get_visible_paths ().size > 0) { - foreach (Path path in glyph.active_paths) { + foreach (Path path in glyph.get_active_paths ()) { // FIXME: other objects s.append ("BF path: "); s.append (BirdFontFile.get_point_data (path)); s.append ("\n"); @@ -405,9 +407,9 @@ string cap = p.replace ("cap: ", ""); if (cap == "round") { - path.line_cap = LineCap.ROUND; + path.line_cap = SvgBird.LineCap.ROUND; } else if (cap == "square") { - path.line_cap = LineCap.SQUARE; + path.line_cap = SvgBird.LineCap.SQUARE; } } } @@ -447,13 +449,13 @@ if (path.points.size > 0) { PenTool.clear_directions (); glyph.add_path (path); - glyph.active_paths.add (path); + glyph.add_active_path (null, path); path.update_region_boundaries (); } PenTool.remove_all_selected_points (); - foreach (Path p in glyph.active_paths) { + foreach (Path p in glyph.get_active_paths ()) { if (p.is_open ()) { foreach (EditPoint e in p.points) { e.set_selected (true);
--- a/libbirdfont/Color.vala +++ b/libbirdfont/Color.vala @@ -22,24 +22,19 @@ namespace BirdFont { - public class Color { - public double r; - public double g; - public double b; - public double a; - + public class Color : SvgBird.Color { public Color (double r, double g, double b, double a) { - this.r = r; - this.g = g; - this.b = b; - this.a = a; + base (r, g, b, a); } + public Color.create_copy (SvgBird.Color color) { + base (color.r, color.g, color.b, color.a); + } + public Color.hsba (double h, double s, double v, double a) { double hue, saturation, value; double f, p, q, t; - - this.a = a; + double r, g, b; if (s == 0.0) { r = v; @@ -100,6 +95,8 @@ assert_not_reached (); } } + + base (r, g, b, a); } public void to_hsva (out double h, out double s, out double v, out double a) { @@ -204,23 +201,11 @@ public static Color magenta () { return new Color (103.0 / 255, 33.0 / 255, 120.0 / 255, 1); } - - public string to_string () { - return @"r: $r, g: $g, b: $b, a: $a"; - } - - public Color copy () { + + public new Color copy () { return new Color (r, g, b, a); - } - - public string to_rgb_hex () { - string s = "#"; - s += Font.to_hex_code ((unichar) Math.rint (r * 254)); - s += Font.to_hex_code ((unichar) Math.rint (g * 254)); - s += Font.to_hex_code ((unichar) Math.rint (b * 254)); - return s; } } }
--- a/libbirdfont/ColorPicker.vala +++ b/libbirdfont/ColorPicker.vala @@ -13,6 +13,7 @@ */ using Cairo; + using SvgBird; namespace BirdFont { @@ -119,7 +120,7 @@ int g = (int) ((tx / Toolbox.allocation_width) * gradient.stops.size); return_if_fail (0 <= g < gradient.stops.size); current_stop = gradient.stops.get (g); - set_color (current_stop.color); + set_color (new Color.create_copy (current_stop.color)); } } @@ -236,7 +237,7 @@ int stop_size = (int) ((double) Toolbox.allocation_width / gradient.stops.size); for (int i = 0; i < gradient.stops.size; i++) { Stop s = gradient.stops.get (i); - c = s.color; + c = new Color.create_copy (s.color); cr.save (); cr.set_source_rgba (c.r, c.g, c.b, c.a); cr.rectangle (i * stop_size, y + 4 * bar_height, stop_size, bar_height);
--- a/libbirdfont/DrawingTools.vala +++ b/libbirdfont/DrawingTools.vala @@ -14,6 +14,7 @@ using Cairo; using Math; + using SvgBird; namespace BirdFont { @@ -191,7 +192,9 @@ }); draw_tools.add_tool (move_background); - move_canvas = new Tool ("move_canvas", t_("Move canvas")); + move_canvas = new Tool ("move_canvas", + t_("Move canvas"), + t_("Press space and click to move the canvas.")); move_canvas.select_action.connect ((self) => { update_drawing_and_background_tools (self); }); @@ -238,7 +241,10 @@ key_tools.add_tool (insert_point_on_path_tool); // quadratic Bézier points - quadratic_points = new Tool ("quadratic_points", t_("Create quadratic Bézier curves")); + quadratic_points = new Tool ("quadratic_points", + t_("Create quadratic Bézier curves"), + t_("All control points will be converted to quadratic points in the TTF format.")); + quadratic_points.select_action.connect ((self) => { point_type = PointType.QUADRATIC; Preferences.set ("point_type", "quadratic_points"); @@ -292,7 +298,7 @@ glyph.selection_boundaries (out x, out y, out w, out h); delta = x_coordinate.get_value () - x + glyph.left_limit; - foreach (Path path in glyph.active_paths) { + foreach (SvgBird.Object path in glyph.active_paths) { path.move (delta, 0); } @@ -335,7 +341,7 @@ glyph.selection_boundaries (out x, out y, out w, out h); - foreach (Path path in glyph.active_paths) { + foreach (Path path in glyph.get_active_paths ()) { path.move (0, y_coordinate.get_value () - (y - h) - font.base_line); } @@ -375,7 +381,7 @@ double x, y, w, h; Glyph glyph = MainWindow.get_current_glyph (); double angle = (self.get_value () / 360) * 2 * PI; - Path last_path; + SvgBird.Object last_path; glyph.selection_boundaries (out x, out y, out w, out h); x += w / 2; @@ -480,7 +486,7 @@ tie = !p.tie_handles; // don't tie end points - foreach (Path path in MainWindow.get_current_glyph ().active_paths) { + foreach (Path path in MainWindow.get_current_glyph ().get_active_paths ()) { if (path.is_open ()) { if (p == path.get_first_point () || p == path.get_last_point ()) { tie = false; @@ -598,9 +604,9 @@ Glyph g = MainWindow.get_current_glyph (); Layer layer = g.get_current_layer (); - foreach (Path p in g.active_paths) { - layer.paths.remove (p); - layer.paths.paths.insert (0, p); + foreach (SvgBird.Object p in g.active_paths) { + layer.remove (p); + layer.objects.objects.insert (0, p); } GlyphCanvas.redraw (); @@ -798,12 +804,12 @@ g.store_undo_state (); if (StrokeTool.add_stroke) { - foreach (Path p in g.active_paths) { + foreach (SvgBird.Object p in g.active_paths) { p.stroke = StrokeTool.stroke_width; p.line_cap = StrokeTool.line_cap; } } else { - foreach (Path p in g.active_paths) { + foreach (SvgBird.Object p in g.active_paths) { p.stroke = 0; } } @@ -838,9 +844,13 @@ StrokeTool.stroke_width = object_stroke.get_value (); if (tool && StrokeTool.add_stroke) { - foreach (Path p in g.active_paths) { + foreach (SvgBird.Object p in g.active_paths) { p.stroke = StrokeTool.stroke_width; - p.reset_stroke (); + + if (p is PathObject) { + Path path = ((PathObject) p).get_path (); + path.reset_stroke (); + } } } @@ -876,12 +886,15 @@ g = MainWindow.get_current_glyph (); g.store_undo_state (); - foreach (Path p in g.active_paths) { - p.line_cap = LineCap.BUTT; - p.reset_stroke (); + foreach (SvgBird.Object p in g.active_paths) { + p.line_cap = SvgBird.LineCap.BUTT; + + if (p is PathObject) { + ((PathObject) p).get_path ().reset_stroke (); + } } - StrokeTool.line_cap = LineCap.BUTT; + StrokeTool.line_cap = SvgBird.LineCap.BUTT; Font f = BirdFont.get_current_font (); f.settings.set_setting ("line_cap", @"butt"); @@ -900,12 +913,15 @@ g = MainWindow.get_current_glyph (); g.store_undo_state (); - foreach (Path p in g.active_paths) { - p.line_cap = LineCap.ROUND; - p.reset_stroke (); + foreach (SvgBird.Object p in g.active_paths) { + p.line_cap = SvgBird.LineCap.ROUND; + + if (p is PathObject) { + ((PathObject) p).get_path ().reset_stroke (); + } } - StrokeTool.line_cap = LineCap.ROUND; + StrokeTool.line_cap = SvgBird.LineCap.ROUND; Font f = BirdFont.get_current_font (); f.settings.set_setting ("line_cap", @"round"); @@ -925,12 +941,15 @@ g = MainWindow.get_current_glyph (); g.store_undo_state (); - foreach (Path p in g.active_paths) { - p.line_cap = LineCap.SQUARE; - p.reset_stroke (); + foreach (SvgBird.Object p in g.active_paths) { + p.line_cap = SvgBird.LineCap.SQUARE; + + if (p is PathObject) { + ((PathObject) p).get_path ().reset_stroke (); + } } - StrokeTool.line_cap = LineCap.SQUARE; + StrokeTool.line_cap = SvgBird.LineCap.SQUARE; Font f = BirdFont.get_current_font (); f.settings.set_setting ("line_cap", @"square"); @@ -1249,7 +1268,7 @@ bool stroke = false; Glyph g = MainWindow.get_current_glyph (); - foreach (Path p in g.active_paths) { + foreach (SvgBird.Object p in g.active_paths) { if (p.stroke > 0) { stroke = true; } @@ -1607,7 +1626,7 @@ int i = 0; layer_tools.tool.clear (); - foreach (Layer layer in g.layers.subgroups) { + foreach (Layer layer in g.layers.get_sublayers ()) { LayerLabel label = new LayerLabel (layer); layer_tools.add_tool (label, 0);
--- /dev/null +++ b/libbirdfont/EmbeddedSvg.vala @@ -1,1 +1,115 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using B; + using Math; + using Cairo; + using SvgBird; + + namespace BirdFont { + + public class EmbeddedSvg : SvgBird.Object { + public string svg_data = ""; + public SvgDrawing drawing = new SvgDrawing (); + + public double x { get; set; } + public double y { get; set; } + + public override double xmin { + get { + return x; + } + + set { + } + } + + public override double xmax { + get { + return x + drawing.width; + } + + set { + } + } + + + public override double ymin { + get { + return y - drawing.height; + } + + set { + } + } + + public override double ymax { + get { + return y; + } + + set { + } + } + + public EmbeddedSvg (SvgDrawing drawing) { + this.drawing = drawing; + } + + public override void update_region_boundaries () { + drawing.update_region_boundaries (); + } + + public override bool is_over (double x, double y) { + return (this.x <= x <= this.x + drawing.width) + && (this.y - drawing.height <= y <= this.y); + } + + public void draw_embedded_svg (Context cr) { + cr.save (); + cr.translate (Glyph.xc () + x, Glyph.yc () - y); + drawing.draw (cr); + cr.restore (); + } + + public override void draw_outline (Context cr) { + } + + public override SvgBird.Object copy () { + EmbeddedSvg svg = new EmbeddedSvg (drawing); + svg.svg_data = svg_data; + return svg; + } + + public override void move (double dx, double dy) { + x += dx; + y += dy; + } + + public override void rotate (double theta, double xc, double yc) { + drawing.rotate (theta, xc, yc); + } + + public override bool is_empty () { + return drawing.is_empty (); + } + + public override void resize (double ratio_x, double ratio_y) { + drawing.resize (ratio_x, ratio_y); + } + + } + + }
--- a/libbirdfont/Expander.vala +++ b/libbirdfont/Expander.vala @@ -61,6 +61,10 @@ public void clear_cache () { cached = null; + + foreach (Tool t in tool) { + t.clear_cache (); + } } public void set_headline (Text h) {
--- a/libbirdfont/ExportTool.vala +++ b/libbirdfont/ExportTool.vala @@ -13,6 +13,7 @@ */ using B; + using SvgBird; namespace BirdFont { @@ -95,37 +96,50 @@ name = glyph.get_name (); - Gee.ArrayList<Path> pl; + Gee.ArrayList<SvgBird.Object> pl; s = new StringBuilder (); glyph_svg = ""; - pl = only_selected_paths ? glyph.active_paths : glyph.get_visible_paths (); - foreach (Path p in pl) { - if (p.stroke > 0) { - s.append (@"<path "); - s.append (@"style=\""); - s.append (@"fill:none;"); - s.append (@"stroke:#000000;"); - s.append (@"stroke-width:$(p.stroke)px;"); - - if (p.line_cap == LineCap.ROUND) { - s.append (@"stroke-linecap:round;"); - } else if (p.line_cap == LineCap.SQUARE) { - s.append (@"stroke-linecap:square;"); + pl = only_selected_paths ? glyph.active_paths : glyph.get_visible_objects (); + + foreach (SvgBird.Object o in pl) { + + if (o is PathObject) { + Path p = ((PathObject) o).get_path (); + + if (p.stroke > 0) { + s.append (@"<path "); + s.append (@"style=\""); + s.append (@"fill:none;"); + s.append (@"stroke:#000000;"); + s.append (@"stroke-width:$(p.stroke)px;"); + + if (p.line_cap == LineCap.ROUND) { + s.append (@"stroke-linecap:round;"); + } else if (p.line_cap == LineCap.SQUARE) { + s.append (@"stroke-linecap:square;"); + } + + s.append (@"\" "); + + s.append (@"d=\"$(Svg.to_svg_path (p, glyph))\" id=\"path_$(name)_$(id)\" />\n"); + id++; } - - s.append (@"\" "); - - s.append (@"d=\"$(Svg.to_svg_path (p, glyph))\" id=\"path_$(name)_$(id)\" />\n"); - id++; + } else { + warning ("Copy and paste for other objects not implemented."); } } if (only_selected_paths) { - foreach (Path p in glyph.active_paths) { - if (p.stroke == 0) { - glyph_svg += Svg.to_svg_path (p, glyph); + foreach (SvgBird.Object p in glyph.active_paths) { + if (p is PathObject) { + Path path = ((PathObject) p).get_path (); + if (path.stroke == 0) { + glyph_svg += Svg.to_svg_path (path, glyph); + } + } else { + warning ("Not implemented"); } } } else {
--- a/libbirdfont/Glyph.vala +++ b/libbirdfont/Glyph.vala @@ -16,6 +16,7 @@ using Math; using Gee; using B; + using SvgBird; namespace BirdFont { @@ -132,8 +133,7 @@ public Layer layers = new Layer (); public int current_layer = 0; - public Gee.ArrayList<Path> active_paths = new Gee.ArrayList<Path> (); - public Gee.ArrayList<Layer> selected_groups = new Gee.ArrayList<Layer> (); + public Gee.ArrayList<SvgBird.Object> active_paths = new Gee.ArrayList<SvgBird.Object> (); // used if this glyph originates from a fallback font public double top_limit = 0; @@ -141,41 +141,12 @@ public double bottom_limit = 0; public Surface? overview_thumbnail = null; + + public double selection_box_width = 0; + public double selection_box_height = 0; + public double selection_box_x = 0; + public double selection_box_y = 0; - public double svg_x = 0; - public double svg_y = 0; - private Rsvg.Handle? svg_drawing = null; - - public string? color_svg_data { - get { - return _color_svg_data; - } - - set { - try { - if (value != null) { - XmlParser xml = new XmlParser ((!) value); - - if (!xml.validate ()) { - warning("Invalid SVG data, skipping import."); - return; - } - - uint8[] svg_array = ((!) value).data; - svg_drawing = new Rsvg.Handle.from_data (svg_array); - } else { - svg_drawing = null; - } - - _color_svg_data = value; - } catch (GLib.Error e) { - warning (e.message); - } - } - } - - private string? _color_svg_data = null; - public Glyph (string name, unichar unichar_code = 0) { this.name = name; this.unichar_code = unichar_code; @@ -189,20 +160,20 @@ public Glyph.no_lines (string name, unichar unichar_code = 0) { this.name = name; this.unichar_code = unichar_code; - } - - public Gee.ArrayList<Path> get_active_paths () { - return active_paths; } public Layer get_current_layer () { - return_val_if_fail (0 <= current_layer < layers.subgroups.size, new Layer ()); - return layers.subgroups.get (current_layer); + if (unlikely (!(0 <= current_layer < layers.objects.size))) { + warning ("Layer index out of bounds."); + return new Layer (); + } + + return layers.get_sublayers ().get (current_layer); } public void set_current_layer (Layer layer) { int i = 0; - foreach (Layer l in layers.subgroups) { + foreach (Layer l in layers.get_sublayers ()) { if (likely (l == layer)) { current_layer = i; return; @@ -211,29 +182,42 @@ } warning ("Layer is not added to glyph."); + } + + public Gee.ArrayList<SvgBird.Object> get_visible_objects () { + return LayerUtils.get_visible_objects (layers); } public Gee.ArrayList<Path> get_visible_paths () { - return layers.get_visible_paths ().paths; + return LayerUtils.get_visible_paths (layers).paths; } public PathList get_visible_path_list () { - return layers.get_visible_paths (); + return LayerUtils.get_visible_paths (layers); + } + + public Gee.ArrayList<SvgBird.Object> get_objects_in_current_layer () { + return get_current_layer ().objects.objects; } public Gee.ArrayList<Path> get_paths_in_current_layer () { - return get_current_layer ().get_all_paths ().paths; + return LayerUtils.get_all_paths (get_current_layer ()).paths; } public Gee.ArrayList<Path> get_all_paths () { - return layers.get_all_paths ().paths; + return LayerUtils.get_all_paths (layers).paths; } public void add_new_layer () { layers.add_layer (new Layer ()); - current_layer = layers.subgroups.size - 1; + current_layer = layers.objects.size - 1; } + public void add_layer (Layer layer) { + layers.add_layer (layer); + current_layer = layers.objects.size - 1; + } + public int get_layer_index (Layer layer) { return layers.index_of (layer); } @@ -273,6 +257,10 @@ public override void close () { undo_list.clear (); redo_list.clear (); + + foreach (Path path in get_all_paths ()) { + path.set_editable (false); + } } public void set_empty_ttf (bool e) { @@ -284,46 +272,69 @@ } public void clear_active_paths () { - selected_groups.clear (); active_paths.clear (); } public void add_active_path (Layer? group, Path? p) { - Path path; - Layer g; - if (p != null) { - path = (!) p; + PathObject path = new PathObject.for_path ((!) p); + add_active_object (group, path); + } else { + add_active_object (group, null); + } + } + + // FIXME: delete group + public void add_active_object (Layer? group, SvgBird.Object? o) { + SvgBird.Object object; - if (Toolbox.get_move_tool ().is_selected ()) { - if (path.stroke > 0) { - Toolbox.set_object_stroke (path.stroke); - } - } + if (o != null) { + object = (!) o; - if (!active_paths.contains (path)) { - active_paths.add (path); + if (!active_paths_contains (object)) { + active_paths.add (object); + } + + if (object is PathObject) { + PathObject path = (PathObject) object; + if (Toolbox.get_move_tool ().is_selected ()) { + if (path.get_path ().stroke > 0) { + Toolbox.set_object_stroke (path.get_path ().stroke); + } + } + + PenTool.active_path = path.get_path (); } - PenTool.active_path = path; } + } - if (group != null) { - g = (!) group; - if (!selected_groups.contains (g)) { - selected_groups.add (g); + public bool active_paths_contains (SvgBird.Object object) { + Glyph glyph = MainWindow.get_current_glyph (); + + if (glyph.active_paths.contains (object)) { + return true; + } + + if (object is PathObject) { + PathObject path = (PathObject) object; + + foreach (SvgBird.Object active in glyph.active_paths) { + if (active is PathObject) { + PathObject path_active = (PathObject) active; + if (path_active.get_path () == path.get_path ()) { + return true; + } + } } } + + return false; } - + public void delete_background () { store_undo_state (); background_image = null; GlyphCanvas.redraw (); - } - - public Path? get_active_path () { - return_val_if_fail (active_paths.size > 0, null); - return active_paths.get (active_paths.size - 1); } public bool boundaries (out double x1, out double y1, out double x2, out double y2) { @@ -375,7 +386,7 @@ px2 = -10000; py2 = -10000; - foreach (Path p in active_paths) { + foreach (SvgBird.Object p in active_paths) { if (p.xmin < px) { px = p.xmin; } @@ -480,11 +491,19 @@ } public virtual void add_path (Path p) { - if (layers.subgroups.size == 0) { + if (layers.objects.size == 0) { layers.add_layer (new Layer ()); } - get_current_layer ().add_path (p); + LayerUtils.add_path (get_current_layer (), p); + } + + public void add_object (SvgBird.Object object) { + if (layers.objects.size == 0) { + layers.add_layer (new Layer ()); + } + + get_current_layer ().add_object (object); } public override void selected_canvas () { @@ -520,7 +539,7 @@ if (index != "") { int i = int.parse (index); - if (0 <= i < layers.subgroups.size) { + if (0 <= i < layers.objects.size) { current_layer = i; } } @@ -773,10 +792,14 @@ return; } } + } + + public void delete_object (SvgBird.Object o) { + layers.remove (o); } public void delete_path (Path p) { - layers.remove_path(p); + LayerUtils.remove_path(layers, p); } public string get_svg_data () { @@ -926,8 +949,9 @@ add_path (path); path.reopen (); path.create_list (); - - add_active_path (null, path); + + PathObject object = new PathObject.for_path (path); + add_active_object (null, object); } if (remaining_points.paths.size > 0) { @@ -1040,7 +1064,7 @@ public void set_active_path (Path p) { p.reopen (); clear_active_paths (); - add_active_path (null, p); + add_active_object (null, new PathObject.for_path (p)); } /** Move view port centrum to this coordinate. */ @@ -1184,61 +1208,17 @@ return -y; } - public Layer? get_path_at (double x, double y) { - Layer? group = null; - bool found = false; - - foreach (Layer layer in get_current_layer ().subgroups) { - foreach (Path pt in layer.paths.paths) { - if (pt.is_over (x, y)) { - found = true; - group = layer; - } + public SvgBird.Object? get_object_at (double x, double y) { + Layer layer = get_current_layer (); + for (int i = layer.objects.size - 1; i >= 0; i--) { + SvgBird.Object o = layer.objects.get_object (i); + + if (o.is_over (x, y)) { + return o; } } - - if (!found) { - foreach (Path pt in get_paths_in_current_layer ()) { - if (pt.is_over (x, y)) { - Layer layer = new Layer (); - layer.is_counter = true; - layer.single_path = true; - layer.add_path (pt); - group = layer; - } - } - } - - return group; - } - - public bool select_path (double x, double y) { - Path? p = null; - bool found = false; - - foreach (Path pt in get_paths_in_current_layer ()) { - if (pt.is_over (x, y)) { - p = pt; - found = true; - } - } - - if (!KeyBindings.has_shift ()) { - clear_active_paths (); - } - - add_active_path (null, p); - - return found; - } - - public bool is_over_selected_path (double x, double y) { - foreach (Path pt in active_paths) { - if (pt.is_over (x, y)) { - return true; - } - } - return false; + + return null; } public void queue_redraw_path (Path path) { @@ -1255,48 +1235,6 @@ double xtb = -view_offset_x - xmax; redraw_area ((int)xtb - 10, (int)yta - 10, (int)(xtb - xta) + 10, (int) (yta - ytb) + 10); - } - - public Path get_closeset_path (double x, double y) { - double d; - EditPoint ep = new EditPoint (); - - Path min_point = new Path (); - double min_distance = double.MAX; - - double xt = path_coordinate_x (x); - double yt = path_coordinate_y (y); - var paths = get_visible_paths (); - - foreach (Path p in paths) { - if (p.is_over (xt, yt)) { - return p; - } - } - - foreach (Path p in paths) { - if (p.points.size == 0) continue; - - p.get_closest_point_on_path (ep, xt, yt); - d = Math.pow (ep.x - xt, 2) + Math.pow (ep.y - yt, 2); - - if (d < min_distance) { - min_distance = d; - min_point = p; - } - - } - - // a path without any editpoints - if (paths.size > 0) { - return paths.get (0); - } - - if (unlikely (min_distance == double.MAX)) { - warning (@"No path found in path_list."); - } - - return min_point; } public void move_selected_edit_point_coordinates (EditPoint selected_point, double xt, double yt) { @@ -1349,8 +1287,9 @@ return; } - foreach (Path path in active_paths) { + foreach (SvgBird.Object object in active_paths) { EditPoint p; + Path path = ((PathObject) object).get_path (); EditPoint pl = path.get_last_point (); if (pl.prev != null) { @@ -1364,7 +1303,6 @@ if (px > x) px -= tw + 60; if (py > y) py -= th + 60; - } else { px = x - 60; py = y - 60; @@ -1405,7 +1343,7 @@ } public void open_path () { - foreach (Path p in get_visible_paths ()) { + foreach (Path p in get_all_paths ()) { p.set_editable (true); p.recalculate_linear_handles (); @@ -1650,157 +1588,91 @@ cr.stroke (); } - /** Draw filled paths. */ - public void draw_paths (Context cr, Color? c = null) { - PathList stroke; - Color color; - bool open; + public void draw_layers (Context cr) { + foreach (SvgBird.Object object in layers.objects) { + if (object is Layer) { + draw_layer (cr, (Layer) object); + } + } - cr.save (); - cr.new_path (); - foreach (Path p in get_visible_paths ()) { - if (c != null) { - color = (!) c; - } else if (p.color != null) { - color = (!) p.color; - } else { - color = Color.black (); - } + draw_bird_font_paths (cr); + } - if (p.stroke > 0) { - stroke = p.get_stroke_fast (); - draw_path_list (stroke, cr, color); - } else { - open = p.is_open (); - - if (open) { - p.close (); - p.recalculate_linear_handles (); - } - - p.draw_path (cr, this, color); - - if (open) { - p.reopen (); - } + public void draw_layer (Context cr, Layer sublayers) { + foreach (SvgBird.Object object in sublayers.objects) { + if (object is EmbeddedSvg) { + EmbeddedSvg svg = (EmbeddedSvg) object; + svg.draw_embedded_svg (cr); } } - cr.fill (); - cr.restore (); } + + public void draw_bird_font_paths (Context cr) { + Tool selected_tool = MainWindow.get_toolbox ().get_current_tool (); + + bool draw_control_points = (selected_tool is PenTool) + || (selected_tool is PointTool) + || (selected_tool is TrackTool) + || (selected_tool is BezierTool); - public void draw_path (Context cr) { - PathList stroke; - Color color; - - cr.save (); - cr.new_path (); - foreach (Path p in get_visible_paths ()) { - if (p.stroke > 0) { - stroke = p.get_stroke_fast (); - - if (p.is_editable ()) { - color = Theme.get_color ("Filled Stroke"); - color.a = 0.8; - } else { - color = Color.black (); + bool has_path = false; + + if (!draw_control_points) { + foreach (SvgBird.Object object in get_visible_objects ()) { + if (object is PathObject + && object.stroke > 0) { + + has_path = true; + PathObject object_path = (PathObject) object; + object_path.draw_path (cr); } - - draw_path_list (stroke, cr, color); } - } - cr.fill (); - cr.restore (); - if (!(MainWindow.get_toolbox ().get_current_tool () is PenTool) - && !(MainWindow.get_toolbox ().get_current_tool () is PointTool) - && !(MainWindow.get_toolbox ().get_current_tool () is TrackTool) - && !(MainWindow.get_toolbox ().get_current_tool () is BezierTool)) { - cr.save (); - cr.new_path (); - foreach (Path p in active_paths) { - if (p.stroke > 0) { - stroke = p.get_stroke_fast (); - color = Theme.get_color ("Selected Objects"); - draw_path_list (stroke, cr, color); - } + if (has_path) { + cr.set_fill_rule (FillRule.WINDING); + cr.set_source_rgba (0, 0, 0, 1); + cr.fill (); } - cr.fill (); - cr.restore (); } - - if (is_open () && Path.fill_open_path) { - cr.save (); - cr.new_path (); - foreach (Path p in get_visible_paths ()) { - if (p.stroke == 0) { - color = p.color == null ? get_path_fill_color () : (!) p.color; - p.draw_path (cr, this, color); + + has_path = false; + + if (!draw_control_points) { + foreach (SvgBird.Object object in get_visible_objects ()) { + if (object is PathObject + && object.stroke == 0) { + + has_path = true; + PathObject object_path = (PathObject) object; + object_path.draw_path (cr); } } - cr.fill (); - cr.restore (); - } - if (is_open ()) { - cr.save (); - cr.new_path (); - foreach (Path p in get_visible_paths ()) { - p.draw_outline (cr); - p.draw_edit_points (cr); + if (has_path) { + cr.set_fill_rule (FillRule.EVEN_ODD); + Theme.color (cr, "Objects"); + cr.fill (); } - cr.restore (); } - - if (!is_open ()) { - // This was good for testing but it is way too slow: - // Svg.draw_svg_path (cr, get_svg_data (), Glyph.xc () + left, Glyph.yc () - baseline); - - cr.save (); - cr.new_path (); - foreach (Path p in get_visible_paths ()) { - if (p.stroke == 0) { - color = p.color == null ? Color.black () : (!) p.color; - p.draw_path (cr, this, color); + + if (draw_control_points) { + foreach (SvgBird.Object object in get_visible_objects ()) { + if (object is PathObject) { + PathObject object_path = (PathObject) object; + Glyph g = MainWindow.get_current_glyph (); + cr.set_line_width (CanvasSettings.stroke_width / g.view_zoom); + object_path.path.draw_path (cr); + object_path.path.draw_control_points (cr); } - } - cr.close_path (); - cr.fill (); - cr.restore (); - - foreach (Path p in active_paths) { - cr.save (); - cr.new_path (); - if (p.stroke == 0) { - p.draw_path (cr, this); - } - cr.close_path (); - cr.fill (); - cr.restore (); } } if (show_orientation_arrow) { foreach (Path p in get_visible_paths ()) { - if (p.stroke > 0) { - stroke = p.get_stroke_fast (); - foreach (Path ps in stroke.paths) { - ps.draw_orientation_arrow (cr, orientation_arrow_opacity); - } - } else { + if (p.stroke == 0) { p.draw_orientation_arrow (cr, orientation_arrow_opacity); } } - } - } - - private Color get_path_fill_color () { - return Theme.get_color ("Fill Color"); - } - - public void draw_path_list (PathList pl, Context cr, Color? c = null) { - foreach (Path p in pl.paths) { - p.draw_path (cr, this, c); } } @@ -1840,16 +1712,13 @@ } if (unlikely (Preferences.draw_boundaries)) { - foreach (Path p in get_visible_paths ()) { - p.draw_boundaries (cmp); + foreach (SvgBird.Object o in get_visible_objects ()) { + draw_boundaries (o, cmp); } } draw_background_glyph (allocation, cmp); - - if (svg_drawing != null) { - juxtapose (allocation, cmp); - } + juxtapose (allocation, cmp); if (BirdFont.show_coordinates) { draw_coordinate (cmp); @@ -1861,38 +1730,29 @@ cmp.restore (); } - if (svg_drawing != null) { - cmp.save (); - cmp.scale (view_zoom, view_zoom); - cmp.translate (-view_offset_x, -view_offset_y); - draw_svg (cmp); - cmp.restore (); - } else { - cmp.save (); - cmp.scale (view_zoom, view_zoom); - cmp.translate (-view_offset_x, -view_offset_y); - draw_path (cmp); - cmp.restore (); + cmp.save (); + cmp.scale (view_zoom, view_zoom); + cmp.translate (-view_offset_x, -view_offset_y); + draw_layers (cmp); + cmp.restore (); + + Tool selected_tool = MainWindow.get_toolbox ().get_current_tool (); + bool draw_selection = active_paths.size > 0 + && (selected_tool is MoveTool || selected_tool is ResizeTool); + + if (draw_selection) { + update_selection_boundaries (); + draw_selection_box (cmp); } - + cmp.save (); tool = MainWindow.get_toolbox ().get_current_tool (); tool.draw_action (tool, cmp, this); cmp.restore (); } - public void draw_svg (Context cr) { - if (svg_drawing != null) { - cr.save (); - cr.translate (xc () + svg_x, yc () - svg_y); - Rsvg.Handle svg_handle = (!) svg_drawing; - svg_handle.render_cairo (cr); - cr.restore (); - } - } - - private void zoom_in_at_point (double x, double y, double amount = 15) { - int n = (int) (-amount); + private void zoom_in_at_point (double x, double y, double zoom = 15) { + int n = (int) (-zoom); zoom_at_point (x, y, n); } @@ -1959,10 +1819,10 @@ g.add_line (line.copy ()); } - g.layers = layers.copy (); + g.layers = (Layer) layers.copy (); - foreach (Path p in active_paths) { - g.active_paths.add (p); + foreach (SvgBird.Object o in active_paths) { + g.active_paths.add (o); } if (background_image != null) { @@ -2032,7 +1892,7 @@ void set_glyph_data (Glyph g) { current_layer = g.current_layer; - layers = g.layers.copy (); + layers = (Layer) g.layers.copy (); left_limit = g.left_limit; right_limit = g.right_limit; @@ -2049,8 +1909,8 @@ } clear_active_paths (); - foreach (Path p in g.active_paths) { - add_active_path (null, p); + foreach (SvgBird.Object p in g.active_paths) { + add_active_object (null, p); } redraw_area (0, 0, allocation.width, allocation.height); @@ -2450,12 +2310,12 @@ public void move_layer_up () { Layer layer = get_current_layer (); - if (current_layer + 2 <= layers.subgroups.size) { - return_if_fail (0 <= current_layer + 2 <= layers.subgroups.size); - layers.subgroups.insert (current_layer + 2, layer); + if (current_layer + 2 <= layers.objects.size) { + return_if_fail (0 <= current_layer + 2 <= layers.objects.size); + layers.objects.objects.insert (current_layer + 2, layer); - return_if_fail (0 <= current_layer + 1 < layers.subgroups.size); - layers.subgroups.remove_at (current_layer); + return_if_fail (0 <= current_layer + 1 < layers.objects.size); + layers.objects.objects.remove_at (current_layer); set_current_layer (layer); } @@ -2465,11 +2325,11 @@ Layer layer = get_current_layer (); if (current_layer >= 1) { - return_if_fail (0 <= current_layer - 1 < layers.subgroups.size); - layers.subgroups.insert (current_layer - 1, layer); + return_if_fail (0 <= current_layer - 1 < layers.objects.size); + layers.objects.objects.insert (current_layer - 1, layer); - return_if_fail (0 <= current_layer + 1 < layers.subgroups.size); - layers.subgroups.remove_at (current_layer + 1); + return_if_fail (0 <= current_layer + 1 < layers.objects.size); + layers.objects.objects.remove_at (current_layer + 1); set_current_layer (layer); } @@ -2542,8 +2402,140 @@ } return g2; + } + + // FIXME: convert everything to the new SvgBird.Object code + public Gee.ArrayList<Path> get_active_paths () { + Gee.ArrayList<Path> paths = new Gee.ArrayList<Path> (); + + foreach (SvgBird.Object object in active_paths) { + if (object is PathObject) { + paths.add (((PathObject) object).get_path ()); + } + } + + return paths; + } + + public void draw_boundaries (SvgBird.Object object, Context cr) { + double x = Glyph.reverse_path_coordinate_x (object.xmin); + double y = Glyph.reverse_path_coordinate_y (object.ymin); + double x2 = Glyph.reverse_path_coordinate_x (object.xmax); + double y2 = Glyph.reverse_path_coordinate_y (object.ymax); + + cr.save (); + + Theme.color (cr, "Default Background"); + cr.set_line_width (2); + cr.rectangle (x, y, x2 - x, y2 - y); + cr.stroke (); + + cr.restore (); + } + + void draw_selection_box (Context cr) { + double x, y, w, h; + double hook_width; + + x = reverse_path_coordinate_x (selection_box_x); + y = reverse_path_coordinate_y (selection_box_y); + w = selection_box_width / ivz (); + h = selection_box_height / ivz (); + + hook_width = w > 20 ? 10 : w * 0.2; + + update_selection_boundaries (); + cr.save (); + + // FIXME: use color theme + cr.set_source_rgba (0, 0, 0, 1); + + cr.set_line_width (1); + cr.move_to (x, y + hook_width); + cr.line_to (x, y); + cr.line_to (x + hook_width, y); + cr.stroke (); + + cr.move_to (x + w - hook_width, y); + cr.line_to (x + w, y); + cr.line_to (x + w, y + hook_width); + cr.stroke (); + + cr.move_to (x + w, y + h - hook_width); + cr.line_to (x + w, y + h); + cr.line_to (x + w - hook_width, y + h); + + cr.move_to (x + hook_width, y + h); + cr.line_to (x, y + h); + cr.line_to (x, y + h - hook_width); + + cr.stroke (); + cr.restore (); + + cr.save (); + cr.set_source_rgba (1, 1, 1, 1); + + cr.set_line_width (1); + cr.move_to (x + 1, y + hook_width); + cr.line_to (x + 1, y + 1); + cr.line_to (x + hook_width, y + 1); + cr.stroke (); + + cr.move_to (x + w - hook_width, y + 1); + cr.line_to (x + w - 1, y + 1); + cr.line_to (x + w - 1, y + hook_width); + cr.stroke (); + + cr.move_to (x + w - 1, y + h - hook_width); + cr.line_to (x + w - 1, y + h - 1); + cr.line_to (x + w - hook_width, y + h - 1); + + cr.move_to (x + hook_width, y + h - 1); + cr.line_to (x + 1, y + h - 1); + cr.line_to (x + 1, y + h - hook_width); + + cr.stroke (); + cr.restore (); + } + + public void update_selection_boundaries () { + get_selection_box_boundaries (out selection_box_x, + out selection_box_y, out selection_box_width, + out selection_box_height); + } + + public void get_selection_box_boundaries (out double x, out double y, out double w, out double h) { + double px, py, px2, py2; + + px = CANVAS_MAX; + py = CANVAS_MAX; + px2 = CANVAS_MIN; + py2 = CANVAS_MIN; + + foreach (Path p in get_active_paths ()) { + if (px > p.xmin) { + px = p.xmin; + } + + if (py > p.ymin) { + py = p.ymin; + } + + if (px2 < p.xmax) { + px2 = p.xmax; + } + + if (py2 < p.ymax) { + py2 = p.ymax; + } + } + + w = px2 - px; + h = py2 - py; + x = px; + y = py2; } } }
--- a/libbirdfont/GlyphSequence.vala +++ b/libbirdfont/GlyphSequence.vala @@ -111,7 +111,7 @@ g = font.get_glyph_by_name (name); - if (g != null) { + if (likely (g != null)) { old.add (g); if (a.alternates.size > 0) { @@ -119,7 +119,7 @@ string alt_name = a.alternates.get (0); Glyph? alt = font.get_glyph_by_name (alt_name); - if (alt != null) { + if (likely (alt != null)) { GlyphSequence replacement = new GlyphSequence (); replacement.add (alt); ligature_sequence.replace (old, replacement);
diff --git libbirdfont/Gradient.vala(deleted)
--- a/libbirdfont/Gradient.vala +++ /dev/null @@ -1,54 +1,1 @@ - /* - Copyright (C) 2015 Johan Mattsson - - This library is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as - published by the Free Software Foundation; either version 3 of the - License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - */ - - using Cairo; - using Math; - - namespace BirdFont { - - public class Gradient : GLib.Object { - public double x1; - public double y1; - public double x2; - public double y2; - - public Gee.ArrayList<Stop> stops; - - public int id = -1; - - public Gradient () { - x1 = 0; - y1 = 0; - x2 = 0; - y2 = 0; - stops = new Gee.ArrayList<Stop> (); - } - - public Gradient copy () { - Gradient g = new Gradient (); - g.x1 = x1; - g.y1 = y1; - g.x2 = x2; - g.y2 = y2; - - foreach (Stop s in stops) { - g.stops.add (s.copy ()); - } - - return g; - } - } - - }
diff --git libbirdfont/Help.vala(new)
--- /dev/null +++ b/libbirdfont/Help.vala @@ -1,1 +1,124 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Cairo; + + namespace BirdFont { + + public class Help { + + bool visible; + TextArea help_text; + Text close; + const int box_margin = 7; + + public Help () { + string v = Preferences.get ("help_visible"); + visible = v == "true"; + help_text = create_help_text (t_("BirdFont is a font editor.")); + + Color color = Theme.get_color ("Menu Foreground"); + close = new Text ("close", 30, 0, color); + close.load_font ("icons.bf"); + } + + public static TextArea create_help_text (string lines) { + Color color = Theme.get_color ("Menu Foreground"); + TextArea text = new TextArea (17, color); + text.min_width = 200; + text.min_height = 100; + text.width = 200; + text.height = 100; + text.allocation = new WidgetAllocation.for_area (0, 0, (int)text.min_width, (int)text.min_height); + text.set_text (lines); + text.layout (); + text.set_editable (false); + text.draw_border = false; + return text; + } + + public void set_help_text (TextArea text) { + if (text != help_text && is_visible ()) { + help_text = text; + GlyphCanvas.redraw (); + } + } + + public void set_visible (bool v) { + visible = v; + Preferences.set ("help_visible", @"$v"); + } + + public bool is_visible () { + return visible; + } + + public void draw (Context cr, WidgetAllocation allocation) { + int margin_x = 10; + int margin_y = 15; + + help_text.allocation = allocation; + help_text.widget_x = allocation.width - help_text.width - margin_x; + help_text.widget_y = allocation.height - help_text.height - margin_y; + + draw_box (cr, allocation); + + help_text.draw (cr); + + double close_x = allocation.width; + close_x -= close.get_sidebearing_extent (); + close_x -= margin_x; + close_x -= box_margin; + + double close_y = allocation.height; + close_y -= help_text.height; + close_y -= margin_y; + close_y -= 2 * box_margin; + + close.widget_x = close_x; + close.widget_y = close_y; + close.draw (cr); + } + + public bool button_press (uint button, double x, double y) { + if (close.widget_x < x < close.widget_x + close.get_sidebearing_extent () + && close.widget_y < y < close.widget_y + close.font_size) { + + set_visible (false); + GlyphCanvas.redraw (); + return true; + } + + return false; + } + + public void draw_box (Context cr, WidgetAllocation allocation) { + cr.save(); + + Theme.color (cr, "Menu Background"); + double x, y, w, h; + x = help_text.widget_x - 2 * box_margin; + y = help_text.widget_y - 2 * box_margin; + w = help_text.width + 2 * box_margin; + h = help_text.height + 2 * box_margin; + Widget.draw_rounded_rectangle (cr, x, y, w, h, 7); + cr.fill (); + + cr.restore(); + } + + } + + }
--- a/libbirdfont/KerningDisplay.vala +++ b/libbirdfont/KerningDisplay.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 2014 2015 Johan Mattsson + Copyright (C) 2012 2014 2015 2016 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -45,6 +45,8 @@ public bool adjust_side_bearings = false; public bool right_side_bearing = true; + + TextArea description; public KerningDisplay () { GlyphSequence w = new GlyphSequence (); @@ -54,6 +56,8 @@ redo_items = new Gee.ArrayList <UndoItem> (); w.set_otf_tags (KerningTools.get_otf_tags ()); first_row.add (w); + + description = Help.create_help_text (t_("Kerning is the process of adjusting the space between two letters. You can cahnge the space between one letter and all other letters in the spacing tab.")); } public GlyphSequence get_first_row () { @@ -189,7 +193,7 @@ cr.save (); glyph.add_help_lines (); cr.translate (kern + x - glyph.get_lsb () - Glyph.xc (), glyph.get_baseline () + y - Glyph.yc ()); - glyph.draw_paths (cr); + glyph.draw_layers (cr); cr.restore (); w = glyph.get_width (); @@ -487,7 +491,8 @@ } public override void selected_canvas () { - KeyBindings.set_require_modifier (true); + KeyBindings.set_require_modifier (true); + MainWindow.get_help ().set_help_text (description); } public void add_kerning_class (int index) {
--- a/libbirdfont/LabelTool.vala +++ b/libbirdfont/LabelTool.vala @@ -79,7 +79,8 @@ }); } - void clear_cache () { + public override void clear_cache () { + base.clear_cache (); selected_cache = null; deselected_cache = null; }
diff --git libbirdfont/Layer.vala(deleted)
--- a/libbirdfont/Layer.vala +++ /dev/null @@ -1,163 +1,1 @@ - /* - Copyright (C) 2015 Johan Mattsson - - This library is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as - published by the Free Software Foundation; either version 3 of the - License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - */ - - namespace BirdFont { - - public class Layer : GLib.Object { - public PathList paths; - public Gee.ArrayList<Layer> subgroups; - public bool visible = true; - public string name = "Layer"; - - public bool is_counter = false; - public Gradient? gradient = null; - public bool single_path = false; - - public Layer () { - paths = new PathList (); - subgroups = new Gee.ArrayList<Layer> (); - } - - public int index_of (Layer sublayer) { - return subgroups.index_of (sublayer); - } - - public PathList get_all_paths () { - PathList p = new PathList (); - - p.append (paths); - - foreach (Layer sublayer in subgroups) { - p.append (sublayer.get_all_paths ()); - } - - return p; - } - - public PathList get_visible_paths () { - PathList p = new PathList (); - - if (visible) { - p.append (paths); - } - - foreach (Layer sublayer in subgroups) { - if (sublayer.visible) { - p.append (sublayer.get_all_paths ()); - } - } - - return p; - } - - public void add_layer (Layer layer) { - subgroups.add (layer); - } - - public void add_path (Path path) { - paths.add (path); - } - - public void remove_path (Path path) { - paths.remove (path); - foreach (Layer sublayer in subgroups) { - sublayer.remove_path (path); - } - } - - public void remove_layer (Layer layer) { - subgroups.remove (layer); - foreach (Layer sublayer in subgroups) { - sublayer.remove_layer (layer); - } - } - - public Layer copy () { - Layer layer = new Layer (); - - layer.name = name; - layer.paths = paths.copy (); - layer.visible = visible; - - foreach (Layer l in subgroups) { - layer.subgroups.add (l.copy ()); - } - - if (gradient != null) { - layer.gradient = ((!) gradient).copy (); - } - - layer.single_path = single_path; - - return layer; - } - - public void get_boundaries (out double x, out double y, out double w, out double h) { - double px, py, px2, py2; - - px = Glyph.CANVAS_MAX; - py = Glyph.CANVAS_MAX; - px2 = Glyph.CANVAS_MIN; - py2 = Glyph.CANVAS_MIN; - - foreach (Path p in get_all_paths ().paths) { - if (px > p.xmin) { - px = p.xmin; - } - - if (py > p.ymin) { - py = p.ymin; - } - - if (px2 < p.xmax) { - px2 = p.xmax; - } - - if (py2 < p.ymax) { - py2 = p.ymax; - } - } - - w = px2 - px; - h = py2 - py; - x = px; - y = py2; - } - - public void print (int indent = 0) { - foreach (Path p in paths.paths) { - for (int i = 0; i < indent; i++) { - stdout.printf ("\t"); - } - stdout.printf (@"Path open: $(p.is_open ())"); - - if (p.color != null) { - stdout.printf (" %s", ((!) p.color).to_rgb_hex ()); - } - - stdout.printf ("\n"); - } - - foreach (Layer l in subgroups) { - for (int i = 0; i < indent; i++) { - stdout.printf ("\t"); - } - stdout.printf ("%s\n", l.name); - l.print (indent + 1); - } - } - } - - }
--- a/libbirdfont/LayerLabel.vala +++ b/libbirdfont/LayerLabel.vala @@ -13,6 +13,7 @@ */ using Cairo; + using SvgBird; namespace BirdFont { @@ -26,9 +27,15 @@ /** Add margin when layer is moves. */ bool active_layer = false; + + static TextArea? help_text_hide = null; public LayerLabel (Layer layer) { base (); + + if (help_text_hide == null) { + update_description (); + } this.layer = layer; this.label = layer.name; @@ -39,10 +46,10 @@ panel_press_action.connect ((selected, button, tx, ty) => { if (y <= ty <= y + h) { - if (tx >= w - 30 * Toolbox.get_scale ()) { + if (tx >= w - 30) { DrawingTools.deselect_layers (); remove_layer (); - } if (tx < 25 * Toolbox.get_scale ()) { + } else if (tx < 25) { layer.visible = !layer.visible; GlyphCanvas.redraw (); BirdFont.get_current_font ().touch (); @@ -74,6 +81,13 @@ MainWindow.get_toolbox ().update_expanders (); redraw (); + } + + if (y <= ty <= y + h) { + if (tx < 25 && help_text_hide != null) { + Help help = MainWindow.get_help (); + help.set_help_text ((!) help_text_hide); + } } return false; @@ -83,7 +97,16 @@ active_layer = false; }); } - + + void update_description () { + help_text_hide = Help.create_help_text (t_("Hide the layer.")); + } + + public override void clear_cache () { + base.clear_cache (); + update_description (); + } + void move_layer_up () { int i; Glyph g = MainWindow.get_current_glyph ();
diff --git libbirdfont/LayerUtils.vala(new)
--- /dev/null +++ b/libbirdfont/LayerUtils.vala @@ -1,1 +1,129 @@ + /* + Copyright (C) 2015 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using SvgBird; + + namespace BirdFont { + + public class LayerUtils { + + public static Gee.ArrayList<SvgBird.Object> get_visible_objects (Layer layer) { + ObjectGroup group = new ObjectGroup (); + add_visible_objects (layer, group); + return group.objects; + } + + public static void add_visible_objects (Layer layer, ObjectGroup objects) { + foreach (SvgBird.Object o in layer.objects) { + if (o is Layer) { + Layer sublayer = (Layer) o; + + if (sublayer.visible) { + add_visible_objects (sublayer, objects); + } + } else { + if (o.visible) { + objects.add (o); + } + } + } + } + + public static PathList get_all_paths (Layer layer) { + PathList paths = new PathList (); + add_paths_to_group (layer, paths); + return paths; + } + + public static void add_paths_to_group (Layer layer, PathList paths) { + foreach (SvgBird.Object o in layer.objects) { + if (o is PathObject) { + PathObject p = (PathObject) o; + paths.add (p.get_path ()); + } else if (o is Layer) { + add_visible_paths_to_group ((Layer) o, paths); + } + } + } + + public static PathList get_visible_paths (Layer layer) { + PathList paths = new PathList (); + add_visible_paths_to_group (layer, paths); + return paths; + } + + public static void add_visible_paths_to_group (Layer layer, PathList paths) { + foreach (SvgBird.Object o in layer.objects) { + if (o.visible) { + if (o is PathObject) { + PathObject p = (PathObject) o; + paths.add (p.get_path ()); + } else if (o is Layer) { + add_visible_paths_to_group ((Layer) o, paths); + } + } + } + } + + public static void add_path (Layer layer, Path path) { + PathObject p = new PathObject.for_path (path); + layer.add_object (p); + } + + public static void append_paths (Layer layer, PathList path_list) { + foreach (Path p in path_list.paths) { + add_path (layer, p); + } + } + + private static PathObject? get_object_path (Layer layer, Path path) { + foreach (SvgBird.Object o in layer.objects) { + if (o is PathObject) { + PathObject p = (PathObject) o; + if (p.get_path () == path) { + return p; + } + } + } + + return null; + } + + public static void remove_path (Layer layer, Path path) { + PathObject? p = get_object_path (layer, path); + + if (p != null) { + layer.objects.remove ((!) p); + } + + foreach (SvgBird.Layer sublayer in layer.get_sublayers ()) { + remove_path (sublayer, path); + } + } + + public static PathList get_paths_in_layer (Layer layer) { + PathList paths = new PathList (); + + foreach (SvgBird.Object object in layer.objects) { + if (object is PathObject) { + paths.add (((PathObject) object).get_path ()); + } + } + + return paths; + } + } + + }
--- a/libbirdfont/LicenseDialog.vala +++ b/libbirdfont/LicenseDialog.vala @@ -28,11 +28,11 @@ static const double margin = 20; public LicenseDialog () { - agreement = new TextArea (font_size); + Color color = Theme.get_color ("Text Tool Box"); + agreement = new TextArea (font_size, color); agreement.min_width = 300; agreement.set_editable (false); agreement.draw_border = false; - agreement.text_color = Theme.get_color ("Text Tool Box"); agreement.set_text ("BirdFont is developed with donations, please consider donating to the project.\n\nThis is the freeware version of BirdFont. You may use it for creating fonts under the SIL Open Font License.\n\nWhich license do you want to use for your font?"); decline = new Button ("Commercial License");
--- a/libbirdfont/MainWindow.vala +++ b/libbirdfont/MainWindow.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 2014 Johan Mattsson + Copyright (C) 2012 2014 2016 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -32,6 +32,7 @@ public static Dialog dialog; public static SpacingTab spacing_tab; public static Task blocking_background_task; + public static Help help; /** Number of pixels per mm */ public static double units = 1; @@ -55,8 +56,13 @@ dialog = new Dialog (); spacing_tab = new SpacingTab (); blocking_background_task = new Task (null); + help = new Help (); tools.select_tool (DrawingTools.bezier_tool); + } + + public static Help get_help () { + return help; } public static void abort_task () {
--- a/libbirdfont/Menu.vala +++ b/libbirdfont/Menu.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2014 2015 Johan Mattsson + Copyright (C) 2014 - 2016 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -299,23 +299,25 @@ }); export_menu.items.add (import_svg_folder); - MenuItem import_color_svg; - import_color_svg = add_menu_item (t_("Import SVG file") + ", " + t_("color"), - "import svg file color", "Glyph"); - import_color_svg.action.connect (() => { - SvgParser.import (SvgType.COLOR); - show_menu = false; - }); - export_menu.items.add (import_color_svg); + if (BirdFont.has_argument ("--test")) { + MenuItem import_color_svg; + import_color_svg = add_menu_item (t_("Import SVG file") + ", " + t_("color"), + "import svg file color", "Glyph"); + import_color_svg.action.connect (() => { + SvgParser.import (SvgType.COLOR); + show_menu = false; + }); + export_menu.items.add (import_color_svg); - MenuItem import_svg_color_folder; - import_svg_color_folder = add_menu_item (t_("Import SVG folder") + ", " + t_("color"), - "import svg folder color", ""); - import_svg_color_folder.action.connect (() => { - SvgParser.import_folder (SvgType.COLOR); - show_menu = false; - }); - export_menu.items.add (import_svg_color_folder); + MenuItem import_svg_color_folder; + import_svg_color_folder = add_menu_item (t_("Import SVG folder") + ", " + t_("color"), + "import svg folder color", ""); + import_svg_color_folder.action.connect (() => { + SvgParser.import_folder (SvgType.COLOR); + show_menu = false; + }); + export_menu.items.add (import_svg_color_folder); + } MenuItem import_background_image = add_menu_item (t_("Import Background Image"), "import background image"); import_background_image.action.connect (() => { @@ -507,7 +509,16 @@ show_menu = false; }); menu.items.add (version); - + + MenuItem help = add_menu_item (t_("Help"), "help"); + help.action.connect (() => { + Help help_box = MainWindow.get_help (); + help_box.set_visible (!help_box.is_visible ()); + GlyphCanvas.redraw (); + show_menu = false; + }); + menu.items.add (help); + set_current_menu (menu); top_menu = menu;
--- a/libbirdfont/MenuItem.vala +++ b/libbirdfont/MenuItem.vala @@ -23,8 +23,8 @@ public double y; // key bindings - public uint modifiers = NONE; - public unichar key = '\0'; + public virtual uint modifiers { get; set; default = NONE; } + public virtual unichar key { get; set; default = '\0'; } public Gee.ArrayList<string> displays = new Gee.ArrayList<string> ();
--- a/libbirdfont/MenuTab.vala +++ b/libbirdfont/MenuTab.vala @@ -11,6 +11,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. */ + + using SvgBird; namespace BirdFont { @@ -122,8 +124,7 @@ Font current_font = BirdFont.get_current_font (); string ttf_name = ExportSettings.get_file_name (current_font) + ".ttf"; string ttf_name_mac = ExportSettings.get_file_name_mac (current_font) + ".ttf"; - - print (@"$ttf_name == $ttf_name_mac"); + if (ttf_name == ttf_name_mac) { MainWindow.show_message (t_("You need to choose a different name for the TTF file with Mac adjustmets.")); ttf_name_mac = ExportSettings.get_file_name_mac (current_font) + " Mac.ttf"; @@ -275,6 +276,7 @@ } DrawingTools.set_stroke_tool_visibility (); + string lock_grid = f.settings.get_setting ("lock_grid"); bool lg = bool.parse (lock_grid); @@ -645,29 +647,29 @@ Gee.ArrayList<Path> paths = new Gee.ArrayList<Path> (); // selected objects - foreach (Path p in g.active_paths) { + foreach (Path p in g.get_active_paths ()) { paths.add (PenTool.simplify (p, false, PenTool.simplification_threshold)); } // selected segments if (paths.size == 0) { - foreach (Path p in g.get_all_paths ()) { + foreach (Path p in g.get_active_paths ()) { g.add_active_path (null, p); } - foreach (Path p in g.active_paths) { + foreach (Path p in g.get_active_paths ()) { paths.add (PenTool.simplify (p, true, PenTool.simplification_threshold)); } } g.store_undo_state (); - foreach (Path p in g.active_paths) { - g.layers.remove_path (p); + foreach (SvgBird.Object o in g.active_paths) { + g.layers.remove (o); } - foreach (Path p in g.active_paths) { - g.layers.remove_path (p); + foreach (Path p in g.get_active_paths ()) { + LayerUtils.remove_path (g.layers, p); } foreach (Path p in paths) {
--- a/libbirdfont/MoveTool.vala +++ b/libbirdfont/MoveTool.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 2013 Johan Mattsson + Copyright (C) 2012 2013 2015 2016 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -14,6 +14,7 @@ using Math; using Cairo; + using SvgBird; namespace BirdFont { @@ -94,8 +95,13 @@ g.store_undo_state (); } - foreach (Path p in g.active_paths) { - g.layers.remove_path (p); + foreach (SvgBird.Object p in g.active_paths) { + if (p is PathObject) { + LayerUtils.remove_path (g.layers, ((PathObject) p).get_path ()); + } else { + g.layers.remove (p); + } + g.update_view (); } @@ -109,9 +115,8 @@ public void move (int x, int y) { Glyph glyph = MainWindow.get_current_glyph (); - double dx = last_x - x; - double dy = last_y - y; - double p = PenTool.precision; + double dx = Glyph.path_coordinate_x (last_x) - Glyph.path_coordinate_x (x); + double dy = Glyph.path_coordinate_y (last_y) - Glyph.path_coordinate_y (y); double delta_x, delta_y; if (!move_path) { @@ -121,26 +126,11 @@ if (move_path && (fabs(dx) > 0 || fabs (dy) > 0)) { moved = true; - delta_x = Glyph.ivz () * -dx * p; - delta_y = Glyph.ivz () * dy * p; + delta_x = -dx; + delta_y = -dy; - if (glyph.color_svg_data != null) { - glyph.svg_x += delta_x; - glyph.svg_y += delta_y; - } else { - foreach (Layer group in glyph.selected_groups) { - if (group.gradient != null) { - Gradient g = (!) group.gradient; - g.x1 += delta_x; - g.x2 += delta_x; - g.y1 += delta_y; - g.y2 += delta_y; - } - } - - foreach (Path path in glyph.active_paths) { - path.move (delta_x, delta_y); - } + foreach (SvgBird.Object object in glyph.active_paths) { + object.move (delta_x, delta_y); } } @@ -167,7 +157,7 @@ if (GridTool.is_visible () && moved) { tie_paths_to_grid (glyph); } else if (GridTool.has_ttf_grid ()) { - foreach (Path p in glyph.active_paths) { + foreach (SvgBird.Object p in glyph.active_paths) { tie_path_to_ttf_grid (p); } } @@ -184,8 +174,11 @@ objects_moved (); DrawingTools.resize_tool.signal_objects_rotated (); - foreach (Path p in glyph.active_paths) { - p.create_full_stroke (); + foreach (SvgBird.Object o in glyph.active_paths) { + if (o is PathObject) { + PathObject path = (PathObject) o; + path.get_path ().create_full_stroke (); + } } } else { objects_deselected (); @@ -194,42 +187,34 @@ public void press (int b, int x, int y) { Glyph glyph = MainWindow.get_current_glyph (); - Path p; + SvgBird.Object object; bool selected = false; - Layer? group; - Layer g; + SvgBird.Object? o; - glyph.store_undo_state (); - group_selection = false; - - if (glyph.color_svg_data == null) { - group = glyph.get_path_at (x, y); - - if (group != null) { - g = (!) group; - return_if_fail (g.paths.paths.size > 0); - p = g.paths.paths.get (0); - selected = glyph.active_paths.contains (p); - - if (!selected && !KeyBindings.has_shift ()) { - glyph.clear_active_paths (); - } - - foreach (Path lp in g.paths.paths) { - if (selected && KeyBindings.has_shift ()) { - glyph.selected_groups.remove ((!) group); - glyph.active_paths.remove (lp); - } else { - glyph.add_active_path ((!) group, lp); - } - } - } else if (!KeyBindings.has_shift ()) { + glyph.store_undo_state (); + double px = Glyph.path_coordinate_x (x); + double py = Glyph.path_coordinate_y (y); + o = glyph.get_object_at (px, py); + + if (o != null) { + object = (!) o; + selected = glyph.active_paths_contains (object); + + if (!selected && !KeyBindings.has_shift ()) { glyph.clear_active_paths (); - } + } - update_selection_boundaries (); + if (selected && KeyBindings.has_shift ()) { + glyph.active_paths.remove (object); + } else { + glyph.add_active_object (null, object); + } + } else if (!KeyBindings.has_shift ()) { + glyph.clear_active_paths (); } - + + update_selection_boundaries (); + move_path = true; last_x = x; @@ -245,8 +230,7 @@ selection_changed (); GlyphCanvas.redraw (); } - - + void select_group () { double x1 = Glyph.path_coordinate_x (Math.fmin (selection_x, last_x)); double y1 = Glyph.path_coordinate_y (Math.fmin (selection_y, last_y)); @@ -256,10 +240,10 @@ glyph.clear_active_paths (); - foreach (Path p in glyph.get_paths_in_current_layer ()) { + foreach (SvgBird.Object p in glyph.get_objects_in_current_layer ()) { if (p.xmin > x1 && p.xmax < x2 && p.ymin < y1 && p.ymax > y2) { - if (p.points.size > 0) { - glyph.add_active_path (null, p); + if (!p.is_empty ()) { + glyph.add_active_object (null, p); } } } @@ -280,32 +264,13 @@ get_selection_box_boundaries (out x, out y, out w, out h); - foreach (Path path in glyph.active_paths) { + foreach (SvgBird.Object path in glyph.active_paths) { path.move (glyph.left_limit - x + w / 2, font.base_line - y + h / 2); } update_selection_boundaries (); objects_moved (); GlyphCanvas.redraw (); - } - - static void draw_selection_box (Context cr) { - double x = Math.fmin (selection_x, last_x); - double y = Math.fmin (selection_y, last_y); - - double w = Math.fabs (selection_x - last_x); - double h = Math.fabs (selection_y - last_y); - - Glyph glyph = MainWindow.get_current_glyph (); - - if (glyph.color_svg_data == null) { - cr.save (); - Theme.color (cr, "Foreground 1"); - cr.set_line_width (2); - cr.rectangle (x, y, w, h); - cr.stroke (); - cr.restore (); - } } public static void get_selection_box_boundaries (out double x, out double y, out double w, out double h) { @@ -317,23 +282,26 @@ px2 = -10000; py2 = -10000; - foreach (Path p in glyph.active_paths) { - p.update_region_boundaries (); - - if (px > p.xmin) { - px = p.xmin; - } + foreach (SvgBird.Object o in glyph.active_paths) { + if (o is PathObject) { + Path p = ((PathObject) o).get_path (); + p.update_region_boundaries (); + + if (px > p.xmin) { + px = p.xmin; + } - if (py > p.ymin) { - py = p.ymin; - } + if (py > p.ymin) { + py = p.ymin; + } - if (px2 < p.xmax) { - px2 = p.xmax; - } - - if (py2 < p.ymax) { - py2 = p.ymax; + if (px2 < p.xmax) { + px2 = p.xmax; + } + + if (py2 < p.ymax) { + py2 = p.ymax; + } } } @@ -367,7 +335,7 @@ break; } - foreach (Path path in glyph.active_paths) { + foreach (SvgBird.Object path in glyph.active_paths) { path.move (x * Glyph.ivz (), y * Glyph.ivz ()); } @@ -378,7 +346,7 @@ glyph.redraw_area (0, 0, glyph.allocation.width, glyph.allocation.height); } - static void tie_path_to_ttf_grid (Path p) { + static void tie_path_to_ttf_grid (SvgBird.Object p) { double sx, sy, qx, qy; sx = p.xmax; @@ -428,7 +396,7 @@ dx_min = Math.fabs (qx - minx); dx_max = Math.fabs (sx - maxx); - foreach (Path p in g.active_paths) { + foreach (SvgBird.Object p in g.active_paths) { if (dy_min < dy_max) { p.move (0, qy - miny); } else { @@ -447,8 +415,10 @@ public static void update_boundaries_for_selection () { Glyph glyph = MainWindow.get_current_glyph (); - foreach (Path p in glyph.active_paths) { - p.update_region_boundaries (); + foreach (SvgBird.Object o in glyph.active_paths) { + if (o is PathObject) { + ((PathObject)o).get_path ().update_region_boundaries (); + } } } @@ -470,14 +440,19 @@ xc = selection_box_center_x; yc = selection_box_center_y; - foreach (Path p in glyph.active_paths) { - if (vertical) { - p.flip_vertical (); - } else { - p.flip_horizontal (); + foreach (SvgBird.Object p in glyph.active_paths) { + if (p is PathObject) { + Path path = ((PathObject) p).get_path (); + + // FIXME: move to object + if (vertical) { + path.flip_vertical (); + } else { + path.flip_horizontal (); + } + + path.reverse (); } - - p.reverse (); } get_selection_box_boundaries (out xc2, out yc2, out w, out h); @@ -485,7 +460,7 @@ dx = -(xc2 - xc); dy = -(yc2 - yc); - foreach (Path p in glyph.active_paths) { + foreach (SvgBird.Object p in glyph.active_paths) { p.move (dx, dy); } @@ -499,9 +474,9 @@ Glyph g = MainWindow.get_current_glyph (); g.clear_active_paths (); - foreach (Path p in g.get_paths_in_current_layer ()) { - if (p.points.size > 0) { - g.add_active_path (null, p); + foreach (SvgBird.Object p in g.get_objects_in_current_layer ()) { + if (!p.is_empty ()) { + g.add_active_object (null, p); } } @@ -509,8 +484,23 @@ update_selection_boundaries (); objects_moved (); + } + + static void draw_selection_box (Context cr) { + double x = Math.fmin (selection_x, last_x); + double y = Math.fmin (selection_y, last_y); + + double w = Math.fabs (selection_x - last_x); + double h = Math.fabs (selection_y - last_y); + + cr.save (); + Theme.color (cr, "Foreground 1"); + cr.set_line_width (2); + cr.rectangle (x, y, w, h); + cr.stroke (); + cr.restore (); } } }
--- a/libbirdfont/OpenFontFormat/FontData.vala +++ b/libbirdfont/OpenFontFormat/FontData.vala @@ -14,7 +14,7 @@ namespace BirdFont { - public class FontData : Object { + public class FontData : GLib.Object { // Read pointer uint rp = 0;
--- a/libbirdfont/OpenFontFormat/GlyfData.vala +++ b/libbirdfont/OpenFontFormat/GlyfData.vala @@ -16,7 +16,7 @@ namespace BirdFont { - public class CoordinateFlags { + public class CoordinateFlags : GLib.Object { /** TTF coordinate flags. */ public static const uint8 NONE = 0;
--- a/libbirdfont/OpenFontFormat/GlyfTable.vala +++ b/libbirdfont/OpenFontFormat/GlyfTable.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2012, 2013, 2014 Johan Mattsson + Copyright (C) 2012 2013 2014 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -163,7 +163,7 @@ g.remove_empty_paths (); unassigned = gc.is_unassigned (); - if (unassigned) { + if (unassigned && gc.get_name () != ".notdef") { unassigned_glyphs.add (gc); } @@ -266,7 +266,6 @@ // flags nflags = glyf_data.get_nflags (); if (unlikely (nflags != npoints)) { - print ("glyf table data:\n"); fd.dump (); warning (@"(nflags != npoints) ($nflags != $npoints) in glyph $(g.name). ncontours: $ncontours"); } @@ -478,7 +477,7 @@ 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 + end_points[i] = dis.read_ushort (); // FIXME: 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");
--- a/libbirdfont/OpenFontFormat/OpenFontFormatReader.vala +++ b/libbirdfont/OpenFontFormat/OpenFontFormatReader.vala @@ -58,7 +58,7 @@ PARSE } - public class OpenFontFormatReader : Object { + public class OpenFontFormatReader : GLib.Object { public DirectoryTable directory_table; public FontData font_data = new FontData ();
--- a/libbirdfont/OpenFontFormat/OpenFontFormatWriter.vala +++ b/libbirdfont/OpenFontFormat/OpenFontFormatWriter.vala @@ -14,7 +14,7 @@ namespace BirdFont { - public class OpenFontFormatWriter : Object { + public class OpenFontFormatWriter : GLib.Object { DataOutputStream os; DataOutputStream os_mac;
--- a/libbirdfont/OpenFontFormat/OtfInputStream.vala +++ b/libbirdfont/OpenFontFormat/OtfInputStream.vala @@ -15,7 +15,7 @@ namespace BirdFont { /** Reader for otf data types. */ - public class OtfInputStream : Object { + public class OtfInputStream : GLib.Object { public FileInputStream fin; public DataInputStream din;
--- a/libbirdfont/OpenFontFormat/OtfTable.vala +++ b/libbirdfont/OpenFontFormat/OtfTable.vala @@ -14,7 +14,7 @@ namespace BirdFont { - public class OtfTable : Object { + public class OtfTable : GLib.Object { public string id = "NO_ID";
--- a/libbirdfont/OpenFontFormat/SvgTable.vala +++ b/libbirdfont/OpenFontFormat/SvgTable.vala @@ -34,19 +34,54 @@ Font font = OpenFontFormatWriter.get_current_font (); GlyphCollection? glyph_collection; GlyphCollection glyphs; - string? svg_data; int gid; + Gee.ArrayList<EmbeddedSvg> embedded_svg; for (int index = 0; index < font.length (); index++) { glyph_collection = font.get_glyph_collection_index (index); if (glyph_collection != null) { glyphs = (!) glyph_collection; - svg_data = glyphs.get_current ().color_svg_data; + embedded_svg = get_embedded_svg (glyphs); - if (svg_data != null) { + if (embedded_svg.size > 0) { gid = glyf_table.get_gid (glyphs.get_name ()); - add_svg_glyph ((!) svg_data, gid, glyphs); + + StringBuilder svg = new StringBuilder (); + svg.append ("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"); + + foreach (EmbeddedSvg embedded in embedded_svg) { + svg.append ("<svg>"); + svg.append ("\n\n"); + svg.append ("<g id="); + svg.append ("\""); + svg.append ("glyph"); + svg.append (@"$gid"); + svg.append ("\" "); + + // scale the internal coordinates from 100 units per em to the + // number of units per em in this font and move the glyph + // in to the em box + Glyph glyph = glyphs.get_current (); + double scale = HeadTable.UNITS; + double x = embedded.x - glyph.left_limit; + double y = font.base_line - embedded.y; + svg.append (@"transform=\"scale($scale) translate($x, $y)\""); + + svg.append (">"); + svg.append ("\n\n"); + + append_svg_glyph (svg, embedded.svg_data, glyphs); + + svg.append ("\n\n"); + svg.append ("</g>\n"); + svg.append ("</svg>"); + } + + SvgTableEntry entry; + entry = new SvgTableEntry ((uint16) gid, svg.str); + entries.add (entry); + glyphs_in_table++; } } @@ -55,8 +90,19 @@ process_svg_data (); } - void add_svg_glyph (string svg_data, int glyph_id, GlyphCollection glyphs) { - SvgTableEntry entry; + Gee.ArrayList<EmbeddedSvg> get_embedded_svg (GlyphCollection glyphs) { + Gee.ArrayList<EmbeddedSvg> svg = new Gee.ArrayList<EmbeddedSvg> (); + + foreach (Object object in glyphs.get_current ().get_visible_objects ()) { + if (object is EmbeddedSvg) { + svg.add ((EmbeddedSvg) object); + } + } + + return svg; + } + + void append_svg_glyph (StringBuilder svg, string svg_data, GlyphCollection glyphs) { Gee.ArrayList<Tag> layer_content; Gee.ArrayList<Tag> svg_tags; Gee.ArrayList<Tag> meta; @@ -94,8 +140,6 @@ } } - StringBuilder svg = new StringBuilder (); - svg.append ("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"); svg.append ("<"); svg.append (svg_root_tag.get_name ()); svg.append (" "); @@ -105,40 +149,14 @@ foreach (Tag tag in svg_tags) { append_tag (svg, tag); } - - svg.append ("\n\n"); - svg.append ("<g id="); - svg.append ("\""); - svg.append ("glyph"); - svg.append (@"$glyph_id"); - svg.append ("\" "); - - // scale the internal coordinates from 100 units per em to the - // number of units per em in this font and move the glyph - // in to the em box - Glyph glyph = glyphs.get_current (); - double scale = HeadTable.UNITS; - double x = glyph.svg_x - glyph.left_limit; - double y = font.base_line - glyph.svg_y; - svg.append (@"transform=\"scale($scale) translate($x, $y)\""); - - svg.append (">"); - svg.append ("\n\n"); foreach (Tag tag in layer_content) { append_tag (svg, tag); } - - svg.append ("\n\n"); - svg.append ("</g>"); - svg.append ("\n\n"); - + svg.append ("</"); svg.append (svg_root_tag.get_name ()); - svg.append (">\n"); - - entry = new SvgTableEntry ((uint16) glyph_id, svg.str); - entries.add (entry); + svg.append (">\n"); } public void append_tag (StringBuilder svg, Tag tag) {
--- a/libbirdfont/OrientationTool.vala +++ b/libbirdfont/OrientationTool.vala @@ -30,8 +30,11 @@ select_action.connect ((self) => { Glyph g = MainWindow.get_current_glyph (); - foreach (Path p in g.active_paths) { - p.reverse (); + foreach (Object o in g.active_paths) { + if (o is PathObject) { + PathObject p = (PathObject) o; + p.get_path ().reverse (); + } } count_down = true; @@ -54,13 +57,17 @@ bool has_clockwise_paths = false; bool has_counter_clockwise_paths = false; - foreach (Path p in glyph.active_paths) { - if (p.is_clockwise ()) { - has_clockwise_paths = true; - } - - if (!p.is_clockwise ()) { - has_counter_clockwise_paths = true; + foreach (Object o in glyph.active_paths) { + if (o is PathObject) { + Path p = ((PathObject) o).get_path (); + + if (p.is_clockwise ()) { + has_clockwise_paths = true; + } + + if (!p.is_clockwise ()) { + has_counter_clockwise_paths = true; + } } }
--- a/libbirdfont/OtfFeatureTable.vala +++ b/libbirdfont/OtfFeatureTable.vala @@ -45,7 +45,7 @@ public override void selected_row (Row row, int column, bool delete_button) { int row_index = row.get_index (); - Object o; + GLib.Object o; String s; AlternateItem a;
--- a/libbirdfont/OverView.vala +++ b/libbirdfont/OverView.vala @@ -13,6 +13,7 @@ */ using Cairo; + using SvgBird; namespace BirdFont { @@ -102,6 +103,13 @@ canvas.set_current_glyph_collection (glyph_collection); set_initial_zoom (); PenTool.update_orientation (); + } + + if (glyph_collection.get_name () == ".notdef") { + Font font = BirdFont.get_current_font (); + if (!font.has_glyph (".notdef")) { + font.add_glyph_collection (glyph_collection); + } } }); @@ -519,7 +527,7 @@ public void process_item_list_update () { string character_string; - Font f = BirdFont.get_current_font (); + Font font = BirdFont.get_current_font (); GlyphCollection? glyphs = null; uint32 index; OverViewItem item; @@ -543,10 +551,11 @@ y = OverViewItem.margin; if (all_available) { - uint font_length = f.length (); + uint font_length = font.length (); + int i; - for (int i = 0; i < item_list_length && index < font_length; i++) { - glyphs = f.get_glyph_collection_index ((uint32) index); + for (i = 0; i < item_list_length && index < font_length; i++) { + glyphs = font.get_glyph_collection_index ((uint32) index); return_if_fail (glyphs != null); glyph = ((!) glyphs).get_current (); @@ -557,15 +566,19 @@ item.set_character (character); item.set_glyphs (glyphs); item.generate_graphics (); - item.x = x; - item.y = y; - - if (glyphs != null) { - item.selected = (selected_items.index_of ((!) glyphs) != -1); - } + item.selected = (selected_items.index_of ((!) glyphs) != -1); visible_items.add (item); index++; + } + + if (i < item_list_length && !font.has_glyph (".notdef")) { + item = new OverViewItem (); + item.set_glyphs (font.get_not_def_character ()); + item.generate_graphics (false); + item.version_menu = null; + item.selected = (selected_items.index_of ((!) glyphs) != -1); + visible_items.add (item); } } else { uint32 glyph_range_size = glyph_range.get_length (); @@ -593,7 +606,7 @@ visible_size = visible_items.size; for (int i = 0; i < visible_size; i++) { item = visible_items.get (i); - glyphs = f.get_glyph_collection_by_name ((!) item.character.to_string ()); + glyphs = font.get_glyph_collection_by_name ((!) item.character.to_string ()); item.set_glyphs (glyphs); if (glyphs != null) { @@ -669,9 +682,8 @@ void draw_empty_canvas (WidgetAllocation allocation, Context cr) { Text t; - cr.save (); - t = new Text (t_("No glyphs in this view."), 24); + t = new Text ("No glyphs in this view.", 24); Theme.text_color (t, "Text Foreground"); t.widget_x = 40; t.widget_y = 30; @@ -1315,7 +1327,8 @@ if (is_null(i.version_menu)) { update = true; } else { - update = !i.version_menu.menu_visible; + VersionList v = (!) i.version_menu; + update = !v.menu_visible; } } index++;
--- a/libbirdfont/OverViewItem.vala +++ b/libbirdfont/OverViewItem.vala @@ -42,7 +42,7 @@ public static double glyph_scale = 1.0; - public VersionList version_menu; + public VersionList? version_menu = null; Text label; private Surface? cache = null; @@ -63,19 +63,22 @@ glyphs = gc; } - public void generate_graphics () { - if (glyphs != null) { - version_menu = new VersionList ((!) glyphs); - version_menu.add_glyph_item.connect ((glyph) => { + public void generate_graphics (bool generate_versions = true) { + if (glyphs != null && generate_versions) { + VersionList versions = new VersionList ((!) glyphs); + + versions.add_glyph_item.connect ((glyph) => { ((!) glyphs).insert_glyph (glyph, true); }); - version_menu.signal_delete_item.connect ((glyph_index) => { + versions.signal_delete_item.connect ((glyph_index) => { OverView v = MainWindow.get_overview (); version_menu = new VersionList ((!) glyphs); v.update_item_list (); GlyphCanvas.redraw (); }); + + version_menu = versions; } info = new CharacterInfo (character, glyphs); @@ -96,7 +99,6 @@ } Glyph g; - Font font; double gx, gy; double x1, x2, y1, y2; double scale_box; @@ -104,7 +106,6 @@ double glyph_width, glyph_height; Surface s; Context c; - Color color = Color.black (); g = ((!) glyphs).get_current (); @@ -124,31 +125,18 @@ c.save (); g.boundaries (out x1, out y1, out x2, out y2); - if (g.color_svg_data != null) { - font = BirdFont.get_current_font (); - glyph_width = g.right_limit - g.left_limit; - glyph_height = font.top_position - font.bottom_position; - } else { - glyph_width = x2 - x1; - glyph_height = y2 - y1; - } + glyph_width = x2 - x1; + glyph_height = y2 - y1; c.save (); c.scale (glyph_scale * Screen.get_scale (), glyph_scale * Screen.get_scale ()); g.add_help_lines (); - if (g.color_svg_data != null) { - gx = 10; - gy = (h / glyph_scale) - 25 / glyph_scale; - c.translate (gx - Glyph.xc () - g.get_lsb (), g.get_baseline () + gy - Glyph.yc ()); - g.draw_svg (c); - } else { - gx = ((w / glyph_scale) - glyph_width) / 2 - g.get_left_side_bearing (); - gy = (h / glyph_scale) - 25 / glyph_scale; - c.translate (gx - Glyph.xc () - g.get_lsb (), g.get_baseline () + gy - Glyph.yc ()); - g.draw_paths (c, color); - } + gx = ((w / glyph_scale) - glyph_width) / 2 - g.get_left_side_bearing (); + gy = (h / glyph_scale) - 25 / glyph_scale; + c.translate (gx - Glyph.xc () - g.get_lsb (), g.get_baseline () + gy - Glyph.yc ()); + g.draw_layers (c); c.restore (); @@ -236,16 +224,17 @@ GlyphCollection g; bool s = (x <= px <= x + width) && (y <= py <= y + height); - if (has_icons () && glyphs != null) { + if (has_icons () && glyphs != null && version_menu != null) { g = (!) glyphs; - version_menu.set_position (x + width - 21, y + height - 18); - a = version_menu.menu_item_action (px, py); // select one item on the menu + VersionList versions = (!) version_menu; + versions.set_position (x + width - 21, y + height - 18); + a = versions.menu_item_action (px, py); // select one item on the menu if (a) { return s; } - version_menu.menu_icon_action (px, py); // click in the open menu + versions.menu_icon_action (px, py); // click in the open menu } info.set_position (x + width - 17, y + height - 22.5); @@ -367,7 +356,10 @@ cc.fill (); if (has_icons ()) { - draw_menu_icon (cc, false); + if (version_menu != null) { + draw_menu_icon (cc, false); + } + draw_character_info_icon (cc); } @@ -464,7 +456,8 @@ public void hide_menu () { if (!is_null (version_menu)) { - version_menu.menu_visible = false; + VersionList v = (!) version_menu; + v.menu_visible = false; } } @@ -484,13 +477,23 @@ } private void draw_menu (Context cr) { - if (glyphs == null || !version_menu.menu_visible) { + if (glyphs == null) { + return; + } + + if (version_menu == null) { + return; + } + + VersionList v = (!) version_menu; + + if (!v.menu_visible) { return; } - version_menu.draw_menu (cr); + v.draw_menu (cr); } } }
--- a/libbirdfont/OverviewTools.vala +++ b/libbirdfont/OverviewTools.vala @@ -285,6 +285,8 @@ } o.undo_items.add (ui); + + MainWindow.get_overview ().update_item_list (); GlyphCanvas.redraw (); } @@ -308,9 +310,16 @@ GlyphRange gr; uint size; OverView overview; - - // All characters - size = BirdFont.get_current_font ().length (); + Font font; + + // All characters including .notdef + font = BirdFont.get_current_font (); + size = font.length (); + + if (!font.has_glyph (".notdef")) { + size++; + } + all_glyphs.number = get_display_value (size); // Default
--- a/libbirdfont/Path.vala +++ b/libbirdfont/Path.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 2013 2014 2015 Johan Mattsson + Copyright (C) 2012 - 2016 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -14,6 +14,7 @@ using Cairo; using Math; + using SvgBird; namespace BirdFont { @@ -67,7 +68,7 @@ private double path_stroke_width = 0; - public LineCap line_cap = LineCap.BUTT; + public SvgBird.LineCap line_cap = SvgBird.LineCap.BUTT; public PathList? full_stroke = null; PathList? fast_stroke = null; StrokeTask? stroke_creator; @@ -83,14 +84,8 @@ bool clockwise_direction = true; // Iterate over each pixel in a path - public delegate bool RasterIterator (double x, double y, double step); - + public delegate bool RasterIterator (double x, double y, double step); public delegate bool SegmentIterator (EditPoint start, EditPoint stop); - - /** The stroke of an outline when the path is not filled. */ - public static double stroke_width = 0; - public static bool show_all_line_handles = true; - public static bool fill_open_path {get; set;} public double rotation = 0; public double skew = 0; @@ -99,27 +94,14 @@ public bool highlight_last_segment = false; public string point_data = ""; + + private static Text? arrow = null; public Color? color = null; public Color? stroke_color = null; - public Gradient? gradient = null; - - private static Text? arrow = null; public Path () { - string width; - - if (unlikely (stroke_width < 1)) { - width = Preferences.get ("stroke_width"); - if (width != "") { - stroke_width = double.parse (width); - } - } - - if (stroke_width < 1) { - stroke_width = 1; - } } public bool is_filled () { @@ -173,22 +155,6 @@ public bool empty () { return points.size == 0; - } - - public void draw_boundaries (Context cr) { - double x = Glyph.reverse_path_coordinate_x (xmin); - double y = Glyph.reverse_path_coordinate_y (ymin); - double x2 = Glyph.reverse_path_coordinate_x (xmax); - double y2 = Glyph.reverse_path_coordinate_y (ymax); - - cr.save (); - - Theme.color (cr, "Default Background"); - cr.set_line_width (2); - cr.rectangle (x, y, x2 - x, y2 - y); - cr.stroke (); - - cr.restore (); } public void draw_outline (Context cr) { @@ -217,7 +183,7 @@ i++; } - // close path + // closed path if (!is_open () && n != null) { if (highlight_last_segment) { cr.stroke (); @@ -242,19 +208,20 @@ } } - public void draw_edit_points (Context cr) { - if (is_editable ()) { - // control points for curvature - foreach (EditPoint e in points) { - if (show_all_line_handles || e.selected_point || e.selected_handle > 0) { - draw_edit_point_handles (e, cr); - } - } - - // control points - foreach (EditPoint e in points) { - draw_edit_point (e, cr); + public void draw_control_points (Context cr) { + // control points for curvature + foreach (EditPoint e in points) { + if (CanvasSettings.show_all_line_handles + || e.selected_point + || e.selected_handle > 0) { + + draw_edit_point_handles (e, cr); } + } + + // on curve control points + foreach (EditPoint e in points) { + draw_edit_point (e, cr); } } @@ -262,7 +229,7 @@ * Call Context.new_path (); before this method and Context.fill () * to show the path. */ - public void draw_path (Context cr, Glyph glyph, Color? color = null) { + public void draw_path (Context cr, Color? color = null) { unowned EditPoint? n = null; unowned EditPoint en; unowned EditPoint em; @@ -274,8 +241,8 @@ return; } - center_x = glyph.allocation.width / 2.0; - center_y = glyph.allocation.height / 2.0; + center_x = Glyph.xc (); + center_y = Glyph.yc (); ex = center_x + points.get (0).x; ey = center_y - points.get (0).y; @@ -308,12 +275,6 @@ } else if (color != null) { c = (!) color; cr.set_source_rgba (c.r, c.g, c.b, c.a); - } else { - if (is_clockwise ()) { - Theme.color_opacity (cr, "Selected Objects", 0.4); - } else { - Theme.color_opacity (cr, "Selected Objects", 0.8); - } } } @@ -350,12 +311,9 @@ cr.translate (x, y); double inverted_zoom = Glyph.ivz (); cr.rotate (-angle); - cr.translate (-x, -y); - + cr.translate (-x, -y); cr.scale (inverted_zoom, inverted_zoom); - arrow_icon.draw_at_baseline (cr, x, y); - cr.restore (); } } @@ -389,7 +347,6 @@ } private static void draw_curve (EditPoint e, EditPoint en, Context cr, bool highlighted = false, double alpha = 1) { - Glyph g = MainWindow.get_current_glyph (); double xa, ya, xb, yb, xc, yc, xd, yd; PointType t = e.get_right_handle ().type; PointType u = en.get_left_handle ().type; @@ -401,8 +358,6 @@ } else { Theme.color (cr, "Highlighted Guide"); } - - cr.set_line_width (stroke_width / g.view_zoom); cr.line_to (xa, ya); // this point makes sense only if it is in the first or last position @@ -469,7 +424,7 @@ get_line_points (e, en, out ax, out ay, out bx, out by); Theme.color (cr, "Handle Color"); - cr.set_line_width (1.7 * (stroke_width / g.view_zoom)); + cr.set_line_width (1.7 * (CanvasSettings.stroke_width / g.view_zoom)); cr.line_to (ax, ay); cr.line_to (bx, by); @@ -599,7 +554,7 @@ public static void draw_control_point (Context cr, double x, double y, Color color, double size = 3.5) { Glyph g = MainWindow.get_current_glyph (); double ivz = 1 / g.view_zoom; - double width = size * Math.sqrt (stroke_width) * ivz; + double width = size * Math.sqrt (CanvasSettings.stroke_width) * ivz; double xc = g.allocation.width / 2.0; double yc = g.allocation.height / 2.0; @@ -822,17 +777,6 @@ new_path.highlight_last_segment = highlight_last_segment; 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) { @@ -994,6 +938,7 @@ points.add (p); p.prev = previous_point; p.next = previous_point.next; + previous_point.next = p; } last_point = p; @@ -2265,12 +2210,6 @@ PathList lines = new PathList (); lines = pl; - - /** // FIXME: Check automatic orientation. - foreach (Path p in pl.paths) { - lines.add (SvgParser.get_lines (p)); - } - */ foreach (Path p in lines.paths) { if (p.points.size > 1 && p != path
--- a/libbirdfont/PathList.vala +++ b/libbirdfont/PathList.vala @@ -11,6 +11,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. */ + + using SvgBird; namespace BirdFont { @@ -77,8 +79,22 @@ } return pl; + } + + public void apply_style (SvgStyle style) { + foreach (Path p in paths) { + if (style.has_stroke ()) { + p.stroke = style.get_stroke_width (); + } else { + p.stroke = 0; + } + + p.line_cap = style.get_line_cap (); + p.reset_stroke (); + p.update_region_boundaries (); + } } } }
diff --git libbirdfont/PathObject.vala(new)
--- /dev/null +++ b/libbirdfont/PathObject.vala @@ -1,1 +1,155 @@ + /* + Copyright (C) 2015 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Cairo; + using SvgBird; + + namespace BirdFont { + + public class PathObject : SvgBird.Object { + + public Path path; + + public override double stroke { + get { + return path.stroke; + } + + set { + path.stroke = value; + } + } + + + // FIXME: flip y axis + public override double xmin { + get { + return path.xmin; + } + + set { + path.xmin = value; + } + + default = Glyph.CANVAS_MAX; + } + + public override double xmax { + get { + return path.xmax; + } + + set { + path.xmax = value; + } + + default = Glyph.CANVAS_MIN; + } + + public override double ymin { + get { + return path.ymin; + } + + set { + path.ymin = value; + } + + default = Glyph.CANVAS_MAX; + } + + public override double ymax { + get { + return path.ymax; + } + + set { + path.ymax = value; + } + + default = Glyph.CANVAS_MIN; + } + + public PathObject () { + base (); + path = new Path (); + update_region_boundaries (); + } + + public PathObject.create_copy (PathObject p) { + base.create_copy (p); + path = p.path.copy (); + } + + public PathObject.for_path (Path path) { + base (); + this.path = path; + } + + public override bool is_over (double x, double y) { + return path.is_over_coordinate (x, y); + } + + public override void draw_outline (Context cr) { + // drawing is handled in Glyph.draw_bird_font_paths + } + + public void draw_path (Context cr) { + path.draw_path (cr); + } + + public static void draw_path_list (PathList pl, Context cr) { + foreach (Path p in pl.paths) { + p.draw_path (cr); + } + } + + public override void move (double dx, double dy) { + path.move (dx, dy); + path.reset_stroke (); + } + + public Path get_path () { + return path; + } + + public override void update_region_boundaries () { + path.update_region_boundaries (); + } + + public override void rotate (double theta, double xc, double yc) { + path.rotate (theta, xc, yc); + } + + public override bool is_empty () { + return path.points.size == 0; + } + + public override void resize (double ratio_x, double ratio_y) { + path.resize (ratio_x, ratio_y); + path.reset_stroke (); + } + + public override SvgBird.Object copy () { + return new PathObject.create_copy (this); + } + + public override string to_string () { + return "PathObject"; + } + + } + + }
--- a/libbirdfont/PenTool.vala +++ b/libbirdfont/PenTool.vala @@ -765,8 +765,11 @@ p.path.reset_stroke (); } - foreach (Path path in g.active_paths) { - path.reset_stroke (); + foreach (Object path in g.active_paths) { + if (path is PathObject) { + PathObject p = (PathObject) path; + p.get_path ().reset_stroke (); + } } } @@ -1055,10 +1058,11 @@ // don't use set point to reflective to on open ends reflective = true; - foreach (Path path in MainWindow.get_current_glyph ().active_paths) { - if (path.is_open () && path.points.size > 0) { - if (selected_handle.parent == path.get_first_point () - || selected_handle.parent == path.get_last_point ()) { + foreach (SvgBird.Object path in MainWindow.get_current_glyph ().active_paths) { + if (path.is_open () && !path.is_empty () && path is PathObject) { + Path p = ((PathObject) path).get_path (); + if (selected_handle.parent == p.get_first_point () + || selected_handle.parent == p.get_last_point ()) { reflective = false; } } @@ -1229,7 +1233,10 @@ if (active_edit_point != null) { glyph.clear_active_paths (); - glyph.add_active_path (null, active_path); + + PathObject path = new PathObject.for_path (active_path); + glyph.add_active_object (null, path); + DrawingTools.update_stroke_settings (); if (KeyBindings.modifier != SHIFT) { @@ -1408,8 +1415,10 @@ foreach (Path merge in paths) { if (merge.points.size > 0) { - if (is_close_to_point (merge.points.get (merge.points.size - 1), px, py)) { - glyph.add_active_path (null, merge); + PathObject merged_path = new PathObject.for_path (merge); + + if (is_close_to_point (merge.points.get (merge.points.size - 1), px, py)) { + glyph.add_active_object (null, merged_path); active_path = merge; merge.reopen (); glyph.open_path (); @@ -1417,7 +1426,7 @@ } if (is_close_to_point (merge.points.get (0), px, py)) { - glyph.add_active_path (null, merge); + glyph.add_active_object (null, merged_path); active_path = merge; clear_directions (); merge.reopen (); @@ -1453,7 +1462,9 @@ force_direction (); glyph.clear_active_paths (); - glyph.add_active_path (null, path); + + PathObject closed_path = new PathObject.for_path (path); + glyph.add_active_object (null, closed_path); if (direction_changed) { path.reverse (); @@ -1492,11 +1503,12 @@ } else { union = merge_open_paths (path, merge); + PathObject union_path = new PathObject.for_path (union); glyph.add_path (union); glyph.delete_path (path); glyph.delete_path (merge); glyph.clear_active_paths (); - glyph.add_active_path (null, union); + glyph.add_active_object (null, union_path); union.reopen (); union.create_list (); @@ -1777,13 +1789,19 @@ active_edit_point = new_point.point; return_val_if_fail (glyph.active_paths.size > 0, new PointSelection.empty ()); - add_selected_point (selected_point, glyph.active_paths.get (glyph.active_paths.size - 1)); + SvgBird.Object object = glyph.active_paths.get (glyph.active_paths.size - 1); + + if (object is PathObject) { + Path path = ((PathObject) object).get_path (); + + add_selected_point (selected_point, path); - active_path = new_point.path; - glyph.clear_active_paths (); - glyph.add_active_path (null, new_point.path); - - move_selected = true; + active_path = new_point.path; + glyph.clear_active_paths (); + glyph.add_active_object (null, object); + + move_selected = true; + } return new_point; } @@ -1819,6 +1837,7 @@ EditPoint inserted; bool stroke = StrokeTool.add_stroke; Glyph g = MainWindow.get_current_glyph (); + PathObject path; if (g.active_paths.size == 0) { np = new Path (); @@ -1826,7 +1845,8 @@ np.stroke = stroke ? StrokeTool.stroke_width : 0; np.line_cap = StrokeTool.line_cap; - g.add_active_path (null, np); + path = new PathObject.for_path (np); + g.add_active_object (null, path); active_path = np; selected_path = np; @@ -1852,7 +1872,8 @@ } g.clear_active_paths (); - g.add_active_path (null, np); + path = new PathObject.for_path (np); + g.add_active_object (null, path); active_path = np; selected_path = np; @@ -1903,7 +1924,7 @@ Glyph g = MainWindow.get_current_glyph (); double distance_to_edit_point = g.view_zoom * get_distance_to_closest_edit_point (event_x, event_y); - if (!Path.show_all_line_handles) { + if (!CanvasSettings.show_all_line_handles) { foreach (PointSelection selected_corner in selected_points) { if (is_close_to_handle (selected_corner.point, event_x, event_y, distance_to_edit_point)) { return true; @@ -1973,7 +1994,7 @@ foreach (Path p in g.get_paths_in_current_layer ()) { foreach (EditPoint ep in p.points) { - if (ep.is_selected () || Path.show_all_line_handles) { + if (ep.is_selected () || CanvasSettings.show_all_line_handles) { left = ep.get_left_handle (); right = ep.get_right_handle (); @@ -2062,7 +2083,8 @@ selected_handle.selected = true; active_path = p.path; - g.add_active_path (null, active_path); + PathObject path = new PathObject.for_path (active_path); + g.add_active_object (null, path); } public static void add_selected_point (EditPoint p, Path path) {
--- a/libbirdfont/QuestionDialog.vala +++ b/libbirdfont/QuestionDialog.vala @@ -28,12 +28,12 @@ double height = 0; public QuestionDialog (string message) { - question = new TextArea (font_size); + Color color = Theme.get_color ("Text Tool Box"); + question = new TextArea (font_size, color); question.min_width = 300; question.min_height = font_size; question.set_editable (false); question.draw_border = false; - question.text_color = Theme.get_color ("Text Tool Box"); question.set_text (message); buttons = new Gee.ArrayList<Button> (); }
--- a/libbirdfont/Renderer/CachedFont.vala +++ /dev/null @@ -1,100 +1,1 @@ - /* - Copyright (C) 2015 Johan Mattsson - - This library is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as - published by the Free Software Foundation; either version 3 of the - License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - */ - - using Gee; - - namespace BirdFont { - - public class CachedFont : GLib.Object { - public Font? font; - - public double top_limit { - get { return _top_limit; } - set { _top_limit = value; } - } - - public double bottom_limit { - get { return _bottom_limit; } - set { _bottom_limit = value; } - } - - public double base_line = 0; - double _top_limit = 92.77; // FIXME: load before first glyph - double _bottom_limit = -24.4; - - static FallbackFont fallback_font { - get { - if (_fallback_font == null) { - _fallback_font = new FallbackFont (); - } - - return (!) _fallback_font; - } - } - static FallbackFont? _fallback_font = null; - - public CachedFont (Font? font) { - Glyph? g; - Glyph glyph; - - this.font = font; - - g = get_glyph_by_name ("a"); - if (g != null) { - glyph = (!) g; - base_line = glyph.baseline; - top_limit = glyph.top_limit; - bottom_limit = glyph.bottom_limit; - } else { - warning("No default chararacter found in font."); - } - } - - public Glyph? get_glyph_by_name (string name) { - Glyph? g = null; - Font f; - Glyph glyph; - - if (font != null) { - f = (!) font; - g = f.get_glyph_by_name (name); - - if (g != null) { - glyph = (!) g; - glyph.top_limit = f.top_limit; - glyph.baseline = f.base_line; - glyph.bottom_limit = f.bottom_limit; - } - } - - if (g == null && name.char_count () == 1) { - f = fallback_font.get_single_glyph_font (name.get_char (0)); - g = f.get_glyph_by_name (name); - - if (g == null) { - return null; - } - - glyph = (!) g; - glyph.top_limit = f.top_limit; - glyph.baseline = f.base_line; - glyph.bottom_limit = f.bottom_limit; - } - - return g; - } - } - - }
diff --git libbirdfont/Renderer/Drawing.vala(deleted)
--- a/libbirdfont/Renderer/Drawing.vala +++ /dev/null @@ -1,51 +1,1 @@ - /* - Copyright (C) 2014 Johan Mattsson - - This library is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as - published by the Free Software Foundation; either version 3 of the - License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - */ - - namespace BirdFont { - - /** Interface for creating drawing callbacks. */ - [Compact] - [CCode (ref_function = "bird_font_drawing_ref", unref_function = "bird_font_drawing_unref")] - public class Drawing { - - public int iterator_refcount = 1; - - public Drawing () { - } - - public void new_path (double x, double y) { - } - - public void curve_to (double xb, double yb, double xc, double yc, double xd, double yd) { - } - - public void close_path (double x, double y) { - } - - public unowned Drawing @ref () { - iterator_refcount++; - return this; - } - - public void unref () { - if (--iterator_refcount == 0) { - this.free (); - } - } - - private extern void free (); - } - - }
--- a/libbirdfont/Renderer/FallbackFont.vala +++ /dev/null @@ -1,338 +1,1 @@ - /* - Copyright (C) 2015 Johan Mattsson - - This library is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as - published by the Free Software Foundation; either version 3 of the - License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - */ - - using Gee; - - [SimpleType] - [CCode (has_type_id = false)] - public extern struct FcConfig { - } - - [CCode (cname = "FcInitLoadConfigAndFonts")] - public extern FcConfig* FcInitLoadConfigAndFonts (); - - [CCode (cname = "FcConfigAppFontAddDir")] - public extern string* FcConfigAppFontAddDir (FcConfig* config, string path); - - [CCode (cname = "FcConfigSetSysRoot")] - public extern void FcConfigSetSysRoot (FcConfig* config, string path); - - [CCode (cname = "FcConfigParseAndLoad")] - public extern bool FcConfigParseAndLoad (FcConfig* config, string path, bool complain); - - [CCode (cname = "FcConfigSetCurrent")] - public extern void FcConfigSetCurrent (FcConfig* config); - - [CCode (cname = "FcConfigCreate")] - public extern FcConfig* FcConfigCreate (); - - [CCode (cname = "FcConfigFilename")] - public extern string FcConfigFilename (string path); - - [CCode (cname = "find_font")] - public extern string? find_font (FcConfig* font_config, string characters); - - [CCode (cname = "find_font_family")] - public extern string? find_font_family (FcConfig* font_config, string characters); - - [CCode (cname = "find_font_file")] - public extern string? find_font_file (FcConfig* font_config, string font_name); - - namespace BirdFont { - - // TODO: use font config - public class FallbackFont : GLib.Object { - Gee.ArrayList<File> font_directories; - - FontFace* default_font = null; - public static FcConfig* font_config = null; - static bool font_config_started = false; - - string default_font_file_name = "Roboto-Regular.ttf"; - string default_font_family_name = "Roboto"; - - Gee.HashMap<unichar, CachePair> glyphs; - Gee.ArrayList<CachePair> cached; - - public int max_cached_fonts = 300; - - string? default_font_file = null; - - public FallbackFont () { - string home = Environment.get_home_dir (); - font_directories = new Gee.ArrayList<File> (); - - if (!font_config_started) { - font_config_started = true; - - IdleSource idle = new IdleSource (); - idle.set_callback (() => { - Task t = new Task (init_font_config); - MainWindow.native_window.run_non_blocking_background_thread (t); - return false; - }); - idle.attach (null); - } - - add_font_folder ("/usr/share/fonts/"); - add_font_folder ("/usr/local/share/fonts/"); - add_font_folder (home + "/.local/share/fonts"); - add_font_folder (home + "/.fonts"); - add_font_folder ("C:\\Windows\\Fonts"); - add_font_folder (home + "/Library/Fonts"); - add_font_folder ("/Library/Fonts"); - add_font_folder ("/Network/Library/Fonts"); - add_font_folder ("/System/Library/Fonts"); - add_font_folder ("/System Folder/Fonts"); - - glyphs = new Gee.HashMap<unichar, CachePair> (); - cached = new Gee.ArrayList<CachePair> (); - - open_default_font (); - } - - ~FallbackFont () { - if (default_font != null) { - close_font (default_font); - } - } - - public void init_font_config () { - FcConfig* config; - - #if MAC - config = FcConfigCreate(); - - string bundle = (!) BirdFont.get_settings_directory ().get_path (); - FcConfigSetSysRoot(config, bundle); - - string path = FcConfigFilename((!) SearchPaths.search_file(null, "fontconfig.settings").get_path ()); - bool loaded = FcConfigParseAndLoad(config, path, true); - - if (!loaded) { - warning ("Fontconfig initialization failed."); - } - - FcConfigSetCurrent (config); - #else - config = FcInitLoadConfigAndFonts (); - #endif - - IdleSource idle = new IdleSource (); - - idle.set_callback (() => { - font_config = config; - printd("Fontconfig loaded\n"); - return false; - }); - idle.attach (null); - } - - public Font get_single_glyph_font (unichar c) { - Font f; - unichar last; - CachePair p; - - if (likely (glyphs.has_key (c))) { - p = glyphs.get (c); - - if (p.referenced < int.MAX) { - p.referenced++; - } - - return p.font; - } - - // remove glyphs from cache if it is full - if (cached.size > max_cached_fonts - 100) { - - cached.sort ((a, b) => { - CachePair pa = (CachePair) a; - CachePair pb = (CachePair) b; - return pb.referenced - pa.referenced; - }); - - int j = 0; - for (int i = cached.size - 1; i > 0; i--) { - if (j > 100) { - break; - } - - j++; - - last = cached.get (i).character; - glyphs.unset (last); - cached.remove_at (i); - } - } - - f = get_single_fallback_glyph_font (c); - p = new CachePair (f, c); - - glyphs.set (c, p); - cached.add (p); - - return (Font) f; - } - - Font get_single_fallback_glyph_font (unichar c) { - string? font_file; - BirdFontFile bf_parser; - Font bf_font; - StringBuilder? glyph_data; - FontFace* font; - - bf_font = new Font (); - font_file = null; - glyph_data = null; - - // don't use fallback font in private use area - if (0xe000 <= c <= 0xf8ff) { - return bf_font; - } - - // control characters - if (c <= 0x001f || (0x007f <= c <= 0x008d)) { - return bf_font; - } - - // check if glyph is available in roboto - if (default_font != null) { - glyph_data = get_glyph_in_font ((!) default_font, c); - } - - // use fontconfig to find a fallback font - if (glyph_data == null) { - font_file = find_font (font_config, (!) c.to_string ()); - if (font_file != null) { - font = open_font ((!) font_file); - glyph_data = get_glyph_in_font (font, c); - close_font (font); - } - } - - if (glyph_data != null) { - bf_parser = new BirdFontFile (bf_font); - bf_parser.load_data (((!) glyph_data).str); - } - - return bf_font; - } - - public StringBuilder? get_glyph_in_font (FontFace* font, unichar c) { - StringBuilder? glyph_data = null; - GlyphCollection gc; - - gc = new GlyphCollection (c, (!)c.to_string ()); - glyph_data = load_glyph (font, (uint) c); - - return glyph_data; - } - - void add_font_folder (string f) { - File folder = File.new_for_path (f); - FileInfo? file_info; - string fn; - string file_attributes; - try { - if (folder.query_exists ()) { - font_directories.add (folder); - - file_attributes = FileAttribute.STANDARD_NAME; - file_attributes += ","; - file_attributes += FileAttribute.STANDARD_TYPE; - var enumerator = folder.enumerate_children (file_attributes, 0); - - while ((file_info = enumerator.next_file ()) != null) { - fn = ((!) file_info).get_name (); - - if (((!)file_info).get_file_type () == FileType.DIRECTORY) { - add_font_folder ((!) get_child (folder, fn).get_path ()); - } - } - } - } catch (GLib.Error e) { - warning (e.message); - } - } - - File search_font_file (string font_file) { - File d, f; - - for (int i = font_directories.size - 1; i >= 0; i--) { - d = font_directories.get (i); - f = get_child (d, font_file); - - if (f.query_exists ()) { - return f; - } - } - - warning (@"The font $font_file not found"); - return File.new_for_path (font_file); - } - - public string? get_default_font_file () { - File font_file; - string? fn = null; - - if (likely (default_font_file != null)) { - return default_font_file; - } - - font_file = SearchPaths.search_file (null, default_font_file_name); - - if (font_file.query_exists ()) { - fn = (!) font_file.get_path (); - } else { - font_file = search_font_file (default_font_file_name); - - if (font_file.query_exists ()) { - fn = (!) font_file.get_path (); - } else { - fn = find_font_file (font_config, default_font_family_name); - } - } - - if (likely (fn != null)) { - default_font_file = fn; - return fn; - } - - warning(default_font_family_name + " not found"); - return null; - } - - void open_default_font () { - string? fn = get_default_font_file (); - - if (fn != null) { - default_font = open_font ((!) fn); - } - } - - class CachePair : GLib.Object { - public Font font; - public unichar character; - public int referenced = 1; - - public CachePair (Font f, unichar c) { - font = f; - character = c; - } - } - } - - }
--- a/libbirdfont/Renderer/FontCache.vala +++ /dev/null @@ -1,84 +1,1 @@ - /* - Copyright (C) 2014 Johan Mattsson - - This library is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as - published by the Free Software Foundation; either version 3 of the - License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - */ - - using Gee; - - namespace BirdFont { - - /** Thread specific font cache. */ - public class FontCache { - public static FallbackFont fallback_font; - - static FontCache? default_cache = null; - Gee.HashMap<string, CachedFont> fonts; - CachedFont fallback; - - public FontCache () { - if (is_null (fallback_font)) { - fallback_font = new FallbackFont (); - } - - fallback = new CachedFont (null); - fonts = new Gee.HashMap<string, CachedFont> (); - } - - public CachedFont get_font (string file_name) { - CachedFont c; - Font f; - bool ok; - - if (file_name == "") { - return fallback; - } - - if (fonts.has_key (file_name)) { - c = fonts.get (file_name); - return c; - } - - f = new Font (); - f.set_file (file_name); - ok = f.load (); - if (!ok) { - stderr.printf ("Can't load %s\n", file_name); - return new CachedFont (null); - } - - c = new CachedFont (f); - - if (file_name == "") { - warning ("No file."); - return c; - } - - fonts.set (file_name, c); - return c; - } - - public static FontCache get_default_cache () { - if (default_cache == null) { - default_cache = new FontCache (); - } - - return (!) default_cache; - } - - public CachedFont get_fallback () { - return fallback; - } - - } - - }
--- a/libbirdfont/Renderer/LineTextArea.vala +++ /dev/null @@ -1,34 +1,1 @@ - /* - Copyright (C) 2014 Johan Mattsson - - This library is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as - published by the Free Software Foundation; either version 3 of the - License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - */ - - using Cairo; - using Math; - - namespace BirdFont { - - public class LineTextArea : TextArea { - - public LineTextArea (double size) { - base (size); - - single_line = true; - min_height = size; - height = min_height; - - layout (); - } - } - - }
diff --git libbirdfont/Renderer/Text.vala(deleted)
--- a/libbirdfont/Renderer/Text.vala +++ /dev/null @@ -1,585 +1,1 @@ - /* - Copyright (C) 2014 2015 Johan Mattsson - - This library is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as - published by the Free Software Foundation; either version 3 of the - License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - */ - - using Cairo; - using Math; - - namespace BirdFont { - - /** Test implementation of a birdfont rendering engine. */ - public class Text : Widget { - FontCache font_cache; - public CachedFont cached_font; - - Surface? cache = null; - - public string text; - - private bool use_cache = true; - - GlyphSequence glyph_sequence { - get { - if (gs == null) { - gs = generate_glyphs (); - } - - return (!) gs; - } - } - - Gee.ArrayList<string> glyph_names; - GlyphSequence? gs = null; - - public delegate void Iterator (Glyph glyph, double kerning, bool last); - public double font_size; - double sidebearing_extent = 0; - - public double r = 0; - public double g = 0; - public double b = 0; - public double a = 1; - double truncated_width = -1; - - double margin_left = 0; - - public Text (string text = "", double size = 17, double margin_bottom = 0) { - this.margin_bottom = margin_bottom; - font_cache = FontCache.get_default_cache (); - cached_font = font_cache.get_fallback (); - - set_text (text); - set_font_size (size); - } - - public void set_use_cache (bool cache) { - use_cache = cache; - } - - public string get_text () { - return text; - } - - /** Set font for this text area. - * @param font_absolute path to the font file or a file name for one of the font files in search paths. - * @return true if the font was found - */ - public bool load_font (string font_file) { - File path; - File f; - FontCache fc; - - f = File.new_for_path (font_file); - path = (f.query_exists ()) ? f : SearchPaths.find_file (null, font_file); - - fc = FontCache.get_default_cache (); - cached_font = fc.get_font ((!) path.get_path ()); - gs = generate_glyphs (); - - return cached_font.font != null; - } - - public void set_font_size (double height_in_pixels) { - font_size = height_in_pixels; - sidebearing_extent = 0; - - if (gs == null) { // ensure height is loaded for the font - gs = generate_glyphs (); - } - } - - public void set_font_cache (FontCache font_cache) { - this.font_cache = font_cache; - } - - public void set_text (string text) { - this.text = text; - gs = null; - sidebearing_extent = 0; - cache = null; - } - - private GlyphSequence generate_glyphs () { - int index; - unichar c; - string name; - Glyph? g; - GlyphSequence gs; - - gs = new GlyphSequence (); - - glyph_names = new Gee.ArrayList<string> (); - index = 0; - while (text.get_next_char (ref index, out c)) { - name = (!) c.to_string (); - g = cached_font.get_glyph_by_name (name); - - gs.glyph.add (g); - glyph_names.add (name); - } - - return gs; - } - - public void iterate (Iterator iter) { - Glyph glyph; - double w, kern; - int wi; - Glyph? prev; - Glyph? g; - GlyphSequence word_with_ligatures; - GlyphRange? gr_left, gr_right; - GlyphSequence word; - KerningClasses kc; - Font empty = Font.empty; - - glyph = new Glyph.no_lines ("", '\0'); - - w = 0; - prev = null; - kern = 0; - - word = glyph_sequence; - wi = 0; - - if (cached_font.font != null) { - word_with_ligatures = word.process_ligatures ((!) cached_font.font); - } else { - word_with_ligatures = word.process_ligatures (new Font ()); - } - - gr_left = null; - gr_right = null; - - if (cached_font.font != null) { - kc = ((!) cached_font.font).get_kerning_classes (); - } else { - kc = new KerningClasses (empty); - } - - if (word_with_ligatures.glyph.size > 0) { - g = word_with_ligatures.glyph.get (0); - if (g != null) { - margin_left = ((!) g).get_left_side_bearing (); - - if (margin_left < 0) { - margin_left = -margin_left; - } else { - margin_left = 0; - } - } - } - - for (int i = 0; i < word_with_ligatures.glyph.size; i++) { - g = word_with_ligatures.glyph.get (i); - - if (g == null || prev == null || wi == 0) { - kern = 0; - } else { - return_if_fail (wi < word_with_ligatures.ranges.size); - return_if_fail (wi - 1 >= 0); - - gr_left = word_with_ligatures.ranges.get (wi - 1); - gr_right = word_with_ligatures.ranges.get (wi); - - kern = kc.get_kerning_for_pair (((!) prev).get_name (), ((!) g).get_name (), gr_left, gr_right); - } - - // process glyph - if (g == null && (0 <= i < glyph_names.size)) { - g = cached_font.get_glyph_by_name (glyph_names.get (i)); - } - - glyph = (g == null) ? new Glyph ("") : (!) g; - iter (glyph, kern, i + 1 == word_with_ligatures.glyph.size); - prev = g; - wi++; - } - } - - // FIXME: some fonts doesn't have on curve extrema - public double get_extent () { - double x = 0; - - iterate ((glyph, kerning, last) => { - double x1, y1, x2, y2; - double lsb; - - lsb = glyph.left_limit; - - if (!last) { - x += (glyph.get_width () + kerning) * get_scale (glyph); - } else { - glyph.boundaries (out x1, out y1, out x2, out y2); - x += (x2 - lsb) * get_scale (glyph); - } - }); - - return x; - } - - public double get_sidebearing_extent () { - double x ; - - if (likely (sidebearing_extent > 0)) { - return sidebearing_extent; - } - - x = 0; - - iterate ((glyph, kerning, last) => { - double lsb; - lsb = glyph.left_limit; - x += (glyph.get_width () + kerning) * get_scale (glyph); - }); - - sidebearing_extent = x; - return x; - } - - public override double get_height () { - return font_size; - } - - public double get_acender () { - double max_height = 0; - - iterate ((glyph, kerning, last) => { - double x1, y1, x2, y2; - double h; - glyph.boundaries (out x1, out y1, out x2, out y2); - h = Math.fmax (y1, y2) - Math.fmin (y1, y2); - h *= get_scale (glyph) - glyph.baseline * get_scale (glyph); - if (h > max_height) { - max_height = h; - } - }); - - return max_height; - } - - public override double get_width () { - double x = 0; - bool first = true; - - iterate ((glyph, kerning, last) => { - double x1, y1, x2, y2; - double lsb; - - lsb = glyph.left_limit; - - if (first) { - glyph.boundaries (out x1, out y1, out x2, out y2); - x += (glyph.get_width () + kerning - Math.fmin (x1, x2)) * get_scale (glyph); - first = false; - } else if (!last) { - x += (glyph.get_width () + kerning) * get_scale (glyph); - } else { - glyph.boundaries (out x1, out y1, out x2, out y2); - x += (x2 - lsb) * get_scale (glyph); - } - }); - - return x; - } - - public double get_decender () { - double decender_max = get_max_decender (); - return decender_max > 0 ? decender_max : 0; - } - - private double get_max_decender () { - double decender = 0; - double decender_max = 0; - - iterate ((glyph, kerning, last) => { - double x1, y1, x2, y2; - double y; - glyph.boundaries (out x1, out y1, out x2, out y2); - y = Math.fmin (y1, y2); - decender = (glyph.baseline - y) * get_scale (glyph); - if (decender > decender_max) { - decender_max = decender; - } - }); - - return decender_max; - } - - public override void draw (Context cr) { - double descender = cached_font.bottom_limit + cached_font.base_line; - double y = widget_y + get_height () + get_font_scale () * descender; // FIXME: - draw_at_baseline (cr, widget_x, y); - } - - public void draw_at_top (Context cr, double px, double py, string cacheid = "") { - double s = get_font_scale (); - double y = py + s * (cached_font.top_limit - cached_font.base_line); - draw_at_baseline (cr, px, y, cacheid); - } - - public void set_source_rgba (double r, double g, double b, double a) { - if (this.r != r || - this.g != g || - this.b != b || - this.a != a) { - - this.r = r; - this.g = g; - this.b = b; - this.a = a; - cache = null; - } - } - - public string get_cache_id (int offset_x, int offset_y) { - string key; - int64 c; - - c = (((int64) (r * 255)) << 24) - | (((int64) (g * 255)) << 16) - | (((int64) (b * 255)) << 8) - | (((int64) (a * 255)) << 0); - - // FIXME: use binary key - key = @"$font_size $c $offset_x $offset_y"; - - return key; - } - - public void draw_at_baseline (Context cr, double px, double py, string cacheid = "") { - if (cache == null) { - cache = draw_on_cache_surface (cacheid); - } - - double screen_scale = Screen.get_scale (); - double font_scale = get_font_scale (); - double cache_y = py - font_scale * (cached_font.top_limit - cached_font.base_line); - - cr.save(); - cr.scale (1 / screen_scale, 1 / screen_scale); - double scaled_x = (px - margin_left) * screen_scale; - double scaled_y = cache_y * screen_scale; - cr.set_source_surface ((!) cache, (int) rint (scaled_x), (int) rint (scaled_y)); - cr.paint (); - cr.restore(); - } - - Surface draw_on_cache_surface (string cacheid) { - double y; - double ratio; - double cc_y; - Context cr; - Surface cache_surface; - double screen_scale = Screen.get_scale(); - double h = font_size * screen_scale + 1; - - ratio = get_font_scale (); - cc_y = (cached_font.top_limit - cached_font.base_line) * ratio; - - // double x = margin_left * ratio; - double x = 0; - double w = get_sidebearing_extent () * screen_scale + x + margin_left + 1; - - cache_surface = Screen.create_background_surface ((int) w, (int) h); - cr = new Context (cache_surface); - cr.scale (screen_scale, screen_scale); - - y = cc_y; - - if (unlikely (cached_font.base_line != 0)) { - warning ("Base line not zero."); - } - - iterate ((glyph, kerning, last) => { - double end; - - x += kerning * ratio; - end = x + glyph.get_width () * ratio; - - // truncation - if (truncated_width > 0 && end > truncated_width) { - return; - } - - if (use_cache) { - draw_chached (cr, glyph, kerning, last, x, y, cc_y, - ratio, cacheid); - } else { - draw_without_cache (cr, glyph, kerning, last, x, y, cc_y, ratio); - } - - x = end; - }); - - return cache_surface; - } - - void draw_without_cache (Context cr, Glyph glyph, double kerning, bool last, - double x, double y, double cc_y, double ratio) { - - double lsb; - - cr.save (); - cr.set_source_rgba (r, g, b, a); - cr.new_path (); - - lsb = glyph.left_limit; - - foreach (Path path in glyph.get_visible_paths ()) { - draw_path (cr, glyph, path, lsb, x, y, ratio); - } - - cr.fill (); - cr.restore (); - - } - - void draw_chached (Context cr, Glyph glyph, double kerning, bool last, - double x, double y, double cc_y, double ratio, - string cacheid = "") { - - double lsb; - Surface cache; - Surface cached_glyph; - Context cc; - string cache_id; - double glyph_margin_left = glyph.get_left_side_bearing (); - - if (glyph_margin_left < 0) { - glyph_margin_left = -glyph_margin_left; - } else { - glyph_margin_left = 0; - } - - double xp = (x - glyph_margin_left * ratio) * Screen.get_scale (); - double yp = (y - cc_y) * Screen.get_scale (); - int offset_x, offset_y; - - offset_x = (int) (10 * (xp - (int) xp)); - offset_y = (int) (10 * (yp - (int) yp)); - - cache_id = (cacheid == "") ? get_cache_id (offset_x, offset_y) : cacheid; - - if (!glyph.has_cache (cache_id)) { - int w = (int) ((2 * glyph_margin_left * ratio + glyph.get_width ()) * ratio) + 2; - int h = (int) font_size + 2; - cache = Screen.create_background_surface (w, h); - cc = new Context (cache); - - cc.scale(Screen.get_scale (), Screen.get_scale ()); - - lsb = glyph.left_limit - glyph_margin_left; - - cc.save (); - cc.set_source_rgba (r, g, b, a); - cc.new_path (); - - foreach (Path path in glyph.get_visible_paths ()) { - draw_path (cc, glyph, path, lsb, glyph_margin_left * ratio + offset_x / 10.0, cc_y + offset_y / 10.0, ratio); - } - - cc.fill (); - cc.restore (); - - if (use_cache) { - glyph.set_cache (cache_id, cache); - } - - cached_glyph = cache; - } else { - cached_glyph = glyph.get_cache (cache_id); - } - - cr.save (); - cr.set_antialias (Cairo.Antialias.NONE); - cr.scale(1 / Screen.get_scale (), 1 / Screen.get_scale ()); - cr.set_source_surface (cached_glyph, - (int) xp, - (int) yp); - cr.paint (); - cr.restore (); - } - - void draw_path (Context cr, Glyph glyph, Path path, - double lsb, double x, double y, double scale) { - - EditPoint e, prev; - double xa, ya, xb, yb, xc, yc, xd, yd; - double by; - double s = get_scale (glyph); - - if (path.points.size > 0) { - if (unlikely (path.is_open ())) { - warning (@"Path is open in $(glyph.get_name ())."); - } - - //path.add_hidden_double_points (); // FIXME: this distorts shapes - - prev = path.points.get (path.points.size - 1); - xa = (prev.x - lsb) * s + x; - ya = y - prev.y * s; - cr.move_to (xa, ya); - - by = (y - cached_font.base_line * s); - for (int i = 0; i < path.points.size; i++) { - e = path.points.get (i).copy (); - - PenTool.convert_point_segment_type (prev, e, PointType.CUBIC); - - xb = (prev.get_right_handle ().x - lsb) * s + x; - yb = by - prev.get_right_handle ().y * s; - - xc = (e.get_left_handle ().x - lsb) * s + x; - yc = by - e.get_left_handle ().y * s; - - xd = (e.x - lsb) * s + x; - yd = by - e.y * s; - - cr.curve_to (xb, yb, xc, yc, xd, yd); - cr.line_to (xd, yd); - - prev = e; - } - } - } - - public double get_baseline_to_bottom (Glyph g) { - return get_scale (g) * (-g.baseline - g.bottom_limit); - } - - public double get_scale (Glyph g) { - double s = g.top_limit - g.bottom_limit; - - if (s == 0) { - s = cached_font.top_limit - cached_font.bottom_limit; - } - - return font_size / s; - } - - public double get_font_scale () { - return font_size / (cached_font.top_limit - cached_font.bottom_limit); - } - - public double get_baseline_to_bottom_for_font () { - return get_font_scale () * (-cached_font.base_line - cached_font.bottom_limit); - } - - public void truncate (double max_width) { - truncated_width = max_width; - } - } - - }
--- a/libbirdfont/Renderer/TextArea.vala +++ /dev/null @@ -1,1666 +1,1 @@ - /* - Copyright (C) 2014 Johan Mattsson - - This library is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as - published by the Free Software Foundation; either version 3 of the - License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - */ - - using Cairo; - using Math; - - namespace BirdFont { - - public class TextArea : Widget { - - public double min_width = 500; - public double min_height = 100; - public double font_size; - public double padding = 3.3; - public bool single_line = false; - public Color text_color = Color.black (); - - public bool draw_carret { - get { return carret_is_visible; } - set { - carret_is_visible = value; - if (!value) { - update_selection = false; - selection_end = carret.copy (); - } - } - } - public bool carret_is_visible = false; - public bool draw_border = true; - - public double width; - public double height; - - Carret carret = new Carret (); - Carret selection_end = new Carret (); - bool update_selection = false; - public bool show_selection = false; - - public signal void scroll (double pixels); - public signal void text_changed (string text); - public signal void enter (string text); - - Gee.ArrayList<Paragraph> paragraphs = new Gee.ArrayList<Paragraph> (); - private static const int DONE = -2; - - int last_paragraph = 0; - string text; - int text_length; - - Gee.ArrayList<TextUndoItem> undo_items = new Gee.ArrayList<TextUndoItem> (); - Gee.ArrayList<TextUndoItem> redo_items = new Gee.ArrayList<TextUndoItem> (); - - bool store_undo_state_at_next_event = false; - - public bool editable; - - public TextArea (double font_size = 20, Color? c = null) { - this.font_size = font_size; - width = min_width; - height = min_height; - editable = true; - - if (c != null) { - text_color = (!) c; - } - } - - public override void focus (bool focus) { - draw_carret = focus; - } - - public override double get_height () { - return height + 2 * padding; - } - - public override double get_width () { - return width + 2 * padding; - } - - public void set_font_size (double z) { - font_size = z; - } - - bool generate_paragraphs () { - Paragraph paragraph; - - int next_paragraph = -1; - - if (is_null (text)) { - warning ("No text"); - return false; - } - - if (last_paragraph == DONE) { - return false; - } - - next_paragraph = text.index_of ("\n", last_paragraph); - - if (next_paragraph == -1) { - paragraph = new Paragraph (text.substring (last_paragraph), font_size, paragraphs.size, text_color); - paragraphs.add (paragraph); - last_paragraph = DONE; - } else { - next_paragraph += "\n".length; - paragraph = new Paragraph (text.substring (last_paragraph, next_paragraph - last_paragraph), font_size, paragraphs.size, text_color); - paragraphs.add (paragraph); - last_paragraph = next_paragraph; - } - - return last_paragraph != DONE; - } - - void generate_all_paragraphs () { - while (generate_paragraphs ()) { - } - } - - public override void key_press (uint keyval) { - unichar c; - TextUndoItem ui; - - if (!editable) { - return; - } - - c = (unichar) keyval; - - switch (c) { - case ' ': - store_undo_edit_state (); - add_character (keyval); - break; - case 'a': - if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { - select_all (); - } else { - add_character (keyval); - } - break; - case 'c': - if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { - ClipTool.copy_text (this); - } else { - add_character (keyval); - } - break; - case 'v': - if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { - ClipTool.paste_text (this); - store_undo_state_at_next_event = true; - } else { - add_character (keyval); - } - break; - case 'y': - if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { - redo (); - } else { - add_character (keyval); - } - break; - case 'z': - if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { - undo (); - } else { - add_character (keyval); - } - break; - case Key.RIGHT: - check_selection (); - move_carret_next (); - break; - case Key.LEFT: - check_selection (); - move_carret_previous (); - break; - case Key.DOWN: - check_selection (); - move_carret_next_row (); - break; - case Key.UP: - check_selection (); - move_carret_previous_row (); - break; - case Key.END: - check_selection (); - move_carret_to_end_of_line (); - break; - case Key.HOME: - check_selection (); - move_carret_to_beginning_of_line (); - break; - case Key.BACK_SPACE: - if (has_selection ()) { - ui = delete_selected_text (); - undo_items.add (ui); - redo_items.clear (); - store_undo_state_at_next_event = true; - } else { - ui = remove_last_character (); - undo_items.add (ui); - redo_items.clear (); - store_undo_state_at_next_event = true; - } - text_changed (get_text ()); - break; - case Key.ENTER: - store_undo_edit_state (); - insert_text ("\n"); - - if (single_line) { - enter (get_text ()); - } - break; - case Key.DEL: - if (has_selection ()) { - ui = delete_selected_text (); - undo_items.add (ui); - redo_items.clear (); - store_undo_state_at_next_event = true; - } else { - ui = remove_next_character (); - undo_items.add (ui); - redo_items.clear (); - store_undo_state_at_next_event = true; - } - text_changed (get_text ()); - break; - default: - if (!KeyBindings.has_ctrl () && !KeyBindings.has_logo ()) { - add_character (keyval); - } - break; - } - - GlyphCanvas.redraw (); - } - - void check_selection () { - if (!has_selection () && KeyBindings.has_shift ()) { - show_selection = true; - selection_end = carret.copy (); - } - - if (!KeyBindings.has_shift ()) { - show_selection = false; - } - } - - private void add_character (uint keyval) { - unichar c = (unichar) keyval; - string s; - - if (!is_modifier_key (keyval) - && !KeyBindings.has_ctrl () - && !KeyBindings.has_alt ()) { - - s = (!) c.to_string (); - - if (s.validate ()) { - if (store_undo_state_at_next_event) { - store_undo_edit_state (); - store_undo_state_at_next_event = false; - } - - insert_text (s); - } - } - } - - Paragraph get_current_paragraph () { - Paragraph p; - - if (unlikely (!(0 <= carret.paragraph < paragraphs.size))) { - warning (@"No paragraph, index: $(carret.paragraph), size: $(paragraphs.size)"); - p = new Paragraph ("", 0, 0, text_color); - paragraphs.add (p); - return p; - } - - p = paragraphs.get (carret.paragraph); - return p; - } - - public void set_text (string t) { - int tl; - - if (single_line) { - text = t.replace ("\n", "").replace ("\r", ""); - } else { - text = t; - } - - tl = t.length; - text_length += tl; - - paragraphs.clear (); - generate_paragraphs (); - - return_if_fail (paragraphs.size != 0); - - carret.paragraph = paragraphs.size - 1; - carret.character_index = paragraphs.get (paragraphs.size - 1).text.length; - selection_end = carret.copy (); - show_selection = false; - - text_changed (get_text ()); - } - - Carret get_selection_start () { - if (carret.paragraph == selection_end.paragraph) { - return carret.character_index < selection_end.character_index ? carret : selection_end; - } - - return carret.paragraph < selection_end.paragraph ? carret : selection_end; - } - - Carret get_selection_stop () { - if (carret.paragraph == selection_end.paragraph) { - return carret.character_index > selection_end.character_index ? carret : selection_end; - } - - return carret.paragraph > selection_end.paragraph ? carret : selection_end; - } - - public string get_selected_text () { - Carret selection_start, selection_stop; - int i; - Paragraph pg; - StringBuilder sb; - - sb = new StringBuilder (); - - if (!has_selection ()) { - return "".dup (); - } - - selection_start = get_selection_start (); - selection_stop = get_selection_stop (); - - if (selection_start.paragraph == selection_stop.paragraph) { - pg = paragraphs.get (selection_start.paragraph); - return pg.text.substring (selection_start.character_index, selection_stop.character_index - selection_start.character_index); - } - - pg = paragraphs.get (selection_start.paragraph); - sb.append (pg.text.substring (selection_start.character_index)); - - for (i = selection_start.paragraph + 1; i < selection_stop.paragraph; i++) { - return_val_if_fail (0 <= i < paragraphs.size, "".dup ()); - pg = paragraphs.get (i); - sb.append (pg.text); - } - - pg = paragraphs.get (selection_stop.paragraph); - sb.append (pg.text.substring (0, selection_stop.character_index)); - - return sb.str; - } - - public void select_all () { - while (last_paragraph != DONE) { - generate_paragraphs (); - } - - if (paragraphs.size > 0) { - carret.paragraph = 0; - carret.character_index = 0; - selection_end.paragraph = paragraphs.size - 1; - selection_end.character_index = paragraphs.get (paragraphs.size - 1).text_length; - show_selection = true; - } - } - - public TextUndoItem delete_selected_text () { - Carret selection_start, selection_stop; - int i; - Paragraph pg, pge; - string e, s, n; - bool same; - TextUndoItem ui; - - ui = new TextUndoItem (carret); - - e = ""; - s = ""; - n = ""; - - if (!has_selection ()) { - warning ("No selected text."); - return ui; - } - - selection_start = get_selection_start (); - selection_stop = get_selection_stop (); - - same = selection_start.paragraph == selection_stop.paragraph; - - if (!same) { - return_val_if_fail (0 <= selection_start.paragraph < paragraphs.size, ui); - pg = paragraphs.get (selection_start.paragraph); - s = pg.text.substring (0, selection_start.character_index); - - return_val_if_fail (0 <= selection_stop.paragraph < paragraphs.size, ui); - pge = paragraphs.get (selection_stop.paragraph); - e = pge.text.substring (selection_stop.character_index); - - if (!s.has_suffix ("\n")) { - ui.deleted.add (pge.copy ()); - ui.edited.add (pg.copy ()); - - pg.set_text (s + e); - pge.set_text (""); - } else { - ui.edited.add (pg.copy ()); - ui.edited.add (pge.copy ()); - - pg.set_text (s); - pge.set_text (e); - } - } else { - return_val_if_fail (0 <= selection_start.paragraph < paragraphs.size, ui); - - pg = paragraphs.get (selection_start.paragraph); - n = pg.text.substring (0, selection_start.character_index); - n += pg.text.substring (selection_stop.character_index); - - if (n == "") { - ui.deleted.add (pg.copy ()); - paragraphs.remove_at (selection_start.paragraph); - } else { - ui.edited.add (pg.copy ()); - } - - pg.set_text (n); - } - - if (e == "" && !same) { - paragraphs.remove_at (selection_stop.paragraph); - } - - for (i = selection_stop.paragraph - 1; i > selection_start.paragraph; i--) { - return_val_if_fail (0 <= i < paragraphs.size, ui); - ui.deleted.add (paragraphs.get (i)); - paragraphs.remove_at (i); - } - - if (s == "" && !same) { - return_val_if_fail (0 <= selection_start.paragraph < paragraphs.size, ui); - paragraphs.remove_at (selection_start.paragraph); - } - - carret = selection_start.copy (); - selection_end = carret.copy (); - - show_selection = false; - update_paragraph_index (); - layout (); - - return ui; - } - - void update_paragraph_index () { - int i = 0; - foreach (Paragraph p in paragraphs) { - p.index = i; - i++; - } - } - - public TextUndoItem remove_last_character () { - TextUndoItem ui; - move_carret_previous (); - ui = remove_next_character (); - return ui; - } - - public TextUndoItem remove_next_character () { - Paragraph paragraph; - Paragraph next_paragraph; - int index; - unichar c; - string np; - TextUndoItem ui; - - ui = new TextUndoItem (carret); - - return_val_if_fail (0 <= carret.paragraph < paragraphs.size, ui); - paragraph = paragraphs.get (carret.paragraph); - - index = carret.character_index; - - paragraph.text.get_next_char (ref index, out c); - - if (index >= paragraph.text_length) { - np = paragraph.text.substring (0, carret.character_index); - - if (carret.paragraph + 1 < paragraphs.size) { - next_paragraph = paragraphs.get (carret.paragraph + 1); - paragraphs.remove_at (carret.paragraph + 1); - - np = np + next_paragraph.text; - - ui.deleted.add (next_paragraph); - } - - paragraph.set_text (np); - ui.edited.add (paragraph); - } else { - np = paragraph.text.substring (0, carret.character_index) + paragraph.text.substring (index); - paragraph.set_text (np); - - if (np == "") { - return_val_if_fail (carret.paragraph > 0, ui); - carret.paragraph--; - paragraph = paragraphs.get (carret.paragraph); - carret.character_index = paragraph.text_length; - - ui.deleted.add (paragraphs.get (carret.paragraph + 1)); - - paragraphs.remove_at (carret.paragraph + 1); - } else { - ui.edited.add (paragraph); - } - } - - update_paragraph_index (); - layout (); - - return ui; - } - - public void move_carret_next () { - unichar c; - - move_carret_one_character (); - - if (KeyBindings.has_ctrl ()) { - while (true) { - c = move_carret_one_character (); - - if (c == '\0' || c == ' ') { - break; - } - } - } - } - - unichar move_carret_one_character () { - Paragraph paragraph; - int index; - unichar c; - - return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); - paragraph = paragraphs.get (carret.paragraph); - - index = carret.character_index; - - paragraph.text.get_next_char (ref index, out c); - - if (index >= paragraph.text_length && carret.paragraph + 1 < paragraphs.size) { - carret.paragraph++; - carret.character_index = 0; - c = ' '; - } else { - carret.character_index = index; - } - - return c; - } - - public void move_carret_previous () { - unichar c; - - move_carret_back_one_character (); - - if (KeyBindings.has_ctrl ()) { - while (true) { - c = move_carret_back_one_character (); - - if (c == '\0' || c == ' ') { - break; - } - } - } - } - - unichar move_carret_back_one_character () { - Paragraph paragraph; - int index, last_index; - unichar c; - - return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); - paragraph = paragraphs.get (carret.paragraph); - - index = 0; - last_index = -1; - - while (paragraph.text.get_next_char (ref index, out c) && index < carret.character_index) { - last_index = index; - } - - if (last_index <= 0 && carret.paragraph > 0) { - carret.paragraph--; - - return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); - paragraph = paragraphs.get (carret.paragraph); - carret.character_index = paragraph.text_length; - - if (paragraph.text.has_suffix ("\n")) { - carret.character_index -= "\n".length; - } - - c = ' '; - } else if (last_index > 0) { - carret.character_index = last_index; - } else { - carret.character_index = 0; - c = ' '; - } - - return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); - - return c; - } - - public void move_carret_next_row () { - double nr = font_size; - - if (carret.desired_y + 2 * font_size >= allocation.height) { - scroll (2 * font_size); - nr = -font_size; - } - - if (carret.desired_y + nr < widget_y + height - padding) { - carret = get_carret_at (carret.desired_x - widget_x - padding, carret.desired_y + nr); - } - } - - public void move_carret_to_end_of_line () { - carret = get_carret_at (widget_x + padding + width, carret.desired_y, false); - } - - public void move_carret_to_beginning_of_line () { - carret = get_carret_at (widget_x, carret.desired_y, false); - } - - public void move_carret_previous_row () { - double nr = -font_size; - - if (carret.desired_y - 2 * font_size < 0) { - scroll (-2 * font_size); - nr = font_size; - } - - if (carret.desired_y + nr > widget_y + padding) { - carret = get_carret_at (carret.desired_x, carret.desired_y + nr); - } - } - - public bool has_selection () { - return show_selection && selection_is_visible (); - } - - private bool selection_is_visible () { - return carret.paragraph != selection_end.paragraph || carret.character_index != selection_end.character_index; - } - - public void insert_text (string t) { - string s; - Paragraph paragraph; - TextUndoItem ui; - Gee.ArrayList<string> pgs; - bool u = false; - - pgs = new Gee.ArrayList<string> (); - - if (single_line) { - s = t.replace ("\n", "").replace ("\r", ""); - pgs.add (s); - } else { - if (t.last_index_of ("\n") > 0) { - string[] parts = t.split ("\n"); - int i; - for (i = 0; i < parts.length -1; i++) { - pgs.add (parts[i]); - pgs.add ("\n"); - } - - pgs.add (parts[parts.length - 1]); - - if (t.has_suffix ("\n")) { - pgs.add ("\n"); - } - } else { - s = t; - pgs.add (s); - } - } - - if (has_selection () && show_selection) { - ui = delete_selected_text (); - u = true; - - if (paragraphs.size == 0) { - paragraphs.add (new Paragraph ("", font_size, 0, text_color)); - } - } else { - ui = new TextUndoItem (carret); - } - - return_if_fail (0 <= carret.paragraph < paragraphs.size); - paragraph = paragraphs.get (carret.paragraph); - - if (pgs.size > 0) { - if (!u) { - ui.edited.add (paragraph.copy ()); - } - - string first = pgs.get (0); - - string end; - string nt = paragraph.text.substring (0, carret.character_index); - - nt += first; - end = paragraph.text.substring (carret.character_index); - - paragraph.set_text (nt); - - int paragraph_index = carret.paragraph; - Paragraph next_paragraph = paragraph; - for (int i = 1; i < pgs.size; i++) { - paragraph_index++; - string next = pgs.get (i); - next_paragraph = new Paragraph (next, font_size, paragraph_index, text_color); - paragraphs.insert (paragraph_index, next_paragraph); - ui.added.add (next_paragraph); - u = true; - } - - carret.paragraph = paragraph_index; - carret.character_index = next_paragraph.text.length; - - next_paragraph.set_text (next_paragraph.text + end); - } - - if (u) { - undo_items.add (ui); - redo_items.clear (); - } - - update_paragraph_index (); - layout (); - - text_changed (get_text ()); - show_selection = false; - } - - public string get_text () { - StringBuilder sb = new StringBuilder (); - - generate_all_paragraphs (); - - foreach (Paragraph p in paragraphs) { - sb.append (p.text); - } - - return sb.str; - } - - Carret get_carret_at (double click_x, double click_y, - bool check_boundaries = true) { - - int i = 0; - double tx, ty; - double p; - string w; - int ch_index; - double min_d = double.MAX; - Carret c = new Carret (); - double dt; - - c.paragraph = -1; - c.desired_x = click_x; - c.desired_y = click_y; - - foreach (Paragraph paragraph in paragraphs) { - if (!check_boundaries || paragraph.text_is_on_screen (allocation, widget_y)) { - ch_index = 0; - - if (paragraph.start_y + widget_y - font_size <= click_y <= paragraph.end_y + widget_y + font_size) { - foreach (Text next_word in paragraph.words) { - double tt_click = click_y - widget_y - padding + font_size; - - w = next_word.text; - - if (next_word.widget_y <= tt_click <= next_word.widget_y + font_size) { - - p = next_word.get_sidebearing_extent (); - - if ((next_word.widget_y <= tt_click <= next_word.widget_y + font_size) - && (next_word.widget_x + widget_x <= click_x <= next_word.widget_x + widget_x + padding + next_word.get_sidebearing_extent ())) { - - tx = widget_x + next_word.widget_x + padding; - ty = widget_y + next_word.widget_y + padding; - - next_word.iterate ((glyph, kerning, last) => { - double cw; - int ci; - double d; - string gc = (!) glyph.get_unichar ().to_string (); - - d = Math.fabs (click_x - tx); - - if (d <= min_d) { - min_d = d; - c.character_index = ch_index; - c.paragraph = i; - } - - cw = (glyph.get_width ()) * next_word.get_font_scale () + kerning; - ci = gc.length; - - tx += cw; - ch_index += ci; - }); - - dt = Math.fabs (click_x - (tx + widget_x + padding)); - if (dt < min_d) { - min_d = dt; - c.character_index = ch_index; - c.paragraph = i; - } - } else { - dt = Math.fabs (click_x - (next_word.widget_x + widget_x + padding + next_word.get_sidebearing_extent ())); - - if (dt < min_d) { - min_d = dt; - c.character_index = ch_index + w.length; - - if (w.has_suffix ("\n")) { - c.character_index -= "\n".length; - } - - c.paragraph = i; - } - - ch_index += w.length; - } - } else { - ch_index += w.length; - } - } - } - } - i++; - } - - if (unlikely (c.paragraph < 0)) { - c.paragraph = paragraphs.size > 0 ? paragraphs.size - 1 : 0; - c.character_index = paragraphs.size > 0 ? paragraphs.get (c.paragraph).text.length : 0; - } - - store_undo_state_at_next_event = true; - - return c; - } - - /** @return offset to click in text. */ - public override void layout () { - double p; - double tx, ty; - string w; - double xmax = 0; - int i = 0; - double dd; - - tx = 0; - ty = font_size; - - if (allocation.width <= 0 || allocation.height <= 0) { - warning ("Parent widget allocation is not set."); - } - - for (i = paragraphs.size - 1; i >= 0 && paragraphs.size > 1; i--) { - if (unlikely (paragraphs.get (i).is_empty ())) { - warning ("Empty paragraph."); - paragraphs.remove_at (i); - update_paragraph_index (); - } - } - - i = 0; - foreach (Paragraph paragraph in paragraphs) { - if (paragraph.need_layout - || (paragraph.text_area_width != width - && paragraph.text_is_on_screen (allocation, widget_y))) { - - paragraph.start_y = ty; - paragraph.start_x = tx; - - paragraph.cached_surface = null; - - foreach (Text next_word in paragraph.words) { - next_word.set_font_size (font_size); - - w = next_word.text; - p = next_word.get_sidebearing_extent (); - - if (unlikely (p == 0)) { - warning (@"Zero width word: $(w)"); - } - - if (w == "") { - break; - } - - if (w == "\n") { - next_word.widget_x = tx; - next_word.widget_y = ty; - - tx = 0; - ty += next_word.font_size; - } else { - if (!single_line) { - if (tx + p + 2 * padding > width || w == "\n") { - tx = 0; - ty += next_word.font_size; - } - } - - if (tx + p > xmax) { - xmax = tx + p; - } - - next_word.widget_x = tx; - next_word.widget_y = ty; - - if (w != "\n") { - tx += p; - } - } - } - - if (tx > xmax) { - xmax = tx; - } - - paragraph.text_area_width = width; - paragraph.width = xmax; - paragraph.end_x = tx; - paragraph.end_y = ty; - paragraph.need_layout = false; - } - - if (xmax > width) { - break; - } - - tx = paragraph.end_x; - ty = paragraph.end_y; - i++; - } - - if (xmax > width) { - this.width = xmax + 2 * padding; - layout (); - return; - } - - this.height = fmax (min_height, ty + 2 * padding); - - if (last_paragraph != DONE) { - this.height = (text_length / (double) last_paragraph) * ty + 2 * padding; // estimate height - } - - if (ty + widget_y < allocation.height && last_paragraph != DONE) { - generate_paragraphs (); - layout (); - return; - } - - ty = font_size; - tx = 0; - - foreach (Paragraph paragraph in paragraphs) { - dd = ty - paragraph.start_y; - - if (dd != 0) { - paragraph.start_y += dd; - paragraph.end_y += dd; - foreach (Text word in paragraph.words) { - word.widget_y += dd; - } - } - - ty = paragraph.end_y; - } - } - - public override void button_press (uint button, double x, double y) { - if (is_over (x, y)) { - carret = get_carret_at (x, y); - selection_end = carret.copy (); - update_selection = true; - } - } - - public override void button_release (uint button, double x, double y) { - update_selection = false; - show_selection = selection_is_visible (); - } - - public override bool motion (double x, double y) { - if (update_selection) { - selection_end = get_carret_at (x, y); - show_selection = selection_is_visible (); - } - - return update_selection; - } - - public override void draw (Context cr) { - Text word; - double tx, ty; - string w; - double scale; - double width; - double x = widget_x; - double y = widget_y; - Carret selection_start, selection_stop; - double carret_x; - double carret_y; - - layout (); - - if (draw_border) { - // background - cr.save (); - cr.set_line_width (1); - Theme.color (cr, "Text Area Background"); - draw_rounded_rectangle (cr, x, y, this.width, this.height - padding, padding); - cr.fill (); - cr.restore (); - - // border - cr.save (); - cr.set_line_width (1); - Theme.color (cr, "Foreground 1"); - draw_rounded_rectangle (cr, x, y, this.width, this.height - padding, padding); - cr.stroke (); - cr.restore (); - } - - cr.save (); - - word = new Text (); - - width = this.width - padding; - x += padding; - scale = word.get_font_scale (); - y += font_size; - - // draw selection background - if (has_selection ()) { - tx = 0; - ty = 0; - - selection_start = get_selection_start (); - selection_stop = get_selection_stop (); - - cr.save (); - Theme.color (cr, "Highlighted 1"); - - for (int i = selection_start.paragraph; i <= selection_stop.paragraph; i++) { - return_if_fail (0 <= i < paragraphs.size); - Paragraph pg = paragraphs.get (i); - - if (pg.text_is_on_screen (allocation, widget_y)) { - int char_index = 0; - - foreach (Text next_word in pg.words) { - double cw = next_word.get_sidebearing_extent (); - bool paint_background = false; - bool partial_start = false; - bool partial_stop = false; - int wl; - - w = next_word.text; - wl = w.length; - scale = next_word.get_font_scale (); - - if (selection_start.paragraph == selection_stop.paragraph) { - partial_start = true; - partial_stop = true; - } else if (selection_start.paragraph < i < selection_stop.paragraph) { - paint_background = true; - } else if (selection_start.paragraph == i) { - paint_background = true; - partial_start = true; - } else if (selection_stop.paragraph == i) { - paint_background = char_index + wl < selection_stop.character_index; - partial_stop = !paint_background; - } - - if (paint_background && !(partial_start || partial_stop)) { - double selection_y = widget_y + next_word.widget_y + scale * -next_word.cached_font.bottom_limit - font_size; - cr.rectangle (widget_x + padding + next_word.widget_x - 1, selection_y, cw + 1, font_size); - cr.fill (); - } - - if (partial_start || partial_stop) { - int index = char_index; - double bx = widget_x + padding + next_word.widget_x + (partial_start ? 0 : 1); - - next_word.iterate ((glyph, kerning, last) => { - double cwi; - int ci; - bool draw = (index >= selection_start.character_index && partial_start && !partial_stop) - || (index < selection_stop.character_index && !partial_start && partial_stop) - || (selection_start.character_index <= index < selection_stop.character_index && partial_start && partial_stop); - - cwi = (glyph.get_width ()) * next_word.get_font_scale () + kerning; - - if (draw) { - double selection_y = widget_y + next_word.widget_y + scale * -next_word.cached_font.bottom_limit - font_size; - cr.rectangle (bx - 1, selection_y, cwi + 1, font_size); - cr.fill (); - } - - bx += cwi; - ci = ((!) glyph.get_unichar ().to_string ()).length; - index += ci; - }); - } - - char_index += w.length; - } - } - } - - cr.restore (); - } - - tx = 0; - ty = 0; - - int first_visible = 0; - int last_visible; - int paragraphs_size = paragraphs.size; - while (first_visible < paragraphs_size) { - if (paragraphs.get (first_visible).text_is_on_screen (allocation, widget_y)) { - break; - } - first_visible++; - } - - last_visible = first_visible; - while (last_visible < paragraphs_size) { - if (!paragraphs.get (last_visible).text_is_on_screen (allocation, widget_y)) { - last_visible++; - break; - } - last_visible++; - } - - if (paragraphs_size == 0) { - if (carret_is_visible) { - draw_carret_at (cr, widget_x + padding, widget_y + font_size + padding); - } - - return; - } - - Context cc; // cached context - Paragraph paragraph; - paragraph = paragraphs.get (0); - - tx = paragraph.start_x; - ty = paragraph.start_y; - - for (int i = first_visible; i < last_visible; i++) { - paragraph = paragraphs.get (i); - - tx = paragraph.start_x; - ty = paragraph.start_y; - - if (paragraph.cached_surface == null) { - paragraph.cached_surface = Screen.create_background_surface ((int) width + 2, paragraph.get_height () + (int) font_size + 2); - cc = new Context ((!) paragraph.cached_surface); - cc.scale (Screen.get_scale(), Screen.get_scale()); - - foreach (Text next_word in paragraph.words) { - if (next_word.text != "\n") { - next_word.draw_at_top (cc, next_word.widget_x, next_word.widget_y - ty); - } - } - } - - if (likely (paragraph.cached_surface != null)) { - // FIXME: subpixel offset in text area - Screen.paint_background_surface(cr, - (!) paragraph.cached_surface, - (int) (x + tx), - (int) (widget_y + paragraph.start_y - font_size + padding)); - } else { - warning ("No paragraph image."); - } - } - - if (carret_is_visible) { - get_carret_position (carret, out carret_x, out carret_y); - - if (carret_y < 0) { - draw_carret_at (cr, widget_x + padding, widget_y + font_size + padding); - } else { - draw_carret_at (cr, carret_x, carret_y); - } - } - - if (has_selection ()) { - get_carret_position (selection_end, out carret_x, out carret_y); - - if (carret_y < 0) { - draw_carret_at (cr, widget_x + padding, widget_y + font_size + padding); - } else { - draw_carret_at (cr, carret_x, carret_y); - } - } - } - - void get_carret_position (Carret carret, out double carret_x, out double carret_y) { - Paragraph paragraph; - double tx; - double ty; - int ch_index; - int wl; - double pos_x, pos_y; - - ch_index = 0; - - carret_x = -1; - carret_y = -1; - - return_if_fail (0 <= carret.paragraph < paragraphs.size); - paragraph = paragraphs.get (carret.paragraph); - - pos_x = -1; - pos_y = -1; - - foreach (Text next_word in paragraph.words) { - string w = next_word.text; - wl = w.length; - - if (carret.character_index == ch_index) { - pos_x = next_word.widget_x + widget_x + padding; - pos_y = widget_y + next_word.widget_y + next_word.get_baseline_to_bottom_for_font (); - } else if (carret.character_index >= ch_index + wl) { - pos_x = next_word.widget_x + next_word.get_sidebearing_extent () + widget_x + padding; - pos_y = widget_y + next_word.widget_y + next_word.get_baseline_to_bottom_for_font (); - - if (next_word.text.has_suffix ("\n")) { - pos_x = widget_x + padding; - pos_y += next_word.font_size; - } - } else if (ch_index < carret.character_index <= ch_index + wl) { - tx = widget_x + next_word.widget_x; - ty = widget_y + next_word.widget_y + next_word.get_baseline_to_bottom_for_font (); - - if (carret.character_index <= ch_index) { - pos_x = widget_x + padding; - pos_y = ty; - } - - next_word.iterate ((glyph, kerning, last) => { - double cw; - int ci; - - cw = (glyph.get_width ()) * next_word.get_font_scale () + kerning; - ci = ((!) glyph.get_unichar ().to_string ()).length; - - if (ch_index < carret.character_index <= ch_index + ci) { - pos_x = tx + cw + padding; - pos_y = ty; - - if (glyph.get_unichar () == '\n') { - pos_x = widget_x + padding; - pos_y += next_word.font_size; - } - } - - tx += cw; - ch_index += ci; - }); - } - - ch_index += wl; - } - - carret_x = pos_x; - carret_y = pos_y; - } - - void draw_carret_at (Context cr, double x, double y) { - cr.save (); - cr.set_source_rgba (0, 0, 0, 0.5); - cr.set_line_width (1); - cr.move_to (x, y); - cr.line_to (x, y - font_size); - cr.stroke (); - cr.restore (); - } - - public void store_undo_edit_state () { - TextUndoItem ui = new TextUndoItem (carret); - ui.edited.add (get_current_paragraph ().copy ()); - undo_items.add (ui); - redo_items.clear (); - } - - public void redo () { - TextUndoItem i; - TextUndoItem undo_item; - - if (redo_items.size > 0) { - i = redo_items.get (redo_items.size - 1); - - undo_item = new TextUndoItem (i.carret); - - i.deleted.sort ((a, b) => { - Paragraph pa = (Paragraph) a; - Paragraph pb = (Paragraph) b; - return pb.index - pa.index; - }); - - i.added.sort ((a, b) => { - Paragraph pa = (Paragraph) a; - Paragraph pb = (Paragraph) b; - return pa.index - pb.index; - }); - - foreach (Paragraph p in i.deleted) { - if (unlikely (!(0 <= p.index < paragraphs.size))) { - warning ("Paragraph not found."); - } else { - undo_item.deleted.add (p.copy ()); - paragraphs.remove_at (p.index); - } - } - - foreach (Paragraph p in i.added) { - if (p.index == paragraphs.size) { - paragraphs.add (p.copy ()); - } else { - if (unlikely (!(0 <= p.index < paragraphs.size))) { - warning (@"Index: $(p.index) out of bounds, size: $(paragraphs.size)"); - } else { - undo_item.added.add (paragraphs.get (p.index).copy ()); - paragraphs.insert (p.index, p.copy ()); - } - } - } - - foreach (Paragraph p in i.edited) { - if (unlikely (!(0 <= p.index < paragraphs.size))) { - warning (@"Index: $(p.index ) out of bounds, size: $(paragraphs.size)"); - return; - } - - undo_item.edited.add (paragraphs.get (p.index).copy ()); - paragraphs.set (p.index, p.copy ()); - } - - redo_items.remove_at (redo_items.size - 1); - undo_items.add (undo_item); - - carret = i.carret.copy (); - layout (); - } - } - - public void undo () { - TextUndoItem i; - TextUndoItem redo_item; - - if (undo_items.size > 0) { - i = undo_items.get (undo_items.size - 1); - redo_item = new TextUndoItem (i.carret); - - i.deleted.sort ((a, b) => { - Paragraph pa = (Paragraph) a; - Paragraph pb = (Paragraph) b; - return pa.index - pb.index; - }); - - i.added.sort ((a, b) => { - Paragraph pa = (Paragraph) a; - Paragraph pb = (Paragraph) b; - return pb.index - pa.index; - }); - - foreach (Paragraph p in i.added) { - if (unlikely (!(0 <= p.index < paragraphs.size))) { - warning ("Paragraph not found."); - } else { - redo_item.added.add (paragraphs.get (p.index).copy ()); - paragraphs.remove_at (p.index); - } - } - - foreach (Paragraph p in i.deleted) { - if (p.index == paragraphs.size) { - paragraphs.add (p.copy ()); - } else { - if (unlikely (!(0 <= p.index < paragraphs.size))) { - warning (@"Index: $(p.index) out of bounds, size: $(paragraphs.size)"); - } else { - redo_item.deleted.add (p.copy ()); - paragraphs.insert (p.index, p.copy ()); - } - } - } - - foreach (Paragraph p in i.edited) { - if (unlikely (!(0 <= p.index < paragraphs.size))) { - warning (@"Index: $(p.index ) out of bounds, size: $(paragraphs.size)"); - return; - } - - redo_item.edited.add (paragraphs.get (p.index).copy ()); - paragraphs.set (p.index, p.copy ()); - } - - undo_items.remove_at (undo_items.size - 1); - redo_items.add (redo_item); - - carret = i.carret.copy (); - layout (); - } - } - - public void set_editable (bool editable) { - this.editable = editable; - } - - public class TextUndoItem : GLib.Object { - public Carret carret; - public Gee.ArrayList<Paragraph> added = new Gee.ArrayList<Paragraph> (); - public Gee.ArrayList<Paragraph> edited = new Gee.ArrayList<Paragraph> (); - public Gee.ArrayList<Paragraph> deleted = new Gee.ArrayList<Paragraph> (); - - public TextUndoItem (Carret c) { - carret = c.copy (); - } - } - - public class Paragraph : GLib.Object { - public double end_x = -10000; - public double end_y = -10000; - - public double start_x = -10000; - public double start_y = -10000; - - public double width = -10000; - public double text_area_width = -10000; - - public string text; - - public Gee.ArrayList<Text> words { - get { - if (words_in_paragraph.size == 0) { - generate_words (); - } - - return words_in_paragraph; - } - } - - private Gee.ArrayList<Text> words_in_paragraph = new Gee.ArrayList<Text> (); - public int text_length; - public bool need_layout = true; - public Surface? cached_surface = null; - double font_size; - public int index; - Color text_color; - - public Paragraph (string text, double font_size, int index, Color c) { - this.index = index; - this.font_size = font_size; - text_color = c; - set_text (text); - } - - public Paragraph copy () { - Paragraph p = new Paragraph (text.dup (), font_size, index, text_color); - p.need_layout = true; - return p; - } - - public bool is_empty () { - return text == ""; - } - - public void set_text (string t) { - this.text = t; - text_length = t.length; - need_layout = true; - words.clear (); - cached_surface = null; - } - - public int get_height () { - return (int) (end_y - start_y) + 1; - } - - public int get_width () { - return (int) width + 1; - } - - public bool text_is_on_screen (WidgetAllocation alloc, double widget_y) { - bool v = (0 <= start_y + widget_y <= alloc.height) - || (0 <= end_y + widget_y <= alloc.height) - || (start_y + widget_y <= 0 && alloc.height <= end_y + widget_y); - return v; - } - - private void generate_words () { - string w; - int p = 0; - bool carret_at_word_end = false; - Text word; - int carret = 0; - int iter_pos = 0; - - return_if_fail (words_in_paragraph.size == 0); - - while (p < text_length) { - w = get_next_word (out carret_at_word_end, ref iter_pos, carret); - - if (w == "") { - break; - } - - word = new Text (w, font_size); - - word.r = text_color.r; - word.g = text_color.g; - word.b = text_color.b; - word.a = text_color.a; - - words_in_paragraph.add (word); - } - } - - string get_next_word (out bool carret_at_end_of_word, ref int iter_pos, int carret) { - int i; - int ni; - int pi; - string n; - int nl; - - carret_at_end_of_word = false; - - if (iter_pos >= text_length) { - carret_at_end_of_word = true; - return "".dup (); - } - - if (text.get_char (iter_pos) == '\n') { - iter_pos += "\n".length; - carret_at_end_of_word = (iter_pos == carret); - return "\n".dup (); - } - - i = text.index_of (" ", iter_pos); - pi = i + " ".length; - - ni = text.index_of ("\t", iter_pos); - if (ni != -1 && ni < pi || i == -1) { - i = ni; - pi = i + "\t".length; - } - - ni = text.index_of ("\n", iter_pos); - if (ni != -1 && ni < pi || i == -1) { - i = ni; - pi = i; - } - - if (iter_pos + iter_pos - pi > text_length || i == -1) { - n = text.substring (iter_pos); - } else { - n = text.substring (iter_pos, pi - iter_pos); - } - - nl = n.length; - if (iter_pos < carret < iter_pos + nl) { - n = text.substring (iter_pos, carret - iter_pos); - nl = n.length; - carret_at_end_of_word = true; - } - - iter_pos += nl; - - if (iter_pos == carret) { - carret_at_end_of_word = true; - } - - return n; - } - } - - public class Carret : GLib.Object { - - public int paragraph = 0; - - public int character_index { - get { - return ci; - } - - set { - ci = value; - } - } - - private int ci = 0; - - public double desired_x = 0; - public double desired_y = 0; - - public Carret () { - } - - public void print () { - stdout.printf (@"paragraph: $paragraph, character_index: $character_index\n"); - } - - public Carret copy () { - Carret c = new Carret (); - - c.paragraph = paragraph; - c.character_index = character_index; - - c.desired_x = desired_x; - c.desired_y = desired_y; - - return c; - } - } - } - - }
diff --git libbirdfont/Renderer/fontconfig.c(deleted)
--- a/libbirdfont/Renderer/fontconfig.c +++ /dev/null @@ -1,141 +1,1 @@ - /* - Copyright (C) 2015 Johan Mattsson - - This library is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as - published by the Free Software Foundation; either version 3 of the - License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - */ - - #include <stdio.h> - #include <glib.h> - #include <fontconfig/fontconfig.h> - - gchar* find_font_with_property (FcConfig* fontconfig, const gchar* characters, const gchar* property) { - FcPattern* pattern; - FcCharSet* character_set; - FcObjectSet* font_properties; - FcFontSet* fonts; - FcPattern* font; - FcChar8* path; - gchar* result; - gchar* remaining_characters; - gunichar character; - - if (fontconfig == NULL) { - g_warning("Font config not loaded."); - return NULL; - } - - result = NULL; - pattern = FcPatternCreate (); - - character_set = FcCharSetCreate (); - - remaining_characters = (gchar*) characters; - while (TRUE) { - character = g_utf8_get_char (remaining_characters); - - if (character == '\0') { - break; - } - - FcCharSetAddChar(character_set, character); - - remaining_characters = g_utf8_next_char (remaining_characters); - } - - FcPatternAddCharSet (pattern, FC_CHARSET, character_set); - FcCharSetDestroy (character_set); - FcPatternAddInteger (pattern, FC_SLANT, FC_SLANT_ROMAN); - - FcPatternAddBool(pattern, FC_SCALABLE, FcTrue); - font_properties = FcObjectSetBuild (property, NULL); - fonts = FcFontList (fontconfig, pattern, font_properties); - - if (fonts && fonts->nfont > 0) { - font = fonts->fonts[0]; - if (FcPatternGetString(font, property, 0, &path) == FcResultMatch) { - result = g_strdup ((gchar*) path); - } - } - - if (fonts) { - FcFontSetDestroy(fonts); - } - - if (pattern) { - FcPatternDestroy(pattern); - } - - return result; - } - - /** Find a fallback font for a set of characters. - * @return A path to the font file. - */ - gchar* find_font (FcConfig* fontconfig, const gchar* characters) { - return find_font_with_property (fontconfig, characters, FC_FILE); - } - - /** Find a fallback font for a set of characters. - * @return Family name of the font. - */ - gchar* find_font_family (FcConfig* fontconfig, const gchar* characters) { - return find_font_with_property (fontconfig, characters, FC_FAMILY); - } - - /** Find a font file from its family name. - * @param font_config fontconfig instance - * @param font_name name of the font - * @return full path to the font file - */ - gchar* find_font_file (FcConfig* font_config, const gchar* font_name) { - const FcChar8* name; - FcPattern* search_pattern; - FcPattern* font; - FcChar8* file; - gchar* path; - FcObjectSet* font_properties; - FcFontSet* fonts; - int i; - - if (font_config == NULL) { - g_warning("Font config not loaded."); - return NULL; - } - - path = NULL; - name = font_name; - - search_pattern = FcPatternCreate (); - FcPatternAddString (search_pattern, FC_FAMILY, name); - FcPatternAddBool (search_pattern, FC_SCALABLE, FcTrue); - FcPatternAddInteger (search_pattern, FC_WEIGHT, FC_WEIGHT_MEDIUM); - FcPatternAddInteger (search_pattern, FC_SLANT, FC_SLANT_ROMAN); - - font_properties = FcObjectSetBuild (FC_FILE, NULL); - fonts = FcFontList (font_config, search_pattern, font_properties); - - if (fonts->nfont > 0) { - for (i = 0; i < fonts->nfont; i++) { - font = fonts->fonts[i]; - - if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) { - path = g_strdup ((gchar*) file); - break; - } - } - FcPatternDestroy (font); - } - - FcPatternDestroy (search_pattern); - - return path; - }
--- a/libbirdfont/ResizeTool.vala +++ b/libbirdfont/ResizeTool.vala @@ -14,6 +14,7 @@ using Math; using Cairo; + using SvgBird; namespace BirdFont { @@ -21,7 +22,7 @@ bool resize_path_proportional = false; bool resize_width = false; - Path? resized_path = null; + SvgBird.Object? resized_path = null; double last_resize_y; double last_resize_x; @@ -63,13 +64,13 @@ }); press_action.connect((self, b, x, y) => { - Path last_path; + SvgBird.Object last_path; Glyph glyph; glyph = MainWindow.get_current_glyph (); glyph.store_undo_state (); - foreach (Path p in glyph.active_paths) { + foreach (SvgBird.Object p in glyph.active_paths) { if (is_over_resize_handle (p, x, y)) { resize_path_proportional = true; resized_path = p; @@ -87,7 +88,7 @@ } } - foreach (Path p in glyph.active_paths) { + foreach (SvgBird.Object p in glyph.active_paths) { if (is_over_rotate_handle (p, x, y)) { rotate_path = true; return; @@ -121,8 +122,11 @@ update_selection_box (); GlyphCanvas.redraw (); - foreach (Path p in MainWindow.get_current_glyph ().active_paths) { - p.create_full_stroke (); + foreach (SvgBird.Object p in MainWindow.get_current_glyph ().active_paths) { + if (p is PathObject) { + PathObject path = (PathObject) p; + path.get_path ().create_full_stroke (); + } } }); @@ -150,10 +154,6 @@ || resize_width) { glyph = MainWindow.get_current_glyph (); - - foreach (Path selected_path in glyph.active_paths) { - selected_path.reset_stroke (); - } GlyphCanvas.redraw (); } @@ -234,9 +234,9 @@ public void rotate_selected_paths (double angle, double cx, double cy) { Glyph glyph = MainWindow.get_current_glyph (); double dx, dy, xc2, yc2, w, h; - Path last_path; + SvgBird.Object last_path; - foreach (Path p in glyph.active_paths) { + foreach (SvgBird.Object p in glyph.active_paths) { p.rotate (angle, cx, cy); } @@ -245,7 +245,7 @@ dx = -(xc2 - cx); dy = -(yc2 - cy); - foreach (Path p in glyph.active_paths) { + foreach (SvgBird.Object p in glyph.active_paths) { p.move (dx, dy); } @@ -287,7 +287,7 @@ rotate_selected_paths (rotation - last_rotate, selection_box_center_x, selection_box_center_y); } - static bool is_over_rotate_handle (Path p, double x, double y) { + static bool is_over_rotate_handle (SvgBird.Object p, double x, double y) { double cx, cy, hx, hy; double size = 10; bool inx, iny; @@ -364,17 +364,16 @@ if (!selected) { glyph.clear_active_paths (); - foreach (Path path in glyph.get_visible_paths ()) { - glyph.add_active_path (null, path); + foreach (SvgBird.Object path in glyph.get_visible_objects ()) { + glyph.add_active_object (null, path); } } get_selection_min (out resize_pos_x, out resize_pos_y); // resize paths - foreach (Path selected_path in glyph.active_paths) { + foreach (SvgBird.Object selected_path in glyph.active_paths) { selected_path.resize (ratio_x, ratio_y); - selected_path.reset_stroke (); } // move paths relative to the updated xmin and xmax @@ -382,7 +381,7 @@ dx = resize_pos_x - selection_minx; dy = resize_pos_y - selection_miny; - foreach (Path selected_path in glyph.active_paths) { + foreach (SvgBird.Object selected_path in glyph.active_paths) { selected_path.move (dx, dy); } @@ -470,7 +469,7 @@ DrawingTools.move_tool.move_to_baseline (); - foreach (Path path in glyph.active_paths) { + foreach (SvgBird.Object path in glyph.active_paths) { path.move (0, -descender * scale); } @@ -481,7 +480,7 @@ Glyph glyph = MainWindow.get_current_glyph (); x = double.MAX; y = double.MAX; - foreach (Path p in glyph.active_paths) { + foreach (SvgBird.Object p in glyph.active_paths) { if (p.xmin < x) { x = p.xmin; } @@ -497,11 +496,11 @@ double h, w; double ratio = get_resize_ratio (x, y); - foreach (Path selected_path in glyph.active_paths) { + foreach (SvgBird.Object selected_path in glyph.active_paths) { h = selected_path.ymax - selected_path.ymin; w = selected_path.xmax - selected_path.xmin; - if (selected_path.points.size <= 1) { + if (selected_path.is_empty ()) { // FIXME: test with one point continue; } @@ -513,13 +512,13 @@ return true; } - bool is_over_resize_handle (Path p, double x, double y) { + bool is_over_resize_handle (SvgBird.Object p, double x, double y) { double handle_x, handle_y; get_resize_handle_position (out handle_x, out handle_y); return Path.distance (handle_x, x, handle_y, y) < 12 * MainWindow.units; } - bool is_over_horizontal_resize_handle (Path p, double x, double y) { + bool is_over_horizontal_resize_handle (SvgBird.Object p, double x, double y) { double handle_x, handle_y; get_horizontal_reseize_handle_position (out handle_x, out handle_y); return Path.distance (handle_x, x, handle_y, y) < 12 * MainWindow.units; @@ -540,26 +539,28 @@ if (!selected_paths) { glyph.clear_active_paths (); - foreach (Path path in glyph.get_visible_paths ()) { - glyph.add_active_path (null, path); + foreach (SvgBird.Object path in glyph.get_visible_objects ()) { + glyph.add_active_object (null, path); } } glyph.selection_boundaries (out x, out y, out w, out h); - foreach (Path path in glyph.active_paths) { - SvgParser.apply_matrix (path, 1, 0, s, 1, 0, 0); - path.skew = skew; - path.update_region_boundaries (); + foreach (SvgBird.Object path in glyph.active_paths) { + if (path is PathObject) { // FIXME: other objects + Path p = ((PathObject) path).get_path (); + SvgParser.apply_matrix (p, 1, 0, s, 1, 0, 0); + p.skew = skew; + path.update_region_boundaries (); + } } glyph.selection_boundaries (out nx, out y, out nw, out h); dx = -(nx - x); - foreach (Path p in glyph.active_paths) { + foreach (SvgBird.Object p in glyph.active_paths) { p.move (dx, 0); - p.reset_stroke (); } dw = (nw - w);
--- a/libbirdfont/SaveCallback.vala +++ b/libbirdfont/SaveCallback.vala @@ -28,7 +28,7 @@ } public void save_as () { - if (MenuTab.has_suppress_event ()) { + if (unlikely (MenuTab.has_suppress_event ())) { warn_if_test ("Event suppressed"); return; } @@ -54,6 +54,7 @@ file_name = @"$(f)"; file = File.new_for_path (file_name); font_file_path = (!) file.get_path (); + if (!file.query_exists ()) { save (); } else {
--- a/libbirdfont/SearchPaths.vala +++ b/libbirdfont/SearchPaths.vala @@ -94,7 +94,7 @@ f = get_file (@"/usr/share/birdfont/" + d + "/", name); if (likely (f.query_exists ())) return f; - return f; + return f; } public static string get_locale_directory () {
--- a/libbirdfont/SettingsTab.vala +++ b/libbirdfont/SettingsTab.vala @@ -43,13 +43,13 @@ stroke_width.new_value_action.connect ((self) => { Glyph g = MainWindow.get_current_glyph (); - Path.stroke_width = stroke_width.get_value (); + CanvasSettings.stroke_width = stroke_width.get_value (); g.redraw_area (0, 0, g.allocation.width, g.allocation.height); Preferences.set ("stroke_width_for_open_paths", stroke_width.get_display_value ()); MainWindow.get_toolbox ().redraw ((int) stroke_width.x, (int) stroke_width.y, 70, 70); }); - Path.stroke_width = stroke_width.get_value (); + CanvasSettings.stroke_width = stroke_width.get_value (); // adjust precision string precision_value = Preferences.get ("precision"); @@ -81,7 +81,7 @@ Tool show_all_line_handles = new Tool ("show_all_line_handles"); show_all_line_handles.select_action.connect((self) => { - Path.show_all_line_handles = !Path.show_all_line_handles; + CanvasSettings.show_all_line_handles = !CanvasSettings.show_all_line_handles; Glyph g = MainWindow.get_current_glyph (); g.redraw_area (0, 0, g.allocation.width, g.allocation.height); }); @@ -89,11 +89,11 @@ Tool fill_open_path = new Tool ("fill_open_path"); fill_open_path.select_action.connect((self) => { - Path.fill_open_path = true; + CanvasSettings.fill_open_path = true; }); fill_open_path.deselect_action.connect((self) => { - Path.fill_open_path = false; + CanvasSettings.fill_open_path = false; }); tools.add (new SettingsItem (fill_open_path, t_("Fill open paths.")));
diff --git libbirdfont/Stop.vala(deleted)
--- a/libbirdfont/Stop.vala +++ /dev/null @@ -1,36 +1,1 @@ - /* - Copyright (C) 2015 Johan Mattsson - - This library is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as - published by the Free Software Foundation; either version 3 of the - License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - */ - - using Cairo; - using Math; - - namespace BirdFont { - - public class Stop : GLib.Object { - public Color color = Color.black (); - public double offset = 0; - - public Stop () { - } - - public Stop copy () { - Stop s = new Stop (); - s.color = color.copy (); - s.offset = offset; - return s; - } - } - - }
--- a/libbirdfont/StrokeTool.vala +++ b/libbirdfont/StrokeTool.vala @@ -14,14 +14,9 @@ using Cairo; using Math; + using SvgBird; namespace BirdFont { - - public enum LineCap { - BUTT, - SQUARE, - ROUND - } public class StrokeTool : GLib.Object { @@ -31,7 +26,7 @@ public static bool show_stroke_tools = false; public static bool convert_stroke = false; - public static LineCap line_cap = LineCap.BUTT; + public static SvgBird.LineCap line_cap = SvgBird.LineCap.BUTT; StrokeTask task; @@ -51,22 +46,27 @@ convert_stroke = true; g.store_undo_state (); - foreach (Path p in g.active_paths) { - if (p.stroke > 0) { - paths.append (p.get_completed_stroke ()); + foreach (SvgBird.Object o in g.active_paths) { + if (o is PathObject) { + PathObject path = (PathObject) o; + Path p = path.get_path (); + + if (p.stroke > 0) { + paths.append (p.get_completed_stroke ()); + } } } if (paths.paths.size > 0) { - foreach (Path p in g.active_paths) { - g.layers.remove_path (p); + foreach (SvgBird.Object o in g.active_paths) { + g.layers.remove (o); } g.active_paths.clear (); foreach (Path np in paths.paths) { g.add_path (np); - g.active_paths.add (np); + g.active_paths.add (new PathObject.for_path (np)); } PenTool.update_orientation (); @@ -134,11 +134,15 @@ g.store_undo_state (); - foreach (Path p in g.active_paths) { - if (p.stroke == 0) { - o.add (p); - } else { - o.append (p.get_completed_stroke ()); + foreach (SvgBird.Object object in g.active_paths) { + if (object is PathObject) { + Path p = ((PathObject) object).get_path (); + + if (p.stroke == 0) { + o.add (p); + } else { + o.append (p.get_completed_stroke ()); + } } } @@ -217,8 +221,8 @@ return; } - foreach (Path p in g.active_paths) { - g.delete_path (p); + foreach (SvgBird.Object object in g.active_paths) { + g.layers.remove (object); } g.clear_active_paths (); @@ -226,8 +230,9 @@ remove_merged_curve_parts (new_paths); foreach (Path p in new_paths.paths) { - g.add_path (p); - g.add_active_path (null, p); + PathObject path = new PathObject.for_path (p); + g.add_object (path); + g.add_active_object (null, path); } PenTool.update_orientation (); @@ -1141,9 +1146,9 @@ } void add_line_cap (Path path, Path stroke1, Path stroke2, bool last_cap) { - if (path.line_cap == LineCap.SQUARE) { + if (path.line_cap == SvgBird.LineCap.SQUARE) { add_square_cap (path, stroke1, stroke2, last_cap); - } else if (path.line_cap == LineCap.ROUND) { + } else if (path.line_cap == SvgBird.LineCap.ROUND) { add_round_cap (path, stroke1, stroke2, last_cap); } }
--- a/libbirdfont/Svg.vala +++ b/libbirdfont/Svg.vala @@ -15,6 +15,8 @@ using Cairo; namespace BirdFont { + + // FIXME: substrings public class Svg {
diff --git libbirdfont/SvgArc.vala(deleted)
--- a/libbirdfont/SvgArc.vala +++ /dev/null @@ -1,182 +1,1 @@ - /* - * BirdFont code from SVG Salamander - * - * Copyright (c) 2004, Mark McKay - * Copyright (c) 2014, Johan Mattsson - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above - * copyright notice, this list of conditions and the following - * disclaimer. - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - * - * Mark McKay can be contacted at mark@kitfox.com. Salamander and other - * projects can be found at http://www.kitfox.com - * - * Created on January 26, 2004, 8:40 PM - * Adapded to BirdFont on Juli 2, 2014, 5:01 PM - */ - - using Math; - - namespace BirdFont { - - /** Convert an SVG arc instruction to a Beziér path. */ - static void add_arc_points (BezierPoints[] bezier_points, ref int bi, double x0, double y0, double rx, double ry, double angle, bool largeArcFlag, bool sweepFlag, double x, double y) { - - // - // Elliptical arc implementation based on the SVG specification notes - // - - double dx2, dy2, cosAngle, sinAngle; - double x1, y1, Prx, Pry, Px1, Py1, radiiCheck; - double sign, sq, coef, cx1, cy1; - double sx2, sy2, cx, cy; - double ux, uy, vx, vy, p, n; - double angleStart, angleExtent; - double s, step, theta; - - // Compute the half distance between the current and the final point - dx2 = (x0 - x) / 2.0; - dy2 = (y0 - y) / 2.0; - - // Convert angle from degrees to radians - angle = 2 * PI * ((angle % 360.0) / 360.0); - - cosAngle = cos (angle); - sinAngle = sin (angle); - - // - // Step 1 : Compute (x1, y1) - // - x1 = cosAngle * dx2 + sinAngle * dy2; - y1 = -sinAngle * dx2 + cosAngle * dy2; - - // Ensure radii are large enough - rx = fabs(rx); - ry = fabs(ry); - Prx = rx * rx; - Pry = ry * ry; - Px1 = x1 * x1; - Py1 = y1 * y1; - - - // Check that radii are large enough - radiiCheck = Px1 / Prx + Py1 / Pry; - - if (radiiCheck > 1) { - rx = sqrt (radiiCheck) * rx; - ry = sqrt (radiiCheck) * ry; - Prx = rx * rx; - Pry = ry * ry; - } - - // - // Step 2 : Compute (cx1, cy1) - // - sign = (largeArcFlag == sweepFlag) ? -1 : 1; - sq = ((Prx * Pry) - (Prx * Py1) - (Pry * Px1)) / ((Prx * Py1) + (Pry * Px1)); - sq = (sq < 0) ? 0 : sq; - coef = (sign * Math.sqrt(sq)); - cx1 = coef * ((rx * y1) / ry); - cy1 = coef * -((ry * x1) / rx); - - // - // Step 3 : Compute (cx, cy) from (cx1, cy1) - // - - sx2 = (x0 + x) / 2.0; - sy2 = (y0 + y) / 2.0; - cx = sx2 - (cosAngle * cx1 - sinAngle * cy1); - cy = sy2 - (sinAngle * cx1 + cosAngle * cy1); - - // - // Step 4 : Compute the angleStart (angle1) and the angleExtent (dangle) - // - - ux = (x1 - cx1) / rx; - uy = (y1 - cy1) / ry; - vx = (-x1 - cx1) / rx; - vy = (-y1 - cy1) / ry; - - // Compute the angle start - n = sqrt((ux * ux) + (uy * uy)); - p = ux; // (1 * ux) + (0 * uy) - sign = (uy < 0) ? -1d : 1d; - angleStart = sign * acos(p / n); - - // Compute the angle extent - n = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy)); - p = ux * vx + uy * vy; - sign = (ux * vy - uy * vx < 0) ? -1d : 1d; - angleExtent = sign * Math.acos(p / n); - - if(!sweepFlag && angleExtent > 0) { - angleExtent -= 2 *PI; - } else if (sweepFlag && angleExtent < 0) { - angleExtent += 2 *PI; - } - angleExtent %= 2 * PI; - angleStart %= 2 * PI; - - angleExtent *= -1; - angleStart *= -1; - - // Approximate the path with Beziér points - s = (angleExtent > 0) ? 1 : -1; - step = fabs (angleExtent) / (2 * fabs (angleExtent)); - - theta = PI - angleStart - angleExtent; - - bezier_points[bi].type = 'L'; - bezier_points[bi].svg_type = 'a'; - - bezier_points[bi].x0 = cx + rx * cos (theta); - bezier_points[bi].y0 = cy + ry * sin (theta); - - bi++; - - for (double a = 0; a < fabs (angleExtent); a += step) { - theta = PI - angleStart - angleExtent + s * a; - - return_if_fail (0 <= bi < bezier_points.length); - - bezier_points[bi].type = 'S'; - bezier_points[bi].svg_type = 'a'; - - bezier_points[bi].x0 = cx + rx * cos (theta); - bezier_points[bi].y0 = cy + ry * sin (theta); - - bezier_points[bi].x1 = cx + rx * cos (theta + 1 * step / 4); - bezier_points[bi].y1 = cy + ry * sin (theta + 1 * step / 4); - - bezier_points[bi].x2 = cx + rx * cos (theta + 2 * step / 4); - bezier_points[bi].y2 = cy + ry * sin (theta + 2 * step / 4); - - bi++; - } - } - - } -
--- a/libbirdfont/SvgFontFormatWriter.vala +++ b/libbirdfont/SvgFontFormatWriter.vala @@ -14,7 +14,7 @@ namespace BirdFont { - class SvgFontFormatWriter : Object { + class SvgFontFormatWriter : GLib.Object { DataOutputStream os;
--- a/libbirdfont/SvgParser.vala +++ b/libbirdfont/SvgParser.vala @@ -14,6 +14,7 @@ using B; using Math; + using SvgBird; namespace BirdFont { @@ -63,18 +64,15 @@ } public static void import_color_svg (Glyph glyph, string path) { - try { - string data; - FileUtils.get_contents (path, out data); - glyph.color_svg_data = data; - - if (glyph.color_svg_data != null) { - glyph.svg_x = glyph.left_limit; - glyph.svg_y = BirdFont.get_current_font ().top_position; - } - } catch (GLib.Error e) { - warning (e.message); - } + EmbeddedSvg drawing = SvgParser.parse_embedded_svg_file (path); + + Layer layer = new Layer (); + layer.name = "SVG"; + layer.add_object (drawing); + + glyph.add_layer (layer); + + // FIXME: update GUI } public static void import_folder (SvgType type) { @@ -127,7 +125,6 @@ string[] lines = xml_data.split ("\n"); bool has_format = false; SvgParser parser = new SvgParser (); - XmlParser xmlparser; foreach (string l in lines) { if (l.index_of ("Illustrator") > -1 || l.index_of ("illustrator") > -1) { @@ -150,22 +147,15 @@ warn_if_test ("No format identifier found in SVG parser.\n"); } - xmlparser = new XmlParser (xml_data); - - if (!xmlparser.validate()) { - warning("Invalid XML in SVG parser."); - } - - path_list = parser.parse_svg_file (xmlparser.get_root_tag ()); + XmlTree xml_tree = new XmlTree (xml_data); + path_list = parser.parse_svg_file (xml_tree.get_root ()); glyph = MainWindow.get_current_glyph (); foreach (Path p in path_list.paths) { - glyph.add_path (p); - } - - foreach (Path p in path_list.paths) { - glyph.add_active_path (null, p); // FIXME: groups - p.update_region_boundaries (); + PathObject path = new PathObject.for_path (p); + glyph.add_object (path); + glyph.add_active_object (null, path); // FIXME: groups + path.update_region_boundaries (); } glyph.close_path (); @@ -197,10 +187,10 @@ import_svg_data (svg_data); } - private PathList parse_svg_file (Tag tag) { + private PathList parse_svg_file (XmlElement tag) { Layer pl = new Layer (); - foreach (Tag t in tag) { + foreach (XmlElement t in tag) { if (t.get_name () == "g") { parse_layer (t, pl); @@ -235,10 +225,10 @@ } } - return pl.get_all_paths (); + return LayerUtils.get_all_paths (pl); } - private void parse_layer (Tag tag, Layer pl) { + private void parse_layer (XmlElement tag, Layer pl) { Layer layer; bool hidden = false; @@ -258,7 +248,7 @@ return; } - foreach (Tag t in tag) { + foreach (XmlElement t in tag) { if (t.get_name () == "path") { parse_path (t, pl); } @@ -266,7 +256,7 @@ if (t.get_name () == "g") { layer = new Layer (); parse_layer (t, layer); - pl.subgroups.add (layer); + pl.objects.add (layer); } if (t.get_name () == "polygon") { @@ -302,12 +292,20 @@ } private void transform (string transform_functions, Layer layer) { - transform_paths (transform_functions, layer.paths); + PathList path_list = new PathList (); + + foreach (SvgBird.Object o in layer.objects) { + if (o is PathObject) { + path_list.add (((PathObject) o).get_path ()); + } + } + + transform_paths (transform_functions, path_list); transform_subgroups (transform_functions, layer); } private void transform_subgroups (string transform_functions, Layer layer) { - foreach (Layer subgroup in layer.subgroups) { + foreach (Layer subgroup in layer.get_sublayers ()) { transform (transform_functions, subgroup); } } @@ -315,7 +313,7 @@ private void transform_paths (string transform_functions, PathList pl) { string data = transform_functions.dup (); string[] functions; - + // use only a single space as separator while (data.index_of (" ") > -1) { data = data.replace (" ", " "); @@ -478,7 +476,7 @@ return param.strip(); } - private void parse_circle (Tag tag, Layer pl) { + private void parse_circle (XmlElement tag, Layer pl) { Path p; double x, y, r; Glyph g; @@ -511,7 +509,7 @@ } } - style = SvgStyle.parse (tag.get_attributes ()); + style = SvgStyle.parse (null, style, tag); if (hidden) { return; @@ -537,11 +535,11 @@ } } - style.apply (npl); - pl.paths.append (npl); + npl.apply_style (style); + append_paths (pl, npl); } - private void parse_ellipse (Tag tag, Layer pl) { + private void parse_ellipse (XmlElement tag, Layer pl) { Path p; double x, y, rx, ry; Glyph g; @@ -579,7 +577,7 @@ } } - style = SvgStyle.parse (tag.get_attributes ()); + style = SvgStyle.parse (null, style, tag); if (hidden) { return; @@ -605,11 +603,11 @@ } } - style.apply (npl); - pl.paths.append (npl); + npl.apply_style (style); + append_paths (pl, npl); } - private void parse_line (Tag tag, Layer pl) { + private void parse_line (XmlElement tag, Layer pl) { Path p; double x1, y1, x2, y2; BezierPoints[] bezier_points; @@ -645,7 +643,7 @@ } } - style = SvgStyle.parse (tag.get_attributes ()); + style = SvgStyle.parse (null, style, tag); if (hidden) { return; @@ -682,11 +680,11 @@ } } - style.apply (npl); - pl.paths.append (npl); + npl.apply_style (style); + append_paths (pl, npl); } - private void parse_rect (Tag tag, Layer pl) { + private void parse_rect (XmlElement tag, Layer layer) { Path p; double x, y, x2, y2; BezierPoints[] bezier_points; @@ -723,7 +721,7 @@ } } - style = SvgStyle.parse (tag.get_attributes ()); + style = SvgStyle.parse (null, style, tag); if (hidden) { return; @@ -783,26 +781,29 @@ } } - style.apply (npl); - pl.paths.append (npl); + npl.apply_style (style); + append_paths (layer, npl); } - private void parse_polygon (Tag tag, Layer pl) { + private void parse_polygon (XmlElement tag, Layer layer) { PathList path_list = get_polyline (tag); foreach (Path p in path_list.paths) { p.close (); } - pl.paths.append (path_list); + append_paths (layer, path_list); } + static void append_paths (Layer layer, PathList pl) { + LayerUtils.append_paths (layer, pl); + } - private void parse_polyline (Tag tag, Layer pl) { - pl.paths.append (get_polyline (tag)); + private void parse_polyline (XmlElement tag, Layer layer) { + append_paths (layer, get_polyline (tag)); } - private PathList get_polyline (Tag tag) { + private PathList get_polyline (XmlElement tag) { Path p = new Path (); bool hidden = false; PathList path_list = new PathList (); @@ -818,14 +819,14 @@ } } - style = SvgStyle.parse (tag.get_attributes ()); + style = SvgStyle.parse (null, style, tag); if (hidden) { return path_list; } path_list.add (p); - style.apply (path_list); + path_list.apply_style (style); foreach (Attribute attr in tag.get_attributes ()) { if (attr.get_name () == "transform") { @@ -836,7 +837,7 @@ return path_list; } - private void parse_path (Tag tag, Layer pl) { + private void parse_path (XmlElement tag, Layer layer) { Glyph glyph = MainWindow.get_current_glyph (); PathList path_list = new PathList (); SvgStyle style = new SvgStyle (); @@ -858,42 +859,52 @@ } } - style = SvgStyle.parse (tag.get_attributes ()); + style = SvgStyle.parse (null, style, tag); if (hidden) { return; } - - pl.paths.append (path_list); - style.apply (path_list); + + foreach (Path path in path_list.paths) { + LayerUtils.add_path (layer, path); + } + + path_list.apply_style (style); // assume the even odd rule is applied and convert the path // to a path using the non-zero rule int inside_count; bool inside; - foreach (Path p1 in pl.paths.paths) { - inside_count = 0; - - foreach (Path p2 in pl.paths.paths) { - if (p1 != p2) { - inside = true; + foreach (SvgBird.Object o1 in layer.objects) { + if (o1 is PathObject) { + Path p1 = ((PathObject) o1).get_path (); + inside_count = 0; + + foreach (SvgBird.Object o2 in layer.objects) { + if (o2 is PathObject) { + Path p2 = ((PathObject) o2).get_path (); - foreach (EditPoint ep in p1.points) { - if (!is_inside (ep, p2)) { - inside = false; + if (p1 != p2) { + inside = true; + + foreach (EditPoint ep in p1.points) { + if (!is_inside (ep, p2)) { + inside = false; + } + } + + if (inside) { + inside_count++; + } } - } - - if (inside) { - inside_count++; } } - } - - if (inside_count % 2 == 0) { - p1.force_direction (Direction.CLOCKWISE); - } else { - p1.force_direction (Direction.COUNTER_CLOCKWISE); + + if (inside_count % 2 == 0) { + p1.force_direction (Direction.CLOCKWISE); + } else { + p1.force_direction (Direction.COUNTER_CLOCKWISE); + } } } @@ -996,49 +1007,6 @@ } return inside; - } - - /** Add space as separator to svg data. - * @param d svg data - */ - static string add_separators (string d) { - string data = d; - - data = data.replace (",", " "); - data = data.replace ("a", " a "); - data = data.replace ("A", " A "); - data = data.replace ("m", " m "); - data = data.replace ("M", " M "); - data = data.replace ("h", " h "); - data = data.replace ("H", " H "); - data = data.replace ("v", " v "); - data = data.replace ("V", " V "); - data = data.replace ("l", " l "); - data = data.replace ("L", " L "); - data = data.replace ("q", " q "); - data = data.replace ("Q", " Q "); - data = data.replace ("c", " c "); - data = data.replace ("C", " C "); - data = data.replace ("t", " t "); - data = data.replace ("T", " T "); - data = data.replace ("s", " s "); - data = data.replace ("S", " S "); - data = data.replace ("zM", " z M "); - data = data.replace ("zm", " z m "); - data = data.replace ("z", " z "); - data = data.replace ("Z", " Z "); - data = data.replace ("-", " -"); - data = data.replace ("e -", "e-"); // minus can be either separator or a negative exponent - data = data.replace ("\t", " "); - data = data.replace ("\r\n", " "); - data = data.replace ("\n", " "); - - // use only a single space as separator - while (data.index_of (" ") > -1) { - data = data.replace (" ", " "); - } - - return data; } public void add_path_to_glyph (string d, Glyph g, bool svg_glyph = false, double units = 1) { @@ -1048,6 +1016,36 @@ } } + public void get_bezier_points (string svg_data, out BezierPoints[] instructions, out int points, bool svg_glyph) { + SvgFile.get_bezier_points (svg_data, out instructions, out points, svg_glyph); + Gee.ArrayList<BezierPoints> bezier_points = new Gee.ArrayList<BezierPoints> (); + BezierPoints[] arc_data = new BezierPoints[8]; + + for (int i = 0; i < points; i++) { + if (instructions[i].type == 'A') { + int arc_index = 0; + + add_arc_points (arc_data, ref arc_index, + instructions[i].x0, instructions[i].y0, + instructions[i].rx, instructions[i].ry, + instructions[i].angle, + instructions[i].large_arc, + instructions[i].sweep, + instructions[i].x1, instructions[i].y1); + + for (int j = 0; j < arc_index; j++) { + bezier_points.add (instructions[j]); + } + } + + bezier_points.add (instructions[i]); + } + + instructions = new BezierPoints[bezier_points.size]; + for (int i = 0; i < bezier_points.size; i++) { + instructions[i] = bezier_points.get (i); + } + } /** * @param d svg data * @param glyph use lines from this glyph but don't add the generated paths @@ -1056,556 +1054,25 @@ * @return the new paths */ public PathList parse_svg_data (string d, Glyph glyph, bool svg_glyph = false, double units = 1) { - double px = 0; - double py = 0; - double px2 = 0; - double py2 = 0; - double cx = 0; - double cy = 0; - string data; Font font; PathList path_list = new PathList (); BezierPoints[] bezier_points; - string[] c; - double arc_rx, arc_ry; - double arc_rotation; - int large_arc; - int arc_sweep; - double arc_dest_x, arc_dest_y; + int points; font = BirdFont.get_current_font (); - - data = add_separators (d); - c = data.split (" "); - bezier_points = new BezierPoints[8 * c.length + 1]; // the arc instruction can use up to eight points - - for (int i = 0; i < 2 * c.length + 1; i++) { - bezier_points[i] = new BezierPoints (); - } - - int bi = 0; - - // parse path - int i = -1; - while (++i < c.length && bi < bezier_points.length) { - if (c[i] == "m") { - while (i + 2 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'M'; - bezier_points[bi].svg_type = 'm'; - - px += parse_double (c[++i]); - - if (svg_glyph) { - py += parse_double (c[++i]); - } else { - py += -parse_double (c[++i]); - } - - bezier_points[bi].x0 = px; - bezier_points[bi].y0 = py; - bi++; - } - } else if (c[i] == "M") { - while (i + 2 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'M'; - bezier_points[bi].svg_type = 'M'; - - px = parse_double (c[++i]); - - if (svg_glyph) { - py = parse_double (c[++i]); - } else { - py = -parse_double (c[++i]); - } - - bezier_points[bi].x0 = px; - bezier_points[bi].y0 = py; - bi++; - } - } else if (c[i] == "h") { - while (i + 1 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'L'; - bezier_points[bi].svg_type = 'h'; - - px += parse_double (c[++i]); - - bezier_points[bi].x0 = px; - bezier_points[bi].y0 = py; - bi++; - } - } else if (i + 1 < c.length && c[i] == "H") { - while (is_point (c[i + 1])) { - bezier_points[bi].type = 'L'; - bezier_points[bi].svg_type = 'H'; - - px = parse_double (c[++i]); - - bezier_points[bi].x0 = px; - bezier_points[bi].y0 = py; - bi++; - } - } else if (c[i] == "v") { - while (i + 1 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'L'; - bezier_points[bi].svg_type = 'v'; - - if (svg_glyph) { - py = py + parse_double (c[++i]); - } else { - py = py - parse_double (c[++i]); - } - - bezier_points[bi].x0 = px; - bezier_points[bi].y0 = py; - bi++; - } - } else if (i + 1 < c.length && c[i] == "V") { - while (is_point (c[i + 1])) { - bezier_points[bi].type = 'L'; - bezier_points[bi].svg_type = 'V'; - - if (svg_glyph) { - py = parse_double (c[++i]); - } else { - py = -parse_double (c[++i]); - } - - bezier_points[bi].x0 = px; - bezier_points[bi].y0 = py; - bi++; - } - } else if (c[i] == "l") { - while (i + 2 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'L'; - bezier_points[bi].svg_type = 'l'; - - cx = px + parse_double (c[++i]); - - if (svg_glyph) { - cy = py + parse_double (c[++i]); - } else { - cy = py - parse_double (c[++i]); - } - - px = cx; - py = cy; - - bezier_points[bi].x0 = cx; - bezier_points[bi].y0 = cy; - bi++; - } - } else if (c[i] == "L") { - while (i + 2 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'L'; - bezier_points[bi].svg_type = 'L'; - - cx = parse_double (c[++i]); - - if (svg_glyph) { - cy = parse_double (c[++i]); - } else { - cy = -parse_double (c[++i]); - } - - px = cx; - py = cy; - - bezier_points[bi].x0 = cx; - bezier_points[bi].y0 = cy; - bi++; - } - } else if (c[i] == "c") { - while (i + 6 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'C'; - bezier_points[bi].svg_type = 'C'; - - cx = px + parse_double (c[++i]); - - if (svg_glyph) { - cy = py + parse_double (c[++i]); - } else { - cy = py - parse_double (c[++i]); - } - - bezier_points[bi].x0 = cx; - bezier_points[bi].y0 = cy; - - cx = px + parse_double (c[++i]); - - if (svg_glyph) { - cy = py + parse_double (c[++i]); - } else { - cy = py - parse_double (c[++i]); - } - - px2 = cx; - py2 = cy; - - bezier_points[bi].x1 = px2; - bezier_points[bi].y1 = py2; - - cx = px + parse_double (c[++i]); - - if (svg_glyph) { - cy = py + parse_double (c[++i]); - } else { - cy = py + -parse_double (c[++i]); - } - - bezier_points[bi].x2 = cx; - bezier_points[bi].y2 = cy; - - px = cx; - py = cy; - - bi++; - } - } else if (c[i] == "C") { - while (i + 6 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'C'; - bezier_points[bi].svg_type = 'C'; - - cx = parse_double (c[++i]); - - if (svg_glyph) { - cy = parse_double (c[++i]); - } else { - cy = -parse_double (c[++i]); - } - - bezier_points[bi].x0 = cx; - bezier_points[bi].y0 = cy; - - cx = parse_double (c[++i]); - - if (svg_glyph) { - cy = parse_double (c[++i]); - } else { - cy = -parse_double (c[++i]); - } - - px2 = cx; - py2 = cy; - - bezier_points[bi].x1 = cx; - bezier_points[bi].y1 = cy; - - cx = parse_double (c[++i]); - - if (svg_glyph) { - cy = parse_double (c[++i]); - } else { - cy = -parse_double (c[++i]); - } - - bezier_points[bi].x2 = cx; - bezier_points[bi].y2 = cy; - - px = cx; - py = cy; - - bi++; - } - } else if (c[i] == "q") { - while (i + 4 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'Q'; - bezier_points[bi].svg_type = 'q'; - - cx = px + parse_double (c[++i]); - - if (svg_glyph) { - cy = py + parse_double (c[++i]); - } else { - cy = py - parse_double (c[++i]); - } - - bezier_points[bi].x0 = cx; - bezier_points[bi].y0 = cy; - - px2 = cx; - py2 = cy; - - cx = px + parse_double (c[++i]); - - if (svg_glyph) { - cy = py + parse_double (c[++i]); - } else { - cy = py - parse_double (c[++i]); - } - - bezier_points[bi].x1 = cx; - bezier_points[bi].y1 = cy; - - px = cx; - py = cy; - - bi++; - } - } else if (c[i] == "Q") { - - while (i + 4 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'Q'; - bezier_points[bi].svg_type = 'Q'; - - cx = parse_double (c[++i]); - - if (svg_glyph) { - cy = parse_double (c[++i]); - } else { - cy = -parse_double (c[++i]); - } - - bezier_points[bi].x0 = cx; - bezier_points[bi].y0 = cy; - - px2 = cx; - py2 = cy; - - cx = parse_double (c[++i]); - - if (svg_glyph) { - cy = parse_double (c[++i]); - } else { - cy = -parse_double (c[++i]); - } - - px = cx; - py = cy; - - bezier_points[bi].x1 = cx; - bezier_points[bi].y1 = cy; - - bi++; - } - } else if (c[i] == "t") { - while (i + 2 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'Q'; - bezier_points[bi].svg_type = 't'; - - // the first point is the reflection - cx = 2 * px - px2; - cy = 2 * py - py2; // if (svg_glyph) ? - - bezier_points[bi].x0 = cx; - bezier_points[bi].y0 = cy; - - px2 = cx; - py2 = cy; - - cx = px + parse_double (c[++i]); - - if (svg_glyph) { - cy = py + parse_double (c[++i]); - } else { - cy = py - parse_double (c[++i]); - } - - px = cx; - py = cy; - - bezier_points[bi].x1 = px; - bezier_points[bi].y1 = py; - - bi++; - } - } else if (c[i] == "T") { - while (i + 2 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'Q'; - bezier_points[bi].svg_type = 'T'; - - // the reflection - cx = 2 * px - px2; - cy = 2 * py - py2; // if (svg_glyph) ? - - bezier_points[bi].x0 = cx; - bezier_points[bi].y0 = cy; - - px2 = cx; - py2 = cy; - - cx = parse_double (c[++i]); - - if (svg_glyph) { - cy = parse_double (c[++i]); - } else { - cy = -parse_double (c[++i]); - } - - px = cx; - py = cy; - - bezier_points[bi].x1 = px; - bezier_points[bi].y1 = py; - - bi++; - } - } else if (c[i] == "s") { - while (i + 4 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'C'; - bezier_points[bi].svg_type = 's'; - - // the first point is the reflection - cx = 2 * px - px2; - cy = 2 * py - py2; // if (svg_glyph) ? - - bezier_points[bi].x0 = cx; - bezier_points[bi].y0 = cy; - - cx = px + parse_double (c[++i]); - - if (svg_glyph) { - cy = py + parse_double (c[++i]); - } else { - cy = py - parse_double (c[++i]); - } - - px2 = cx; - py2 = cy; - - bezier_points[bi].x1 = px2; - bezier_points[bi].y1 = py2; - - cx = px + parse_double (c[++i]); - - if (svg_glyph) { - cy = py + parse_double (c[++i]); - } else { - cy = py - parse_double (c[++i]); - } - - bezier_points[bi].x2 = cx; - bezier_points[bi].y2 = cy; - - px = cx; - py = cy; - - bi++; - } - } else if (c[i] == "S") { - while (i + 4 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'C'; - bezier_points[bi].svg_type = 'S'; - - // the reflection - cx = 2 * px - px2; - cy = 2 * py - py2; // if (svg_glyph) ? - - bezier_points[bi].x0 = cx; - bezier_points[bi].y0 = cy; - - // the other two are regular cubic points - cx = parse_double (c[++i]); - - if (svg_glyph) { - cy = parse_double (c[++i]); - } else { - cy = -parse_double (c[++i]); - } - - px2 = cx; - py2 = cy; - - bezier_points[bi].x1 = px2; - bezier_points[bi].y1 = py2; - - cx = parse_double (c[++i]); - - if (svg_glyph) { - cy = parse_double (c[++i]); - } else { - cy = -parse_double (c[++i]); - } - - bezier_points[bi].x2 = cx; - bezier_points[bi].y2 = cy; - - px = cx; - py = cy; - - bi++; - } - } else if (c[i] == "a") { - while (i + 7 < c.length && is_point (c[i + 1])) { - arc_rx = parse_double (c[++i]); - arc_ry = parse_double (c[++i]); - - arc_rotation = parse_double (c[++i]); - large_arc = parse_int (c[++i]); - arc_sweep = parse_int (c[++i]); - - cx = px + parse_double (c[++i]); - - if (svg_glyph) { - cy = py + parse_double (c[++i]); - } else { - cy = py - parse_double (c[++i]); - } - - arc_dest_x = cx; - arc_dest_y = cy; - - add_arc_points (bezier_points, ref bi, px, py, arc_rx, arc_ry, arc_rotation, large_arc == 1, arc_sweep == 1, cx, cy); - - px = cx; - py = cy; - - - } - } else if (i + 7 < c.length && c[i] == "A") { - while (is_point (c[i + 1])) { - arc_rx = parse_double (c[++i]); - arc_ry = parse_double (c[++i]); - - arc_rotation = parse_double (c[++i]); - large_arc = parse_int (c[++i]); - arc_sweep = parse_int (c[++i]); - - cx = parse_double (c[++i]); - - if (svg_glyph) { - cy = parse_double (c[++i]); - } else { - cy = -parse_double (c[++i]); - } - - arc_dest_x = cx; - arc_dest_y = cy; - - add_arc_points (bezier_points, ref bi, px, py, arc_rx, arc_ry, arc_rotation, large_arc == 1, arc_sweep == 1, cx, cy); - - px = cx; - py = cy; - - - } - } else if (c[i] == "z") { - bezier_points[bi].type = 'z'; - bezier_points[bi].svg_type = 'z'; - - bi++; - } else if (c[i] == "Z") { - bezier_points[bi].type = 'z'; - bezier_points[bi].svg_type = 'z'; - - bi++; - } else if (c[i] == "") { - } else if (c[i] == " ") { - } else { - warning (@"Unknown instruction: $(c[i])"); - } - } - - if (bi == 0) { + SvgFile.get_bezier_points (d, out bezier_points, out points, svg_glyph); + + if (points == 0) { warning ("No points in path."); return path_list; } - move_and_resize (bezier_points, bi, svg_glyph, units, glyph); + move_and_resize (bezier_points, points, svg_glyph, units, glyph); if (format == SvgFormat.ILLUSTRATOR) { - path_list = create_paths_illustrator (bezier_points, bi); + path_list = create_paths_illustrator (bezier_points, points); } else { - path_list = create_paths_inkscape (bezier_points, bi); + path_list = create_paths_inkscape (bezier_points, points); } // TODO: Find out if it is possible to tie handles. @@ -2016,29 +1483,13 @@ return path_list; } - // TODO: implement a default svg parser - - static int parse_int (string? s) { - if (is_null (s)) { - warning ("null instead of string"); - return 0; - } - - if (!is_point ((!) s)) { - warning (@"Expecting an integer got: $((!) s)"); - return 0; - } - - return int.parse ((!) s); - } - - static double parse_double (string? s) { - if (is_null (s)) { + public static double parse_double (string? s) { + if (unlikely (is_null (s))) { warning ("Got null instead of expected string."); return 0; } - if (!is_point ((!) s)) { + if (unlikely (!is_point ((!) s))) { warning (@"Expecting a double got: $((!) s)"); return 0; } @@ -2056,7 +1507,7 @@ } Path parse_poly_data (string polygon_points) { - string data = add_separators (polygon_points); + string data = SvgFile.add_separators (polygon_points); string[] c = data.split (" "); Path path; BezierPoints[] bezier_points = new BezierPoints[c.length + 1]; @@ -2077,7 +1528,7 @@ } bezier_points[bi] = new BezierPoints (); - bezier_points[bi].type == 'L'; + bezier_points[bi].type = 'L'; bezier_points[bi].x0 = parse_double (c[i]); bezier_points[bi].y0 = -parse_double (c[i + 1]); bi++; @@ -2096,8 +1547,84 @@ path.recalculate_linear_handles (); return path; + } + + public static EmbeddedSvg parse_embedded_svg_file (string path) { + string xml_data; + + try { + FileUtils.get_contents (path, out xml_data); + return parse_embedded_svg_data (xml_data); + } catch (GLib.Error error) { + warning (error.message); + } + + SvgDrawing drawing = new SvgDrawing (); + return new EmbeddedSvg (drawing); + } + + public static EmbeddedSvg parse_embedded_svg_data (string xml_data) { + XmlTree tree = new XmlTree (xml_data); + SvgDrawing drawing = new SvgDrawing (); + SvgFile svg_file = new SvgFile (); + + XmlElement root = tree.get_root (); + drawing = svg_file.parse_svg_file (root); + EmbeddedSvg svg = new EmbeddedSvg (drawing); + svg.svg_data = xml_data; + return svg; + } + + /** Convert an SVG arc instruction to a Beziér path. */ + public static void add_arc_points (BezierPoints[] bezier_points, ref int bi, + double x0, double y0, double rx, double ry, double angle, + bool largeArcFlag, bool sweepFlag, double x, double y) { + + double angleStart, angleExtent; + double s, step, theta; + double cx, cy; + + cx = 0; + cy = 0; + + // Approximate the path with Beziér points + SvgBird.get_arc_arguments (x0, y0, rx, ry, angle, largeArcFlag, sweepFlag, x, y, + out angleStart, out angleExtent, out cx, out cx); + + s = (angleExtent > 0) ? 1 : -1; + step = fabs (angleExtent) / (2 * fabs (angleExtent)); + + theta = PI - angleStart - angleExtent; + + bezier_points[bi].type = 'C'; + bezier_points[bi].svg_type = 'a'; + + bezier_points[bi].x0 = cx + rx * cos (theta); + bezier_points[bi].y0 = cy + ry * sin (theta); + + bi++; + + for (double a = 0; a < fabs (angleExtent); a += step) { + theta = PI - angleStart - angleExtent + s * a; + + return_if_fail (0 <= bi < bezier_points.length); + + bezier_points[bi].type = 'S'; + bezier_points[bi].svg_type = 'a'; + + bezier_points[bi].x0 = cx + rx * cos (theta); + bezier_points[bi].y0 = cy + ry * sin (theta); + + bezier_points[bi].x1 = cx + rx * cos (theta + 1 * step / 4); + bezier_points[bi].y1 = cy + ry * sin (theta + 1 * step / 4); + + bezier_points[bi].x2 = cx + rx * cos (theta + 2 * step / 4); + bezier_points[bi].y2 = cy + ry * sin (theta + 2 * step / 4); + + bi++; + } } } }
diff --git libbirdfont/SvgStyle.vala(deleted)
--- a/libbirdfont/SvgStyle.vala +++ /dev/null @@ -1,125 +1,1 @@ - /* - Copyright (C) 2015 Johan Mattsson - - This library is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as - published by the Free Software Foundation; either version 3 of the - License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - */ - - using B; - using Math; - - namespace BirdFont { - - public class SvgStyle { - - Gee.HashMap<string, string> style; - - public SvgStyle () { - style = new Gee.HashMap<string, string> (); - } - - public LineCap get_line_cap () { - string l; - - if (!style.has_key ("stroke-linecap")) { - return LineCap.BUTT; - } - - l = style.get ("stroke-linecap"); - - if (l == "round") { - return LineCap.ROUND; - } else if (l == "square") { - return LineCap.SQUARE; - } - - return LineCap.BUTT; - } - - public bool has_stroke () { - bool s = true; - - if (style.has_key ("stroke")) { - s = style.get ("stroke") != "none"; - } - - return get_stroke_width () > 0 && s; - } - - public double get_stroke_width () { - if (!style.has_key ("stroke-width")) { - return 0; - } - - return double.parse (style.get ("stroke-width")); - } - - - public static SvgStyle parse (Attributes attributes) { - SvgStyle s = new SvgStyle (); - - foreach (Attribute a in attributes) { - if (a.get_name () == "style") { - s.parse_key_value_pairs (a.get_content ()); - } - - if (a.get_name () == "stroke-width") { - s.style.set ("stroke-width", a.get_content ()); - } - - if (a.get_name () == "stroke") { - s.style.set ("stroke", a.get_content ()); - } - - if (a.get_name () == "fill") { - s.style.set ("fill", a.get_content ()); - } - } - - return s; - } - - void parse_key_value_pairs (string svg_style) { - string[] p = svg_style.split (";"); - string[] pair; - string k, v; - - foreach (string kv in p) { - pair = kv.split (":"); - - if (pair.length != 2) { - warning ("pair.length != 2"); - continue; - } - - k = pair[0]; - v = pair[1]; - - style.set (k, v); - } - } - - public void apply (PathList path_list) { - foreach (Path p in path_list.paths) { - if (has_stroke ()) { - p.stroke = get_stroke_width (); - } else { - p.stroke = 0; - } - - p.line_cap = get_line_cap (); - p.reset_stroke (); - p.update_region_boundaries (); - } - } - } - - }
--- a/libbirdfont/TabContent.vala +++ b/libbirdfont/TabContent.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2014 2015 Johan Mattsson + Copyright (C) 2014 - 2016 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -72,7 +72,7 @@ public static void draw (WidgetAllocation allocation, Context cr) { AbstractMenu menu; Dialog dialog; - + if (unlikely (MenuTab.has_suppress_event ())) { cr.save (); Theme.color (cr, "Background 1"); @@ -110,6 +110,12 @@ if (text_input_visible) { draw_text_input (allocation, cr); } + } + + Help help = MainWindow.get_help (); + + if (help.is_visible ()) { + help.draw (cr, allocation); } } @@ -212,6 +218,12 @@ } last_press_time = GLib.get_real_time (); + + Help help = MainWindow.get_help (); + + if (help.button_press (button, x, y)) { + return; // event consumed by help text + } if (MainWindow.get_dialog ().visible) { MainWindow.get_dialog ().button_press (button, x, y);
--- a/libbirdfont/Test.vala +++ b/libbirdfont/Test.vala @@ -14,7 +14,7 @@ namespace BirdFont { - public class Test : Object { + public class Test : GLib.Object { public Callback callback; public string name; double time_stamp;
--- a/libbirdfont/TestCases.vala +++ b/libbirdfont/TestCases.vala @@ -33,7 +33,6 @@ add (test_drawing, "Pen tool"); add (test_delete_points, "Delete edit points"); add (test_convert_to_quadratic_bezier_path, "Convert to quadratic path"); - add (test_over_path, "Over path"); add (test_export, "Export"); add (test_background_coordinates, "Background coordinates"); add (test_spin_button, "Spin button"); @@ -580,58 +579,6 @@ 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"); - test_select_action (pen_tool); - test_open_next_glyph (); - - g = MainWindow.get_current_glyph (); - - test_click_action (pen_tool, 3, 10, 10); - test_click_action (pen_tool, 3, 10, 10); - test_click_action (pen_tool, 3, 100, 10); - test_click_action (pen_tool, 3, 100, 100); - test_click_action (pen_tool, 3, 10, 100); - test_click_action (pen_tool, 2, 0, 0); - - g.close_path (); - - warn_if_fail (g.active_paths.size == 0); - - g.select_path (50, 50); - - warn_if_fail (g.active_paths.size == 1); - - p.add (-10, 10); - p.add (10, 10); - p.add (10, -10); - p.add (-10, -10); - p.update_region_boundaries(); - 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)); - } - } }
--- /dev/null +++ b/libbirdfont/TextRendering/CachedFont.vala @@ -1,1 +1,100 @@ + /* + Copyright (C) 2015 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Gee; + + namespace BirdFont { + + public class CachedFont : GLib.Object { + public Font? font; + + public double top_limit { + get { return _top_limit; } + set { _top_limit = value; } + } + + public double bottom_limit { + get { return _bottom_limit; } + set { _bottom_limit = value; } + } + + public double base_line = 0; + double _top_limit = 92.77; // FIXME: load before first glyph + double _bottom_limit = -24.4; + + static FallbackFont fallback_font { + get { + if (_fallback_font == null) { + _fallback_font = new FallbackFont (); + } + + return (!) _fallback_font; + } + } + static FallbackFont? _fallback_font = null; + + public CachedFont (Font? font) { + Glyph? g; + Glyph glyph; + + this.font = font; + + g = get_glyph_by_name ("a"); + if (g != null) { + glyph = (!) g; + base_line = glyph.baseline; + top_limit = glyph.top_limit; + bottom_limit = glyph.bottom_limit; + } else { + warning("No default chararacter found in font."); + } + } + + public Glyph? get_glyph_by_name (string name) { + Glyph? g = null; + Font f; + Glyph glyph; + + if (font != null) { + f = (!) font; + g = f.get_glyph_by_name (name); + + if (g != null) { + glyph = (!) g; + glyph.top_limit = f.top_limit; + glyph.baseline = f.base_line; + glyph.bottom_limit = f.bottom_limit; + } + } + + if (g == null && name.char_count () == 1) { + f = fallback_font.get_single_glyph_font (name.get_char (0)); + g = f.get_glyph_by_name (name); + + if (g == null) { + return null; + } + + glyph = (!) g; + glyph.top_limit = f.top_limit; + glyph.baseline = f.base_line; + glyph.bottom_limit = f.bottom_limit; + } + + return g; + } + } + + }
--- /dev/null +++ b/libbirdfont/TextRendering/Drawing.vala @@ -1,1 +1,51 @@ + /* + Copyright (C) 2014 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + namespace BirdFont { + + /** Interface for creating drawing callbacks. */ + [Compact] + [CCode (ref_function = "bird_font_drawing_ref", unref_function = "bird_font_drawing_unref")] + public class Drawing { + + public int iterator_refcount = 1; + + public Drawing () { + } + + public void new_path (double x, double y) { + } + + public void curve_to (double xb, double yb, double xc, double yc, double xd, double yd) { + } + + public void close_path (double x, double y) { + } + + public unowned Drawing @ref () { + iterator_refcount++; + return this; + } + + public void unref () { + if (--iterator_refcount == 0) { + this.free (); + } + } + + private extern void free (); + } + + }
--- /dev/null +++ b/libbirdfont/TextRendering/FallbackFont.vala @@ -1,1 +1,338 @@ + /* + Copyright (C) 2015 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Gee; + + [SimpleType] + [CCode (has_type_id = false)] + public extern struct FcConfig { + } + + [CCode (cname = "FcInitLoadConfigAndFonts")] + public extern FcConfig* FcInitLoadConfigAndFonts (); + + [CCode (cname = "FcConfigAppFontAddDir")] + public extern string* FcConfigAppFontAddDir (FcConfig* config, string path); + + [CCode (cname = "FcConfigSetSysRoot")] + public extern void FcConfigSetSysRoot (FcConfig* config, string path); + + [CCode (cname = "FcConfigParseAndLoad")] + public extern bool FcConfigParseAndLoad (FcConfig* config, string path, bool complain); + + [CCode (cname = "FcConfigSetCurrent")] + public extern void FcConfigSetCurrent (FcConfig* config); + + [CCode (cname = "FcConfigCreate")] + public extern FcConfig* FcConfigCreate (); + + [CCode (cname = "FcConfigFilename")] + public extern string FcConfigFilename (string path); + + [CCode (cname = "find_font")] + public extern string? find_font (FcConfig* font_config, string characters); + + [CCode (cname = "find_font_family")] + public extern string? find_font_family (FcConfig* font_config, string characters); + + [CCode (cname = "find_font_file")] + public extern string? find_font_file (FcConfig* font_config, string font_name); + + namespace BirdFont { + + // TODO: use font config + public class FallbackFont : GLib.Object { + Gee.ArrayList<File> font_directories; + + FontFace* default_font = null; + public static FcConfig* font_config = null; + static bool font_config_started = false; + + string default_font_file_name = "Roboto-Regular.ttf"; + string default_font_family_name = "Roboto"; + + Gee.HashMap<unichar, CachePair> glyphs; + Gee.ArrayList<CachePair> cached; + + public int max_cached_fonts = 300; + + string? default_font_file = null; + + public FallbackFont () { + string home = Environment.get_home_dir (); + font_directories = new Gee.ArrayList<File> (); + + if (!font_config_started) { + font_config_started = true; + + IdleSource idle = new IdleSource (); + idle.set_callback (() => { + Task t = new Task (init_font_config); + MainWindow.native_window.run_non_blocking_background_thread (t); + return false; + }); + idle.attach (null); + } + + add_font_folder ("/usr/share/fonts/"); + add_font_folder ("/usr/local/share/fonts/"); + add_font_folder (home + "/.local/share/fonts"); + add_font_folder (home + "/.fonts"); + add_font_folder ("C:\\Windows\\Fonts"); + add_font_folder (home + "/Library/Fonts"); + add_font_folder ("/Library/Fonts"); + add_font_folder ("/Network/Library/Fonts"); + add_font_folder ("/System/Library/Fonts"); + add_font_folder ("/System Folder/Fonts"); + + glyphs = new Gee.HashMap<unichar, CachePair> (); + cached = new Gee.ArrayList<CachePair> (); + + open_default_font (); + } + + ~FallbackFont () { + if (default_font != null) { + close_font (default_font); + } + } + + public void init_font_config () { + FcConfig* config; + + #if MAC + config = FcConfigCreate(); + + string bundle = (!) BirdFont.get_settings_directory ().get_path (); + FcConfigSetSysRoot(config, bundle); + + string path = FcConfigFilename((!) SearchPaths.search_file(null, "fontconfig.settings").get_path ()); + bool loaded = FcConfigParseAndLoad(config, path, true); + + if (!loaded) { + warning ("Fontconfig initialization failed."); + } + + FcConfigSetCurrent (config); + #else + config = FcInitLoadConfigAndFonts (); + #endif + + IdleSource idle = new IdleSource (); + + idle.set_callback (() => { + font_config = config; + printd("Fontconfig loaded\n"); + return false; + }); + idle.attach (null); + } + + public Font get_single_glyph_font (unichar c) { + Font f; + unichar last; + CachePair p; + + if (likely (glyphs.has_key (c))) { + p = glyphs.get (c); + + if (p.referenced < int.MAX) { + p.referenced++; + } + + return p.font; + } + + // remove glyphs from cache if it is full + if (cached.size > max_cached_fonts - 100) { + + cached.sort ((a, b) => { + CachePair pa = (CachePair) a; + CachePair pb = (CachePair) b; + return pb.referenced - pa.referenced; + }); + + int j = 0; + for (int i = cached.size - 1; i > 0; i--) { + if (j > 100) { + break; + } + + j++; + + last = cached.get (i).character; + glyphs.unset (last); + cached.remove_at (i); + } + } + + f = get_single_fallback_glyph_font (c); + p = new CachePair (f, c); + + glyphs.set (c, p); + cached.add (p); + + return (Font) f; + } + + Font get_single_fallback_glyph_font (unichar c) { + string? font_file; + BirdFontFile bf_parser; + Font bf_font; + StringBuilder? glyph_data; + FontFace* font; + + bf_font = new Font (); + font_file = null; + glyph_data = null; + + // don't use fallback font in private use area + if (0xe000 <= c <= 0xf8ff) { + return bf_font; + } + + // control characters + if (c <= 0x001f || (0x007f <= c <= 0x008d)) { + return bf_font; + } + + // check if glyph is available in roboto + if (default_font != null) { + glyph_data = get_glyph_in_font ((!) default_font, c); + } + + // use fontconfig to find a fallback font + if (glyph_data == null) { + font_file = find_font (font_config, (!) c.to_string ()); + if (font_file != null) { + font = open_font ((!) font_file); + glyph_data = get_glyph_in_font (font, c); + close_font (font); + } + } + + if (glyph_data != null) { + bf_parser = new BirdFontFile (bf_font); + bf_parser.load_data (((!) glyph_data).str); + } + + return bf_font; + } + + public StringBuilder? get_glyph_in_font (FontFace* font, unichar c) { + StringBuilder? glyph_data = null; + GlyphCollection gc; + + gc = new GlyphCollection (c, (!)c.to_string ()); + glyph_data = load_glyph (font, (uint) c); + + return glyph_data; + } + + void add_font_folder (string f) { + File folder = File.new_for_path (f); + FileInfo? file_info; + string fn; + string file_attributes; + try { + if (folder.query_exists ()) { + font_directories.add (folder); + + file_attributes = FileAttribute.STANDARD_NAME; + file_attributes += ","; + file_attributes += FileAttribute.STANDARD_TYPE; + var enumerator = folder.enumerate_children (file_attributes, 0); + + while ((file_info = enumerator.next_file ()) != null) { + fn = ((!) file_info).get_name (); + + if (((!)file_info).get_file_type () == FileType.DIRECTORY) { + add_font_folder ((!) get_child (folder, fn).get_path ()); + } + } + } + } catch (GLib.Error e) { + warning (e.message); + } + } + + File search_font_file (string font_file) { + File d, f; + + for (int i = font_directories.size - 1; i >= 0; i--) { + d = font_directories.get (i); + f = get_child (d, font_file); + + if (f.query_exists ()) { + return f; + } + } + + warning (@"The font $font_file not found"); + return File.new_for_path (font_file); + } + + public string? get_default_font_file () { + File font_file; + string? fn = null; + + if (likely (default_font_file != null)) { + return default_font_file; + } + + font_file = SearchPaths.search_file (null, default_font_file_name); + + if (font_file.query_exists ()) { + fn = (!) font_file.get_path (); + } else { + font_file = search_font_file (default_font_file_name); + + if (font_file.query_exists ()) { + fn = (!) font_file.get_path (); + } else { + fn = find_font_file (font_config, default_font_family_name); + } + } + + if (likely (fn != null)) { + default_font_file = fn; + return fn; + } + + warning(default_font_family_name + " not found"); + return null; + } + + void open_default_font () { + string? fn = get_default_font_file (); + + if (fn != null) { + default_font = open_font ((!) fn); + } + } + + class CachePair : GLib.Object { + public Font font; + public unichar character; + public int referenced = 1; + + public CachePair (Font f, unichar c) { + font = f; + character = c; + } + } + } + + }
--- /dev/null +++ b/libbirdfont/TextRendering/FontCache.vala @@ -1,1 +1,84 @@ + /* + Copyright (C) 2014 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Gee; + + namespace BirdFont { + + /** Thread specific font cache. */ + public class FontCache { + public static FallbackFont fallback_font; + + static FontCache? default_cache = null; + Gee.HashMap<string, CachedFont> fonts; + CachedFont fallback; + + public FontCache () { + if (is_null (fallback_font)) { + fallback_font = new FallbackFont (); + } + + fallback = new CachedFont (null); + fonts = new Gee.HashMap<string, CachedFont> (); + } + + public CachedFont get_font (string file_name) { + CachedFont c; + Font f; + bool ok; + + if (file_name == "") { + return fallback; + } + + if (fonts.has_key (file_name)) { + c = fonts.get (file_name); + return c; + } + + f = new Font (); + f.set_file (file_name); + ok = f.load (); + if (!ok) { + stderr.printf ("Can't load %s\n", file_name); + return new CachedFont (null); + } + + c = new CachedFont (f); + + if (file_name == "") { + warning ("No file."); + return c; + } + + fonts.set (file_name, c); + return c; + } + + public static FontCache get_default_cache () { + if (default_cache == null) { + default_cache = new FontCache (); + } + + return (!) default_cache; + } + + public CachedFont get_fallback () { + return fallback; + } + + } + + }
--- /dev/null +++ b/libbirdfont/TextRendering/LineTextArea.vala @@ -1,1 +1,34 @@ + /* + Copyright (C) 2014 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Cairo; + using Math; + + namespace BirdFont { + + public class LineTextArea : TextArea { + + public LineTextArea (double size) { + base (size); + + single_line = true; + min_height = size; + height = min_height; + + layout (); + } + } + + }
--- /dev/null +++ b/libbirdfont/TextRendering/Text.vala @@ -1,1 +1,593 @@ + /* + Copyright (C) 2014 2015 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Cairo; + using Math; + + namespace BirdFont { + + /** Test implementation of a birdfont rendering engine. */ + public class Text : Widget { + FontCache font_cache; + public CachedFont cached_font; + + Surface? cache = null; + + public string text; + + private bool use_cache = true; + + GlyphSequence glyph_sequence { + get { + if (gs == null) { + gs = generate_glyphs (); + } + + return (!) gs; + } + } + + Gee.ArrayList<string> glyph_names; + GlyphSequence? gs = null; + + public delegate void Iterator (Glyph glyph, double kerning, bool last); + public double font_size; + double sidebearing_extent = 0; + + public double r = 0; + public double g = 0; + public double b = 0; + public double a = 1; + double truncated_width = -1; + + double margin_left = 0; + + public Text (string text = "", double size = 17, double margin_bottom = 0, Color? color = null) { + this.margin_bottom = margin_bottom; + font_cache = FontCache.get_default_cache (); + cached_font = font_cache.get_fallback (); + + if (color != null) { + Color c = (!) color; + r = c.r; + g = c.g; + b = c.b; + a = c.a; + } + + set_text (text); + set_font_size (size); + } + + public void set_use_cache (bool cache) { + use_cache = cache; + } + + public string get_text () { + return text; + } + + /** Set font for this text area. + * @param font_absolute path to the font file or a file name for one of the font files in search paths. + * @return true if the font was found + */ + public bool load_font (string font_file) { + File path; + File f; + FontCache fc; + + f = File.new_for_path (font_file); + path = (f.query_exists ()) ? f : SearchPaths.find_file (null, font_file); + + fc = FontCache.get_default_cache (); + cached_font = fc.get_font ((!) path.get_path ()); + gs = generate_glyphs (); + + return cached_font.font != null; + } + + public void set_font_size (double height_in_pixels) { + font_size = height_in_pixels; + sidebearing_extent = 0; + + if (gs == null) { // ensure height is loaded for the font + gs = generate_glyphs (); + } + } + + public void set_font_cache (FontCache font_cache) { + this.font_cache = font_cache; + } + + public void set_text (string text) { + this.text = text; + gs = null; + sidebearing_extent = 0; + cache = null; + } + + private GlyphSequence generate_glyphs () { + int index; + unichar c; + string name; + Glyph? g; + GlyphSequence gs; + + gs = new GlyphSequence (); + + glyph_names = new Gee.ArrayList<string> (); + index = 0; + while (text.get_next_char (ref index, out c)) { + name = (!) c.to_string (); + g = cached_font.get_glyph_by_name (name); + + gs.glyph.add (g); + glyph_names.add (name); + } + + return gs; + } + + public void iterate (Iterator iter) { + Glyph glyph; + double w, kern; + int wi; + Glyph? prev; + Glyph? g; + GlyphSequence word_with_ligatures; + GlyphRange? gr_left, gr_right; + GlyphSequence word; + KerningClasses kc; + Font empty = Font.empty; + + glyph = new Glyph.no_lines ("", '\0'); + + w = 0; + prev = null; + kern = 0; + + word = glyph_sequence; + wi = 0; + + if (cached_font.font != null) { + word_with_ligatures = word.process_ligatures ((!) cached_font.font); + } else { + word_with_ligatures = word.process_ligatures (new Font ()); + } + + gr_left = null; + gr_right = null; + + if (cached_font.font != null) { + kc = ((!) cached_font.font).get_kerning_classes (); + } else { + kc = new KerningClasses (empty); + } + + if (word_with_ligatures.glyph.size > 0) { + g = word_with_ligatures.glyph.get (0); + if (g != null) { + margin_left = ((!) g).get_left_side_bearing (); + + if (margin_left < 0) { + margin_left = -margin_left; + } else { + margin_left = 0; + } + } + } + + for (int i = 0; i < word_with_ligatures.glyph.size; i++) { + g = word_with_ligatures.glyph.get (i); + + if (g == null || prev == null || wi == 0) { + kern = 0; + } else { + return_if_fail (wi < word_with_ligatures.ranges.size); + return_if_fail (wi - 1 >= 0); + + gr_left = word_with_ligatures.ranges.get (wi - 1); + gr_right = word_with_ligatures.ranges.get (wi); + + kern = kc.get_kerning_for_pair (((!) prev).get_name (), ((!) g).get_name (), gr_left, gr_right); + } + + // process glyph + if (g == null && (0 <= i < glyph_names.size)) { + g = cached_font.get_glyph_by_name (glyph_names.get (i)); + } + + glyph = (g == null) ? new Glyph ("") : (!) g; + iter (glyph, kern, i + 1 == word_with_ligatures.glyph.size); + prev = g; + wi++; + } + } + + // FIXME: some fonts doesn't have on curve extrema + public double get_extent () { + double x = 0; + + iterate ((glyph, kerning, last) => { + double x1, y1, x2, y2; + double lsb; + + lsb = glyph.left_limit; + + if (!last) { + x += (glyph.get_width () + kerning) * get_scale (glyph); + } else { + glyph.boundaries (out x1, out y1, out x2, out y2); + x += (x2 - lsb) * get_scale (glyph); + } + }); + + return x; + } + + public double get_sidebearing_extent () { + double x ; + + if (likely (sidebearing_extent > 0)) { + return sidebearing_extent; + } + + x = 0; + + iterate ((glyph, kerning, last) => { + double lsb; + lsb = glyph.left_limit; + x += (glyph.get_width () + kerning) * get_scale (glyph); + }); + + sidebearing_extent = x; + return x; + } + + public override double get_height () { + return font_size; + } + + public double get_acender () { + double max_height = 0; + + iterate ((glyph, kerning, last) => { + double x1, y1, x2, y2; + double h; + glyph.boundaries (out x1, out y1, out x2, out y2); + h = Math.fmax (y1, y2) - Math.fmin (y1, y2); + h *= get_scale (glyph) - glyph.baseline * get_scale (glyph); + if (h > max_height) { + max_height = h; + } + }); + + return max_height; + } + + public override double get_width () { + double x = 0; + bool first = true; + + iterate ((glyph, kerning, last) => { + double x1, y1, x2, y2; + double lsb; + + lsb = glyph.left_limit; + + if (first) { + glyph.boundaries (out x1, out y1, out x2, out y2); + x += (glyph.get_width () + kerning - Math.fmin (x1, x2)) * get_scale (glyph); + first = false; + } else if (!last) { + x += (glyph.get_width () + kerning) * get_scale (glyph); + } else { + glyph.boundaries (out x1, out y1, out x2, out y2); + x += (x2 - lsb) * get_scale (glyph); + } + }); + + return x; + } + + public double get_decender () { + double decender_max = get_max_decender (); + return decender_max > 0 ? decender_max : 0; + } + + private double get_max_decender () { + double decender = 0; + double decender_max = 0; + + iterate ((glyph, kerning, last) => { + double x1, y1, x2, y2; + double y; + glyph.boundaries (out x1, out y1, out x2, out y2); + y = Math.fmin (y1, y2); + decender = (glyph.baseline - y) * get_scale (glyph); + if (decender > decender_max) { + decender_max = decender; + } + }); + + return decender_max; + } + + public override void draw (Context cr) { + double descender = cached_font.bottom_limit + cached_font.base_line; + double y = widget_y + get_height () + get_font_scale () * descender; // FIXME: + draw_at_baseline (cr, widget_x, y); + } + + public void draw_at_top (Context cr, double px, double py, string cacheid = "") { + double s = get_font_scale (); + double y = py + s * (cached_font.top_limit - cached_font.base_line); + draw_at_baseline (cr, px, y, cacheid); + } + + public void set_source_rgba (double r, double g, double b, double a) { + if (this.r != r || + this.g != g || + this.b != b || + this.a != a) { + + this.r = r; + this.g = g; + this.b = b; + this.a = a; + cache = null; + } + } + + public string get_cache_id (int offset_x, int offset_y) { + string key; + int64 c; + + c = (((int64) (r * 255)) << 24) + | (((int64) (g * 255)) << 16) + | (((int64) (b * 255)) << 8) + | (((int64) (a * 255)) << 0); + + // FIXME: use binary key + key = @"$font_size $c $offset_x $offset_y"; + + return key; + } + + public void draw_at_baseline (Context cr, double px, double py, string cacheid = "") { + if (cache == null) { + cache = draw_on_cache_surface (cacheid); + } + + double screen_scale = Screen.get_scale (); + double font_scale = get_font_scale (); + double cache_y = py - font_scale * (cached_font.top_limit - cached_font.base_line); + + cr.save(); + cr.scale (1 / screen_scale, 1 / screen_scale); + double scaled_x = (px - margin_left) * screen_scale; + double scaled_y = cache_y * screen_scale; + cr.set_source_surface ((!) cache, (int) rint (scaled_x), (int) rint (scaled_y)); + cr.paint (); + cr.restore(); + } + + Surface draw_on_cache_surface (string cacheid) { + double y; + double ratio; + double cc_y; + Context cr; + Surface cache_surface; + double screen_scale = Screen.get_scale(); + double h = font_size * screen_scale + 1; + + ratio = get_font_scale (); + cc_y = (cached_font.top_limit - cached_font.base_line) * ratio; + + // double x = margin_left * ratio; + double x = 0; + double w = get_sidebearing_extent () * screen_scale + x + margin_left + 1; + + cache_surface = Screen.create_background_surface ((int) w, (int) h); + cr = new Context (cache_surface); + cr.scale (screen_scale, screen_scale); + + y = cc_y; + + if (unlikely (cached_font.base_line != 0)) { + warning ("Base line not zero."); + } + + iterate ((glyph, kerning, last) => { + double end; + + x += kerning * ratio; + end = x + glyph.get_width () * ratio; + + // truncation + if (truncated_width > 0 && end > truncated_width) { + return; + } + + if (use_cache) { + draw_chached (cr, glyph, kerning, last, x, y, cc_y, + ratio, cacheid); + } else { + draw_without_cache (cr, glyph, kerning, last, x, y, cc_y, ratio); + } + + x = end; + }); + + return cache_surface; + } + + void draw_without_cache (Context cr, Glyph glyph, double kerning, bool last, + double x, double y, double cc_y, double ratio) { + + double lsb; + + cr.save (); + cr.set_source_rgba (r, g, b, a); + cr.new_path (); + + lsb = glyph.left_limit; + + foreach (Path path in glyph.get_visible_paths ()) { + draw_path (cr, glyph, path, lsb, x, y, ratio); + } + + cr.fill (); + cr.restore (); + + } + + void draw_chached (Context cr, Glyph glyph, double kerning, bool last, + double x, double y, double cc_y, double ratio, + string cacheid = "") { + + double lsb; + Surface cache; + Surface cached_glyph; + Context cc; + string cache_id; + double glyph_margin_left = glyph.get_left_side_bearing (); + + if (glyph_margin_left < 0) { + glyph_margin_left = -glyph_margin_left; + } else { + glyph_margin_left = 0; + } + + double xp = (x - glyph_margin_left * ratio) * Screen.get_scale (); + double yp = (y - cc_y) * Screen.get_scale (); + int offset_x, offset_y; + + offset_x = (int) (10 * (xp - (int) xp)); + offset_y = (int) (10 * (yp - (int) yp)); + + cache_id = (cacheid == "") ? get_cache_id (offset_x, offset_y) : cacheid; + + if (!glyph.has_cache (cache_id)) { + int w = (int) ((2 * glyph_margin_left * ratio + glyph.get_width ()) * ratio) + 2; + int h = (int) font_size + 2; + cache = Screen.create_background_surface (w, h); + cc = new Context (cache); + + cc.scale(Screen.get_scale (), Screen.get_scale ()); + + lsb = glyph.left_limit - glyph_margin_left; + + cc.save (); + cc.set_source_rgba (r, g, b, a); + cc.new_path (); + + foreach (Path path in glyph.get_visible_paths ()) { + draw_path (cc, glyph, path, lsb, glyph_margin_left * ratio + offset_x / 10.0, cc_y + offset_y / 10.0, ratio); + } + + cc.fill (); + cc.restore (); + + if (use_cache) { + glyph.set_cache (cache_id, cache); + } + + cached_glyph = cache; + } else { + cached_glyph = glyph.get_cache (cache_id); + } + + cr.save (); + cr.set_antialias (Cairo.Antialias.NONE); + cr.scale(1 / Screen.get_scale (), 1 / Screen.get_scale ()); + cr.set_source_surface (cached_glyph, + (int) xp, + (int) yp); + cr.paint (); + cr.restore (); + } + + void draw_path (Context cr, Glyph glyph, Path path, + double lsb, double x, double y, double scale) { + + EditPoint e, prev; + double xa, ya, xb, yb, xc, yc, xd, yd; + double by; + double s = get_scale (glyph); + + if (path.points.size > 0) { + if (unlikely (path.is_open ())) { + warning (@"Path is open in $(glyph.get_name ())."); + } + + //path.add_hidden_double_points (); // FIXME: this distorts shapes + + prev = path.points.get (path.points.size - 1); + xa = (prev.x - lsb) * s + x; + ya = y - prev.y * s; + cr.move_to (xa, ya); + + by = (y - cached_font.base_line * s); + for (int i = 0; i < path.points.size; i++) { + e = path.points.get (i).copy (); + + PenTool.convert_point_segment_type (prev, e, PointType.CUBIC); + + xb = (prev.get_right_handle ().x - lsb) * s + x; + yb = by - prev.get_right_handle ().y * s; + + xc = (e.get_left_handle ().x - lsb) * s + x; + yc = by - e.get_left_handle ().y * s; + + xd = (e.x - lsb) * s + x; + yd = by - e.y * s; + + cr.curve_to (xb, yb, xc, yc, xd, yd); + cr.line_to (xd, yd); + + prev = e; + } + } + } + + public double get_baseline_to_bottom (Glyph g) { + return get_scale (g) * (-g.baseline - g.bottom_limit); + } + + public double get_scale (Glyph g) { + double s = g.top_limit - g.bottom_limit; + + if (s == 0) { + s = cached_font.top_limit - cached_font.bottom_limit; + } + + return font_size / s; + } + + public double get_font_scale () { + return font_size / (cached_font.top_limit - cached_font.bottom_limit); + } + + public double get_baseline_to_bottom_for_font () { + return get_font_scale () * (-cached_font.base_line - cached_font.bottom_limit); + } + + public void truncate (double max_width) { + truncated_width = max_width; + } + } + + }
--- /dev/null +++ b/libbirdfont/TextRendering/TextArea.vala @@ -1,1 +1,1660 @@ + /* + Copyright (C) 2014 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Cairo; + using Math; + + namespace BirdFont { + + public class TextArea : Widget { + + public double min_width = 500; + public double min_height = 100; + public double font_size; + public double padding = 3.3; + public bool single_line = false; + + protected Color text_color = Color.black (); + + public bool draw_carret { + get { return carret_is_visible; } + set { + carret_is_visible = value; + if (!value) { + update_selection = false; + selection_end = carret.copy (); + } + } + } + public bool carret_is_visible = false; + public bool draw_border = true; + + public double width; + public double height; + + Carret carret = new Carret (); + Carret selection_end = new Carret (); + bool update_selection = false; + public bool show_selection = false; + + public signal void scroll (double pixels); + public signal void text_changed (string text); + public signal void enter (string text); + + Gee.ArrayList<Paragraph> paragraphs = new Gee.ArrayList<Paragraph> (); + private static const int DONE = -2; + + int last_paragraph = 0; + string text; + int text_length; + + Gee.ArrayList<TextUndoItem> undo_items = new Gee.ArrayList<TextUndoItem> (); + Gee.ArrayList<TextUndoItem> redo_items = new Gee.ArrayList<TextUndoItem> (); + + bool store_undo_state_at_next_event = false; + + public bool editable; + + public TextArea (double font_size = 20, Color? c = null) { + this.font_size = font_size; + width = min_width; + height = min_height; + editable = true; + + if (c != null) { + text_color = (!) c; + } + } + + public override void focus (bool focus) { + draw_carret = focus; + } + + public override double get_height () { + return height + 2 * padding; + } + + public override double get_width () { + return width + 2 * padding; + } + + public void set_font_size (double z) { + font_size = z; + } + + bool generate_paragraphs () { + Paragraph paragraph; + + int next_paragraph = -1; + + if (is_null (text)) { + warning ("No text"); + return false; + } + + if (last_paragraph == DONE) { + return false; + } + + next_paragraph = text.index_of ("\n", last_paragraph); + + if (next_paragraph == -1) { + paragraph = new Paragraph (text.substring (last_paragraph), font_size, paragraphs.size, text_color); + paragraphs.add (paragraph); + last_paragraph = DONE; + } else { + next_paragraph += "\n".length; + paragraph = new Paragraph (text.substring (last_paragraph, next_paragraph - last_paragraph), font_size, paragraphs.size, text_color); + paragraphs.add (paragraph); + last_paragraph = next_paragraph; + } + + return last_paragraph != DONE; + } + + void generate_all_paragraphs () { + while (generate_paragraphs ()) { + } + } + + public override void key_press (uint keyval) { + unichar c; + TextUndoItem ui; + + if (!editable) { + return; + } + + c = (unichar) keyval; + + switch (c) { + case ' ': + store_undo_edit_state (); + add_character (keyval); + break; + case 'a': + if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { + select_all (); + } else { + add_character (keyval); + } + break; + case 'c': + if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { + ClipTool.copy_text (this); + } else { + add_character (keyval); + } + break; + case 'v': + if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { + ClipTool.paste_text (this); + store_undo_state_at_next_event = true; + } else { + add_character (keyval); + } + break; + case 'y': + if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { + redo (); + } else { + add_character (keyval); + } + break; + case 'z': + if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { + undo (); + } else { + add_character (keyval); + } + break; + case Key.RIGHT: + check_selection (); + move_carret_next (); + break; + case Key.LEFT: + check_selection (); + move_carret_previous (); + break; + case Key.DOWN: + check_selection (); + move_carret_next_row (); + break; + case Key.UP: + check_selection (); + move_carret_previous_row (); + break; + case Key.END: + check_selection (); + move_carret_to_end_of_line (); + break; + case Key.HOME: + check_selection (); + move_carret_to_beginning_of_line (); + break; + case Key.BACK_SPACE: + if (has_selection ()) { + ui = delete_selected_text (); + undo_items.add (ui); + redo_items.clear (); + store_undo_state_at_next_event = true; + } else { + ui = remove_last_character (); + undo_items.add (ui); + redo_items.clear (); + store_undo_state_at_next_event = true; + } + text_changed (get_text ()); + break; + case Key.ENTER: + store_undo_edit_state (); + insert_text ("\n"); + + if (single_line) { + enter (get_text ()); + } + break; + case Key.DEL: + if (has_selection ()) { + ui = delete_selected_text (); + undo_items.add (ui); + redo_items.clear (); + store_undo_state_at_next_event = true; + } else { + ui = remove_next_character (); + undo_items.add (ui); + redo_items.clear (); + store_undo_state_at_next_event = true; + } + text_changed (get_text ()); + break; + default: + if (!KeyBindings.has_ctrl () && !KeyBindings.has_logo ()) { + add_character (keyval); + } + break; + } + + GlyphCanvas.redraw (); + } + + void check_selection () { + if (!has_selection () && KeyBindings.has_shift ()) { + show_selection = true; + selection_end = carret.copy (); + } + + if (!KeyBindings.has_shift ()) { + show_selection = false; + } + } + + private void add_character (uint keyval) { + unichar c = (unichar) keyval; + string s; + + if (!is_modifier_key (keyval) + && !KeyBindings.has_ctrl () + && !KeyBindings.has_alt ()) { + + s = (!) c.to_string (); + + if (s.validate ()) { + if (store_undo_state_at_next_event) { + store_undo_edit_state (); + store_undo_state_at_next_event = false; + } + + insert_text (s); + } + } + } + + Paragraph get_current_paragraph () { + Paragraph p; + + if (unlikely (!(0 <= carret.paragraph < paragraphs.size))) { + warning (@"No paragraph, index: $(carret.paragraph), size: $(paragraphs.size)"); + p = new Paragraph ("", 0, 0, text_color); + paragraphs.add (p); + return p; + } + + p = paragraphs.get (carret.paragraph); + return p; + } + + public void set_text (string t) { + int tl; + + if (single_line) { + text = t.replace ("\n", "").replace ("\r", ""); + } else { + text = t; + } + + tl = t.length; + text_length += tl; + + paragraphs.clear (); + generate_paragraphs (); + + return_if_fail (paragraphs.size != 0); + + carret.paragraph = paragraphs.size - 1; + carret.character_index = paragraphs.get (paragraphs.size - 1).text.length; + selection_end = carret.copy (); + show_selection = false; + + text_changed (get_text ()); + } + + Carret get_selection_start () { + if (carret.paragraph == selection_end.paragraph) { + return carret.character_index < selection_end.character_index ? carret : selection_end; + } + + return carret.paragraph < selection_end.paragraph ? carret : selection_end; + } + + Carret get_selection_stop () { + if (carret.paragraph == selection_end.paragraph) { + return carret.character_index > selection_end.character_index ? carret : selection_end; + } + + return carret.paragraph > selection_end.paragraph ? carret : selection_end; + } + + public string get_selected_text () { + Carret selection_start, selection_stop; + int i; + Paragraph pg; + StringBuilder sb; + + sb = new StringBuilder (); + + if (!has_selection ()) { + return "".dup (); + } + + selection_start = get_selection_start (); + selection_stop = get_selection_stop (); + + if (selection_start.paragraph == selection_stop.paragraph) { + pg = paragraphs.get (selection_start.paragraph); + return pg.text.substring (selection_start.character_index, selection_stop.character_index - selection_start.character_index); + } + + pg = paragraphs.get (selection_start.paragraph); + sb.append (pg.text.substring (selection_start.character_index)); + + for (i = selection_start.paragraph + 1; i < selection_stop.paragraph; i++) { + return_val_if_fail (0 <= i < paragraphs.size, "".dup ()); + pg = paragraphs.get (i); + sb.append (pg.text); + } + + pg = paragraphs.get (selection_stop.paragraph); + sb.append (pg.text.substring (0, selection_stop.character_index)); + + return sb.str; + } + + public void select_all () { + while (last_paragraph != DONE) { + generate_paragraphs (); + } + + if (paragraphs.size > 0) { + carret.paragraph = 0; + carret.character_index = 0; + selection_end.paragraph = paragraphs.size - 1; + selection_end.character_index = paragraphs.get (paragraphs.size - 1).text_length; + show_selection = true; + } + } + + public TextUndoItem delete_selected_text () { + Carret selection_start, selection_stop; + int i; + Paragraph pg, pge; + string e, s, n; + bool same; + TextUndoItem ui; + + ui = new TextUndoItem (carret); + + e = ""; + s = ""; + n = ""; + + if (!has_selection ()) { + warning ("No selected text."); + return ui; + } + + selection_start = get_selection_start (); + selection_stop = get_selection_stop (); + + same = selection_start.paragraph == selection_stop.paragraph; + + if (!same) { + return_val_if_fail (0 <= selection_start.paragraph < paragraphs.size, ui); + pg = paragraphs.get (selection_start.paragraph); + s = pg.text.substring (0, selection_start.character_index); + + return_val_if_fail (0 <= selection_stop.paragraph < paragraphs.size, ui); + pge = paragraphs.get (selection_stop.paragraph); + e = pge.text.substring (selection_stop.character_index); + + if (!s.has_suffix ("\n")) { + ui.deleted.add (pge.copy ()); + ui.edited.add (pg.copy ()); + + pg.set_text (s + e); + pge.set_text (""); + } else { + ui.edited.add (pg.copy ()); + ui.edited.add (pge.copy ()); + + pg.set_text (s); + pge.set_text (e); + } + } else { + return_val_if_fail (0 <= selection_start.paragraph < paragraphs.size, ui); + + pg = paragraphs.get (selection_start.paragraph); + n = pg.text.substring (0, selection_start.character_index); + n += pg.text.substring (selection_stop.character_index); + + if (n == "") { + ui.deleted.add (pg.copy ()); + paragraphs.remove_at (selection_start.paragraph); + } else { + ui.edited.add (pg.copy ()); + } + + pg.set_text (n); + } + + if (e == "" && !same) { + paragraphs.remove_at (selection_stop.paragraph); + } + + for (i = selection_stop.paragraph - 1; i > selection_start.paragraph; i--) { + return_val_if_fail (0 <= i < paragraphs.size, ui); + ui.deleted.add (paragraphs.get (i)); + paragraphs.remove_at (i); + } + + if (s == "" && !same) { + return_val_if_fail (0 <= selection_start.paragraph < paragraphs.size, ui); + paragraphs.remove_at (selection_start.paragraph); + } + + carret = selection_start.copy (); + selection_end = carret.copy (); + + show_selection = false; + update_paragraph_index (); + layout (); + + return ui; + } + + void update_paragraph_index () { + int i = 0; + foreach (Paragraph p in paragraphs) { + p.index = i; + i++; + } + } + + public TextUndoItem remove_last_character () { + TextUndoItem ui; + move_carret_previous (); + ui = remove_next_character (); + return ui; + } + + public TextUndoItem remove_next_character () { + Paragraph paragraph; + Paragraph next_paragraph; + int index; + unichar c; + string np; + TextUndoItem ui; + + ui = new TextUndoItem (carret); + + return_val_if_fail (0 <= carret.paragraph < paragraphs.size, ui); + paragraph = paragraphs.get (carret.paragraph); + + index = carret.character_index; + + paragraph.text.get_next_char (ref index, out c); + + if (index >= paragraph.text_length) { + np = paragraph.text.substring (0, carret.character_index); + + if (carret.paragraph + 1 < paragraphs.size) { + next_paragraph = paragraphs.get (carret.paragraph + 1); + paragraphs.remove_at (carret.paragraph + 1); + + np = np + next_paragraph.text; + + ui.deleted.add (next_paragraph); + } + + paragraph.set_text (np); + ui.edited.add (paragraph); + } else { + np = paragraph.text.substring (0, carret.character_index) + paragraph.text.substring (index); + paragraph.set_text (np); + + if (np == "") { + return_val_if_fail (carret.paragraph > 0, ui); + carret.paragraph--; + paragraph = paragraphs.get (carret.paragraph); + carret.character_index = paragraph.text_length; + + ui.deleted.add (paragraphs.get (carret.paragraph + 1)); + + paragraphs.remove_at (carret.paragraph + 1); + } else { + ui.edited.add (paragraph); + } + } + + update_paragraph_index (); + layout (); + + return ui; + } + + public void move_carret_next () { + unichar c; + + move_carret_one_character (); + + if (KeyBindings.has_ctrl ()) { + while (true) { + c = move_carret_one_character (); + + if (c == '\0' || c == ' ') { + break; + } + } + } + } + + unichar move_carret_one_character () { + Paragraph paragraph; + int index; + unichar c; + + return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); + paragraph = paragraphs.get (carret.paragraph); + + index = carret.character_index; + + paragraph.text.get_next_char (ref index, out c); + + if (index >= paragraph.text_length && carret.paragraph + 1 < paragraphs.size) { + carret.paragraph++; + carret.character_index = 0; + c = ' '; + } else { + carret.character_index = index; + } + + return c; + } + + public void move_carret_previous () { + unichar c; + + move_carret_back_one_character (); + + if (KeyBindings.has_ctrl ()) { + while (true) { + c = move_carret_back_one_character (); + + if (c == '\0' || c == ' ') { + break; + } + } + } + } + + unichar move_carret_back_one_character () { + Paragraph paragraph; + int index, last_index; + unichar c; + + return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); + paragraph = paragraphs.get (carret.paragraph); + + index = 0; + last_index = -1; + + while (paragraph.text.get_next_char (ref index, out c) && index < carret.character_index) { + last_index = index; + } + + if (last_index <= 0 && carret.paragraph > 0) { + carret.paragraph--; + + return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); + paragraph = paragraphs.get (carret.paragraph); + carret.character_index = paragraph.text_length; + + if (paragraph.text.has_suffix ("\n")) { + carret.character_index -= "\n".length; + } + + c = ' '; + } else if (last_index > 0) { + carret.character_index = last_index; + } else { + carret.character_index = 0; + c = ' '; + } + + return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); + + return c; + } + + public void move_carret_next_row () { + double nr = font_size; + + if (carret.desired_y + 2 * font_size >= allocation.height) { + scroll (2 * font_size); + nr = -font_size; + } + + if (carret.desired_y + nr < widget_y + height - padding) { + carret = get_carret_at (carret.desired_x - widget_x - padding, carret.desired_y + nr); + } + } + + public void move_carret_to_end_of_line () { + carret = get_carret_at (widget_x + padding + width, carret.desired_y, false); + } + + public void move_carret_to_beginning_of_line () { + carret = get_carret_at (widget_x, carret.desired_y, false); + } + + public void move_carret_previous_row () { + double nr = -font_size; + + if (carret.desired_y - 2 * font_size < 0) { + scroll (-2 * font_size); + nr = font_size; + } + + if (carret.desired_y + nr > widget_y + padding) { + carret = get_carret_at (carret.desired_x, carret.desired_y + nr); + } + } + + public bool has_selection () { + return show_selection && selection_is_visible (); + } + + private bool selection_is_visible () { + return carret.paragraph != selection_end.paragraph || carret.character_index != selection_end.character_index; + } + + public void insert_text (string t) { + string s; + Paragraph paragraph; + TextUndoItem ui; + Gee.ArrayList<string> pgs; + bool u = false; + + pgs = new Gee.ArrayList<string> (); + + if (single_line) { + s = t.replace ("\n", "").replace ("\r", ""); + pgs.add (s); + } else { + if (t.last_index_of ("\n") > 0) { + string[] parts = t.split ("\n"); + int i; + for (i = 0; i < parts.length -1; i++) { + pgs.add (parts[i]); + pgs.add ("\n"); + } + + pgs.add (parts[parts.length - 1]); + + if (t.has_suffix ("\n")) { + pgs.add ("\n"); + } + } else { + s = t; + pgs.add (s); + } + } + + if (has_selection () && show_selection) { + ui = delete_selected_text (); + u = true; + + if (paragraphs.size == 0) { + paragraphs.add (new Paragraph ("", font_size, 0, text_color)); + } + } else { + ui = new TextUndoItem (carret); + } + + return_if_fail (0 <= carret.paragraph < paragraphs.size); + paragraph = paragraphs.get (carret.paragraph); + + if (pgs.size > 0) { + if (!u) { + ui.edited.add (paragraph.copy ()); + } + + string first = pgs.get (0); + + string end; + string nt = paragraph.text.substring (0, carret.character_index); + + nt += first; + end = paragraph.text.substring (carret.character_index); + + paragraph.set_text (nt); + + int paragraph_index = carret.paragraph; + Paragraph next_paragraph = paragraph; + for (int i = 1; i < pgs.size; i++) { + paragraph_index++; + string next = pgs.get (i); + next_paragraph = new Paragraph (next, font_size, paragraph_index, text_color); + paragraphs.insert (paragraph_index, next_paragraph); + ui.added.add (next_paragraph); + u = true; + } + + carret.paragraph = paragraph_index; + carret.character_index = next_paragraph.text.length; + + next_paragraph.set_text (next_paragraph.text + end); + } + + if (u) { + undo_items.add (ui); + redo_items.clear (); + } + + update_paragraph_index (); + layout (); + + text_changed (get_text ()); + show_selection = false; + } + + public string get_text () { + StringBuilder sb = new StringBuilder (); + + generate_all_paragraphs (); + + foreach (Paragraph p in paragraphs) { + sb.append (p.text); + } + + return sb.str; + } + + Carret get_carret_at (double click_x, double click_y, + bool check_boundaries = true) { + + int i = 0; + double tx, ty; + double p; + string w; + int ch_index; + double min_d = double.MAX; + Carret c = new Carret (); + double dt; + + c.paragraph = -1; + c.desired_x = click_x; + c.desired_y = click_y; + + foreach (Paragraph paragraph in paragraphs) { + if (!check_boundaries || paragraph.text_is_on_screen (allocation, widget_y)) { + ch_index = 0; + + if (paragraph.start_y + widget_y - font_size <= click_y <= paragraph.end_y + widget_y + font_size) { + foreach (Text next_word in paragraph.words) { + double tt_click = click_y - widget_y - padding + font_size; + + w = next_word.text; + + if (next_word.widget_y <= tt_click <= next_word.widget_y + font_size) { + + p = next_word.get_sidebearing_extent (); + + if ((next_word.widget_y <= tt_click <= next_word.widget_y + font_size) + && (next_word.widget_x + widget_x <= click_x <= next_word.widget_x + widget_x + padding + next_word.get_sidebearing_extent ())) { + + tx = widget_x + next_word.widget_x + padding; + ty = widget_y + next_word.widget_y + padding; + + next_word.iterate ((glyph, kerning, last) => { + double cw; + int ci; + double d; + string gc = (!) glyph.get_unichar ().to_string (); + + d = Math.fabs (click_x - tx); + + if (d <= min_d) { + min_d = d; + c.character_index = ch_index; + c.paragraph = i; + } + + cw = (glyph.get_width ()) * next_word.get_font_scale () + kerning; + ci = gc.length; + + tx += cw; + ch_index += ci; + }); + + dt = Math.fabs (click_x - (tx + widget_x + padding)); + if (dt < min_d) { + min_d = dt; + c.character_index = ch_index; + c.paragraph = i; + } + } else { + dt = Math.fabs (click_x - (next_word.widget_x + widget_x + padding + next_word.get_sidebearing_extent ())); + + if (dt < min_d) { + min_d = dt; + c.character_index = ch_index + w.length; + + if (w.has_suffix ("\n")) { + c.character_index -= "\n".length; + } + + c.paragraph = i; + } + + ch_index += w.length; + } + } else { + ch_index += w.length; + } + } + } + } + i++; + } + + if (unlikely (c.paragraph < 0)) { + c.paragraph = paragraphs.size > 0 ? paragraphs.size - 1 : 0; + c.character_index = paragraphs.size > 0 ? paragraphs.get (c.paragraph).text.length : 0; + } + + store_undo_state_at_next_event = true; + + return c; + } + + /** @return offset to click in text. */ + public override void layout () { + double p; + double tx, ty; + string w; + double xmax = 0; + int i = 0; + double dd; + + tx = 0; + ty = font_size; + + if (allocation.width <= 0 || allocation.height <= 0) { + warning ("Parent widget allocation is not set."); + } + + for (i = paragraphs.size - 1; i >= 0 && paragraphs.size > 1; i--) { + if (unlikely (paragraphs.get (i).is_empty ())) { + paragraphs.remove_at (i); + update_paragraph_index (); + } + } + + i = 0; + foreach (Paragraph paragraph in paragraphs) { + if (paragraph.need_layout + || (paragraph.text_area_width != width + && paragraph.text_is_on_screen (allocation, widget_y))) { + + paragraph.start_y = ty; + paragraph.start_x = tx; + + paragraph.cached_surface = null; + + foreach (Text next_word in paragraph.words) { + next_word.set_font_size (font_size); + + w = next_word.text; + p = next_word.get_sidebearing_extent (); + + if (unlikely (p == 0)) { + warning (@"Zero width word: $(w)"); + } + + if (w == "") { + break; + } + + if (w == "\n") { + next_word.widget_x = tx; + next_word.widget_y = ty; + + tx = 0; + ty += next_word.font_size; + } else { + if (!single_line) { + if (tx + p + 2 * padding > width || w == "\n") { + tx = 0; + ty += next_word.font_size; + } + } + + if (tx + p > xmax) { + xmax = tx + p; + } + + next_word.widget_x = tx; + next_word.widget_y = ty; + + if (w != "\n") { + tx += p; + } + } + } + + if (tx > xmax) { + xmax = tx; + } + + paragraph.text_area_width = width; + paragraph.width = xmax; + paragraph.end_x = tx; + paragraph.end_y = ty; + paragraph.need_layout = false; + } + + if (xmax > width) { + break; + } + + tx = paragraph.end_x; + ty = paragraph.end_y; + i++; + } + + if (xmax > width) { + this.width = xmax + 2 * padding; + layout (); + return; + } + + this.height = fmax (min_height, ty + 2 * padding); + + if (last_paragraph != DONE) { + this.height = (text_length / (double) last_paragraph) * ty + 2 * padding; // estimate height + } + + if (ty + widget_y < allocation.height && last_paragraph != DONE) { + generate_paragraphs (); + layout (); + return; + } + + ty = font_size; + tx = 0; + + foreach (Paragraph paragraph in paragraphs) { + dd = ty - paragraph.start_y; + + if (dd != 0) { + paragraph.start_y += dd; + paragraph.end_y += dd; + foreach (Text word in paragraph.words) { + word.widget_y += dd; + } + } + + ty = paragraph.end_y; + } + } + + public override void button_press (uint button, double x, double y) { + if (is_over (x, y)) { + carret = get_carret_at (x, y); + selection_end = carret.copy (); + update_selection = true; + } + } + + public override void button_release (uint button, double x, double y) { + update_selection = false; + show_selection = selection_is_visible (); + } + + public override bool motion (double x, double y) { + if (update_selection) { + selection_end = get_carret_at (x, y); + show_selection = selection_is_visible (); + } + + return update_selection; + } + + public override void draw (Context cr) { + Text word; + double tx, ty; + string w; + double scale; + double width; + double x = widget_x; + double y = widget_y; + Carret selection_start, selection_stop; + double carret_x; + double carret_y; + + layout (); + + if (draw_border) { + // background + cr.save (); + cr.set_line_width (1); + Theme.color (cr, "Text Area Background"); + draw_rounded_rectangle (cr, x, y, this.width, this.height - padding, padding); + cr.fill (); + cr.restore (); + + // border + cr.save (); + cr.set_line_width (1); + Theme.color (cr, "Foreground 1"); + draw_rounded_rectangle (cr, x, y, this.width, this.height - padding, padding); + cr.stroke (); + cr.restore (); + } + + cr.save (); + + word = new Text (); + + width = this.width - padding; + x += padding; + scale = word.get_font_scale (); + y += font_size; + + // draw selection background + if (has_selection ()) { + tx = 0; + ty = 0; + + selection_start = get_selection_start (); + selection_stop = get_selection_stop (); + + cr.save (); + Theme.color (cr, "Highlighted 1"); + + for (int i = selection_start.paragraph; i <= selection_stop.paragraph; i++) { + return_if_fail (0 <= i < paragraphs.size); + Paragraph pg = paragraphs.get (i); + + if (pg.text_is_on_screen (allocation, widget_y)) { + int char_index = 0; + + foreach (Text next_word in pg.words) { + double cw = next_word.get_sidebearing_extent (); + bool paint_background = false; + bool partial_start = false; + bool partial_stop = false; + int wl; + + w = next_word.text; + wl = w.length; + scale = next_word.get_font_scale (); + + if (selection_start.paragraph == selection_stop.paragraph) { + partial_start = true; + partial_stop = true; + } else if (selection_start.paragraph < i < selection_stop.paragraph) { + paint_background = true; + } else if (selection_start.paragraph == i) { + paint_background = true; + partial_start = true; + } else if (selection_stop.paragraph == i) { + paint_background = char_index + wl < selection_stop.character_index; + partial_stop = !paint_background; + } + + if (paint_background && !(partial_start || partial_stop)) { + double selection_y = widget_y + next_word.widget_y + scale * -next_word.cached_font.bottom_limit - font_size; + cr.rectangle (widget_x + padding + next_word.widget_x - 1, selection_y, cw + 1, font_size); + cr.fill (); + } + + if (partial_start || partial_stop) { + int index = char_index; + double bx = widget_x + padding + next_word.widget_x + (partial_start ? 0 : 1); + + next_word.iterate ((glyph, kerning, last) => { + double cwi; + int ci; + bool draw = (index >= selection_start.character_index && partial_start && !partial_stop) + || (index < selection_stop.character_index && !partial_start && partial_stop) + || (selection_start.character_index <= index < selection_stop.character_index && partial_start && partial_stop); + + cwi = (glyph.get_width ()) * next_word.get_font_scale () + kerning; + + if (draw) { + double selection_y = widget_y + next_word.widget_y + scale * -next_word.cached_font.bottom_limit - font_size; + cr.rectangle (bx - 1, selection_y, cwi + 1, font_size); + cr.fill (); + } + + bx += cwi; + ci = ((!) glyph.get_unichar ().to_string ()).length; + index += ci; + }); + } + + char_index += w.length; + } + } + } + + cr.restore (); + } + + tx = 0; + ty = 0; + + int first_visible = 0; + int last_visible; + int paragraphs_size = paragraphs.size; + while (first_visible < paragraphs_size) { + if (paragraphs.get (first_visible).text_is_on_screen (allocation, widget_y)) { + break; + } + first_visible++; + } + + last_visible = first_visible; + while (last_visible < paragraphs_size) { + if (!paragraphs.get (last_visible).text_is_on_screen (allocation, widget_y)) { + last_visible++; + break; + } + last_visible++; + } + + if (paragraphs_size == 0) { + if (carret_is_visible) { + draw_carret_at (cr, widget_x + padding, widget_y + font_size + padding); + } + + return; + } + + Context cc; // cached context + Paragraph paragraph; + paragraph = paragraphs.get (0); + + tx = paragraph.start_x; + ty = paragraph.start_y; + + for (int i = first_visible; i < last_visible; i++) { + paragraph = paragraphs.get (i); + + tx = paragraph.start_x; + ty = paragraph.start_y; + + if (paragraph.cached_surface == null) { + paragraph.cached_surface = Screen.create_background_surface ((int) width + 2, paragraph.get_height () + (int) font_size + 2); + cc = new Context ((!) paragraph.cached_surface); + cc.scale (Screen.get_scale(), Screen.get_scale()); + + foreach (Text next_word in paragraph.words) { + if (next_word.text != "\n") { + next_word.draw_at_top (cc, next_word.widget_x, next_word.widget_y - ty); + } + } + } + + if (likely (paragraph.cached_surface != null)) { + // FIXME: subpixel offset in text area + Screen.paint_background_surface(cr, + (!) paragraph.cached_surface, + (int) (x + tx), + (int) (widget_y + paragraph.start_y - font_size + padding)); + } else { + warning ("No paragraph image."); + } + } + + if (carret_is_visible) { + get_carret_position (carret, out carret_x, out carret_y); + + if (carret_y < 0) { + draw_carret_at (cr, widget_x + padding, widget_y + font_size + padding); + } else { + draw_carret_at (cr, carret_x, carret_y); + } + } + + if (has_selection ()) { + get_carret_position (selection_end, out carret_x, out carret_y); + + if (carret_y < 0) { + draw_carret_at (cr, widget_x + padding, widget_y + font_size + padding); + } else { + draw_carret_at (cr, carret_x, carret_y); + } + } + } + + void get_carret_position (Carret carret, out double carret_x, out double carret_y) { + Paragraph paragraph; + double tx; + double ty; + int ch_index; + int wl; + double pos_x, pos_y; + + ch_index = 0; + + carret_x = -1; + carret_y = -1; + + return_if_fail (0 <= carret.paragraph < paragraphs.size); + paragraph = paragraphs.get (carret.paragraph); + + pos_x = -1; + pos_y = -1; + + foreach (Text next_word in paragraph.words) { + string w = next_word.text; + wl = w.length; + + if (carret.character_index == ch_index) { + pos_x = next_word.widget_x + widget_x + padding; + pos_y = widget_y + next_word.widget_y + next_word.get_baseline_to_bottom_for_font (); + } else if (carret.character_index >= ch_index + wl) { + pos_x = next_word.widget_x + next_word.get_sidebearing_extent () + widget_x + padding; + pos_y = widget_y + next_word.widget_y + next_word.get_baseline_to_bottom_for_font (); + + if (next_word.text.has_suffix ("\n")) { + pos_x = widget_x + padding; + pos_y += next_word.font_size; + } + } else if (ch_index < carret.character_index <= ch_index + wl) { + tx = widget_x + next_word.widget_x; + ty = widget_y + next_word.widget_y + next_word.get_baseline_to_bottom_for_font (); + + if (carret.character_index <= ch_index) { + pos_x = widget_x + padding; + pos_y = ty; + } + + next_word.iterate ((glyph, kerning, last) => { + double cw; + int ci; + + cw = (glyph.get_width ()) * next_word.get_font_scale () + kerning; + ci = ((!) glyph.get_unichar ().to_string ()).length; + + if (ch_index < carret.character_index <= ch_index + ci) { + pos_x = tx + cw + padding; + pos_y = ty; + + if (glyph.get_unichar () == '\n') { + pos_x = widget_x + padding; + pos_y += next_word.font_size; + } + } + + tx += cw; + ch_index += ci; + }); + } + + ch_index += wl; + } + + carret_x = pos_x; + carret_y = pos_y; + } + + void draw_carret_at (Context cr, double x, double y) { + cr.save (); + cr.set_source_rgba (0, 0, 0, 0.5); + cr.set_line_width (1); + cr.move_to (x, y); + cr.line_to (x, y - font_size); + cr.stroke (); + cr.restore (); + } + + public void store_undo_edit_state () { + TextUndoItem ui = new TextUndoItem (carret); + ui.edited.add (get_current_paragraph ().copy ()); + undo_items.add (ui); + redo_items.clear (); + } + + public void redo () { + TextUndoItem i; + TextUndoItem undo_item; + + if (redo_items.size > 0) { + i = redo_items.get (redo_items.size - 1); + + undo_item = new TextUndoItem (i.carret); + + i.deleted.sort ((a, b) => { + Paragraph pa = (Paragraph) a; + Paragraph pb = (Paragraph) b; + return pb.index - pa.index; + }); + + i.added.sort ((a, b) => { + Paragraph pa = (Paragraph) a; + Paragraph pb = (Paragraph) b; + return pa.index - pb.index; + }); + + foreach (Paragraph p in i.deleted) { + if (unlikely (!(0 <= p.index < paragraphs.size))) { + warning ("Paragraph not found."); + } else { + undo_item.deleted.add (p.copy ()); + paragraphs.remove_at (p.index); + } + } + + foreach (Paragraph p in i.added) { + if (p.index == paragraphs.size) { + paragraphs.add (p.copy ()); + } else { + if (unlikely (!(0 <= p.index < paragraphs.size))) { + warning (@"Index: $(p.index) out of bounds, size: $(paragraphs.size)"); + } else { + undo_item.added.add (paragraphs.get (p.index).copy ()); + paragraphs.insert (p.index, p.copy ()); + } + } + } + + foreach (Paragraph p in i.edited) { + if (unlikely (!(0 <= p.index < paragraphs.size))) { + warning (@"Index: $(p.index ) out of bounds, size: $(paragraphs.size)"); + return; + } + + undo_item.edited.add (paragraphs.get (p.index).copy ()); + paragraphs.set (p.index, p.copy ()); + } + + redo_items.remove_at (redo_items.size - 1); + undo_items.add (undo_item); + + carret = i.carret.copy (); + layout (); + } + } + + public void undo () { + TextUndoItem i; + TextUndoItem redo_item; + + if (undo_items.size > 0) { + i = undo_items.get (undo_items.size - 1); + redo_item = new TextUndoItem (i.carret); + + i.deleted.sort ((a, b) => { + Paragraph pa = (Paragraph) a; + Paragraph pb = (Paragraph) b; + return pa.index - pb.index; + }); + + i.added.sort ((a, b) => { + Paragraph pa = (Paragraph) a; + Paragraph pb = (Paragraph) b; + return pb.index - pa.index; + }); + + foreach (Paragraph p in i.added) { + if (unlikely (!(0 <= p.index < paragraphs.size))) { + warning ("Paragraph not found."); + } else { + redo_item.added.add (paragraphs.get (p.index).copy ()); + paragraphs.remove_at (p.index); + } + } + + foreach (Paragraph p in i.deleted) { + if (p.index == paragraphs.size) { + paragraphs.add (p.copy ()); + } else { + if (unlikely (!(0 <= p.index < paragraphs.size))) { + warning (@"Index: $(p.index) out of bounds, size: $(paragraphs.size)"); + } else { + redo_item.deleted.add (p.copy ()); + paragraphs.insert (p.index, p.copy ()); + } + } + } + + foreach (Paragraph p in i.edited) { + if (unlikely (!(0 <= p.index < paragraphs.size))) { + warning (@"Index: $(p.index ) out of bounds, size: $(paragraphs.size)"); + return; + } + + redo_item.edited.add (paragraphs.get (p.index).copy ()); + paragraphs.set (p.index, p.copy ()); + } + + undo_items.remove_at (undo_items.size - 1); + redo_items.add (redo_item); + + carret = i.carret.copy (); + layout (); + } + } + + public void set_editable (bool editable) { + this.editable = editable; + } + + public class TextUndoItem : GLib.Object { + public Carret carret; + public Gee.ArrayList<Paragraph> added = new Gee.ArrayList<Paragraph> (); + public Gee.ArrayList<Paragraph> edited = new Gee.ArrayList<Paragraph> (); + public Gee.ArrayList<Paragraph> deleted = new Gee.ArrayList<Paragraph> (); + + public TextUndoItem (Carret c) { + carret = c.copy (); + } + } + + public class Paragraph : GLib.Object { + public double end_x = -10000; + public double end_y = -10000; + + public double start_x = -10000; + public double start_y = -10000; + + public double width = -10000; + public double text_area_width = -10000; + + public string text; + + public Gee.ArrayList<Text> words { + get { + if (words_in_paragraph.size == 0) { + generate_words (); + } + + return words_in_paragraph; + } + } + + private Gee.ArrayList<Text> words_in_paragraph = new Gee.ArrayList<Text> (); + public int text_length; + public bool need_layout = true; + public Surface? cached_surface = null; + double font_size; + public int index; + Color text_color; + + public Paragraph (string text, double font_size, int index, Color c) { + this.index = index; + this.font_size = font_size; + text_color = c; + set_text (text); + } + + public Paragraph copy () { + Paragraph p = new Paragraph (text.dup (), font_size, index, text_color); + p.need_layout = true; + return p; + } + + public bool is_empty () { + return text == ""; + } + + public void set_text (string t) { + this.text = t; + text_length = t.length; + need_layout = true; + words.clear (); + cached_surface = null; + } + + public int get_height () { + return (int) (end_y - start_y) + 1; + } + + public int get_width () { + return (int) width + 1; + } + + public bool text_is_on_screen (WidgetAllocation alloc, double widget_y) { + bool v = (0 <= start_y + widget_y <= alloc.height) + || (0 <= end_y + widget_y <= alloc.height) + || (start_y + widget_y <= 0 && alloc.height <= end_y + widget_y); + return v; + } + + private void generate_words () { + string w; + int p = 0; + bool carret_at_word_end = false; + Text word; + int carret = 0; + int iter_pos = 0; + + return_if_fail (words_in_paragraph.size == 0); + + while (p < text_length) { + w = get_next_word (out carret_at_word_end, ref iter_pos, carret); + + if (w == "") { + break; + } + + word = new Text (w, font_size, 0, text_color); + words_in_paragraph.add (word); + } + } + + string get_next_word (out bool carret_at_end_of_word, ref int iter_pos, int carret) { + int i; + int ni; + int pi; + string n; + int nl; + + carret_at_end_of_word = false; + + if (iter_pos >= text_length) { + carret_at_end_of_word = true; + return "".dup (); + } + + if (text.get_char (iter_pos) == '\n') { + iter_pos += "\n".length; + carret_at_end_of_word = (iter_pos == carret); + return "\n".dup (); + } + + i = text.index_of (" ", iter_pos); + pi = i + " ".length; + + ni = text.index_of ("\t", iter_pos); + if (ni != -1 && ni < pi || i == -1) { + i = ni; + pi = i + "\t".length; + } + + ni = text.index_of ("\n", iter_pos); + if (ni != -1 && ni < pi || i == -1) { + i = ni; + pi = i; + } + + if (iter_pos + iter_pos - pi > text_length || i == -1) { + n = text.substring (iter_pos); + } else { + n = text.substring (iter_pos, pi - iter_pos); + } + + nl = n.length; + if (iter_pos < carret < iter_pos + nl) { + n = text.substring (iter_pos, carret - iter_pos); + nl = n.length; + carret_at_end_of_word = true; + } + + iter_pos += nl; + + if (iter_pos == carret) { + carret_at_end_of_word = true; + } + + return n; + } + } + + public class Carret : GLib.Object { + + public int paragraph = 0; + + public int character_index { + get { + return ci; + } + + set { + ci = value; + } + } + + private int ci = 0; + + public double desired_x = 0; + public double desired_y = 0; + + public Carret () { + } + + public void print () { + stdout.printf (@"paragraph: $paragraph, character_index: $character_index\n"); + } + + public Carret copy () { + Carret c = new Carret (); + + c.paragraph = paragraph; + c.character_index = character_index; + + c.desired_x = desired_x; + c.desired_y = desired_y; + + return c; + } + } + } + + }
--- /dev/null +++ b/libbirdfont/TextRendering/fontconfig.c @@ -1,1 +1,141 @@ + /* + Copyright (C) 2015 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + #include <stdio.h> + #include <glib.h> + #include <fontconfig/fontconfig.h> + + gchar* find_font_with_property (FcConfig* fontconfig, const gchar* characters, const gchar* property) { + FcPattern* pattern; + FcCharSet* character_set; + FcObjectSet* font_properties; + FcFontSet* fonts; + FcPattern* font; + FcChar8* path; + gchar* result; + gchar* remaining_characters; + gunichar character; + + if (fontconfig == NULL) { + g_warning("Font config not loaded."); + return NULL; + } + + result = NULL; + pattern = FcPatternCreate (); + + character_set = FcCharSetCreate (); + + remaining_characters = (gchar*) characters; + while (TRUE) { + character = g_utf8_get_char (remaining_characters); + + if (character == '\0') { + break; + } + + FcCharSetAddChar(character_set, character); + + remaining_characters = g_utf8_next_char (remaining_characters); + } + + FcPatternAddCharSet (pattern, FC_CHARSET, character_set); + FcCharSetDestroy (character_set); + FcPatternAddInteger (pattern, FC_SLANT, FC_SLANT_ROMAN); + + FcPatternAddBool(pattern, FC_SCALABLE, FcTrue); + font_properties = FcObjectSetBuild (property, NULL); + fonts = FcFontList (fontconfig, pattern, font_properties); + + if (fonts && fonts->nfont > 0) { + font = fonts->fonts[0]; + if (FcPatternGetString(font, property, 0, &path) == FcResultMatch) { + result = g_strdup ((gchar*) path); + } + } + + if (fonts) { + FcFontSetDestroy(fonts); + } + + if (pattern) { + FcPatternDestroy(pattern); + } + + return result; + } + + /** Find a fallback font for a set of characters. + * @return A path to the font file. + */ + gchar* find_font (FcConfig* fontconfig, const gchar* characters) { + return find_font_with_property (fontconfig, characters, FC_FILE); + } + + /** Find a fallback font for a set of characters. + * @return Family name of the font. + */ + gchar* find_font_family (FcConfig* fontconfig, const gchar* characters) { + return find_font_with_property (fontconfig, characters, FC_FAMILY); + } + + /** Find a font file from its family name. + * @param font_config fontconfig instance + * @param font_name name of the font + * @return full path to the font file + */ + gchar* find_font_file (FcConfig* font_config, const gchar* font_name) { + const FcChar8* name; + FcPattern* search_pattern; + FcPattern* font; + FcChar8* file; + gchar* path; + FcObjectSet* font_properties; + FcFontSet* fonts; + int i; + + if (font_config == NULL) { + g_warning("Font config not loaded."); + return NULL; + } + + path = NULL; + name = font_name; + + search_pattern = FcPatternCreate (); + FcPatternAddString (search_pattern, FC_FAMILY, name); + FcPatternAddBool (search_pattern, FC_SCALABLE, FcTrue); + FcPatternAddInteger (search_pattern, FC_WEIGHT, FC_WEIGHT_MEDIUM); + FcPatternAddInteger (search_pattern, FC_SLANT, FC_SLANT_ROMAN); + + font_properties = FcObjectSetBuild (FC_FILE, NULL); + fonts = FcFontList (font_config, search_pattern, font_properties); + + if (fonts->nfont > 0) { + for (i = 0; i < fonts->nfont; i++) { + font = fonts->fonts[i]; + + if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) { + path = g_strdup ((gchar*) file); + break; + } + } + FcPatternDestroy (font); + } + + FcPatternDestroy (search_pattern); + + return path; + }
--- a/libbirdfont/Theme.vala +++ b/libbirdfont/Theme.vala @@ -103,8 +103,8 @@ Theme.set_default_color ("Stroke Color", 141 / 255.0, 141 / 255.0, 141 / 255.0, 1); Theme.set_default_color ("Handle Color", 141 / 255.0, 141 / 255.0, 141 / 255.0, 1); Theme.set_default_color ("Fill Color", 0.5, 0.5, 0.5, 1); - Theme.set_default_color ("Selected Objects", 35 / 255.0, 131 / 255.0, 194 / 255.0, 1); - + Theme.set_default_color ("Objects", 0, 0, 0, 1); + Theme.set_default_color ("Background 1", 1, 1, 1, 1); Theme.set_default_color ("Dialog Background", 238 / 255.0, 239 / 255.0, 243 / 255.0, 1); Theme.set_default_color ("Menu Background", 222 / 255.0, 221 / 255.0, 226 / 255.0, 1); @@ -210,7 +210,7 @@ Theme.set_default_color ("Stroke Color", 141 / 255.0, 141 / 255.0, 141 / 255.0, 1); Theme.set_default_color ("Handle Color", 141 / 255.0, 141 / 255.0, 141 / 255.0, 1); Theme.set_default_color ("Fill Color", 0.5, 0.5, 0.5, 1); - Theme.set_default_color ("Selected Objects", 35 / 255.0, 131 / 255.0, 194 / 255.0, 1); + Theme.set_default_color ("Objects", 0, 0, 0, 1); Theme.set_default_color ("Background 1", 1, 1, 1, 1); Theme.set_default_color ("Dialog Background", 55 / 255.0, 56 / 255.0, 62 / 255.0, 1); @@ -315,7 +315,7 @@ Theme.set_default_color ("Stroke Color", 141 / 255.0, 141 / 255.0, 141 / 255.0, 1); Theme.set_default_color ("Handle Color", 141 / 255.0, 141 / 255.0, 141 / 255.0, 1); Theme.set_default_color ("Fill Color", 0, 0, 0, 1); - Theme.set_default_color ("Selected Objects", 0, 0, 0, 1); + Theme.set_default_color ("Objects", 0, 0, 0, 1); Theme.set_default_color ("Background 1", 1, 1, 1, 1); Theme.set_default_color ("Dialog Background", 1, 1, 1, 1); @@ -424,6 +424,7 @@ t_("Handle Color"); t_("Fill Color"); t_("Selected Objects"); + t_("Objects"); t_("Background 1"); t_("Dialog Background");
--- a/libbirdfont/Tool.vala +++ b/libbirdfont/Tool.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2012, 2014 Johan Mattsson + Copyright (C) 2012 2014 2016 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -66,6 +66,8 @@ bool show_bg = true; public string tip = ""; + string extented_description = ""; + TextArea? help = null; public bool persistent = false; public bool editor_events = false; @@ -82,8 +84,9 @@ public signal void redraw_tool (); /** Create tool with a certain name and load icon "name".png */ - public Tool (string? name = null, string tip = "") { + public Tool (string? name = null, string tip = "", string extended_description = "") { this.tip = tip; + this.extented_description = extended_description; icon_font = new Text (); @@ -122,10 +125,37 @@ panel_move_action.connect ((self, x, y) => { if (is_active ()) { wait_for_tooltip (); + + Help help_box = MainWindow.get_help (); + + if (help == null) { + help = update_help (); + } + + help_box.set_help_text ((!) help); redraw (); } return false; }); + } + + public virtual void clear_cache () { + help = null; + } + + public TextArea? update_help () { + string t = get_tip (); + string description = t; + + if (t != "" && extented_description != "") { + description += "\n\n"; + } + + if (extented_description != "") { + description += extented_description; + } + + return Help.create_help_text (description); } public virtual string get_tip () { @@ -221,6 +251,10 @@ } public string get_key_binding () { + if (is_null (MainWindow.get_menu ())) { + return ""; + } + ToolItem? ti = MainWindow.get_menu ().get_item_for_tool (this); ToolItem t;
--- a/libbirdfont/ToolItem.vala +++ b/libbirdfont/ToolItem.vala @@ -18,6 +18,28 @@ public Tool tool; + public override uint modifiers { + get { + return base.modifiers; + } + + set { + base.modifiers = value; + tool.update_help (); + } + } + + public override unichar key { + get { + return base.key; + } + + set { + base.key = value; + tool.update_help (); + } + } + public ToolItem (Tool tool) { base (tool.tip, tool.name);
--- a/libbirdfont/TrackTool.vala +++ b/libbirdfont/TrackTool.vala @@ -121,8 +121,11 @@ start_update_timer (); drawing = true; - foreach (Path path in glyph.active_paths) { - path.create_full_stroke (); // cache merged stroke parts + foreach (Object path in glyph.active_paths) { + if (path is PathObject) { + // cache merged stroke parts + ((PathObject) path).get_path ().create_full_stroke (); + } } } }); @@ -146,8 +149,9 @@ g = MainWindow.get_current_glyph (); if (g.active_paths.size > 0) { // set type for last point - p = g.active_paths.get (g.active_paths.size - 1); - + Object o = g.active_paths.get (g.active_paths.size - 1); + p = ((PathObject) o).get_path (); + if (p.points.size > 1) { previous = p.points.get (p.points.size - 1); previous.type = DrawingTools.point_type; @@ -164,8 +168,10 @@ add_endpoint_and_merge (x, y); } - foreach (Path path in g.active_paths) { - convert_hidden_points (path); + foreach (Object path in g.active_paths) { + if (path is PathObject) { + convert_hidden_points (((PathObject) path).get_path ()); + } } g.clear_active_paths (); @@ -226,12 +232,17 @@ } } } - - // FIXME: double check + void set_tie () { Glyph glyph = MainWindow.get_current_glyph (); var paths = glyph.get_visible_paths (); - Path p = paths.get (paths.size - 1); + Path p; + + if (paths.size == 0) { + return; + } + + p = paths.get (paths.size - 1); foreach (EditPoint ep in p.points) { if (ep.get_right_handle ().is_line () || ep.get_left_handle ().is_line ()) { @@ -403,7 +414,14 @@ return; } - p = glyph.active_paths.get (glyph.active_paths.size - 1); + Object o = glyph.active_paths.get (glyph.active_paths.size - 1); + + if (unlikely (!(o is PathObject))) { + warning ("Object is not a path"); + return; + } + + p = ((PathObject) o).get_path (); p.reopen (); px = Glyph.path_coordinate_x (x); py = Glyph.path_coordinate_y (y); @@ -411,7 +429,8 @@ added_points++; PenTool.convert_point_to_line (new_point, false); - new_point.set_point_type (PointType.HIDDEN); + new_point.set_point_type (PointType.HIDDEN); + p.recalculate_linear_handles_for_point (new_point); if (p.points.size > 1) { glyph.redraw_segment (new_point, new_point.get_prev ()); @@ -488,7 +507,15 @@ return new Path (); } - return glyph.active_paths.get (glyph.active_paths.size - 1); + Object o = glyph.active_paths.get (glyph.active_paths.size - 1); + + if (likely (o is PathObject)) { + return ((PathObject) o).get_path (); + } + + warning ("Active object is a path."); + + return new Path (); } /** Delete all points close to the pixel at x,y. */
--- /dev/null +++ b/libsvgbird/AttributePattern.vala @@ -1,1 +1,144 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using B; + using Math; + + namespace SvgBird { + + public enum AttributePatternType { + NONE, + ANYTHING, + LIST, + EQUALS, + STARTS_WITH + } + + public class AttributePattern : GLib.Object { + public string name = ""; + public string? content = null; + public AttributePatternType type = AttributePatternType.NONE; + + public AttributePattern copy () { + AttributePattern a = new AttributePattern (); + a.name = name; + a.content = content; + a.type = type; + return a; + } + + public bool match (Attributes attributes) { + switch (type) { + case AttributePatternType.ANYTHING: + return match_attribute_name (attributes); + case AttributePatternType.LIST: + return match_list (attributes); + case AttributePatternType.EQUALS: + return attribute_equals (attributes); + case AttributePatternType.STARTS_WITH: + return attribute_start_with (attributes); + } + + return false; + } + + string remove_hypen (string content) { + int hyphen = content.index_of ("-"); + + if (hyphen == -1) { + return content; + } + + return content.substring (0, hyphen); + } + + bool attribute_start_with (Attributes attributes) { + foreach (Attribute attribute in attributes) { + if (attribute.get_name () == name + && remove_hypen (attribute.get_content ()) == ((!) content)) { + return true; + } + } + + return false; + } + + bool attribute_equals (Attributes attributes) { + foreach (Attribute attribute in attributes) { + if (attribute.get_name () == name + && attribute.get_content () == ((!) content)) { + return true; + } + } + + return false; + } + + bool match_attribute_name (Attributes attributes) { + foreach (Attribute attribute in attributes) { + if (attribute.get_name () == name) { + return true; + } + } + + return false; + } + + bool match_list (Attributes attributes) { + if (content == null) { + return false; + } + + string[] list = ((!) content).split (" "); + foreach (Attribute attribute in attributes) { + if (attribute.get_name () == name) { + + string attribute_content = attribute.get_content (); + foreach (string list_item in list) { + if (attribute_content == list_item) { + return true; + } + } + } + } + + return false; + } + + public string to_string () { + string c; + + if (content == null) { + c = "null"; + } else { + c = (!) content; + } + + switch (type) { + case AttributePatternType.ANYTHING: + return "[" + name + "]"; + case AttributePatternType.LIST: + return "[" + name + "~=" + c + "]"; + case AttributePatternType.EQUALS: + return "[" + name + "=" + c + "]"; + case AttributePatternType.STARTS_WITH: + return "[" + name + "|=" + c + "]"; + } + + return "No attributes."; + } + } + + }
--- /dev/null +++ b/libsvgbird/BezierPoints.vala @@ -1,1 +1,46 @@ + /* + Copyright (C) 2014 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + namespace SvgBird { + + /** Bezier point container for the SVG parser. */ + public class BezierPoints { + public unichar type = '\0'; + public unichar svg_type = '\0'; + public double x0 = 0; + public double y0 = 0; + public double x1 = 0; + public double y1 = 0; + public double x2 = 0; + public double y2 = 0; + + // arc arguments + public double rx = 0; + public double ry = 0; + public double angle = 0; + public bool large_arc = false; + public bool sweep = false; + // the arc instructions begins at x0, y0 and ends at x1, x1 + + public string to_string () { + if (svg_type == 'A' || svg_type == 'a') { + return @"SVG type:$((!) svg_type.to_string ()) $x0,$y0 $x1,$y1 rx=$rx, ry=$ry, angle=$angle, large_arc=$large_arc, sweep=$sweep)"; + } + + return @"$((!)type.to_string ()) $x0,$y0 $x1,$y1 $x2,$y2 SVG:$((!)svg_type.to_string ())"; + } + } + + }
diff --git libsvgbird/Circle.vala(new)
--- /dev/null +++ b/libsvgbird/Circle.vala @@ -1,1 +1,69 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Cairo; + + namespace SvgBird { + + public class Circle : Object { + + public double cx = 0; + public double cy = 0; + public double r = 0; + + public Circle () { + } + + public Circle.create_copy (Circle c) { + Object.copy_attributes (c, this); + c.cx = cx; + c.cx = cy; + c.r = r; + } + + public override bool is_over (double x, double y) { + return false; + } + + public override void draw_outline (Context cr) { + cr.arc (cx, cy, r, 0, 2 * Math.PI); + } + + public override void move (double dx, double dy) { + } + + public override void update_region_boundaries () { + } + + public override void rotate (double theta, double xc, double yc) { + } + + public override bool is_empty () { + return false; + } + + public override void resize (double ratio_x, double ratio_y) { + } + + public override Object copy () { + return new Circle.create_copy (this); + } + + public override string to_string () { + return "Circle"; + } + } + + }
diff --git libsvgbird/ClipPath.vala(new)
--- /dev/null +++ b/libsvgbird/ClipPath.vala @@ -1,1 +1,39 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Cairo; + + namespace SvgBird { + + public class ClipPath : GLib.Object { + Layer layer = new Layer (); + + public string id { + get { + return layer.id; + } + } + + public ClipPath (Layer layer) { + this.layer = layer; + } + + public void apply (Context cr) { + layer.draw_outline (cr); + cr.clip (); + } + } + + }
diff --git libsvgbird/Color.vala(new)
--- /dev/null +++ b/libsvgbird/Color.vala @@ -1,1 +1,287 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + namespace SvgBird { + + public class Color { + public double r; + public double g; + public double b; + public double a; + + public Color (double r, double g, double b, double a) { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + public static Color? parse (string? svg_color) { + if (svg_color == null) { + return null; + } + + string color = (!) svg_color; + uint32 c; + string[] arguments; + Color parsed = new Color (0, 0, 0, 1); + + if (color == "none") { + return null; + } + + if (!color.has_prefix ("#")) { + color = get_hex_for_name (color); + } + + color = color.replace ("#", ""); + + if (color.char_count () == 6) { + color.scanf ("%x", out c); + parsed.r = (uint8)((c & 0xFF0000) >> 16) / 254.0; + parsed.g = (uint8)((c & 0x00FF00) >> 8)/ 254.0; + parsed.b = (uint8)(c & 0x0000FF) / 254.0; + } else if (color.char_count () == 3) { + color.scanf ("%x", out c); + parsed.r = (uint8)(((c & 0xF00) >> 4) | ((c & 0xF00) >> 8)) / 254.0; + parsed.g = (uint8)((c & 0x0F0) | ((c & 0x0F0) >> 4)) / 254.0; + parsed.b = (uint8)(((c & 0x00F) << 4) | (c & 0x00F)) / 254.0; + } else if (color.index_of ("%") > -1) { + color = color.replace ("rgb", ""); + color = color.replace (" ", ""); + color = color.replace ("\t", ""); + color = color.replace ("%", ""); + arguments = color.split (","); + + return_val_if_fail (arguments.length == 3, parsed); + arguments[0].scanf ("%lf", out parsed.r); + arguments[1].scanf ("%lf", out parsed.g); + arguments[2].scanf ("%lf", out parsed.b); + } else if (color.index_of ("rgb") > -1) { + color = color.replace ("rgb", ""); + color = color.replace (" ", ""); + color = color.replace ("\t", ""); + arguments = color.split (","); + + return_val_if_fail (arguments.length == 3, parsed); + + int r, g, b; + arguments[0].scanf ("%d", out r); + parsed.r = r / 254.0; + + arguments[1].scanf ("%d", out g); + parsed.g = g / 254.0; + + arguments[2].scanf ("%d", out b); + parsed.b = b / 254.0; + } else { + warning ("Unknown color type: " + color); + } + + return parsed; + } + + public string to_rgb_hex () { + StringBuilder rgb = new StringBuilder (); + rgb.append ("#"); + rgb.append_printf ("%02x", (int) Math.rint (r * 254)); + rgb.append_printf ("%02x", (int) Math.rint (g * 254)); + rgb.append_printf ("%02x", (int) Math.rint (b * 254)); + return rgb.str; + } + + public string to_string () { + StringBuilder rgba = new StringBuilder (); + rgba.append (to_rgb_hex ()); + rgba.append_printf ("%x", (int) Math.rint (a * 254)); + return rgba.str; + } + + public Color copy () { + return new Color (r, g, b, a); + } + + public static string get_hex_for_name (string name) { + string color = name.down (); + + if (color == "black") return "#000000"; + if (color == "silver") return "#C0C0C0"; + if (color == "gray") return "#808080"; + if (color == "maroon") return "#800000"; + if (color == "red") return "#FF0000"; + if (color == "purple") return "#800080"; + if (color == "white") return "#FFFFFF"; + if (color == "fuchsia") return "#FF00FF"; + if (color == "green") return "#008000"; + if (color == "lime") return "#00FF00"; + if (color == "olive") return "#808000"; + if (color == "yellow") return "#FFFF00"; + if (color == "navy") return "#000080"; + if (color == "blue") return "#0000FF"; + if (color == "teal") return "#008080"; + if (color == "aqua") return "#00FFFF"; + if (color == "aliceblue") return "#f0f8ff"; + if (color == "antiquewhite") return "#faebd7"; + if (color == "aqua") return "#00ffff"; + if (color == "aquamarine") return "#7fffd4"; + if (color == "azure") return "#f0ffff"; + if (color == "beige") return "#f5f5dc"; + if (color == "bisque") return "#ffe4c4"; + if (color == "black") return "#000000"; + if (color == "blanchedalmond") return "#ffebcd"; + if (color == "blue") return "#0000ff"; + if (color == "blueviolet") return "#8a2be2"; + if (color == "brown") return "#a52a2a"; + if (color == "burlywood") return "#deb887"; + if (color == "cadetblue") return "#5f9ea0"; + if (color == "chartreuse") return "#7fff00"; + if (color == "chocolate") return "#d2691e"; + if (color == "coral") return "#ff7f50"; + if (color == "cornflowerblue") return "#6495ed"; + if (color == "cornsilk") return "#fff8dc"; + if (color == "crimson") return "#dc143c"; + if (color == "cyan") return "#00ffff"; + if (color == "darkblue") return "#00008b"; + if (color == "darkcyan") return "#008b8b"; + if (color == "darkgoldenrod") return "#b8860b"; + if (color == "darkgray") return "#a9a9a9"; + if (color == "darkgreen") return "#006400"; + if (color == "darkgrey") return "#a9a9a9"; + if (color == "darkkhaki") return "#bdb76b"; + if (color == "darkmagenta") return "#8b008b"; + if (color == "darkolivegreen") return "#556b2f"; + if (color == "darkorange") return "#ff8c00"; + if (color == "darkorchid") return "#9932cc"; + if (color == "darkred") return "#8b0000"; + if (color == "darksalmon") return "#e9967a"; + if (color == "darkseagreen") return "#8fbc8f"; + if (color == "darkslateblue") return "#483d8b"; + if (color == "darkslategray") return "#2f4f4f"; + if (color == "darkslategrey") return "#2f4f4f"; + if (color == "darkturquoise") return "#00ced1"; + if (color == "darkviolet") return "#9400d3"; + if (color == "deeppink") return "#ff1493"; + if (color == "deepskyblue") return "#00bfff"; + if (color == "dimgray") return "#696969"; + if (color == "dimgrey") return "#696969"; + if (color == "dodgerblue") return "#1e90ff"; + if (color == "firebrick") return "#b22222"; + if (color == "floralwhite") return "#fffaf0"; + if (color == "forestgreen") return "#228b22"; + if (color == "fuchsia") return "#ff00ff"; + if (color == "gainsboro") return "#dcdcdc"; + if (color == "ghostwhite") return "#f8f8ff"; + if (color == "gold") return "#ffd700"; + if (color == "goldenrod") return "#daa520"; + if (color == "gray") return "#808080"; + if (color == "green") return "#008000"; + if (color == "greenyellow") return "#adff2f"; + if (color == "grey") return "#808080"; + if (color == "honeydew") return "#f0fff0"; + if (color == "hotpink") return "#ff69b4"; + if (color == "indianred") return "#cd5c5c"; + if (color == "indigo") return "#4b0082"; + if (color == "ivory") return "#fffff0"; + if (color == "khaki") return "#f0e68c"; + if (color == "lavender") return "#e6e6fa"; + if (color == "lavenderblush") return "#fff0f5"; + if (color == "lawngreen") return "#7cfc00"; + if (color == "lemonchiffon") return "#fffacd"; + if (color == "lightblue") return "#add8e6"; + if (color == "lightcoral") return "#f08080"; + if (color == "lightcyan") return "#e0ffff"; + if (color == "lightgoldenrodyellow") return "#fafad2"; + if (color == "lightgray") return "#d3d3d3"; + if (color == "lightgreen") return "#90ee90"; + if (color == "lightgrey") return "#d3d3d3"; + if (color == "lightpink") return "#ffb6c1"; + if (color == "lightsalmon") return "#ffa07a"; + if (color == "lightseagreen") return "#20b2aa"; + if (color == "lightskyblue") return "#87cefa"; + if (color == "lightslategray") return "#778899"; + if (color == "lightslategrey") return "#778899"; + if (color == "lightsteelblue") return "#b0c4de"; + if (color == "lightyellow") return "#ffffe0"; + if (color == "lime") return "#00ff00"; + if (color == "limegreen") return "#32cd32"; + if (color == "linen") return "#faf0e6"; + if (color == "magenta") return "#ff00ff"; + if (color == "maroon") return "#800000"; + if (color == "mediumaquamarine") return "#66cdaa"; + if (color == "mediumblue") return "#0000cd"; + if (color == "mediumorchid") return "#ba55d3"; + if (color == "mediumpurple") return "#9370db"; + if (color == "mediumseagreen") return "#3cb371"; + if (color == "mediumslateblue") return "#7b68ee"; + if (color == "mediumspringgreen") return "#00fa9a"; + if (color == "mediumturquoise") return "#48d1cc"; + if (color == "mediumvioletred") return "#c71585"; + if (color == "midnightblue") return "#191970"; + if (color == "mintcream") return "#f5fffa"; + if (color == "mistyrose") return "#ffe4e1"; + if (color == "moccasin") return "#ffe4b5"; + if (color == "navajowhite") return "#ffdead"; + if (color == "navy") return "#000080"; + if (color == "oldlace") return "#fdf5e6"; + if (color == "olive") return "#808000"; + if (color == "olivedrab") return "#6b8e23"; + if (color == "orange") return "#ffa500"; + if (color == "orangered") return "#ff4500"; + if (color == "orchid") return "#da70d6"; + if (color == "palegoldenrod") return "#eee8aa"; + if (color == "palegreen") return "#98fb98"; + if (color == "paleturquoise") return "#afeeee"; + if (color == "palevioletred") return "#db7093"; + if (color == "papayawhip") return "#ffefd5"; + if (color == "peachpuff") return "#ffdab9"; + if (color == "peru") return "#cd853f"; + if (color == "pink") return "#ffc0cb"; + if (color == "plum") return "#dda0dd"; + if (color == "powderblue") return "#b0e0e6"; + if (color == "purple") return "#800080"; + if (color == "red") return "#ff0000"; + if (color == "rosybrown") return "#bc8f8f"; + if (color == "royalblue") return "#4169e1"; + if (color == "saddlebrown") return "#8b4513"; + if (color == "salmon") return "#fa8072"; + if (color == "sandybrown") return "#f4a460"; + if (color == "seagreen") return "#2e8b57"; + if (color == "seashell") return "#fff5ee"; + if (color == "sienna") return "#a0522d"; + if (color == "silver") return "#c0c0c0"; + if (color == "skyblue") return "#87ceeb"; + if (color == "slateblue") return "#6a5acd"; + if (color == "slategray") return "#708090"; + if (color == "slategrey") return "#708090"; + if (color == "snow") return "#fffafa"; + if (color == "springgreen") return "#00ff7f"; + if (color == "steelblue") return "#4682b4"; + if (color == "tan") return "#d2b48c"; + if (color == "teal") return "#008080"; + if (color == "thistle") return "#d8bfd8"; + if (color == "tomato") return "#ff6347"; + if (color == "turquoise") return "#40e0d0"; + if (color == "violet") return "#ee82ee"; + if (color == "wheat") return "#f5deb3"; + if (color == "white") return "#ffffff"; + if (color == "whitesmoke") return "#f5f5f5"; + if (color == "yellow") return "#ffff00"; + if (color == "yellowgreen") return "#9acd32"; + + return "#000000"; + } + } + + }
diff --git libsvgbird/Defs.vala(new)
--- /dev/null +++ b/libsvgbird/Defs.vala @@ -1,1 +1,139 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using B; + using Math; + + namespace SvgBird { + + public class Defs { + public Gee.ArrayList<ClipPath> clip_paths = new Gee.ArrayList<ClipPath> (); + public Gee.ArrayList<Gradient> gradients = new Gee.ArrayList<Gradient> (); + public StyleSheet style_sheet = new StyleSheet (); + + public void add (Gradient g) { + gradients.add (g); + } + + public ClipPath? get_clip_path_for_url (string? url) { + if (url == null) { + return null; + } + + string tag_id = get_id_from_url ((!) url); + return get_clip_path_for_id (tag_id); + } + + public ClipPath? get_clip_path_for_id (string id) { + string tag_id; + + if (id.has_prefix ("#")) { + tag_id = id.substring ("#".length); + } else { + tag_id = id; + } + + foreach (ClipPath clip_path in clip_paths) { + if (clip_path.id == tag_id) { + return clip_path; + } + } + + return null; + } + + public static string get_id_from_url (string url) { + if (unlikely (!is_url (url))) { + return ""; + } + + int p1 = url.index_of ("("); + if (unlikely (p1 == -1)) { + warning ("Not an URL: " + url); + return ""; + } + + int p2 = url.index_of (")"); + if (unlikely (p2 == -1 || p2 < p1)) { + warning ("Not an URL: " + url); + return ""; + } + + p1 += "(".length; + int length = p2 - p1; + return url.substring (p1, length); + } + + public Gradient? get_gradient_for_url (string? url) { + if (url == null) { + return null; + } + + string tag_id = get_id_from_url ((!) url); + return get_gradient_for_id (tag_id); + } + + public Gradient? get_gradient_for_id (string id) { + string tag_id; + + if (id.has_prefix ("#")) { + tag_id = id.substring ("#".length); + } else { + tag_id = id; + } + + foreach (Gradient gradient in gradients) { + if (gradient.id == tag_id) { + return gradient; + } + } + + return null; + } + + public static bool is_url (string? attribute) { + if (attribute == null) { + return false; + } + + return ((!) attribute).has_prefix ("url"); + } + + public Defs shallow_copy () { + Defs d = new Defs (); + + foreach (Gradient g in gradients) { + d.add (g); + } + + d.style_sheet = style_sheet.shallow_copy (); + + return d; + } + + public Defs copy () { + Defs d = new Defs (); + + foreach (Gradient g in gradients) { + d.add (g.copy ()); + } + + d.style_sheet = style_sheet.copy (); + + return d; + } + } + + }
diff --git libsvgbird/Doubles.vala(new)
--- /dev/null +++ b/libsvgbird/Doubles.vala @@ -1,1 +1,92 @@ + /* + Copyright (C) 2015 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Cairo; + + namespace SvgBird { + + public class Doubles : GLib.Object { + public PointValue* data; + public int size = 0; + int capacity = 10; + + public Doubles () { + data = new PointValue[capacity]; + } + + ~Doubles () { + delete data; + data = null; + } + + public Doubles.for_capacity (int capacity) { + data = new PointValue[capacity]; + this.capacity = capacity; + } + + void increase_capacity () { + int new_capacity = 2 * capacity; + PointValue* new_data = new PointValue[new_capacity]; + Posix.memcpy (new_data, data, sizeof (PointValue) * size); + delete data; + data = new_data; + capacity = new_capacity; + } + + public void add_type (uchar type) { + if (size >= capacity) { + increase_capacity (); + } + + data[size].type = type; + size++; + } + + public void add (double d) { + if (size >= capacity) { + increase_capacity (); + } + + data[size].value = d; + size++; + } + + public Doubles copy () { + Doubles d = new Doubles (); + delete d.data; + d.data = new double[capacity]; + d.capacity = capacity; + d.size = size; + Posix.memcpy (d.data, data, sizeof (PointValue) * size); + return d; + } + + public double get_double (int index) { + if (unlikely (index < 0)) { + warning ("index < 0"); + return 0; + } + + if (unlikely (index >= size)) { + warning ("index >= size"); + return 0; + } + + return data[index].value; + } + } + + } +
diff --git libsvgbird/Ellipse.vala(new)
--- /dev/null +++ b/libsvgbird/Ellipse.vala @@ -1,1 +1,76 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Cairo; + using Math; + + namespace SvgBird { + + public class Ellipse : Object { + + public double cx = 0; + public double cy = 0; + public double rx = 0; + public double ry = 0; + + public Ellipse () { + } + + public Ellipse.create_copy (Ellipse c) { + Object.copy_attributes (c, this); + c.cx = cx; + c.cx = cy; + c.rx = rx; + c.ry = ry; + } + + public override bool is_over (double x, double y) { + return false; + } + + public override void draw_outline (Context cr) { + cr.save (); + cr.translate (cx, cy); + cr.scale (rx, ry); + cr.arc (0, 0, 1, 0, 2 * PI); + cr.restore (); + } + + public override void move (double dx, double dy) { + } + + public override void update_region_boundaries () { + } + + public override void rotate (double theta, double xc, double yc) { + } + + public override bool is_empty () { + return false; + } + + public override void resize (double ratio_x, double ratio_y) { + } + + public override Object copy () { + return new Ellipse.create_copy (this); + } + + public override string to_string () { + return "Ellipse"; + } + } + + }
diff --git libsvgbird/EmptyObject.vala(new)
--- /dev/null +++ b/libsvgbird/EmptyObject.vala @@ -1,1 +1,57 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Cairo; + + namespace SvgBird { + + public class EmptyObject : Object { + + public EmptyObject () { + } + + public override bool is_over (double x, double y) { + return false; + } + + public override void draw_outline (Context cr) { + } + + public override void move (double dx, double dy) { + } + + public override void update_region_boundaries () { + } + + public override void rotate (double theta, double xc, double yc) { + } + + public override bool is_empty () { + return false; + } + + public override void resize (double ratio_x, double ratio_y) { + } + + public override Object copy () { + return new EmptyObject (); + } + + public override string to_string () { + return "Empty"; + } + } + + }
diff --git libsvgbird/Gradient.vala(new)
--- /dev/null +++ b/libsvgbird/Gradient.vala @@ -1,1 +1,80 @@ + /* + Copyright (C) 2015 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Cairo; + using Math; + + namespace SvgBird { + + public class Gradient : GLib.Object { + public double x1; + public double y1; + public double x2; + public double y2; + + public Gee.ArrayList<Stop> stops; + + public string id = ""; + public string? href = null; + public SvgTransforms transforms; + + public Gradient () { + x1 = 0; + y1 = 0; + x2 = 0; + y2 = 0; + stops = new Gee.ArrayList<Stop> (); + transforms = new SvgTransforms (); + } + + public Gradient copy () { + Gradient g = new Gradient (); + g.x1 = x1; + g.y1 = y1; + g.x2 = x2; + g.y2 = y2; + + foreach (Stop s in stops) { + g.stops.add (s.copy ()); + } + + return g; + } + + public void copy_stops (Gradient g) { + foreach (Stop stop in g.stops) { + stops.add (stop.copy ()); + } + } + + public string to_string () { + StringBuilder description = new StringBuilder (); + description.append ("Gradient: "); + description.append (@"x1=$x1, y1=$y1, x2=$x2, y2=$y2"); + + foreach (Stop stop in stops) { + description.append (" "); + description.append (stop.to_string ()); + } + + return description.str; + } + + public Matrix get_matrix () { + return transforms.get_matrix (); + } + } + + }
diff --git libsvgbird/Layer.vala(new)
--- /dev/null +++ b/libsvgbird/Layer.vala @@ -1,1 +1,184 @@ + /* + Copyright (C) 2015 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Cairo; + + namespace SvgBird { + + public class Layer : Object { + public ObjectGroup objects; + public string name = "Layer"; + + public Layer () { + objects = new ObjectGroup (); + transforms = new SvgTransforms (); + } + + public void draw (Context cr) { + cr.save (); + apply_transform (cr); + + if (clip_path != null) { + ClipPath clipping = (!) clip_path; + clipping.apply (cr); + } + + foreach (Object object in objects) { + cr.save (); + object.apply_transform (cr); + + if (object.clip_path != null) { + ClipPath clipping = (!) object.clip_path; + clipping.apply (cr); + } + + if (object is Layer) { + Layer sublayer = (Layer) object; + sublayer.draw (cr); + } else { + object.draw_outline (cr); + object.paint (cr); + } + cr.restore (); + } + + cr.restore (); + } + + public override void draw_outline (Context cr) { + apply_transform (cr); + + foreach (Object object in objects) { + cr.save (); + object.apply_transform (cr); + object.draw_outline (cr); + cr.restore (); + } + + cr.restore (); + } + + public int index_of (Layer sublayer) { + return objects.index_of (sublayer); + } + + public void add_layer (Layer layer) { + objects.add (layer); + } + + public void add_object (Object object) { + objects.add (object); + } + + public void remove (Object o) { + objects.remove (o); + } + + public void remove_layer (Layer layer) { + objects.remove (layer); + + foreach (Object object in objects) { + if (object is Layer) { + Layer sublayer = (Layer) object; + sublayer.remove_layer (layer); + } + } + } + + public static void copy_layer (Layer from, Layer to) { + to.name = from.name; + to.objects = from.objects.copy (); + } + + public override Object copy () { + Layer layer = new Layer (); + copy_layer (this, layer); + Object.copy_attributes (this, layer); + return layer; + } + + public Gee.ArrayList<Layer> get_sublayers () { + Gee.ArrayList<Layer> sublayers = new Gee.ArrayList<Layer> (); + + foreach (Object object in objects) { + if (likely (object is Layer)) { + Layer sublayer = (Layer) object; + sublayers.add (sublayer); + } else { + warning ("An object in the group " + name + " is not a layer."); + } + } + + return sublayers; + } + + public void print (int indent = 0) { + stdout.printf (@"Layer: $(name)"); + + if (!visible) { + stdout.printf (" hidden"); + } + + stdout.printf (@" $(transforms) $(style)"); + + stdout.printf (@"\n"); + + foreach (Object object in objects) { + stdout.printf (@"$(object.to_string ()) $(object.transforms) $(object.style)"); + + if (!object.visible) { + stdout.printf (" hidden"); + } + + stdout.printf ("\n"); + + for (int i = 0; i < indent; i++) { + stdout.printf ("\t"); + } + + if (object is Layer) { + Layer sublayer = (Layer) object; + sublayer.print (indent + 1); + } + } + } + + + public override bool is_over (double x, double y) { + return false; + } + + public override void move (double dx, double dy) { + } + + public override void update_region_boundaries () { + } + + public override void rotate (double theta, double xc, double yc) { + } + + public override bool is_empty () { + return false; + } + + public override void resize (double ratio_x, double ratio_y) { + } + + public override string to_string () { + return "Layer: " + name; + } + } + + }
diff --git libsvgbird/Line.vala(new)
--- /dev/null +++ b/libsvgbird/Line.vala @@ -1,1 +1,73 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Cairo; + + namespace SvgBird { + + public class Line : Object { + + public double x1 = 0; + public double y1 = 0; + public double x2 = 0; + public double y2 = 0; + + public Line () { + } + + public Line.create_copy (Line c) { + Object.copy_attributes (c, this); + c.x1 = x1; + c.y1 = y1; + c.x2 = x2; + c.y2 = y2; + } + + public override bool is_over (double x, double y) { + return false; + } + + public override void draw_outline (Context cr) { + cr.move_to (x1, y1); + cr.line_to (x1, y1); + cr.line_to (x2, y2); + } + + public override void move (double dx, double dy) { + } + + public override void update_region_boundaries () { + } + + public override void rotate (double theta, double xc, double yc) { + } + + public override bool is_empty () { + return false; + } + + public override void resize (double ratio_x, double ratio_y) { + } + + public override Object copy () { + return new Line.create_copy (this); + } + + public override string to_string () { + return "Line"; + } + } + + }
diff --git libsvgbird/LineCap.vala(new)
--- /dev/null +++ b/libsvgbird/LineCap.vala @@ -1,1 +1,24 @@ + /* + Copyright (C) 2015 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + namespace SvgBird { + + public enum LineCap { + BUTT, + SQUARE, + ROUND + } + + }
diff --git libsvgbird/Object.vala(new)
--- /dev/null +++ b/libsvgbird/Object.vala @@ -1,1 +1,159 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Cairo; + using Math; + + namespace SvgBird { + + public abstract class Object : GLib.Object { + bool open = false; + + public bool visible = true; + public SvgStyle style = new SvgStyle (); + public SvgTransforms transforms = new SvgTransforms (); + public ClipPath? clip_path = null; + public string id = ""; + public string css_class = ""; + + public virtual Color? color { get; set; } // FIXME: keep this in svg style + public virtual Color? stroke_color { get; set; } + public virtual Gradient? gradient { get; set; } + + /** Path boundaries */ + public virtual double xmax { get; set; } + public virtual double xmin { get; set; } + public virtual double ymax { get; set; } + public virtual double ymin { get; set; } + + public virtual double rotation { get; set; } + public virtual double stroke { get; set; } + public virtual LineCap line_cap { get; set; default = LineCap.BUTT; } + public virtual bool fill { get; set; } + + public Object () { + } + + public Object.create_copy (Object o) { + open = o.open; + } + + public void set_open (bool open) { + this.open = open; + } + + public bool is_open () { + return open; + } + + public abstract void update_region_boundaries (); + public abstract bool is_over (double x, double y); + public abstract void draw_outline (Context cr); + + public abstract Object copy (); + public abstract void move (double dx, double dy); + public abstract void rotate (double theta, double xc, double yc); + public abstract bool is_empty (); + public abstract void resize (double ratio_x, double ratio_y); + + public static void copy_attributes (Object from, Object to) { + to.open = from.open; + + to.color = from.color; + to.stroke_color = from.stroke_color; + to.gradient = from.gradient; + + to.xmax = from.xmax; + to.xmin = from.xmin; + to.ymax = from.ymax; + to.ymin = from.ymin; + + to.rotation = from.rotation; + to.stroke = from.stroke; + to.line_cap = from.line_cap; + to.fill = from.fill; + } + + public virtual string to_string () { + return "Object"; + } + + public void paint (Context cr) { + Color fill, stroke; + bool need_fill = style.fill_gradient != null || style.fill != null; + bool need_stroke = style.stroke_gradient != null || style.stroke != null; + + cr.set_line_width (style.stroke_width); + + if (style.fill_gradient != null) { + apply_gradient (cr, (!) style.fill_gradient); + } else if (style.fill != null) { + fill = (!) style.fill; + cr.set_source_rgba (fill.r, fill.g, fill.b, fill.a); + } + + if (need_fill) { + if (need_stroke) { + cr.fill_preserve (); + } else { + cr.fill (); + } + } + + if (style.stroke_gradient != null) { + apply_gradient (cr, (!) style.stroke_gradient); + } else if (style.stroke != null) { + stroke = (!) style.stroke; + cr.set_source_rgba (stroke.r, stroke.g, stroke.b, stroke.a); + } + + if (need_stroke) { + cr.stroke (); + } + } + + public void apply_gradient (Context cr, Gradient? gradient) { + Cairo.Pattern pattern; + Gradient g; + + if (gradient != null) { + g = (!) gradient; + + pattern = new Cairo.Pattern.linear (g.x1, g.y1, g.x2, g.y2); + + Matrix gradient_matrix = g.get_matrix (); + gradient_matrix.invert (); + pattern.set_matrix (gradient_matrix); + + foreach (Stop s in g.stops) { + Color c = s.color; + pattern.add_color_stop_rgba (s.offset, c.r, c.g, c.b, c.a); + } + + cr.set_source (pattern); + } + } + + public void apply_transform (Context cr) { + Matrix view_matrix = cr.get_matrix (); + Matrix object_matrix = transforms.get_matrix (); + + object_matrix.multiply (object_matrix, view_matrix); + cr.set_matrix (object_matrix); + } + + } + + }
diff --git libsvgbird/ObjectGroup.vala(new)
--- /dev/null +++ b/libsvgbird/ObjectGroup.vala @@ -1,1 +1,77 @@ + /* + Copyright (C) 2015 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + namespace SvgBird { + + public class ObjectGroup : GLib.Object { + public Gee.ArrayList<Object> objects; + + public int size { + get { + return objects.size; + } + } + + public ObjectGroup () { + objects = new Gee.ArrayList<Object> (); + } + + public Object get_object (int index) { + if (unlikely (index < 0 || index >= size)) { + warning ("Index out of bounds."); + return new EmptyObject (); + } + + return objects.get (index); + } + + public int index_of (Object o) { + return objects.index_of (o); + } + + public Gee.Iterator<Object> iterator () { + return objects.iterator (); + } + + public void remove (Object p) { + objects.remove (p); + } + + public void add (Object p) { + objects.add (p); + } + + public void clear () { + objects.clear (); + } + + public void append (ObjectGroup group) { + foreach (Object o in group.objects) { + objects.add (o); + } + } + + public ObjectGroup copy () { + ObjectGroup objects_copy = new ObjectGroup (); + + foreach (Object o in objects) { + objects_copy.add (o.copy ()); + } + + return objects_copy; + } + } + + }
diff --git libsvgbird/PointValue.vala(new)
--- /dev/null +++ b/libsvgbird/PointValue.vala @@ -1,1 +1,29 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + namespace SvgBird { + + [CCode (cheader_filename="point_value.h")] + public extern struct PointValue { + uchar type; + double value; + } + + public static const uchar POINT_NONE = 0; + public static const uchar POINT_ARC = 1; + public static const uchar POINT_CUBIC = 2; + public static const uchar POINT_LINE = 3; + + }
diff --git libsvgbird/Points.vala(new)
--- /dev/null +++ b/libsvgbird/Points.vala @@ -1,1 +1,41 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Cairo; + + namespace SvgBird { + + public class Points : GLib.Object { + public Doubles point_data = new Doubles.for_capacity (100); + public double x = 0; + public double y = 0; + public bool closed = false; + public int size { + get { + return point_data.size; + } + } + + public void add (double p) { + point_data.add (p); + } + + public void add_type (uchar type) { + point_data.add_type (type); + } + } + + } +
diff --git libsvgbird/Polygon.vala(new)
--- /dev/null +++ b/libsvgbird/Polygon.vala @@ -1,1 +1,72 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Cairo; + using Math; + + namespace SvgBird { + + public class Polygon : Object { + + public Doubles points = new Doubles (); + + public Polygon () { + } + + public override bool is_over (double x, double y) { + return false; + } + + public override void draw_outline (Context cr) { + if (points.size > 2) { + cr.move_to (points.data[0].value, points.data[1].value); + + for (int i = 2; i < points.size - 1; i += 2) { + cr.line_to (points.data[i].value, points.data[i + 1].value); + } + + cr.close_path (); + } + } + + public override void move (double dx, double dy) { + } + + public override void update_region_boundaries () { + } + + public override void rotate (double theta, double xc, double yc) { + } + + public override bool is_empty () { + return false; + } + + public override void resize (double ratio_x, double ratio_y) { + } + + public override Object copy () { + Polygon p = new Polygon (); + p.points = points.copy (); + Object.copy_attributes (this, p); + return p; + } + + public override string to_string () { + return "Polygon"; + } + } + + }
diff --git libsvgbird/Polyline.vala(new)
--- /dev/null +++ b/libsvgbird/Polyline.vala @@ -1,1 +1,70 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Cairo; + using Math; + + namespace SvgBird { + + public class Polyline : Object { + + public Doubles points = new Doubles (); + + public Polyline () { + } + + public override bool is_over (double x, double y) { + return false; + } + + public override void draw_outline (Context cr) { + if (points.size > 2) { + cr.move_to (points.data[0].value, points.data[1].value); + + for (int i = 2; i < points.size - 1; i += 2) { + cr.line_to (points.data[i].value, points.data[i + 1].value); + } + } + } + + public override void move (double dx, double dy) { + } + + public override void update_region_boundaries () { + } + + public override void rotate (double theta, double xc, double yc) { + } + + public override bool is_empty () { + return false; + } + + public override void resize (double ratio_x, double ratio_y) { + } + + public override Object copy () { + Polyline p = new Polyline (); + p.points = points.copy (); + Object.copy_attributes (this, p); + return p; + } + + public override string to_string () { + return "Polyline"; + } + } + + }
diff --git libsvgbird/Rectangle.vala(new)
--- /dev/null +++ b/libsvgbird/Rectangle.vala @@ -1,1 +1,98 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Cairo; + using Math; + + namespace SvgBird { + + public class Rectangle : Object { + + public double x = 0; + public double y = 0; + public double width = 0; + public double height = 0; + public double rx = 0; + public double ry = 0; + + public Rectangle () { + } + + public override bool is_over (double x, double y) { + return false; + } + + public override void draw_outline (Context cr) { + if (rx == 0 && ry == 0) { + cr.rectangle (x, y, width, height); + } else { + draw_rounded_corners (cr); + } + } + + public void draw_rounded_corners (Context cr) { + cr.new_path (); + elliptical_arc (cr, x + width - rx, y + ry, -PI / 2, 0); + elliptical_arc (cr, x + width - rx, y + height - ry, 0, PI / 2); + elliptical_arc (cr, x + rx, y + height - ry, PI / 2, PI); + elliptical_arc (cr, x + rx, y + ry, PI, PI + PI / 2); + cr.close_path (); + } + + public void elliptical_arc (Context cr, double x, double y, double angle_start, double angle_stop) { + cr.save (); + cr.translate (x + rx, y + ry); + cr.scale (rx, ry); + cr.arc (0, 0, 1, angle_start, angle_stop); + cr.restore (); + } + + public override void move (double dx, double dy) { + x += dx; + y += dy; + } + + public override void update_region_boundaries () { + } + + public override void rotate (double theta, double xc, double yc) { + } + + public override bool is_empty () { + return false; + } + + public override void resize (double ratio_x, double ratio_y) { + } + + public override Object copy () { + Rectangle r = new Rectangle (); + + r.x = x; + r.y = y; + r.width = width; + r.height = height; + + Object.copy_attributes (this, r); + + return r; + } + + public override string to_string () { + return "Rectangle"; + } + } + + }
diff --git libsvgbird/Selector.vala(new)
--- /dev/null +++ b/libsvgbird/Selector.vala @@ -1,1 +1,73 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using B; + using Math; + + namespace SvgBird { + + public class Selector : GLib.Object { + + Gee.ArrayList<SelectorPattern> patterns = new Gee.ArrayList<SelectorPattern> (); + public SvgStyle style { get; set; } + + public Selector (string pattern, SvgStyle style) { + string[] selector_patterns = pattern.split (","); + + for (int i = 0; i < selector_patterns.length; i++) { + patterns.add (new SelectorPattern (selector_patterns[i])); + } + + this.style = style; + } + + public Selector.copy_constructor (Selector selector) { + style = selector.style.copy (); + + foreach (SelectorPattern pattern in selector.patterns) { + patterns.add (pattern.copy ()); + } + } + + public string to_string () { + StringBuilder s = new StringBuilder (); + + foreach (SelectorPattern pattern in patterns) { + if (s.str != "") { + s.append (", "); + } + + s.append (pattern.to_string ()); + } + + return s.str; + } + + public Selector copy () { + return new Selector.copy_constructor (this); + } + + public bool match (XmlElement tag, string? id, string? css_class) { + foreach (SelectorPattern pattern in patterns) { + if (pattern.match (tag, id, css_class)) { + return true; + } + } + + return false; + } + } + + }
--- /dev/null +++ b/libsvgbird/SelectorPattern.vala @@ -1,1 +1,136 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using B; + using Math; + + namespace SvgBird { + + /** A CSS selector pattern. */ + public class SelectorPattern : GLib.Object { + Gee.ArrayList<SelectorTag> tags = new Gee.ArrayList<SelectorTag> (); + + public SelectorPattern.empty () { + } + + public SelectorPattern (string pattern) { + string p = pattern.strip (); + string[] elements = p.split (" "); + + foreach (string element in elements) { + if (element != "") { + tags.add (new SelectorTag (element)); + } + } + } + + public string to_string () { + StringBuilder s = new StringBuilder (); + + foreach (SelectorTag tag in tags) { + if (s.str != "") { + s.append (" "); + } + + s.append (tag.to_string ()); + } + + return s.str; + } + public SelectorPattern copy () { + SelectorPattern pattern = new SelectorPattern.empty (); + + foreach (SelectorTag tag in tags) { + pattern.tags.add (tag.copy ()); + } + + return pattern; + } + + public bool match (XmlElement tag, string? id, string? css_class) { + for (int i = tags.size - 1; i >= 0; i--) { + SelectorTag pattern = tags.get (i); + + if (pattern.name == ">") { + if (i - 1 < 0) { + return false; + } + + string parent = tags.get (i - 1).name; + + if (!has_parent (tag, parent)) { + return false; + } + } else if (pattern.name == "+") { + if (i - 1 < 0) { + return false; + } + + string previous = tags.get (i - 1).name; + + if (!is_immediately_following (tag, previous)) { + return false; + } + } else if (!pattern.match (tag, id, css_class)) { + return false; + } + } + + return true; + } + + public bool is_immediately_following (XmlElement tag, string previous) { + return get_previous (tag) == tag; + } + + XmlElement? get_previous (XmlElement? xml_parent) { + if (xml_parent == null) { + return null; + } + + XmlElement element = (!) xml_parent; + XmlElement? parent = element.get_parent (); + + if (parent != null) { + XmlElement? previous = null; + + foreach (XmlElement e in (!) parent) { + if (e == xml_parent) { + return previous; + } + + previous = e; + } + } + + return null; + } + + public bool has_parent (XmlElement tag, string parent) { + XmlElement? xml_parent = tag.get_parent (); + + while (xml_parent != null) { + if (((!) xml_parent).get_name () == parent) { + return true; + } + + xml_parent = tag.get_parent (); + } + + return false; + } + } + + }
diff --git libsvgbird/SelectorTag.vala(new)
--- /dev/null +++ b/libsvgbird/SelectorTag.vala @@ -1,1 +1,201 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using B; + using Math; + + namespace SvgBird { + + /** A part of a CSS selector pattern. */ + public class SelectorTag : GLib.Object { + public string name; + string? id = null; + string? css_class = null; + Gee.ArrayList<AttributePattern>? attribute_patterns = null; + + public SelectorTag.empty () { + } + + public SelectorTag (string pattern) { + string tag_pattern = pattern.strip (); + int id_separator = tag_pattern.index_of ("#"); + int class_separator = tag_pattern.index_of ("."); + int attribute_separator = tag_pattern.index_of ("["); + + if (attribute_separator != -1) { + parse_attributes (tag_pattern.substring (attribute_separator)); + } + + if (id_separator != -1) { + name = tag_pattern.substring (0, id_separator); + + id_separator += "#".length; + if (attribute_separator == -1) { + id = tag_pattern.substring (id_separator); + } else { + id = tag_pattern.substring (id_separator, attribute_separator - id_separator); + } + } else if (class_separator != -1) { + name = tag_pattern.substring (0, class_separator); + + class_separator += ".".length; + if (attribute_separator == -1) { + css_class = tag_pattern.substring (class_separator); + } else { + css_class = tag_pattern.substring (class_separator, attribute_separator - class_separator); + } + } else { + if (attribute_separator == -1) { + name = tag_pattern; + } else { + css_class = tag_pattern.substring (0, attribute_separator); + } + } + } + + public SelectorTag copy () { + SelectorTag tag = new SelectorTag.empty(); + + tag.name = name; + tag.id = id; + tag.css_class = css_class; + + if (attribute_patterns != null) { + foreach (AttributePattern p in (!) attribute_patterns) { + ((!) attribute_patterns).add (p.copy ()); + } + } + + return tag; + } + + void parse_attributes (string attributes) { + int index = 0; + Gee.ArrayList<AttributePattern> patterns = new Gee.ArrayList<AttributePattern> (); + + while (index != -1) { + int start = attributes.index_of ("[", index); + + if (start == -1) { + return; + } + + int stop = attributes.index_of ("]", start); + + if (stop == -1) { + return; + } + + AttributePattern pattern; + pattern = parse_attribute (attributes.substring (start, stop)); + patterns.add (pattern); + + index = stop + "]".length; + } + + attribute_patterns = patterns; + } + + AttributePattern parse_attribute (string attribute) { + int starts_with = attribute.index_of ("|="); + int in_list = attribute.index_of ("~="); + int equals = attribute.index_of ("="); + AttributePattern pattern = new AttributePattern (); + + if (starts_with != -1) { + pattern.name = attribute.substring (0, starts_with); + pattern.type = AttributePatternType.STARTS_WITH; + pattern.content = attribute.substring (0, equals + "~=".length); + } else if (in_list != -1) { + pattern.name = attribute.substring (0, in_list); + pattern.type = AttributePatternType.LIST; + pattern.content = attribute.substring (0, equals + "~=".length); + } else if (equals != -1) { + pattern.name = attribute.substring (0, equals); + pattern.type = AttributePatternType.EQUALS; + pattern.content = attribute.substring (0, equals + "=".length); + } else { + pattern.name = attribute; + pattern.type = AttributePatternType.ANYTHING; + } + + return pattern; + } + + public bool match (XmlElement tag, string? id, string? css_class) { + string tag_name = tag.get_name (); + + if (this.name != "*" && this.name != "" && tag_name != "") { + if (this.name != tag_name) { + return false; + } + } + + if (this.id != null) { + if (id == null) { + return false; + } + + if (((!) this.id) != ((!) id)) { + return false; + } + } + + if (this.css_class != null) { + if (css_class == null) { + return false; + } + + if (((!) this.css_class) != ((!) css_class)) { + return false; + } + } + + if (attribute_patterns != null) { + foreach (AttributePattern pattern in (!) attribute_patterns) { + if (!pattern.match (tag.get_attributes ())) { + return false; + } + } + } + + return true; + } + + public string to_string () { + StringBuilder s = new StringBuilder (); + s.append ((!) name); + + if (id != null) { + s.append ("#"); + s.append ((!) id); + } + + if (css_class != null) { + s.append ("."); + s.append ((!) css_class); + } + + if (attribute_patterns != null) { + foreach (AttributePattern a in (!) attribute_patterns) { + s.append (a.to_string ()); + } + } + + return s.str; + } + } + + }
diff --git libsvgbird/Stop.vala(new)
--- /dev/null +++ b/libsvgbird/Stop.vala @@ -1,1 +1,40 @@ + /* + Copyright (C) 2015 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Cairo; + using Math; + + namespace SvgBird { + + public class Stop : GLib.Object { + public Color color = new Color (0, 0, 0, 1); + public double offset = 0; + + public Stop () { + } + + public Stop copy () { + Stop s = new Stop (); + s.color = color.copy (); + s.offset = offset; + return s; + } + + public string to_string () { + return @"Stop: $(offset), " + color.to_string (); + } + } + + }
diff --git libsvgbird/StyleSheet.vala(new)
--- /dev/null +++ b/libsvgbird/StyleSheet.vala @@ -1,1 +1,161 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using B; + using Math; + + namespace SvgBird { + + + public class StyleSheet : GLib.Object { + + public Gee.ArrayList<Selector> styles = new Gee.ArrayList<Selector> (); + + public StyleSheet () { + } + + /** Copy references to all selectors in this style sheet. */ + public StyleSheet shallow_copy () { + StyleSheet style_sheet = new StyleSheet (); + + foreach (Selector selector in styles) { + style_sheet.styles.add (selector); + } + + return style_sheet; + } + + public StyleSheet copy () { + StyleSheet style_sheet = new StyleSheet (); + + foreach (Selector selector in styles) { + style_sheet.styles.add (selector.copy ()); + } + + return style_sheet; + } + + public void apply_style (XmlElement tag, SvgStyle style) { + string? id = null; + string? css_class = null; + + foreach (Attribute attribute in tag.get_attributes ()) { + string name = attribute.get_name (); + + if (name == "id") { + id = attribute.get_content (); + } else if (name == "class") { + css_class = attribute.get_content (); + } + } + + foreach (Selector selector in styles) { + if (selector.match (tag, id, css_class)) { + style.apply (selector.style); + } + } + } + + public void merge (StyleSheet style_sheet) { + foreach (Selector selector in style_sheet.styles) { + styles.add (selector.copy ()); + } + } + + public static StyleSheet parse (Defs defs, XmlElement style_tag) { + StyleSheet style_sheet = new StyleSheet (); + string css = style_tag.get_content (); + css = get_cdata (css); + css = add_separators (css); + css = replace_whitespace (css); + + int index = 0; + int start_bracket_length = "{".length; + int end_bracket_length = "}".length; + int css_length = css.length; + + while (index < css_length) { + int style_rules_start = css.index_of ("{", index); + int style_rules_end = css.index_of ("}", style_rules_start); + + if (style_rules_start == -1 || style_rules_end == -1) { + break; + } + + int selector_end = style_rules_start - start_bracket_length; + string selectors = css.substring (index, selector_end - index); + + int style_start = style_rules_start + start_bracket_length; + int style_end = style_rules_end; + string style_rules = css.substring (style_start, style_end - style_start); + + index = style_rules_end + end_bracket_length; + SvgStyle style = new SvgStyle.for_properties (defs, style_rules); + + Selector selector = new Selector (selectors, style); + style_sheet.styles.add (selector); + } + + return style_sheet; + } + + public static string get_cdata (string tag_content) { + StringBuilder data = new StringBuilder (); + + int index = 0; + int cdata_tag_length = "<![CDATA[".length; + int cdata_end_tag_length = "]]>".length; + int content_length = tag_content.length; + + while (index < content_length) { + int cdata_start = tag_content.index_of ("<![CDATA[", index); + int cdata_end = tag_content.index_of ("]]>", cdata_start); + + if (cdata_start == -1 || cdata_end == -1) { + break; + } + + cdata_start += cdata_tag_length; + data.append (tag_content.substring (cdata_start, cdata_end - cdata_start)); + index = cdata_end + cdata_end_tag_length; + } + + if (index < tag_content.length) { + data.append (tag_content.substring (index)); + } + + return data.str; + } + + public static string add_separators (string data) { + string style_data = data.replace (">", " > "); + style_data = data.replace ("+", " + "); + return style_data; + } + + public static string replace_whitespace (string data) { + string style_data = data.replace ("\n", " "); + style_data = style_data.replace ("\r", " "); + style_data = style_data.replace ("\t", " "); + + while (style_data.index_of (" ") != -1) { + style_data = style_data.replace (" ", " "); + } + + return style_data; + } + } + + }
diff --git libsvgbird/SvgArc.vala(new)
--- /dev/null +++ b/libsvgbird/SvgArc.vala @@ -1,1 +1,155 @@ + /* + * BirdFont code from SVG Salamander + * + * Copyright (c) 2004, Mark McKay + * Copyright (c) 2014, Johan Mattsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Mark McKay can be contacted at mark@kitfox.com. Salamander and other + * projects can be found at http://www.kitfox.com + * + * Created on January 26, 2004, 8:40 PM + * Adapded to BirdFont on Juli 2, 2014, 5:01 PM + */ + + using Math; + + namespace SvgBird { + + public static void get_arc_arguments (double x0, double y0, + double rx, double ry, double angle, bool largeArcFlag, + bool sweepFlag, double x, double y, + out double angle_start, out double angle_extent, + out double center_x, out double center_y) { + + // + // Elliptical arc implementation based on the SVG specification notes + // + + double cx, cy; + double dx2, dy2, cosAngle, sinAngle; + double x1, y1, Prx, Pry, Px1, Py1, radiiCheck; + double sign, sq, coef, cx1, cy1; + double sx2, sy2; + double ux, uy, vx, vy, p, n; + double angleStart, angleExtent; + + // Compute the half distance between the current and the final point + dx2 = (x0 - x) / 2.0; + dy2 = (y0 - y) / 2.0; + + // Convert angle from degrees to radians + angle = angle % (2 * Math.PI); + + cosAngle = cos (angle); + sinAngle = sin (angle); + + // + // Step 1 : Compute (x1, y1) + // + x1 = cosAngle * dx2 + sinAngle * dy2; + y1 = -sinAngle * dx2 + cosAngle * dy2; + + // Ensure radii are large enough + rx = fabs(rx); + ry = fabs(ry); + Prx = rx * rx; + Pry = ry * ry; + Px1 = x1 * x1; + Py1 = y1 * y1; + + // Check that radii are large enough + radiiCheck = Px1 / Prx + Py1 / Pry; + + if (radiiCheck > 1) { + rx = sqrt (radiiCheck) * rx; + ry = sqrt (radiiCheck) * ry; + Prx = rx * rx; + Pry = ry * ry; + } + + // + // Step 2 : Compute (cx1, cy1) + // + sign = (largeArcFlag == sweepFlag) ? -1 : 1; + sq = ((Prx * Pry) - (Prx * Py1) - (Pry * Px1)) / ((Prx * Py1) + (Pry * Px1)); + sq = (sq < 0) ? 0 : sq; + coef = (sign * Math.sqrt(sq)); + cx1 = coef * ((rx * y1) / ry); + cy1 = coef * -((ry * x1) / rx); + + // + // Step 3 : Compute (cx, cy) from (cx1, cy1) + // + + sx2 = (x0 + x) / 2.0; + sy2 = (y0 + y) / 2.0; + cx = sx2 + (cosAngle * cx1 - sinAngle * cy1); + cy = sy2 + (sinAngle * cx1 + cosAngle * cy1); + + // + // Step 4 : Compute the angleStart (angle1) and the angleExtent (dangle) + // + + ux = (x1 - cx1) / rx; + uy = (y1 - cy1) / ry; + vx = (-x1 - cx1) / rx; + vy = (-y1 - cy1) / ry; + + // Compute the angle start + n = sqrt((ux * ux) + (uy * uy)); + p = ux; // (1 * ux) + (0 * uy) + sign = (uy < 0) ? -1d : 1d; + angleStart = sign * acos(p / n); + + // Compute the angle extent + n = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy)); + p = ux * vx + uy * vy; + sign = (ux * vy - uy * vx < 0) ? -1d : 1d; + angleExtent = sign * Math.acos(p / n); + + if(!sweepFlag && angleExtent > 0) { + angleExtent -= 2 * PI; + } else if (sweepFlag && angleExtent < 0) { + angleExtent += 2 * PI; + } + + center_x = cx; + center_y = cy; + + angleExtent = -angleExtent; + angleStart = -angleStart; + + angle_start = angleStart; + angle_extent = angleExtent; + } + + + } +
diff --git libsvgbird/SvgDrawing.vala(new)
--- /dev/null +++ b/libsvgbird/SvgDrawing.vala @@ -1,1 +1,91 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + + using B; + using Cairo; + using Math; + + namespace SvgBird { + + public class SvgDrawing : Object { + public Layer root_layer = new Layer (); + public Defs defs = new Defs (); + + public double x = 0; + public double y = 0; + + public double width { + get { + return svg_width; + } + + set { + svg_width = value; + } + } + public double svg_width = 0; + + + public double height = 0; + + public override void update_region_boundaries () { + } + + public override bool is_over (double x, double y) { + return (this.x <= x <= this.x + width) + && (this.y <= y <= this.y + height); + } + + public void draw (Context cr) { + root_layer.draw (cr); + } + + public override void draw_outline (Context cr) { + root_layer.draw_outline (cr); + } + + public override Object copy () { + SvgDrawing drawing = new SvgDrawing (); + drawing.root_layer = (Layer) root_layer.copy (); + drawing.defs = defs.copy (); + drawing.x = x; + drawing.y = y; + drawing.width = width; + drawing.height = height; + return drawing; + } + + public override void move (double dx, double dy) { + x += dx; + y += dy; + } + + public override void rotate (double theta, double xc, double yc) { + } + + public override bool is_empty () { + return false; + } + + public override void resize (double ratio_x, double ratio_y) { + } + + public override string to_string () { + return @"SvgDrawing x: $x, y: $y, width: $width, height: $height"; + } + } + + }
diff --git libsvgbird/SvgFile.vala(new)
--- /dev/null +++ b/libsvgbird/SvgFile.vala @@ -1,1 +1,1361 @@ + /* + Copyright (C) 2016 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using B; + using Cairo; + using Math; + + namespace SvgBird { + + public class SvgFile : GLib.Object { + + SvgDrawing drawing; + + public SvgFile () { + } + + public SvgDrawing parse_svg_data (string xml_data) { + XmlTree tree = new XmlTree (xml_data); + return parse_svg_file (tree.get_root ()); + } + + public SvgDrawing parse_svg_file (XmlElement svg_tag) { + drawing = new SvgDrawing (); + + SvgStyle style = new SvgStyle (); + SvgStyle.parse (drawing.defs, style, svg_tag); + + foreach (Attribute attr in svg_tag.get_attributes ()) { + if (attr.get_name () == "width") { + drawing.width = parse_number (attr.get_content ()); + } + + if (attr.get_name () == "height") { + drawing.height = parse_number (attr.get_content ()); + } + } + + foreach (XmlElement t in svg_tag) { + string name = t.get_name (); + + if (name == "g") { + parse_layer (drawing.root_layer, style, t); + } + + if (name == "defs") { + parse_defs (drawing, t); + } + + if (name == "a") { + parse_link (drawing.root_layer, style, svg_tag); + } + + parse_object (drawing.root_layer, style, t); + } + + set_object_properties (drawing, new SvgStyle (), svg_tag); + + return drawing; + } + + private void parse_layer (Layer layer, SvgStyle parent_style, XmlElement tag) { + foreach (XmlElement t in tag) { + string name = t.get_name (); + + if (name == "g") { + Layer sublayer = new Layer (); + parse_layer (layer, parent_style, t); + layer.objects.add (sublayer); + } + + if (name == "a") { + parse_link (layer, parent_style, t); + } + + parse_object (layer, parent_style, t); + } + + set_object_properties (layer, parent_style, tag); + } + + void parse_clip_path (SvgDrawing drawing, XmlElement tag) { + ClipPath clip_path; + + Layer layer = new Layer (); + parse_layer (layer, new SvgStyle (), tag); + clip_path = new ClipPath (layer); + + drawing.defs.clip_paths.add (clip_path); + } + + void parse_defs (SvgDrawing drawing, XmlElement tag) { + foreach (XmlElement t in tag) { + // FIXME: radial + string name = t.get_name (); + + if (name == "linearGradient") { + parse_linear_gradient (drawing, t); + } else if (name == "clipPath") { + parse_clip_path (drawing, t); + } + } + + foreach (Gradient gradient in drawing.defs.gradients) { + if (gradient.href != null) { + Gradient? referenced; + referenced = drawing.defs.get_gradient_for_id ((!) gradient.href); + + if (referenced != null) { + gradient.copy_stops ((!) referenced); + } + + gradient.href = null; + } + } + + foreach (XmlElement t in tag) { + // FIXME: radial + string name = t.get_name (); + + if (name == "style") { + drawing.defs.style_sheet = StyleSheet.parse (drawing.defs, t); + } + } + + } + + void parse_linear_gradient (SvgDrawing drawing, XmlElement tag) { + Gradient gradient = new Gradient (); + + drawing.defs.add (gradient); + + foreach (Attribute attr in tag.get_attributes ()) { + string name = attr.get_name (); + + // FIXME: gradientUnits + + if (name == "gradientTransform") { + gradient.transforms = parse_transform (attr.get_content ()); + } + + if (name == "href") { + gradient.href = attr.get_content (); + } + + if (name == "x1") { + gradient.x1 = parse_number (attr.get_content ()); + } + + if (name == "y1") { + gradient.y1 = parse_number (attr.get_content ()); + } + + if (name == "x2") { + gradient.x2 = parse_number (attr.get_content ()); + } + + if (name == "y2") { + gradient.y2 = parse_number (attr.get_content ()); + } + + if (name == "id") { + gradient.id = attr.get_content (); + } + } + + foreach (XmlElement t in tag) { + // FIXME: radial + string name = t.get_name (); + + if (name == "stop") { + parse_stop (gradient, t); + } + } + } + + void parse_stop (Gradient gradient, XmlElement tag) { + SvgStyle parent_style = new SvgStyle (); // not inherited + SvgStyle style = SvgStyle.parse (drawing.defs, parent_style, tag); + Stop stop = new Stop (); + + gradient.stops.add (stop); + + foreach (Attribute attr in tag.get_attributes ()) { + string name = attr.get_name (); + + if (name == "offset") { + string stop_offset = attr.get_content (); + + if (stop_offset.index_of ("%") > -1) { + stop_offset = stop_offset.replace ("%", ""); + stop.offset = parse_number (stop_offset) / 100.0; + } else { + stop.offset = parse_number (stop_offset); + } + } + } + + string? stop_color = style.style.get ("stop-color"); + string? stop_opacity = style.style.get ("stop-opacity"); + Color? color = new Color (0, 0, 0, 1); + + if (stop_color != null) { + color = Color.parse (stop_color); + + if (color != null) { + stop.color = (!) color; + } + } + + if (stop_opacity != null && color != null) { + ((!) color).a = parse_number (stop_opacity); + } + } + + // links are ignored, add the content to the layer + void parse_link (Layer layer, SvgStyle parent_style, XmlElement tag) { + parse_layer (layer, parent_style, tag); + } + + void parse_object (Layer layer, SvgStyle parent_style, XmlElement tag) { + string name = tag.get_name (); + + if (name == "path") { + parse_path (layer, parent_style, tag); + } + + if (name == "polygon") { + parse_polygon (layer, parent_style, tag); + } + + if (name == "polyline") { + parse_polyline (layer, parent_style, tag); + } + + if (name == "rect") { + parse_rect (layer, parent_style, tag); + } + + if (name == "circle") { + parse_circle (layer, parent_style, tag); + } + + if (name == "ellipse") { + parse_ellipse (layer, parent_style, tag); + } + + if (name == "line") { + parse_line (layer, parent_style, tag); + } + } + + private void parse_polygon (Layer layer, SvgStyle parent_style, XmlElement tag) { + Polygon polygon = new Polygon (); + + foreach (Attribute attr in tag.get_attributes ()) { + if (attr.get_name () == "points") { + string data = add_separators (attr.get_content ()); + string[] point_data = data.split (" "); + + foreach (string number in point_data) { + polygon.points.add (parse_number (number)); + } + } + } + + set_object_properties (polygon, parent_style, tag); + layer.add_object (polygon); + } + + void set_object_properties (Object object, SvgStyle parent_style, XmlElement tag) { + Attributes attributes = tag.get_attributes (); + + foreach (Attribute attribute in attributes) { + string name = attribute.get_name (); + + if (name == "id") { + object.id = attribute.get_content (); + } else if (name == "class") { + object.css_class = attribute.get_content (); + } + } + + object.clip_path = get_clip_path (attributes); + object.transforms = get_transform (attributes); + object.style = SvgStyle.parse (drawing.defs, parent_style, tag); + object.visible = is_visible (tag); + } + + ClipPath? get_clip_path (Attributes attributes) { + foreach (Attribute attribute in attributes) { + if (attribute.get_name () == "clip-path") { + return drawing.defs.get_clip_path_for_url (attribute.get_content ()); + } + } + + return null; + } + + private void parse_polyline (Layer layer, SvgStyle parent_style, XmlElement tag) { + Polyline polyline = new Polyline (); + + foreach (Attribute attr in tag.get_attributes ()) { + if (attr.get_name () == "points") { + string data = add_separators (attr.get_content ()); + string[] point_data = data.split (" "); + + foreach (string number in point_data) { + polyline.points.add (parse_number (number)); + } + } + } + + set_object_properties (polyline, parent_style, tag); + layer.add_object (polyline); + } + + private void parse_rect (Layer layer, SvgStyle parent_style, XmlElement tag) { + Rectangle rectangle = new Rectangle (); + + foreach (Attribute attr in tag.get_attributes ()) { + string attribute = attr.get_name (); + + if (attribute == "x") { + rectangle.x = parse_number (attr.get_content ()); + } + + if (attribute == "y") { + rectangle.y = parse_number (attr.get_content ()); + } + + if (attribute == "width") { + rectangle.width = parse_number (attr.get_content ()); + } + + if (attribute == "height") { + rectangle.height = parse_number (attr.get_content ()); + } + + if (attribute == "rx") { + rectangle.rx = parse_number (attr.get_content ()); + } + + if (attribute == "ry") { + rectangle.ry = parse_number (attr.get_content ()); + } + } + + set_object_properties (rectangle, parent_style, tag); + layer.add_object (rectangle); + } + + private void parse_circle (Layer layer, SvgStyle parent_style, XmlElement tag) { + Circle circle = new Circle (); + + foreach (Attribute attr in tag.get_attributes ()) { + string name = attr.get_name (); + + if (name == "cx") { + circle.cx = parse_number (attr.get_content ()); + } + + if (name == "cy") { + circle.cy = parse_number (attr.get_content ()); + } + + if (name == "r") { + circle.r = parse_number (attr.get_content ()); + } + } + + set_object_properties (circle, parent_style, tag); + layer.add_object (circle); + } + + private void parse_ellipse (Layer layer, SvgStyle parent_style, XmlElement tag) { + Ellipse ellipse = new Ellipse (); + + foreach (Attribute attr in tag.get_attributes ()) { + string name = attr.get_name (); + + if (name == "cx") { + ellipse.cx = parse_number (attr.get_content ()); + } + + if (name == "cy") { + ellipse.cy = parse_number (attr.get_content ()); + } + + if (name == "rx") { + ellipse.rx = parse_number (attr.get_content ()); + } + + if (name == "ry") { + ellipse.ry = parse_number (attr.get_content ()); + } + } + + set_object_properties (ellipse, parent_style, tag); + layer.add_object (ellipse); + } + + private void parse_line (Layer layer, SvgStyle parent_style, XmlElement tag) { + Line line = new Line (); + + foreach (Attribute attr in tag.get_attributes ()) { + string name = attr.get_name (); + + if (name == "x1") { + line.x1 = parse_number (attr.get_content ()); + } + + if (name == "y1") { + line.y1 = parse_number (attr.get_content ()); + } + + if (name == "x2") { + line.x2 = parse_number (attr.get_content ()); + } + + if (name == "y2") { + line.y2 = parse_number (attr.get_content ()); + } + } + + set_object_properties (line, parent_style, tag); + layer.add_object (line); + } + + // FIXME: reverse order? + public SvgTransforms parse_transform (string transforms) { + string[] functions; + string transform = transforms; + SvgTransforms transform_functions; + + transform_functions = new SvgTransforms (); + + transform = transform.replace ("\t", " "); + transform = transform.replace ("\n", " "); + transform = transform.replace ("\r", " "); + + // use only a single space as separator + while (transform.index_of (" ") > -1) { + transform = transform.replace (" ", " "); + } + + if (unlikely (transform.index_of (")") == -1)) { + warning ("No parenthesis in transform function."); + return transform_functions; + } + + // add separator + transform = transform.replace (") ", "|"); + transform = transform.replace (")", "|"); + functions = transform.split ("|"); + + for (int i = 0; i < functions.length; i++) { + if (functions[i].has_prefix ("translate")) { + transform_functions.add (translate (functions[i])); + } + + if (functions[i].has_prefix ("scale")) { + transform_functions.add (scale (functions[i])); + } + + if (functions[i].has_prefix ("matrix")) { + transform_functions.add (matrix (functions[i])); + } + + // TODO: rotate etc. + } + + return transform_functions; + } + + private SvgTransform matrix (string function) { + string parameters = get_transform_parameters (function); + string[] p = parameters.split (" "); + SvgTransform transform = new SvgTransform (); + transform.type = TransformType.MATRIX; + + if (unlikely (p.length != 6)) { + warning ("Expecting six parameters for matrix transformation."); + return transform; + } + + for (int i = 0; i < 6; i++) { + double argument = parse_double (p[i]); + transform.arguments.add (argument); + } + + return transform; + } + + private static string remove_unit (string d) { + string s = d.replace ("pt", ""); + s = s.replace ("pc", ""); + s = s.replace ("mm", ""); + s = s.replace ("cm", ""); + s = s.replace ("in", ""); + s = s.replace ("px", ""); + return s; + } + + public static double parse_number (string? number_with_unit) { + if (number_with_unit == null) { + return 0; + } + + string d = (!) number_with_unit; + string s = remove_unit (d); + double n = parse_double (s); + + if (d.has_suffix ("pt")) { + n *= 1.25; + } else if (d.has_suffix ("pc")) { + n *= 15; + } else if (d.has_suffix ("mm")) { + n *= 3.543307; + } else if (d.has_suffix ("cm")) { + n *= 35.43307; + } else if (d.has_suffix ("in")) { + n *= 90; + } + + return n; + } + + private SvgTransform scale (string function) { + string parameters = get_transform_parameters (function); + string[] p = parameters.split (" "); + SvgTransform transform = new SvgTransform (); + transform.type = TransformType.SCALE; + + if (p.length > 0) { + transform.arguments.add (parse_double (p[0])); + } + + if (p.length > 1) { + transform.arguments.add (parse_double (p[1])); + } + + return transform; + } + + private SvgTransform translate (string function) { + string parameters = get_transform_parameters (function); + string[] p = parameters.split (" "); + SvgTransform transform = new SvgTransform (); + transform.type = TransformType.TRANSLATE; + + if (p.length > 0) { + transform.arguments.add (parse_double (p[0])); + } + + if (p.length > 1) { + transform.arguments.add (parse_double (p[1])); + } + + return transform; + } + + private string get_transform_parameters (string function) { + int i; + string param = ""; + + i = function.index_of ("("); + return_val_if_fail (i != -1, param); + param = function.substring (i); + + param = param.replace ("(", ""); + param = param.replace ("\n", " "); + param = param.replace ("\t", " "); + param = param.replace (",", " "); + + while (param.index_of (" ") > -1) { + param.replace (" ", " "); + } + + return param.strip(); + } + + private bool is_visible (XmlElement tag) { + bool hidden = false; + + foreach (Attribute attr in tag.get_attributes ()) { + if (attr.get_name () == "display" && attr.get_content () == "none") { + hidden = true; + } + + if (attr.get_name () == "visibility" + && (attr.get_content () == "hidden" + || attr.get_content () == "collapse")) { + hidden = true; + } + } + + return !hidden; + } +