The Birdfont Source Code


All Repositories / birdfont.git / commitdiff – RSS feed

Merge ../birdfont-2.x

These changes was commited to the Birdfont repository Thu, 16 Jun 2016 17:51:05 +0000.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git
[Thu, 16 Jun 2016 17:51:05 +0000]

Updated Files

README.md
birdfont-test/TestRunner.vala
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/BackgroundTools.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/FileDialogTab.vala
libbirdfont/Glyph.vala
libbirdfont/GlyphCanvas.vala
libbirdfont/GlyphRange.vala
libbirdfont/GlyphSequence.vala
libbirdfont/Gradient.vala
libbirdfont/Help.vala
libbirdfont/ImportUtils.vala
libbirdfont/KerningDisplay.vala
libbirdfont/LabelTool.vala
libbirdfont/Layer.vala
libbirdfont/LayerLabel.vala
libbirdfont/LayerUtils.vala
libbirdfont/LicenseDialog.vala
libbirdfont/Ligatures.vala
libbirdfont/MainWindow.vala
libbirdfont/Menu.vala
libbirdfont/MenuItem.vala
libbirdfont/MenuTab.vala
libbirdfont/MoveTool.vala
libbirdfont/OpenFontFormat/ContextualLigature.vala
libbirdfont/OpenFontFormat/DirectoryTable.vala
libbirdfont/OpenFontFormat/FontData.vala
libbirdfont/OpenFontFormat/GlyfData.vala
libbirdfont/OpenFontFormat/GlyfTable.vala
libbirdfont/OpenFontFormat/HheaTable.vala
libbirdfont/OpenFontFormat/OpenFontFormatReader.vala
libbirdfont/OpenFontFormat/OpenFontFormatWriter.vala
libbirdfont/OpenFontFormat/Os2Table.vala
libbirdfont/OpenFontFormat/OtfInputStream.vala
libbirdfont/OpenFontFormat/OtfTable.vala
libbirdfont/OpenFontFormat/PostTable.vala
libbirdfont/OpenFontFormat/SvgTable.vala
libbirdfont/OpenFontFormat/SvgTableEntry.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/SpinButton.vala
libbirdfont/Stop.vala
libbirdfont/StrokeTool.vala
libbirdfont/Svg.vala
libbirdfont/SvgArc.vala
libbirdfont/SvgFont.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
libbirdgems/fit_cubic.c
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
resources/linux/birdfont-import.1
scripts/builder.py
scripts/version.py
--- a/README.md +++ b/README.md @@ -19,15 +19,15 @@ packages with a -dev or -devel affix: valac - python3-doit font-roboto - libxmlbird-dev + python3-doit libgee-dev libglib2.0-dev libgtk-3-dev libwebkitgtk-3.0-dev libnotify-dev libsqlite3-dev + libxmlbird-dev XML Bird is available from [birdfont.org][xmlbird].
--- a/birdfont-test/TestRunner.vala +++ b/birdfont-test/TestRunner.vala @@ -37,7 +37,7 @@ if (type == "SVG") { File f = File.new_for_path (file); Font font = new Font (); - import_svg_file (font, f); + import_svg_file (font, f, SvgType.REGULAR); } else if (type == "BF") { Font font = new Font (); font.set_font_file (file);
--- 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; @@ -109,7 +107,7 @@ if (fd.get_name () == "Preview") { uri = Preview.get_uri (); html = Preview.get_html_with_absolute_paths (); - + try { html_canvas.load_html (html, uri); } catch (Error e) { @@ -486,8 +484,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,20 +50,33 @@ 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 - - def has_posixvala (): - posixvala = test_library_version ('posixvala') - if not posixvala: - print (OKGREEN + 'Glib will be used instead of Posix (libc).' + ENDC) - return 'False' - else: - print (OKGREEN + 'Using posix profile.' + ENDC) - return 'True' + + 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 configure(gtk, libbgee, valac): global gee @@ -82,7 +96,6 @@ 'libsoup-2.4', 'libnotify', 'sqlite3', - 'xmlbird' ] else: libs = [ @@ -90,25 +103,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 +198,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,13 +85,15 @@ --pkg webkit2gtk-3.0 \ --pkg libnotify \ --pkg xmlbird \ - --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) \ @@ -107,7 +116,7 @@ $(pkg-config --libs webkit2gtk-3.0) \ $(pkg-config --libs xmlbird) \ $(pkg-config --libs libnotify) \ - -L./build -L./build/bin -l birdgems\ + -L./build -L./build/bin -l birdgems -l svgbird \ -o build/bin/""" + target_binary birdfont = Builder('birdfont', @@ -121,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 + """ \ @@ -136,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) \ @@ -160,7 +172,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 birdfont_export = Builder('birdfont-export', @@ -174,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 + """\ @@ -189,13 +201,15 @@ --pkg gio-2.0 \ --pkg cairo \ --pkg xmlbird \ - --pkg libbirdfont + --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) \ @@ -213,7 +227,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 birdfont_import = Builder('birdfont-import', @@ -227,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 + """\ @@ -243,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) \ @@ -259,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) \ @@ -267,7 +284,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 birdfont_autotrace = Builder('birdfont-autotrace', @@ -281,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 + """\ @@ -295,13 +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 \ """ cc_command = config.CC + " " + config.CFLAGS.get("libbirdfont", "") + """ \ @@ -310,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 + """) \ @@ -331,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', @@ -345,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 + """\ @@ -440,13 +518,15 @@ --pkg gio-2.0 \ --pkg cairo \ --pkg xmlbird \ - --pkg libbirdfont + --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) \ @@ -463,7 +543,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 test = Builder('birdfont-test', @@ -479,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 {
--- a/libbirdfont/BackgroundTools.vala +++ b/libbirdfont/BackgroundTools.vala @@ -112,6 +112,7 @@ add_part (selection); } + parts.clear_cache (); parts.redraw (); }
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 @@ -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 { @@ -446,7 +448,7 @@ 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); } @@ -454,38 +456,110 @@ os.put_string ("\t</glyph>\n"); } - void write_layer (Layer layer, DataOutputStream os) throws GLib.Error { - string data; + void write_embedded_svg (EmbeddedSvg svg, DataOutputStream os) throws GLib.Error { + XmlParser xml = new XmlParser ((!) svg.svg_data); - // FIXME: name etc. + 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"); + + 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"); + } + } + + void write_tag_attributes (DataOutputStream os, Tag tag) throws GLib.Error { + bool first = true; + + foreach (Attribute attribute in tag.get_attributes ()) { + string ns = attribute.get_namespace (); + + if (!first) { + os.put_string (" "); + } + + if (ns != "") { + os.put_string (ns); + os.put_string (":"); + } + + os.put_string (attribute.get_name ()); + os.put_string ("="); + os.put_string ("\""); + os.put_string (attribute.get_content ()); + os.put_string ("\""); + + first = false; + } + } + + void write_layer (Layer layer, DataOutputStream os) throws GLib.Error { 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) { @@ -1407,7 +1481,7 @@ foreach (Tag t in tag) { if (t.get_name () == "background") { parse_background_scale (glyph, t); - } + } } foreach (Path p in glyph.get_all_paths ()) { @@ -1418,6 +1492,33 @@ gc.set_unassigned (unassigned); master.insert_glyph (glyph, selected || selected_id == id); + } + + void parse_embedded_svg (Layer layer, Tag tag) { + string type = ""; + double x = 0; + double y = 0; + + foreach (Attribute attribute in tag.get_attributes ()) { + if (attribute.get_name () == "x") { + x = parse_double (attribute.get_content ()); + } + + if (attribute.get_name () == "y") { + y = parse_double (attribute.get_content ()); + } + + if (attribute.get_name () == "type") { + type = attribute.get_content (); + } + } + + if (type == "svg") { + EmbeddedSvg svg = SvgParser.parse_embedded_svg_data (tag.get_content ()); + svg.x = x; + svg.y = y; + layer.add_object (svg); + } } Layer parse_layer (Tag tag) { @@ -1437,7 +1538,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); } } @@ -1472,7 +1577,7 @@ } } - return path; + return path; } private static void line (Path path, string px, string py) { @@ -1878,7 +1983,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) { @@ -261,7 +263,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"); @@ -406,9 +408,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; } } } @@ -448,13 +450,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 { @@ -194,6 +195,7 @@ move_canvas = new Tool ("move_canvas", t_("Move canvas") + "\n" + t_("Ctrl + Shift + Click") + "\n" + t_("Space + Click") + "\n"); + move_canvas.select_action.connect ((self) => { update_drawing_and_background_tools (self); }); @@ -240,7 +242,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"); @@ -294,7 +299,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); } @@ -337,7 +342,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); } @@ -377,7 +382,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; @@ -482,7 +487,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; @@ -600,9 +605,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 (); @@ -800,12 +805,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; } } @@ -840,9 +845,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 (); + } } } @@ -878,12 +887,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"); @@ -902,12 +914,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"); @@ -927,12 +942,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"); @@ -1253,7 +1271,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; } @@ -1611,7 +1629,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,120 @@ + /* + 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 left { + get { + return x + drawing.left; + } + + set { + } + } + + public override double right { + get { + return x + drawing.right; + } + + set { + } + } + + public override double top { + get { + return drawing.top - y; + } + + set { + } + } + + public override double bottom { + get { + return drawing.bottom - 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 (xmin <= x <= xmax) + && (ymin <= y <= ymax); + } + + 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 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); + } + + public override SvgBird.Object copy () { + EmbeddedSvg svg = new EmbeddedSvg ((SvgDrawing) drawing.copy ()); + SvgBird.Object.copy_attributes (this, svg); + svg.svg_data = svg_data; + svg.x = x; + svg.y = y; + return svg; + } + + public override string to_string () { + return "Embedded SVG"; + } + } + + }
--- 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/FileDialogTab.vala +++ b/libbirdfont/FileDialogTab.vala @@ -203,11 +203,14 @@ } files.sort (); + update_rows (); layout (); base.selected_canvas (); scroll_to (0); + MainWindow.show_scrollbar (); + update_scrollbar (); } public void show_text_area (string text) { @@ -260,6 +263,8 @@ action.file_selected ((!) f.get_path ()); } } + + MainWindow.show_scrollbar (); } public override string get_label () { @@ -273,6 +278,7 @@ public override void button_release (int button, double ex, double ey) { base.button_release (button, ex, ey); show_text_area (selected_filename); + MainWindow.show_scrollbar (); } class SelectedFile : GLib.Object {
--- a/libbirdfont/Glyph.vala +++ b/libbirdfont/Glyph.vala @@ -15,6 +15,8 @@ using Cairo; using Math; using Gee; + using B; + using SvgBird; namespace BirdFont { @@ -129,22 +131,26 @@ public static bool show_orientation_arrow = false; public static double orientation_arrow_opacity = 1; - public Layer layers = new Layer (); + public Layer layers = new Layer.with_name ("Root"); 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 is fetched from a fallback font + // used if this glyph originates from a fallback font public double top_limit = 0; public double baseline = 0; 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 Glyph (string name, unichar unichar_code = 0) { this.name = name; this.unichar_code = unichar_code; - + add_help_lines (); left_limit = -28; @@ -154,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; @@ -176,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); } @@ -238,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) { @@ -249,52 +272,75 @@ } 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) { - var paths = get_all_paths (); + var objects = get_visible_objects (); - if (paths.size == 0) { + if (objects.size == 0) { x1 = 0; y1 = 0; x2 = 0; @@ -307,29 +353,29 @@ y1 = CANVAS_MAX; y2 = CANVAS_MIN; - foreach (Path p in paths) { - p.update_region_boundaries (); + foreach (Layer layer in layers.get_sublayers ()) { + layer.update_boundaries (Matrix.identity ()); + + print (@"layer.right: $(layer.right)\n"); + + if (layer.xmin < x1) { + x1 = layer.xmin; + } - if (p.points.size > 1) { - if (p.xmin < x1) { - x1 = p.xmin; - } + if (layer.xmax > x2) { + x2 = layer.xmax; + } - if (p.xmax > x2) { - x2 = p.xmax; - } + if (layer.ymin < y1) { + y1 = layer.ymin; + } - if (p.ymin < y1) { - y1 = p.ymin; - } - - if (p.ymax > y2) { - y2 = p.ymax; - } + if (layer.ymax > y2) { + y2 = layer.ymax; } } - - return x1 != double.MAX; + + return x1 != CANVAS_MAX; } public void selection_boundaries (out double x, out double y, out double w, out double h) { @@ -340,21 +386,21 @@ px2 = -10000; py2 = -10000; - foreach (Path p in active_paths) { - if (p.xmin < px) { - px = p.xmin; + foreach (SvgBird.Object object in active_paths) { + if (object.xmin < px) { + px = object.xmin; } - if (p.ymin < py) { - py = p.ymin; + if (object.ymin < py) { + py = object.ymin; } - if (p.xmax > px2) { - px2 = p.xmax; + if (object.xmax > px2) { + px2 = object.xmax; } - if (p.ymax > py2) { - py2 = p.ymax; + if (object.ymax > py2) { + py2 = object.ymax; } } @@ -445,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 () { @@ -485,13 +539,19 @@ if (index != "") { int i = int.parse (index); - if (0 <= i < layers.subgroups.size) { + if (0 <= i < layers.objects.size) { current_layer = i; } } DrawingTools.update_layers (); MainWindow.get_toolbox ().update_expanders (); + + print(@"PRECOPY $(name)\n"); + print_layers (layers); + // layers = (Layer) layers.copy (); // FIXME: DELETE + print("\n"); + print_layers (layers); } void update_zoom_bar () { @@ -629,9 +689,9 @@ double max_x, min_x, max_y, min_y; PathList pl; - var paths = get_all_paths (); + var objects = get_visible_objects (); - if (paths.size == 0) { + if (objects.size == 0) { x1 = 0; y1 = 0; x2 = 0; @@ -644,17 +704,22 @@ max_y = CANVAS_MIN; min_y = CANVAS_MAX; - // FIXME: optimize - foreach (Path p in paths) { + foreach (SvgBird.Object object in objects) { - if (p.stroke > 0) { - pl = p.get_stroke_fast (); + if (object is PathObject) { + PathObject o = (PathObject) object; + + if (object.stroke > 0) { + pl = o.get_path ().get_stroke_fast (); - foreach (Path part in pl.paths) { - boundaries_for_path (part, ref min_x, ref max_x, ref min_y, ref max_y); + foreach (Path part in pl.paths) { + boundaries_for_path (part, ref min_x, ref max_x, ref min_y, ref max_y); + } + } else { + boundaries_for_path (o.get_path (), ref min_x, ref max_x, ref min_y, ref max_y); } } else { - boundaries_for_path (p, ref min_x, ref max_x, ref min_y, ref max_y); + boundaries_for_object (object, ref min_x, ref max_x, ref min_y, ref max_y); } } @@ -663,10 +728,27 @@ x2 = max_x; y2 = min_y; - return max_x != CANVAS_MIN; + return true; } - void boundaries_for_path (Path p, ref double min_x, ref double max_x, + void boundaries_for_object (SvgBird.Object object, + ref double min_x, ref double max_x, + ref double min_y, ref double max_y) { + max_x = object.xmax; + min_x = object.xmin; + max_y = object.ymax; + min_y = object.ymin; + + if (object is PathObject) { + PathObject o = (PathObject) object; + boundaries_for_path (o.get_path (), + ref max_x, ref min_x, + ref max_y, ref min_y); + } + } + + void boundaries_for_path (Path path, + ref double min_x, ref double max_x, ref double min_y, ref double max_y) { double max_x2, max_y2, min_x2, min_y2; @@ -676,7 +758,8 @@ max_y2 = max_y; min_y2 = min_y; - p.all_of_path ((x, y, t) => { + // FIXME: Optimize + path.all_of_path ((x, y, t) => { if (x > max_x2) { max_x2 = x; } @@ -696,10 +779,21 @@ return true; }); - max_x = max_x2; - min_x = min_x2; - max_y = max_y2; - min_y = min_y2; + if (max_x2 > max_x) { + max_x = max_x2; + } + + if (min_x2 < min_x) { + min_x = min_x2; + } + + if (max_y2 < max_y) { + max_y = max_y2; + } + + if (min_x2 < min_y) { + min_y = min_y2; + } } bool has_top_line () { @@ -738,10 +832,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 () { @@ -891,8 +989,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) { @@ -1007,7 +1106,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. */ @@ -1141,61 +1240,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) { @@ -1212,48 +1267,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) { @@ -1306,8 +1319,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) { @@ -1321,7 +1335,6 @@ if (px > x) px -= tw + 60; if (py > y) py -= th + 60; - } else { px = x - 60; py = y - 60; @@ -1362,7 +1375,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 (); @@ -1405,7 +1418,7 @@ } warning (@"No line with label $name found"); - return new Line ("Err"); + return new Line ("Error"); } public override void zoom_in () { @@ -1607,157 +1620,110 @@ cr.stroke (); } - /** Draw filled paths. */ - public void draw_paths (Context cr, Color? c = null) { - PathList stroke; - Color color; - bool open; - - cr.save (); - cr.new_path (); - foreach (Path p in get_visible_paths ()) { - if (c != null) { - color = (!) c; - } else if (p.color != null) { - color = (!) p.color; - } else { - color = Color.black (); - } - - if (p.stroke > 0) { - stroke = p.get_stroke_fast (); - draw_path_list (stroke, cr, color); - } else { - open = p.is_open (); - - if (open) { - p.close (); - p.recalculate_linear_handles (); - } - - p.draw_path (cr, this, color); + public void draw_layers (Context cr) { + foreach (SvgBird.Object object in layers.objects) { + if (object is Layer) { + Layer layer = (Layer) object; - if (open) { - p.reopen (); + if (layer.visible) { + draw_layer (cr, layer); } } } - cr.fill (); - cr.restore (); + + draw_bird_font_paths (cr); } - 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 (); + public void draw_layer (Context cr, Layer sublayers) { + foreach (SvgBird.Object object in sublayers.objects) { + if (object is EmbeddedSvg) { + EmbeddedSvg svg = (EmbeddedSvg) object; + + if (svg.visible) { + svg.draw_embedded_svg (cr); } - - draw_path_list (stroke, cr, color); } } - 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); + + if (!is_open ()) { + draw_control_points = false; + } - 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); + 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); } } - cr.fill (); - cr.restore (); + + if (has_path) { + cr.set_fill_rule (FillRule.WINDING); + cr.set_source_rgba (0, 0, 0, 1); + cr.fill (); + } } - - 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.WINDING); + Theme.color (cr, "Objects"); + cr.fill (); } - cr.restore (); } + + 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 (); - 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); + if (object_path.path.stroke > 0) { + cr.set_line_width (CanvasSettings.stroke_width / g.view_zoom); + PathObject.draw_path_list (object_path.path.get_stroke_fast (), cr); + Theme.color (cr, "Objects"); + cr.fill (); + } - cr.save (); - cr.new_path (); - foreach (Path p in get_visible_paths ()) { - if (p.stroke == 0) { - color = p.color == null ? Color.black () : (!) p.color; - p.draw_path (cr, this, color); + cr.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); } } @@ -1797,8 +1763,8 @@ } 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); } } @@ -1815,22 +1781,29 @@ cmp.restore (); } - if (!is_empty ()) { - cmp.save (); - cmp.scale (view_zoom, view_zoom); - cmp.translate (-view_offset_x, -view_offset_y); - draw_path (cmp); - cmp.restore (); + cmp.save (); + 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 (); } - 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); } @@ -1875,6 +1848,37 @@ if (clear_redo) { redo_list.clear (); + } + } + + public void print_layers (Layer layer, int indent = 0) { + stdout.printf (@"Layer ($indent): $(layer.name)"); + + if (!layer.visible) { + stdout.printf (" hidden"); + } + + stdout.printf (@" $(layer.transforms) $(layer.style)"); + stdout.printf (@"\n"); + + foreach (SvgBird.Object object in layer.objects) { + if (object is Layer) { + Layer sublayer = (Layer) object; + print_layers (sublayer, indent + 1); + } else { + stdout.printf (@"$(object.to_string ()) $(object.transforms) $(object.style)"); + + if (!object.visible) { + stdout.printf (" hidden"); + } + + stdout.printf ("\n"); + + if (object is EmbeddedSvg) { + EmbeddedSvg embedded = (EmbeddedSvg) object; + print_layers (embedded.drawing.root_layer, indent + 1); + } + } } } @@ -1897,10 +1901,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) { @@ -1970,7 +1974,11 @@ void set_glyph_data (Glyph g) { current_layer = g.current_layer; - layers = g.layers.copy (); + + layers = (Layer) g.layers.copy (); + + print ("\n\n"); + print_layers (g.layers); left_limit = g.left_limit; right_limit = g.right_limit; @@ -1987,8 +1995,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); @@ -2388,12 +2396,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); } @@ -2403,11 +2411,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); } @@ -2480,8 +2488,143 @@ } return g2; + } + + public Gee.ArrayList<SvgBird.Object> get_active_objects () { + return active_paths; + } + + 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 (SvgBird.Object p in get_active_objects ()) { + 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/GlyphCanvas.vala +++ b/libbirdfont/GlyphCanvas.vala @@ -81,7 +81,7 @@ } public void redraw_area (int x, int y, int w, int h) { - if (MenuTab.has_suppress_event ()) { + if (unlikely (MenuTab.has_suppress_event ())) { warning ("Do not call redraw from background thread."); } else { signal_redraw_area (x, y, w, h);
--- a/libbirdfont/GlyphRange.vala +++ b/libbirdfont/GlyphRange.vala @@ -475,10 +475,9 @@ } public unichar get_character (uint32 index) { - int64 ti; - string chr; UniRange r; - unichar c; + unichar c; + string chr; UniRange? range; uint32 range_start_index;
--- a/libbirdfont/GlyphSequence.vala +++ b/libbirdfont/GlyphSequence.vala @@ -100,9 +100,18 @@ foreach (Alternate a in alternates) { GlyphSequence old = new GlyphSequence (); - Glyph? g = font.get_glyph_by_name (a.glyph_name); + string name; + Glyph? g; + + name = a.glyph_name; + + if (name == "space") { + name = " "; + } + + g = font.get_glyph_by_name (name); - if (g != null) { + if (likely (g != null)) { old.add (g); if (a.alternates.size > 0) { @@ -110,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/ImportUtils.vala +++ b/libbirdfont/ImportUtils.vala @@ -34,6 +34,7 @@ BirdFont.current_font = new Font (); BirdFont.current_glyph_collection = new GlyphCollection.with_glyph ('\0', ""); MainWindow.init (); + SvgType type = SvgType.REGULAR; if (arg.length < 3) { print_import_help (arg); @@ -43,7 +44,11 @@ bf_file = build_absoulute_path (arg[1]); for (int i = 2; i < arg.length; i++) { - svg_files.add (arg[i]); + if (arg[i] == "--color") { + type = SvgType.COLOR; + } else { + svg_files.add (arg[i]); + } } bf = File.new_for_path (bf_file); @@ -79,7 +84,7 @@ foreach (string f in svg_files) { svg = File.new_for_path (f); - imported = import_svg_file (font, svg); + imported = import_svg_file (font, svg, type); if (!imported) { stdout.printf (t_("Failed to import") + " " + f + "\n"); @@ -93,7 +98,7 @@ return 0; } - public static bool import_svg_file (Font font, File svg_file) { + public static bool import_svg_file (Font font, File svg_file, SvgType type) { string file_name = (!) svg_file.get_basename (); string glyph_name; StringBuilder n; @@ -158,10 +163,14 @@ stdout.printf (@"$(glyph.version_id)"); stdout.printf ("\n"); - SvgParser.import_svg ((!) svg_file.get_path ()); + if (type == SvgType.COLOR) { + SvgParser.import_color_svg (glyph, (!) svg_file.get_path ()); + } else { + SvgParser.import_svg ((!) svg_file.get_path ()); + } return true; } }
--- 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,14 +46,18 @@ 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 (); MainWindow.get_current_glyph ().clear_active_paths (); + + Glyph g = MainWindow.get_current_glyph (); + print ("\n\n"); + g.print_layers (g.layers); } else { active_layer = true; select_layer (); @@ -74,6 +85,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 +101,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/Ligatures.vala +++ b/libbirdfont/Ligatures.vala @@ -58,6 +58,10 @@ lig = new GlyphSequence (); foreach (string n in font.get_names (ligature)) { + if (n == "space") { + n = " "; + } + gc = font.get_glyph_collection_by_name (n); if (gc == null) { @@ -69,6 +73,10 @@ gs = new GlyphSequence (); foreach (string s in subst_names) { + if (s == "space") { + s = " "; + } + gc = font.get_glyph_collection_by_name (s); if (gc == null) {
--- 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 @@ -287,17 +287,37 @@ MenuItem import_svg = add_menu_item (t_("Import SVG file"), "import svg file", "Glyph"); import_svg.action.connect (() => { - SvgParser.import (); + SvgParser.import (SvgType.REGULAR); show_menu = false; }); export_menu.items.add (import_svg); - + MenuItem import_svg_folder = add_menu_item (t_("Import SVG folder"), "import svg folder", ""); import_svg_folder.action.connect (() => { - SvgParser.import_folder (); + SvgParser.import_folder (SvgType.REGULAR); show_menu = false; }); export_menu.items.add (import_svg_folder); + + 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_background_image = add_menu_item (t_("Import Background Image"), "import background image"); import_background_image.action.connect (() => { @@ -489,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); @@ -326,6 +328,7 @@ MainWindow.get_toolbox ().update_expanders (); MainWindow.get_toolbox ().update_all_expanders (); Toolbox.redraw_tool_box (); + OverViewItem.glyph_scale = 1; } // FIXME: background thread @@ -644,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,21 +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; - - 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; - } - } + delta_x = -dx; + delta_y = -dy; - 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); } } @@ -162,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); } } @@ -179,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 (); @@ -189,42 +187,36 @@ 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; - - 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); - + 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 (); } - 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); - } - } + 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 (); } - - move_path = true; update_selection_boundaries (); - + + move_path = true; + last_x = x; last_y = y; @@ -238,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)); @@ -249,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); } } } @@ -273,30 +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); - - 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) { @@ -308,23 +282,26 @@ px2 = -10000; py2 = -10000; - foreach (Path p in glyph.active_paths) { - p.update_region_boundaries (); + 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 (px > o.xmin) { + px = o.xmin; } - if (py > p.ymin) { - py = p.ymin; + if (py > o.ymin) { + py = o.ymin; } - if (px2 < p.xmax) { - px2 = p.xmax; + if (px2 < o.xmax) { + px2 = o.xmax; } - if (py2 < p.ymax) { - py2 = p.ymax; + if (py2 < o.ymax) { + py2 = o.ymax; } } @@ -358,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 ()); } @@ -366,10 +343,10 @@ PenTool.reset_stroke (); update_selection_boundaries (); objects_moved (); - glyph.redraw_area (0, 0, glyph.allocation.width, glyph.allocation.height); + GlyphCanvas.redraw (); } - 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; @@ -419,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 { @@ -438,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 (); + } } } @@ -461,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); @@ -476,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); } @@ -490,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); } } @@ -500,8 +484,25 @@ update_selection_boundaries (); objects_moved (); + + ResizeTool.update_selection_box (); + } + + 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/ContextualLigature.vala +++ b/libbirdfont/OpenFontFormat/ContextualLigature.vala @@ -169,6 +169,11 @@ gs = new GlyphSequence (); foreach (string s in font.get_names (context)) { + + if (s == "space") { + s = " "; + } + gc = font.get_glyph_collection_by_name (s); if (gc == null) {
--- a/libbirdfont/OpenFontFormat/DirectoryTable.vala +++ b/libbirdfont/OpenFontFormat/DirectoryTable.vala @@ -33,6 +33,7 @@ public Os2Table os_2_table; public PostTable post_table; public LocaTable loca_table; + public SvgTable svg_table; public OffsetTable offset_table; @@ -57,6 +58,7 @@ name_table = new NameTable (); os_2_table = new Os2Table (glyf_table, hmtx_table, hhea_table); post_table = new PostTable (glyf_table); + svg_table = new SvgTable (); id = "Directory table"; @@ -81,6 +83,7 @@ post_table.process (); kern_table.process (); gpos_table.process (glyf_table); + svg_table.process (glyf_table); offset_table.process (); process_directory (); // this table @@ -101,6 +104,10 @@ tables.add (gsub_table); tables.add (os_2_table); + + if (svg_table.has_glyphs ()) { + tables.add (svg_table); + } // tables.append (gdef_table); // invalid table
--- 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 @@ -165,7 +165,7 @@ g.remove_empty_paths (); unassigned = gc.is_unassigned (); - if (unassigned) { + if (unassigned && gc.get_name () != ".notdef") { unassigned_glyphs.add (gc); } @@ -275,7 +275,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"); } @@ -487,7 +486,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/HheaTable.vala +++ b/libbirdfont/OpenFontFormat/HheaTable.vala @@ -111,8 +111,7 @@ FontData fd = new FontData (); Fixed version = 1 << 16; Font font = OpenFontFormatWriter.get_current_font (); - int upm, total_height; - + fd.add_fixed (version); // table version ascender = (int16) rint (font.top_limit * HeadTable.UNITS);
--- 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/Os2Table.vala +++ b/libbirdfont/OpenFontFormat/Os2Table.vala @@ -104,7 +104,6 @@ fd.add_u32 (0); // ulUnicodeRange3 Bits 64-95 fd.add_u32 (0); // ulUnicodeRange4 Bits 96-127 } else { - warning(@"unicodeRange1: $unicodeRange1 unicodeRange2: $unicodeRange2 unicodeRange3 $unicodeRange3 unicodeRange4: $unicodeRange4"); fd.add_u32 (unicodeRange1); // ulUnicodeRange1 Bits 0-31 fd.add_u32 (unicodeRange2); // ulUnicodeRange2 Bits 32-63 fd.add_u32 (unicodeRange3); // ulUnicodeRange3 Bits 64-95
--- 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/PostTable.vala +++ b/libbirdfont/OpenFontFormat/PostTable.vala @@ -1101,8 +1101,6 @@ assert (names.size == 0); add_standard_names (); - - print ("Adding post names\n"); for (int i = 1; i < glyf_table.glyphs.size; i++) { gc = glyf_table.glyphs.get (i);
--- /dev/null +++ b/libbirdfont/OpenFontFormat/SvgTable.vala @@ -1,1 +1,244 @@ + /* + 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; + + namespace BirdFont { + + public class SvgTable : OtfTable { + + int glyphs_in_table = 0; + Gee.ArrayList<SvgTableEntry> entries; + + public SvgTable () { + id = "SVG "; + entries = new Gee.ArrayList<SvgTableEntry> (); + } + + public bool has_glyphs () { + return glyphs_in_table > 0; + } + + public void process (GlyfTable glyf_table) throws GLib.Error { + Font font = OpenFontFormatWriter.get_current_font (); + GlyphCollection? glyph_collection; + GlyphCollection glyphs; + 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; + embedded_svg = get_embedded_svg (glyphs); + + if (embedded_svg.size > 0) { + gid = glyf_table.get_gid (glyphs.get_name ()); + + 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++; + } + } + } + + process_svg_data (); + } + + 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; + XmlParser xml; + Tag svg_root_tag; + Font font; + + font = OpenFontFormatWriter.get_current_font (); + + layer_content = new Gee.ArrayList<Tag> (); + svg_tags = new Gee.ArrayList<Tag> (); + meta = new Gee.ArrayList<Tag> (); + xml = new XmlParser (svg_data); + + if (!xml.validate ()) { + warning("Invalid SVG data in TTF table."); + return; + } + + svg_root_tag = xml.get_root_tag (); + + foreach (Tag tag in svg_root_tag) { + string name = tag.get_name(); + + if (name == "defs") { + svg_tags.add (tag); + } else if (name == "style") { + svg_tags.add (tag); + } else if (name == "metadata") { + meta.add (tag); + } else if (name == "sodipodi") { + meta.add (tag); + } else { + layer_content.add (tag); + } + } + + svg.append ("<"); + svg.append (svg_root_tag.get_name ()); + svg.append (" "); + append_tag_attributes (svg, svg_root_tag); + svg.append (">"); + + foreach (Tag tag in svg_tags) { + append_tag (svg, tag); + } + + foreach (Tag tag in layer_content) { + append_tag (svg, tag); + } + + svg.append ("</"); + svg.append (svg_root_tag.get_name ()); + svg.append (">\n"); + } + + public void append_tag (StringBuilder svg, Tag tag) { + string content = tag.get_content (); + + svg.append ("<"); + svg.append (tag.get_name ()); + + svg.append (" "); + append_tag_attributes (svg, tag); + + if (content == "") { + svg.append (" /"); + } + + svg.append (">"); + + if (content != "") { + svg.append (content); + svg.append ("</"); + svg.append (tag.get_name ()); + svg.append (">"); + } + } + + public void append_tag_attributes (StringBuilder svg, Tag tag) { + bool first = true; + + foreach (Attribute attribute in tag.get_attributes ()) { + string ns = attribute.get_namespace (); + + if (!first) { + svg.append (" "); + } + + if (ns != "") { + svg.append (ns); + svg.append (":"); + } + + svg.append (attribute.get_name ()); + svg.append ("="); + svg.append ("\""); + svg.append (attribute.get_content ()); + svg.append ("\""); + + first = false; + } + } + + public void process_svg_data () throws GLib.Error { + FontData fd = new FontData (); + + int32 svg_index_offset = 10; + + fd.add_ushort (0); // version + fd.add_ulong (svg_index_offset); + fd.add_ulong (0); // reserved + + uint32 document_offset = 2 + 12 * entries.size; + + // SVG Documents Index + fd.add_ushort ((uint16) entries.size); + + foreach (SvgTableEntry entry in entries) { + fd.add_ushort (entry.glyph_id); // start + fd.add_ushort (entry.glyph_id); // end + fd.add_ulong (document_offset); // offset + fd.add_ulong (entry.data.length_with_padding ()); // length + + document_offset += entry.data.length_with_padding (); + } + + foreach (SvgTableEntry entry in entries) { + fd.append (entry.data); + } + + fd.pad (); + + this.font_data = fd; + } + } + + }
--- /dev/null +++ b/libbirdfont/OpenFontFormat/SvgTableEntry.vala @@ -1,1 +1,29 @@ + /* + 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 SvgTableEntry : GLib.Object { + public FontData data; + public uint16 glyph_id; + + public SvgTableEntry (uint16 gid, string svg) { + glyph_id = gid; + data = new FontData (); + data.add_str (svg); + } + } + + }
--- 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 { @@ -93,7 +94,7 @@ TabBar tabs = MainWindow.get_tab_bar (); string n = glyph_collection.get_current ().name; bool selected = tabs.select_char (n); - Glyph g = glyph_collection.get_current (); + GlyphTab glyph_tab; if (!selected) { @@ -101,6 +102,13 @@ tabs.add_tab (glyph_tab, true, glyph_collection); set_glyph_zoom (glyph_collection); 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); + } } }); @@ -390,7 +398,6 @@ KeyBindings.set_require_modifier (true); update_scrollbar (); update_zoom_bar (); - OverViewItem.glyph_scale = 1; update_item_list (); selected_item = get_selected_item (); GlyphCanvas.redraw (); @@ -496,7 +503,7 @@ return new OverViewItem (); } - if (!(0 <= selected < visible_items.size)) { + if (!(0 <= selected < visible_items.size)) { return selected_item; } @@ -525,7 +532,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; @@ -549,10 +556,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 (); @@ -562,10 +570,20 @@ item = new OverViewItem (); item.set_character (character); item.set_glyphs (glyphs); - item.x = x; - item.y = y; + item.generate_graphics (); + 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_notdef_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,8 +611,17 @@ 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) { + item.selected = (selected_items.index_of ((!) glyphs) != -1); + } + } + + for (int i = 0; i < visible_size; i++) { + item = visible_items.get (i); + item.generate_graphics (); } } @@ -602,30 +629,23 @@ y = OverViewItem.margin; visible_size = visible_items.size; - int selected_index; - bool selected_item; double full_width = OverViewItem.full_width (); for (int i = 0; i < visible_size; i++) { item = visible_items.get (i); - selected_item = false; - if (all_available) { - glyphs = f.get_glyph_collection_index ((uint32) i); + glyphs = font.get_glyph_collection_index ((uint32) i); } else { - glyphs = f.get_glyph_collection_by_name ((!) item.character.to_string ()); + glyphs = font.get_glyph_collection_by_name ((!) item.character.to_string ()); } if (glyphs != null) { - selected_index = selected_items.index_of ((!) glyphs); - selected_item = (selected_index != -1); + int selected_index = selected_items.index_of ((!) glyphs); + item.selected = (selected_index != -1); } item.adjust_scale (); - - selected_item |= (i == selected); - item.selected = selected_item; item.x = x + view_offset_x; item.y = y + view_offset_y; @@ -638,6 +658,7 @@ } } + get_selected_item ().selected = true; update_scheduled = false; } @@ -674,9 +695,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; @@ -1309,7 +1329,7 @@ } public override void button_press (uint button, double x, double y) { - OverViewItem i; + OverViewItem selected_item; int index = 0; int selected_index = -1; bool update = false; @@ -1325,15 +1345,14 @@ } for (int j = 0; j < visible_items.size; j++) { - i = visible_items.get (j); + selected_item = visible_items.get (j); - if (i.click (button, x, y)) { + if (selected_item.click (button, x, y)) { selected = index; selected_item = get_selected_item (); if (KeyBindings.has_shift ()) { if (selected_item.glyphs != null) { - selected_index = selected_items.index_of ((!) selected_item.glyphs); if (selected_index == -1) { selected_items.add ((!) selected_item.glyphs); @@ -1344,6 +1363,8 @@ selected_item = get_selected_item (); } } + + update = true; } else { selected_items.clear (); if (selected_item.glyphs != null) { @@ -1351,15 +1372,15 @@ } } - if (!is_null (i.version_menu)) { - update = !((!)i).version_menu.menu_visible; + if (!is_null (selected_item.version_menu)) { + update = !((!) selected_item.version_menu).menu_visible; } else { update = true; } } index++; } - + if (update) { update_item_list (); }
--- a/libbirdfont/OverViewItem.vala +++ b/libbirdfont/OverViewItem.vala @@ -25,7 +25,11 @@ public GlyphCollection? glyphs; public double x; public double y; - public bool selected = false; + + public bool selected { + get; set; + } + public CharacterInfo info; public static double DEFAULT_WIDTH = 100; @@ -38,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; @@ -56,20 +60,25 @@ } public void set_glyphs (GlyphCollection? gc) { - glyphs = gc; - - if (glyphs != null) { - version_menu = new VersionList ((!) glyphs); - version_menu.add_glyph_item.connect ((glyph) => { + glyphs = gc; + } + + 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); @@ -81,7 +90,7 @@ truncate_label (); } - draw_background (); + draw_background (); } public void draw_glyph_from_font () { @@ -90,7 +99,6 @@ } Glyph g; - Font font; double gx, gy; double x1, x2, y1, y2; double scale_box; @@ -98,7 +106,6 @@ double glyph_width, glyph_height; Surface s; Context c; - Color color = Color.black (); g = ((!) glyphs).get_current (); @@ -106,10 +113,10 @@ cache = g.overview_thumbnail; return; } - + w = width; h = height; - + scale_box = width / DEFAULT_WIDTH; s = Screen.create_background_surface ((int) width, (int) height - 20); @@ -117,21 +124,20 @@ c.save (); g.boundaries (out x1, out y1, out x2, out y2); - + 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 (); - + 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); - g.draw_paths (c, color); c.restore (); cache = s; @@ -139,26 +145,16 @@ } public void draw_background () { - Glyph g; - Font font; - double gx, gy; - double x1, x2, y1, y2; double scale_box; - double w, h; - double glyph_width, glyph_height; Surface s; Context c; - Color color = Color.black (); - - w = width; - h = height; - + scale_box = width / DEFAULT_WIDTH; s = Screen.create_background_surface ((int) width, (int) height - 20); c = new Context (s); - if (glyphs != null) { // FIXME: lock + if (glyphs != null) { draw_glyph_from_font (); } else { c.scale (Screen.get_scale (), Screen.get_scale ()); @@ -213,10 +209,6 @@ s.append_unichar (character); return s.str; - } - - public void set_selected (bool s) { - selected = s; } public static double full_width () { @@ -232,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); @@ -280,7 +273,6 @@ cr.restore (); draw_thumbnail (cr, x, y + height); - draw_caption (cr); draw_menu (cr); } @@ -293,21 +285,22 @@ if (glyphs != null) { font = BirdFont.get_current_font (); g = ((!) glyphs).get_current (); - g.boundaries (out x1, out y1, out x2, out y2); - - glyph_width = x2 - x1; - glyph_height = y2 - y1; + + if (g.boundaries (out x1, out y1, out x2, out y2)) { + glyph_width = x2 - x1; + glyph_height = y2 - y1; - if (glyph_scale == 1) { - // caption height is 20 - glyph_scale = (height - 20) / (font.top_limit - font.bottom_limit); - } + if (glyph_scale == 1) { + // caption height is 20 + glyph_scale = (height - 20) / (font.top_limit - font.bottom_limit); + } + + scale = glyph_scale; + gx = ((width / scale) - glyph_width) / 2; - scale = glyph_scale; - gx = ((width / scale) - glyph_width) / 2; - - if (gx < 0) { - glyph_scale = 1 + 2 * gx / width; + if (gx < 0) { + glyph_scale = 1 + 2 * gx / width; + } } } } @@ -363,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); } @@ -420,6 +416,10 @@ bool has_menu () { return glyphs != null; + } + + public void clear_cache () { + cache = null; } public void draw_label_background (Context cr) { @@ -456,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; } } @@ -476,13 +477,23 @@ } private void draw_menu (Context cr) { - if (likely (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 @@ -329,6 +329,8 @@ } o.undo_items.add (ui); + + MainWindow.get_overview ().update_item_list (); GlyphCanvas.redraw (); } @@ -352,9 +354,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 @@ -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; @@ -300,7 +267,9 @@ } // fill path - cr.close_path (); + if (!is_open ()) { + cr.close_path (); + } if (this.color != null) { c = (!) this.color; @@ -308,12 +277,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); - } } } @@ -356,7 +319,6 @@ cr.translate (-x * zoom, -y * zoom); arrow_icon.draw_at_baseline (cr, x * zoom, y * zoom); - cr.restore (); } } @@ -390,7 +352,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; @@ -402,8 +363,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 @@ -470,7 +429,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); @@ -600,7 +559,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; @@ -823,21 +782,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) { - return is_over_coordinate_var (x, y); } public static double point_distance (EditPoint p1, EditPoint p2) { @@ -894,7 +838,7 @@ } /** Variable precision */ - public bool is_over_coordinate_var (double x, double y) { + public bool is_over_coordinate (double x, double y) { int insides = 0; Path path; @@ -922,11 +866,6 @@ } public bool is_over_boundry (double x, double y) { - if (unlikely (ymin == double.MAX || ymin == 10000)) { - warning ("bounding box is not calculated, run update_region_boundaries first."); - update_region_boundaries (); - } - return (ymin <= y <= ymax) && (xmin <= x <= xmax); } @@ -995,6 +934,7 @@ points.add (p); p.prev = previous_point; p.next = previous_point.next; + previous_point.next = p; } last_point = p; @@ -2266,12 +2206,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 left { + get { + return path.xmin; + } + + set { + } + } + + public override double right { + get { + return path.xmax; + } + + set { + } + } + + public override double top { + get { + return -path.ymax; + } + + set { + } + } + + public override double bottom { + get { + return -path.ymin; + } + + set { + } + } + + public override double stroke { + get { + return path.stroke; + } + + set { + path.stroke = value; + } + } + + 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) { + if (path.stroke > 0) { + draw_path_list (path.get_completed_stroke (), cr); + } else { + 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 () { + xmin = Glyph.CANVAS_MAX; + xmax = Glyph.CANVAS_MIN; + ymin = Glyph.CANVAS_MAX; + ymax = Glyph.CANVAS_MIN; + + path.update_region_boundaries (); + + xmin = path.xmin; + xmax = path.xmax; + ymin = path.ymin; + ymax = path.ymax; + } + + 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 (); + } } } @@ -1052,10 +1055,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; } } @@ -1226,7 +1230,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) { @@ -1405,8 +1412,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 (); @@ -1414,7 +1423,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 (); @@ -1444,14 +1453,15 @@ } // join path with it self - if (is_close_to_point (path.points.get (0), px, py)) { - + if (is_close_to_point (path.points.get (0), px, py) && path.points.size > 2) { close_path (path); glyph.close_path (); 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 (); @@ -1490,11 +1500,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 (); @@ -1775,13 +1786,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; } @@ -1817,6 +1834,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 (); @@ -1824,7 +1842,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; @@ -1850,7 +1869,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; @@ -1901,7 +1921,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; @@ -1971,7 +1991,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 (); @@ -2060,7 +2080,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,337 +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 ("Cannot load fontconfig."); - } - - FcConfigSetCurrent (config); - #else - config = FcInitLoadConfigAndFonts (); - #endif - - IdleSource idle = new IdleSource (); - - idle.set_callback (() => { - font_config = config; - 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,592 +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) { - double none = 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 = "") { - double x, y; - double ratio; - double cc_y; - - 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 py = cc_y; - - 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 (); } @@ -165,36 +165,38 @@ Text handle; Glyph g = MainWindow.get_current_glyph (); - if (!rotate_path) { - if (!resize_width) { - handle = proportional_handle; - get_resize_handle_position (out handle.widget_x, out handle.widget_y); + if (!move_paths) { + if (!rotate_path) { + if (!resize_width) { + handle = proportional_handle; + get_resize_handle_position (out handle.widget_x, out handle.widget_y); + + handle.widget_x -= handle.get_sidebearing_extent () / 2; + handle.widget_y -= handle.get_height () / 2; + + handle.draw (cr); + } - handle.widget_x -= handle.get_sidebearing_extent () / 2; - handle.widget_y -= handle.get_height () / 2; + if (!resize_path_proportional) { + handle = horizontal_handle; + + get_horizontal_reseize_handle_position (out handle.widget_x, + out handle.widget_y); + + handle.widget_x -= handle.get_sidebearing_extent () / 2; + handle.widget_y -= handle.get_height () / 2; + + handle.draw (cr); + } + } + + if (!resize_path_proportional && !resize_width + && g.active_paths.size > 0) { - handle.draw (cr); - } - - if (!resize_path_proportional) { - handle = horizontal_handle; - - get_horizontal_reseize_handle_position (out handle.widget_x, - out handle.widget_y); - - handle.widget_x -= handle.get_sidebearing_extent () / 2; - handle.widget_y -= handle.get_height () / 2; - - handle.draw (cr); + draw_rotate_handle (cr); } } - - if (!resize_path_proportional && !resize_width - && g.active_paths.size > 0) { - - draw_rotate_handle (cr); - } - + MoveTool.draw_actions (cr); }); @@ -234,9 +236,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 +247,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); } @@ -255,13 +257,15 @@ if (glyph.active_paths.size > 0) { last_path = glyph.active_paths.get (glyph.active_paths.size - 1); - rotation = last_path.rotation; if (rotation > PI) { rotation -= 2 * PI; } - last_rotate = rotation; + if (last_rotate > PI) { + last_rotate -= 2 * PI; + } + signal_objects_rotated (); } } @@ -287,7 +291,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; @@ -315,8 +319,8 @@ cr.rectangle (cx - 2.5, cy - 2.5, 5, 5); cr.fill (); - hx = cos (rotation) * get_rotated_handle_length (); - hy = sin (rotation) * get_rotated_handle_length (); + hx = cos (last_rotate) * get_rotated_handle_length (); + hy = sin (last_rotate) * get_rotated_handle_length (); cr.set_line_width (1); cr.move_to (cx, cy); @@ -364,17 +368,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 +385,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); } @@ -402,7 +405,7 @@ } } - void update_selection_box () { + public static void update_selection_box () { MoveTool.update_boundaries_for_selection (); MoveTool.get_selection_box_boundaries (out selection_box_center_x, out selection_box_center_y, out selection_box_width, @@ -470,7 +473,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 +484,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 +500,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 +516,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 +543,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.")));
--- a/libbirdfont/SpinButton.vala +++ b/libbirdfont/SpinButton.vala @@ -44,6 +44,12 @@ /** Lock the button to a fixed value. */ public bool locked = false; + + static Gee.ArrayList<Text> digits; + static double text_height = 14; + static Text period; + static Text comma; + static Text minus; public SpinButton (string? name = null, string tip = "") { base (null , tip); @@ -51,7 +57,7 @@ if (name != null) { base.name = (!) name; } - + set_icon ("spin_button"); panel_press_action.connect ((selected, button, tx, ty) => { @@ -144,6 +150,23 @@ decrease (); return true; }); + + if (is_null (digits)) { + add_digits (); + } + } + + void add_digits () { + digits = new Gee.ArrayList<Text> (); + + for (int i = 0; i < 10; i++) { + Text digit = new Text (@"$i", text_height); + digits.add (digit); + } + + period = new Text (".", text_height); + comma = new Text (",", text_height); + minus = new Text ("-", text_height); } public void show_icon (bool i) { @@ -451,18 +474,31 @@ } return v; + } + + Text get_glyph (unichar character) { + Text text; + + if ('0' <= character <= '9') { + int digit_index = int.parse ((!) character.to_string ()); + text = digits.get (digit_index); + } else if (character == '.') { + text = period; + } else if (character == ',') { + text = comma; + } else if (character == '-') { + text = minus; + } else { + text = new Text ((!) character.to_string (), text_height); + } + + return text; } public override void draw_tool (Context cr, double px, double py) { - double scale = Toolbox.get_scale (); - double text_height = 14 * scale; - string display_value = get_short_display_value (); - Text text = new Text (display_value, text_height); double x = x - px; double y = y - py; - - double text_x = x + (w - text.get_sidebearing_extent ()) / 2 + 1; - double text_y = y + (h - text_height) / 2; + string display_value = get_short_display_value (); if (!show_icon_tool_icon || waiting_for_icon_switch) { if (is_selected ()) { @@ -481,18 +517,51 @@ base.draw_tool (cr, px, py); if (!show_icon_tool_icon || waiting_for_icon_switch) { - if (is_selected ()) { - Theme.text_color (text, "Selected Tool Foreground"); - } else { - Theme.text_color (text, "Tool Foreground"); + unichar digit; + int index; + Text text; + double extent = 0; + double decender = 0; + double carret = 0; + double total_extent = 0; + double x_offset; + + index = 0; + while (display_value.get_next_char (ref index, out digit)) { + text = get_glyph (digit); + total_extent += text.get_sidebearing_extent (); } - text.widget_x = text_x; - text.widget_y = text_y + text.get_decender (); - text.draw (cr); + x_offset = (w - total_extent) / 2 + 1; + + index = 0; + while (display_value.get_next_char (ref index, out digit)) { + text = get_glyph (digit); + extent = text.get_sidebearing_extent (); + + if (decender < text.get_decender ()) { + decender = text.get_decender (); + } + + if (is_selected ()) { + Theme.text_color (text, "Selected Tool Foreground"); + } else { + Theme.text_color (text, "Tool Foreground"); + } + + double text_x = x + carret + x_offset;; + double text_y = y + (h - text_height) / 2; + + text.widget_x = text_x; + text.widget_y = text_y + decender; + text.draw (cr); + + carret += extent; + + } } } } }
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/SvgFont.vala +++ b/libbirdfont/SvgFont.vala @@ -253,11 +253,6 @@ glyph.right_limit = glyph.left_limit + advance * units; // FIXME: add svg font ligatures - /* - if (ligature != "") { - glyph.set_ligature_substitution (ligature); - } - */ glyph_collection = new GlyphCollection (unicode_value, glyph_name); glyph_collection.insert_glyph (glyph, true);
--- 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 { @@ -21,6 +22,11 @@ NONE, INKSCAPE, ILLUSTRATOR + } + + public enum SvgType { + COLOR, + REGULAR } public class SvgParser { @@ -34,7 +40,7 @@ format = f; } - public static void import () { + public static void import (SvgType type) { FileChooser fc = new FileChooser (); fc.file_selected.connect ((p) => { string path; @@ -44,14 +50,32 @@ } path = (!) p; - import_svg (path); + + if (type == SvgType.REGULAR) { + import_svg (path); + } else if (type == SvgType.COLOR) { + Glyph glyph = MainWindow.get_current_glyph (); + import_color_svg (glyph, path); + } }); fc.add_extension ("svg"); MainWindow.file_chooser (t_("Import"), fc, FileChooser.LOAD); + } + + public static void import_color_svg (Glyph glyph, string path) { + 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 () { + public static void import_folder (SvgType type) { FileChooser fc = new FileChooser (); fc.file_selected.connect ((p) => { string path; @@ -78,7 +102,7 @@ if (file_name.has_suffix (".svg")) { svg = get_child (svg_folder, file_name); - imported = import_svg_file (font, svg); + imported = import_svg_file (font, svg, type); if (!imported) { warning ("Can't import %s.", (!) svg.get_path ()); @@ -101,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) { @@ -124,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 (); @@ -171,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); @@ -209,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; @@ -232,7 +248,7 @@ return; } - foreach (Tag t in tag) { + foreach (XmlElement t in tag) { if (t.get_name () == "path") { parse_path (t, pl); } @@ -240,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") { @@ -276,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); } } @@ -289,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 (" ", " "); @@ -452,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; @@ -485,7 +509,7 @@ } } - style = SvgStyle.parse (tag.get_attributes ()); + style = SvgStyle.parse (null, style, tag); if (hidden) { return; @@ -511,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; @@ -553,7 +577,7 @@ } } - style = SvgStyle.parse (tag.get_attributes ()); + style = SvgStyle.parse (null, style, tag); if (hidden) { return; @@ -579,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; @@ -619,7 +643,7 @@ } } - style = SvgStyle.parse (tag.get_attributes ()); + style = SvgStyle.parse (null, style, tag); if (hidden) { return; @@ -656,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; @@ -697,7 +721,7 @@ } } - style = SvgStyle.parse (tag.get_attributes ()); + style = SvgStyle.parse (null, style, tag); if (hidden) { return; @@ -757,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 (); @@ -792,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") { @@ -810,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 (); @@ -832,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); + } } } @@ -970,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) { @@ -1022,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 @@ -1030,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. @@ -1987,29 +1480,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; } @@ -2030,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]; @@ -2051,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++; @@ -2070,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); } } @@ -216,6 +222,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,337 @@ + /* + 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; + 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 @@ -109,7 +109,6 @@ end_point.path.reverse (); } - Path path = end_point.path; glyph.set_active_path (end_point.path); } else { p = new Path (); @@ -125,8 +124,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 (); + } } } }); @@ -150,8 +152,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 = PointType.CUBIC; @@ -167,10 +170,13 @@ return_if_fail (drawing); add_endpoint_and_merge (x, y); } - - foreach (Path path in g.active_paths) { - convert_hidden_points (path); - path.update_region_boundaries (); + + foreach (Object path in g.active_paths) { + if (path is PathObject) { + Path freehand_path = ((PathObject) path).get_path (); + convert_hidden_points (freehand_path); + freehand_path.update_region_boundaries (); + } } g.clear_active_paths (); @@ -231,12 +237,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 ()) { @@ -405,7 +416,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 (); EditPoint last_point = new EditPoint (); @@ -422,10 +440,11 @@ added_points++; PenTool.convert_point_to_line (new_point, false); + new_point.set_point_type (PointType.HIDDEN); p.recalculate_linear_handles_for_point (new_point); - last_point.get_right_handle ().length = 0.000001; + last_point.get_right_handle ().length = 0.000001; if (p.points.size > 1) { glyph.redraw_segment (new_point, new_point.get_prev ()); @@ -501,7 +520,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. */ @@ -530,10 +557,6 @@ * @return the last removed point. */ public void convert_points_to_line () { - EditPoint ep, last_point; - double sum_x, sum_y, nx, ny; - int px, py; - EditPoint average, previous; Path p; Glyph glyph; Gee.ArrayList<EditPoint> points; @@ -557,9 +580,6 @@ warning ("Missing point."); return; } - - sum_x = 0; - sum_y = 0; int start = p.points.size - 1 - added_points; int stop = p.points.size - 1;
--- a/libbirdgems/fit_cubic.c +++ b/libbirdgems/fit_cubic.c @@ -22,10 +22,10 @@ #include "GraphicsGems.h" - #ifdef MAC - #include <malloc/malloc.h> - #else + #ifdef __linux__ #include <malloc.h> + #else + #include <stdlib.h> #endif #include <math.h>
--- /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,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; + + namespace SvgBird { + + public class Circle : Object { + + public double cx = 0; + public double cy = 0; + public double r = 0; + + public Circle () { + } + + public override bool is_over (double x, double y) { + return false; + } + + public override void draw_outline (Context cr) { + cr.move_to (cx + r, cy); + 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 () { + Circle c = new Circle (); + + Object.copy_attributes (this, c); + c.cx = cx; + c.cy = cy; + c.r = r; + + return c; + } + + public override string to_string () { + return "Circle"; + } + } + + }
diff --git libsvgbird/ClipPath.vala(new)
--- /dev/null +++ b/libsvgbird/ClipPath.vala @@ -1,1 +1,44 @@ + /* + 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 (); + } + + public ClipPath copy () { + ClipPath path = new ClipPath (layer); + return path; + } + } + + }
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,141 @@ + /* + 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; + } + + tag_id = tag_id.down (); + + foreach (Gradient gradient in gradients) { + if (gradient.id.down () == 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 PointValue[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,102 @@ + /* + 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 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.move_to (1, 0); + 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 () { + Ellipse e = new Ellipse (); + Object.copy_attributes (this, e); + e.cx = cx; + e.cy = cy; + e.rx = rx; + e.ry = ry; + return e; + } + + public override string to_string () { + return "Ellipse"; + } + + public override void update_boundaries (Matrix view_matrix) { + Matrix object_matrix = transforms.get_matrix (); + object_matrix.multiply (object_matrix, view_matrix); + + double radius_x = rx + style.stroke_width / 2; + double radius_y = ry + style.stroke_width / 2; + + double px, py; + + top = CANVAS_MAX; + bottom = CANVAS_MIN; + left = CANVAS_MAX; + right = CANVAS_MIN; + + for (double a = 0; a < 2 * PI; a += (2 * PI) / 20) { + px = cx + radius_x * cos (a); + py = cy + radius_y * sin (a); + + object_matrix.transform_point (ref px, ref py); + + top = fmin (top, py); + bottom = fmax (bottom, py); + left = fmin (left, px); + right = fmax (right, px); + } + } + } + + }
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,86 @@ + /* + 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 ()); + } + + g.id = id; + g.href = href; + transforms = transforms.copy (); + + print (@"$(this)\n"); + + 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 $(id): "); + 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,217 @@ + /* + 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 Layer : Object { + public ObjectGroup objects; + public string name = "Layer"; + + public Layer () { + objects = new ObjectGroup (); + transforms = new SvgTransforms (); + } + + public Layer.with_name (string name) { + this.name = name; + objects = new ObjectGroup (); + transforms = new SvgTransforms (); + } + + public override void update_boundaries (Matrix view_matrix) { + if (objects.size == 0) { + return; + } + + top = CANVAS_MAX; + bottom = CANVAS_MIN; + left = CANVAS_MAX; + right = CANVAS_MIN; + + Matrix layer_matrix = transforms.get_matrix (); + layer_matrix.multiply (layer_matrix, view_matrix); + + foreach (Object object in objects) { + object.update_boundaries (layer_matrix); + + left = fmin (left, object.left); + right = fmax (right, object.right); + top = fmin (top, object.top); + bottom = fmax (bottom, object.bottom); + } + } + + 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); + update_boundaries (Matrix.identity ()); + } + + public void add_object (Object object) { + objects.add (object); + update_boundaries (Matrix.identity ()); + } + + public void remove (Object o) { + objects.remove (o); + update_boundaries (Matrix.identity ()); + } + + 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); + } + } + + update_boundaries (Matrix.identity ()); + } + + 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); + x1 = c.x1; + y1 = c.y1; + x2 = c.x2; + y2 = c.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,239 @@ + /* + 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 left { get; set; } + public virtual double right { get; set; } + public virtual double top { get; set; } + public virtual double bottom { get; set; } + + public virtual double boundaries_height { + get { + return bottom - top; + } + } + + public virtual double boundaries_width { + get { + return right - left; + } + } + + /** Cartesian coordinates for the old BirdFont system. */ + public double xmax { + get { + return right; + } + + set { + right = value; + } + } + + public double xmin { + get { + return left; + } + + set { + left = value; + } + } + + public double ymin { + get { + return -top - boundaries_height; + } + + set { + top = boundaries_height - value; + } + } + + public double ymax { + get { + return -top; + } + + set { + top = -value; + } + } + + // FIXME: DELETE + 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 const double CANVAS_MAX = 100000; + public const double CANVAS_MIN = -100000; + + 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; + + if (from.color != null) { + to.color = ((!) from.color).copy (); + } else { + to.color = null; + } + + if (from.stroke_color != null) { + to.stroke_color = ((!) from.stroke_color).copy (); + } else { + to.stroke_color = null; + } + + if (to.gradient != null) { + to.gradient = ((!) from.gradient).copy (); + } else { + to.gradient = null; + } + + 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; + + to.style = from.style.copy (); + to.transforms = from.transforms.copy (); + + if (from.clip_path != null) { + to.clip_path = ((!) from.clip_path).copy (); + } + } + + 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); + } + + public virtual void update_boundaries (Matrix view_matrix) { + } + } + + }
diff --git libsvgbird/ObjectGroup.vala(new)
--- /dev/null +++ b/libsvgbird/ObjectGroup.vala @@ -1,1 +1,84 @@ + /* + 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; + } + + public void print_objects () { + foreach (Object o in objects) { + stdout.printf (o.to_string ()); + stdout.printf ("\n"); + } + } + } + + }
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,50 @@ + /* + 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); + } + + public Points copy () { + Points p = new Points (); + p.point_data = point_data.copy (); + p.x = x; + p.y = y; + p.closed = closed; + return p; + } + } + + } +
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,102 @@ + /* + 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; + + /** Corner radius */ + 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.rx = rx; + r.ry = ry; + 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; + } + } +