Commit 7fe8383
Clipping of SVG paths
Changed files (21)
modifiedbirdui/Component.valamodifiedlibbirdfont/EmbeddedSvg.valamodifiedlibbirdfont/Glyph.valamodifiedlibbirdfont/PathObject.valamodifiedlibsvgbird/Circle.valamodifiedlibsvgbird/Defs.valamodifiedlibsvgbird/Ellipse.valamodifiedlibsvgbird/Layer.valamodifiedlibsvgbird/Line.valamodifiedlibsvgbird/Object.valamodifiedlibsvgbird/Polygon.valamodifiedlibsvgbird/Polyline.valamodifiedlibsvgbird/Rectangle.valamodifiedlibsvgbird/Selector.valamodifiedlibsvgbird/SelectorPattern.valamodifiedlibsvgbird/SelectorTag.valamodifiedlibsvgbird/StyleSheet.valamodifiedlibsvgbird/SvgDrawing.valamodifiedlibsvgbird/SvgFile.valamodifiedlibsvgbird/SvgPath.valamodifiedlibsvgbird/SvgStyle.vala
Diff
@@ -53,22 +53,38 @@ class Component : GLib.Object { string? css_class = null; string? id = null; + Overflow overflow = Overflow.VISIBLE;+ 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 ();+ }+ } } public Component.load (string file_name) { load_file (file_name); } - private void inherit_style (Defs defs) {+ private void inherit_styles_sheet (Defs defs) { this.defs = defs; } @@ -76,12 +92,9 @@ class Component : GLib.Object { } protected void parse_style (XmlElement style_tag) {- Defs definitions = new Defs (); - definitions.style_sheet = StyleSheet.parse (definitions, style_tag);-- Defs subscope_definitions = defs.shallow_copy ();- subscope_definitions.style_sheet.merge (definitions.style_sheet);- defs = subscope_definitions;+ 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) {@@ -90,7 +103,7 @@ class Component : GLib.Object { if (attribute_name == "file") { string file_name = attribute.get_content ();- SvgComponent svg = new SvgComponent (component_tag, defs, file_name);+ SvgComponent svg = new SvgComponent (svg_tag, defs, file_name); add_component (svg); } }@@ -98,37 +111,48 @@ class Component : GLib.Object { protected void add_component (Component component) { components.add (component);- component.inherit_style (defs);- component.style = SvgStyle.parse (defs, style, (!) component.component_tag);+ component.inherit_styles_sheet (defs);+ component.style = SvgStyle.parse (defs, style, component.component_tag);+ + component.set_overflow_property_from_css ();+ component.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) {- bool parse_style = false;+ string type = ""; foreach (Attribute attribute in layout_tag.get_attributes ()) { string attribute_name = attribute.get_name (); if (attribute_name == "type") {- if (attribute.get_content () == "hbox") {- HBox hbox = new HBox (layout_tag, defs);- add_component (hbox);- } else if (attribute.get_content () == "vbox") {- VBox hbox = new VBox (layout_tag, defs);- add_component (hbox);- } else {- warning ("Layout of type " + attribute.get_content ()- + " is not implemented in this verison.");- }- } else if (attribute_name == "id") {- id = attribute.get_content ();- } else if (attribute_name == "class") {- css_class = attribute.get_content ();- } else if (attribute_name == "style") {- // style will be parsed later in add_component- } else {- unused_attribute (attribute_name);+ 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 ("Layout of type " + type+ + " is supported in this verison.");+ } } protected void parse_component (XmlElement component_tag) {@@ -237,6 +261,12 @@ class Component : GLib.Object { foreach (Component component in components) { cairo.save (); cairo.translate (component.x, component.y);+ + if (component.overflow == Overflow.HIDDEN) {+ cairo.rectangle (0, 0, component.width, component.height);+ cairo.clip ();+ }+ component.draw (cairo); cairo.restore (); }@@ -76,7 +76,7 @@ public class EmbeddedSvg : SvgBird.Object { && (this.y - drawing.height <= y <= this.y); } - public override void draw (Context cr) {+ public override void draw_outline (Context cr) { cr.save (); cr.translate (Glyph.xc () + x, Glyph.yc () - y); drawing.draw (cr);@@ -1857,7 +1857,7 @@ public class Glyph : FontDisplay { g.add_line (line.copy ()); } - g.layers = layers.copy ();+ g.layers = (Layer) layers.copy (); foreach (SvgBird.Object o in active_paths) { g.active_paths.add (o);@@ -1930,7 +1930,7 @@ public class Glyph : FontDisplay { void set_glyph_data (Glyph g) { current_layer = g.current_layer;- layers = g.layers.copy ();+ layers = (Layer) g.layers.copy (); left_limit = g.left_limit; right_limit = g.right_limit;@@ -88,7 +88,7 @@ public class PathObject : SvgBird.Object { return path.is_over_coordinate (x, y); } - public override void draw (Context cr) {+ public override void draw_outline (Context cr) { draw_path (cr); } @@ -36,12 +36,8 @@ public class Circle : Object { return false; } - public override void draw (Context cr) {- cr.save ();+ public override void draw_outline (Context cr) { cr.arc (cx, cy, r, 0, 2 * Math.PI);- apply_transform (cr); - paint (cr);- cr.restore (); } public override void move (double dx, double dy) {@@ -18,42 +18,71 @@ 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 Gradient? get_gradient_for_url (string? url) {++ public ClipPath? get_clip_path_for_url (string? url) { if (url == null) { return null; } - string tag_id = (!) url;-- if (unlikely (!is_url (tag_id))) {- 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 = tag_id.index_of ("(");+ int p1 = url.index_of ("("); if (unlikely (p1 == -1)) {- warning ("Not an URL: " + tag_id);- return null;+ warning ("Not an URL: " + url);+ return ""; } - int p2 = tag_id.index_of (")");+ int p2 = url.index_of (")"); if (unlikely (p2 == -1 || p2 < p1)) {- warning ("Not an URL: " + tag_id);- return null;+ warning ("Not an URL: " + url);+ return ""; } p1 += "(".length; int length = p2 - p1;- tag_id = tag_id.substring (p1, length);+ 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;@@ -39,17 +39,12 @@ public class Ellipse : Object { return false; } - public override void draw (Context cr) {+ public override void draw_outline (Context cr) { cr.save (); cr.translate (cx, cy); cr.scale (rx, ry); cr.arc (0, 0, 1, 0, 2 * PI);- cr.restore (); - - cr.save ();- apply_transform (cr); - paint (cr);- cr.restore ();+ cr.restore (); } public override void move (double dx, double dy) {@@ -12,17 +12,16 @@ Lesser General Public License for more details. */ +using Cairo;+ namespace SvgBird { -public class Layer : GLib.Object {+public class Layer : Object { public ObjectGroup objects; - public Gee.ArrayList<Layer> subgroups;- public bool visible = true;+ public Gee.ArrayList<Layer> subgroups; // FIXME: delete public string name = "Layer";- - public SvgTransforms transforms;- + public Layer () { objects = new ObjectGroup (); subgroups = new Gee.ArrayList<Layer> ();@@ -86,16 +85,17 @@ public class Layer : GLib.Object { public static void copy_layer (Layer from, Layer to) { to.name = from.name; to.objects = from.objects.copy ();- to.visible = from.visible; foreach (Layer l in from.subgroups) {- to.subgroups.add (l.copy ());+ Layer layer = (Layer) l.copy ();+ to.subgroups.add (layer); } }- - public Layer copy () {++ public override Object copy () { Layer layer = new Layer (); copy_layer (this, layer);+ Object.copy_attributes (this, layer); return layer; } @@ -165,6 +165,37 @@ public class Layer : GLib.Object { l.print (indent + 1); } }+++ public override bool is_over (double x, double y) {+ return false;+ }+ + public override void draw_outline (Context cr) {+ foreach (Object object in objects) {+ object.draw_outline (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 string to_string () {+ return "Layer: " + name;+ } } }@@ -38,14 +38,10 @@ public class Line : Object { return false; } - public override void draw (Context cr) {- cr.save ();+ public override void draw_outline (Context cr) { cr.move_to (x1, y1); cr.line_to (x1, y1); cr.line_to (x2, y2);- apply_transform (cr); - paint (cr);- cr.restore (); } public override void move (double dx, double dy) {@@ -23,6 +23,9 @@ public abstract class Object : GLib.Object { 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; }@@ -56,7 +59,22 @@ public abstract class Object : GLib.Object { public abstract void update_region_boundaries (); public abstract bool is_over (double x, double y);- public abstract void draw (Context cr);+ public abstract void draw_outline (Context cr);++ public void draw (Context cr) {+ cr.save ();+ apply_transform (cr);+ + if (clip_path != null) {+ ClipPath clipping = (!) clip_path;+ clipping.apply (cr);+ }+ + draw_outline (cr);+ paint (cr);+ cr.restore ();+ }+ public abstract Object copy (); public abstract void move (double dx, double dy); public abstract void rotate (double theta, double xc, double yc);@@ -27,11 +27,8 @@ public class Polygon : Object { public override bool is_over (double x, double y) { return false; }- - public override void draw (Context cr) {- cr.save ();- apply_transform (cr);- + + public override void draw_outline (Context cr) { if (points.size > 2) { cr.move_to (points.data[0].value, points.data[1].value); @@ -40,10 +37,7 @@ public class Polygon : Object { } cr.close_path ();- }- - paint (cr);- cr.restore ();+ } } public override void move (double dx, double dy) {@@ -28,10 +28,7 @@ public class Polyline : Object { return false; } - public override void draw (Context cr) {- cr.save ();- apply_transform (cr);- + public override void draw_outline (Context cr) { if (points.size > 2) { cr.move_to (points.data[0].value, points.data[1].value); @@ -39,9 +36,6 @@ public class Polyline : Object { cr.line_to (points.data[i].value, points.data[i + 1].value); } }- - paint (cr);- cr.restore (); } public override void move (double dx, double dy) {@@ -33,18 +33,12 @@ public class Rectangle : Object { return false; } - public override void draw (Context cr) {- cr.save ();- apply_transform (cr);- + public override void draw_outline (Context cr) { if (rx == 0 && ry == 0) { cr.rectangle (x, y, width, height); } else { draw_rounded_corners (cr); }- - paint (cr);- cr.restore (); } public void draw_rounded_corners (Context cr) {@@ -36,10 +36,24 @@ public class Selector : GLib.Object { style = selector.style.copy (); foreach (SelectorPattern pattern in selector.patterns) {- selector.patterns.add (pattern.copy ());+ 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); }@@ -23,7 +23,7 @@ public class SelectorPattern : GLib.Object { public SelectorPattern.empty () { }- + public SelectorPattern (string pattern) { string p = pattern.strip (); string[] elements = p.split (" ");@@ -34,7 +34,20 @@ public class SelectorPattern : GLib.Object { } } }- ++ 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 (); @@ -63,7 +63,7 @@ public class SelectorTag : GLib.Object { } } }-+ public SelectorTag copy () { SelectorTag tag = new SelectorTag.empty(); @@ -20,10 +20,9 @@ namespace SvgBird { public class StyleSheet : GLib.Object { - public Gee.ArrayList<Selector> styles;+ public Gee.ArrayList<Selector> styles = new Gee.ArrayList<Selector> (); public StyleSheet () {- styles = new Gee.ArrayList<Selector> (); } /** Copy references to all selectors in this style sheet. */@@ -70,7 +69,7 @@ public class StyleSheet : GLib.Object { public void merge (StyleSheet style_sheet) { foreach (Selector selector in style_sheet.styles) {- styles.add (selector);+ styles.add (selector.copy ()); } } @@ -25,7 +25,19 @@ public class SvgDrawing : Object { public double x = 0; public double y = 0;- public double width = 0;+ + public double width {+ get { + return svg_width;+ }+ + set {+ svg_width = value;+ }+ }+ public double svg_width = 0;+ + public double height = 0; public override void update_region_boundaries () {@@ -36,21 +48,27 @@ public class SvgDrawing : Object { && (this.y <= y <= this.y + height); } - public override void draw (Context cr) {+ public override void draw_outline (Context cr) { cr.save (); cr.translate (x, y);-+ root_layer.draw_outline (cr);+ + // FIXME: update layer structure foreach (Object o in root_layer.get_visible_objects ().objects) { o.draw (cr); }-+ cr.restore (); } public override Object copy () { SvgDrawing drawing = new SvgDrawing ();- drawing.root_layer = root_layer.copy ();+ drawing.root_layer = (Layer) root_layer.copy (); drawing.defs = defs.copy ();+ drawing.x = x;+ drawing.y = y;+ drawing.width = width;+ drawing.height = height; return drawing; } @@ -68,6 +86,10 @@ public class SvgDrawing : Object { public override void resize (double ratio_x, double ratio_y) { }++ public override string to_string () {+ return @"SvgDrawing x: $x, y: $y, width: $width, height: $height";+ } } }@@ -24,7 +24,7 @@ public class SvgFile : GLib.Object { public SvgFile () { }-+ public SvgDrawing parse_svg_data (string xml_data) { XmlTree tree = new XmlTree (xml_data); return parse_svg_file (tree.get_root ());@@ -63,31 +63,13 @@ public class SvgFile : GLib.Object { parse_object (drawing.root_layer, style, t); }++ set_object_properties (drawing, new SvgStyle (), svg_tag); return drawing; } - private void parse_layer (Layer layer, SvgStyle parent_style, XmlElement tag) {- bool hidden = false;-- foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "display" && attr.get_content () == "none") {- hidden = true;- }- - if (attr.get_name () == "visibility"- && (attr.get_content () == "hidden" - || attr.get_content () == "collapse")) {- hidden = true;- }- }- - if (hidden) {- layer.visible = !hidden;- }- - SvgStyle style = SvgStyle.parse (drawing.defs, parent_style, tag);- + private void parse_layer (Layer layer, SvgStyle parent_style, XmlElement tag) { foreach (XmlElement t in tag) { string name = t.get_name (); @@ -98,17 +80,23 @@ public class SvgFile : GLib.Object { } if (name == "a") {- parse_link (layer, style, t);+ parse_link (layer, parent_style, t); } - parse_object (layer, style, t);+ parse_object (layer, parent_style, t); } - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "transform") {- layer.transforms = parse_transform (attr.get_content ());- }- }+ set_object_properties (layer, parent_style, tag);+ }++ void parse_clip_path (SvgDrawing drawing, XmlElement tag) {+ ClipPath clip_path;+ + Layer layer = new Layer ();+ parse_layer (layer, new SvgStyle (), tag);+ clip_path = new ClipPath (layer);+ + drawing.defs.clip_paths.add (clip_path); } void parse_defs (SvgDrawing drawing, XmlElement tag) {@@ -118,6 +106,8 @@ public class SvgFile : GLib.Object { if (name == "linearGradient") { parse_linear_gradient (drawing, t);+ } else if (name == "clipPath") {+ parse_clip_path (drawing, t); } } @@ -284,12 +274,37 @@ public class SvgFile : GLib.Object { } } - polygon.transforms = get_transform (tag.get_attributes ());- polygon.style = SvgStyle.parse (drawing.defs, parent_style, tag);- polygon.visible = is_visible (tag); - + set_object_properties (polygon, parent_style, tag); layer.add_object (polygon);-+ }+ + void set_object_properties (Object object, SvgStyle parent_style, XmlElement tag) {+ Attributes attributes = tag.get_attributes ();+ + foreach (Attribute attribute in attributes) {+ string name = attribute.get_name ();+ + if (name == "id") {+ object.id = attribute.get_content ();+ } else if (name == "class") {+ object.css_class = attribute.get_content ();+ }+ }+ + object.clip_path = get_clip_path (attributes);+ object.transforms = get_transform (attributes);+ object.style = SvgStyle.parse (drawing.defs, parent_style, tag);+ object.visible = is_visible (tag);+ }+ + ClipPath? get_clip_path (Attributes attributes) {+ foreach (Attribute attribute in attributes) {+ if (attribute.get_name () == "clip-path") {+ return drawing.defs.get_clip_path_for_url (attribute.get_content ());+ }+ }+ + return null; } private void parse_polyline (Layer layer, SvgStyle parent_style, XmlElement tag) {@@ -306,10 +321,7 @@ public class SvgFile : GLib.Object { } } - polyline.transforms = get_transform (tag.get_attributes ());- polyline.style = SvgStyle.parse (drawing.defs, parent_style, tag);- polyline.visible = is_visible (tag); - + set_object_properties (polyline, parent_style, tag); layer.add_object (polyline); } @@ -344,10 +356,7 @@ public class SvgFile : GLib.Object { } } - rectangle.transforms = get_transform (tag.get_attributes ());- rectangle.style = SvgStyle.parse (drawing.defs, parent_style, tag);- rectangle.visible = is_visible (tag); - + set_object_properties (rectangle, parent_style, tag); layer.add_object (rectangle); } @@ -370,10 +379,7 @@ public class SvgFile : GLib.Object { } } - circle.transforms = get_transform (tag.get_attributes ());- circle.style = SvgStyle.parse (drawing.defs, parent_style, tag);- circle.visible = is_visible (tag); - + set_object_properties (circle, parent_style, tag); layer.add_object (circle); } @@ -400,10 +406,7 @@ public class SvgFile : GLib.Object { } } - ellipse.transforms = get_transform (tag.get_attributes ());- ellipse.style = SvgStyle.parse (drawing.defs, parent_style, tag);- ellipse.visible = is_visible (tag); - + set_object_properties (ellipse, parent_style, tag); layer.add_object (ellipse); } @@ -430,10 +433,7 @@ public class SvgFile : GLib.Object { } } - line.transforms = get_transform (tag.get_attributes ());- line.style = SvgStyle.parse (drawing.defs, parent_style, tag);- line.visible = is_visible (tag); - + set_object_properties (line, parent_style, tag); layer.add_object (line); } @@ -627,10 +627,7 @@ public class SvgFile : GLib.Object { } } - path.transforms = get_transform (tag.get_attributes ());- path.style = SvgStyle.parse (drawing.defs, parent_style, tag);- path.visible = is_visible (tag); - + set_object_properties (path, parent_style, tag); layer.add_object (path); } @@ -684,7 +681,6 @@ public class SvgFile : GLib.Object { double angle_extent; double center_x; double center_y;- //double rotation = Math.PI * (b.angle / 180.0); double rotation = b.angle; get_arc_arguments (b.x0, b.y0, b.rx, b.ry,@@ -30,10 +30,7 @@ public class SvgPath : Object { return false; } - public override void draw (Context cr) {- cr.save ();- cr.new_path ();- + public override void draw_outline (Context cr) { foreach (Points p in points) { cr.move_to (p.x, p.y); draw_points (cr, p);@@ -42,10 +39,6 @@ public class SvgPath : Object { cr.close_path (); } }- - apply_transform (cr); - paint (cr);- cr.restore (); } public void draw_points (Context cr, Points path) {@@ -33,6 +33,17 @@ public class SvgStyle : GLib.Object { public SvgStyle () { } + public bool property_equals (string property, string value) {+ string? p = get_css_property (property);+ + if (p == null) {+ return false;+ }+ + string css_property = (!) p;+ return css_property == value;+ }+ public bool has_css_property (string property) { return style.has_key (property); }@@ -85,7 +96,7 @@ public class SvgStyle : GLib.Object { public SvgStyle copy () { SvgStyle style = new SvgStyle ();- style.inherit (this);+ style.apply (this); return style; }