From 267299811df952d08324a39008f52c19641de9e0 Mon Sep 17 00:00:00 2001 From: Tavmjong Bah Date: Tue, 30 Jan 2018 09:33:01 +0100 Subject: Move classes derived from SPObject to own directory. A lot of header clean-up. --- src/object/CMakeLists.txt | 180 ++ src/object/README | 118 + src/object/box3d-side.cpp | 274 ++ src/object/box3d-side.h | 64 + src/object/box3d.cpp | 1359 +++++++++ src/object/box3d.h | 104 + src/object/color-profile.cpp | 1376 +++++++++ src/object/color-profile.h | 110 + src/object/filters/CMakeLists.txt | 53 + src/object/filters/blend.cpp | 291 ++ src/object/filters/blend.h | 54 + src/object/filters/colormatrix.cpp | 160 ++ src/object/filters/colormatrix.h | 54 + src/object/filters/componenttransfer-funcnode.cpp | 216 ++ src/object/filters/componenttransfer-funcnode.h | 63 + src/object/filters/componenttransfer.cpp | 187 ++ src/object/filters/componenttransfer.h | 58 + src/object/filters/composite.cpp | 334 +++ src/object/filters/composite.h | 76 + src/object/filters/convolvematrix.cpp | 319 +++ src/object/filters/convolvematrix.h | 66 + src/object/filters/diffuselighting.cpp | 327 +++ src/object/filters/diffuselighting.h | 72 + src/object/filters/displacementmap.cpp | 257 ++ src/object/filters/displacementmap.h | 62 + src/object/filters/distantlight.cpp | 167 ++ src/object/filters/distantlight.h | 58 + src/object/filters/flood.cpp | 181 ++ src/object/filters/flood.h | 54 + src/object/filters/gaussian-blur.cpp | 136 + src/object/filters/gaussian-blur.h | 56 + src/object/filters/image.cpp | 262 ++ src/object/filters/image.h | 68 + src/object/filters/merge.cpp | 116 + src/object/filters/merge.h | 47 + src/object/filters/mergenode.cpp | 106 + src/object/filters/mergenode.h | 52 + src/object/filters/morphology.cpp | 162 ++ src/object/filters/morphology.h | 54 + src/object/filters/offset.cpp | 138 + src/object/filters/offset.h | 51 + src/object/filters/pointlight.cpp | 192 ++ src/object/filters/pointlight.h | 60 + src/object/filters/sp-filter-primitive.cpp | 278 ++ src/object/filters/sp-filter-primitive.h | 67 + src/object/filters/specularlighting.cpp | 341 +++ src/object/filters/specularlighting.h | 78 + src/object/filters/spotlight.cpp | 322 +++ src/object/filters/spotlight.h | 76 + src/object/filters/tile.cpp | 109 + src/object/filters/tile.h | 50 + src/object/filters/turbulence.cpp | 230 ++ src/object/filters/turbulence.h | 63 + src/object/object-set.cpp | 392 +++ src/object/object-set.h | 500 ++++ src/object/persp3d-reference.cpp | 110 + src/object/persp3d-reference.h | 68 + src/object/persp3d.cpp | 581 ++++ src/object/persp3d.h | 130 + src/object/sp-anchor.cpp | 189 ++ src/object/sp-anchor.h | 42 + src/object/sp-clippath.cpp | 316 +++ src/object/sp-clippath.h | 127 + src/object/sp-conn-end-pair.cpp | 345 +++ src/object/sp-conn-end-pair.h | 96 + src/object/sp-conn-end.cpp | 304 ++ src/object/sp-conn-end.h | 59 + src/object/sp-defs.cpp | 107 + src/object/sp-defs.h | 44 + src/object/sp-desc.cpp | 32 + src/object/sp-desc.h | 29 + src/object/sp-dimensions.cpp | 55 + src/object/sp-dimensions.h | 41 + src/object/sp-ellipse.cpp | 792 ++++++ src/object/sp-ellipse.h | 113 + src/object/sp-factory.cpp | 364 +++ src/object/sp-factory.h | 43 + src/object/sp-filter-reference.cpp | 22 + src/object/sp-filter-reference.h | 34 + src/object/sp-filter-units.h | 21 + src/object/sp-filter.cpp | 532 ++++ src/object/sp-filter.h | 113 + src/object/sp-flowdiv.cpp | 463 +++ src/object/sp-flowdiv.h | 96 + src/object/sp-flowregion.cpp | 392 +++ src/object/sp-flowregion.h | 54 + src/object/sp-flowtext.cpp | 749 +++++ src/object/sp-flowtext.h | 106 + src/object/sp-font-face.cpp | 829 ++++++ src/object/sp-font-face.h | 123 + src/object/sp-font.cpp | 205 ++ src/object/sp-font.h | 46 + src/object/sp-glyph-kerning.cpp | 190 ++ src/object/sp-glyph-kerning.h | 74 + src/object/sp-glyph.cpp | 289 ++ src/object/sp-glyph.h | 72 + src/object/sp-gradient-reference.cpp | 22 + src/object/sp-gradient-reference.h | 33 + src/object/sp-gradient-spread.h | 23 + src/object/sp-gradient-units.h | 21 + src/object/sp-gradient-vector.h | 41 + src/object/sp-gradient.cpp | 1204 ++++++++ src/object/sp-gradient.h | 238 ++ src/object/sp-guide.cpp | 545 ++++ src/object/sp-guide.h | 111 + src/object/sp-hatch-path.cpp | 340 +++ src/object/sp-hatch-path.h | 95 + src/object/sp-hatch.cpp | 739 +++++ src/object/sp-hatch.h | 186 ++ src/object/sp-image.cpp | 805 ++++++ src/object/sp-image.h | 73 + src/object/sp-item-group.cpp | 1022 +++++++ src/object/sp-item-group.h | 125 + src/object/sp-item-rm-unsatisfied-cns.cpp | 45 + src/object/sp-item-rm-unsatisfied-cns.h | 20 + src/object/sp-item-transform.cpp | 429 +++ src/object/sp-item-transform.h | 29 + src/object/sp-item-update-cns.cpp | 47 + src/object/sp-item-update-cns.h | 23 + src/object/sp-item.cpp | 1739 ++++++++++++ src/object/sp-item.h | 434 +++ src/object/sp-line.cpp | 170 ++ src/object/sp-line.h | 55 + src/object/sp-linear-gradient.cpp | 100 + src/object/sp-linear-gradient.h | 44 + src/object/sp-lpe-item.cpp | 1071 +++++++ src/object/sp-lpe-item.h | 117 + src/object/sp-marker-loc.h | 31 + src/object/sp-marker.cpp | 508 ++++ src/object/sp-marker.h | 107 + src/object/sp-mask.cpp | 319 +++ src/object/sp-mask.h | 113 + src/object/sp-mesh-array.cpp | 3102 +++++++++++++++++++++ src/object/sp-mesh-array.h | 231 ++ src/object/sp-mesh-gradient.cpp | 277 ++ src/object/sp-mesh-gradient.h | 43 + src/object/sp-mesh-patch.cpp | 138 + src/object/sp-mesh-patch.h | 51 + src/object/sp-mesh-row.cpp | 122 + src/object/sp-mesh-row.h | 46 + src/object/sp-metadata.cpp | 147 + src/object/sp-metadata.h | 39 + src/object/sp-missing-glyph.cpp | 144 + src/object/sp-missing-glyph.h | 40 + src/object/sp-namedview.cpp | 1188 ++++++++ src/object/sp-namedview.h | 143 + src/object/sp-object-group.cpp | 84 + src/object/sp-object-group.h | 46 + src/object/sp-object.cpp | 1702 +++++++++++ src/object/sp-object.h | 901 ++++++ src/object/sp-offset.cpp | 1224 ++++++++ src/object/sp-offset.h | 107 + src/object/sp-paint-server-reference.h | 44 + src/object/sp-paint-server.cpp | 94 + src/object/sp-paint-server.h | 105 + src/object/sp-path.cpp | 462 +++ src/object/sp-path.h | 76 + src/object/sp-pattern.cpp | 708 +++++ src/object/sp-pattern.h | 148 + src/object/sp-polygon.cpp | 183 ++ src/object/sp-polygon.h | 35 + src/object/sp-polyline.cpp | 132 + src/object/sp-polyline.h | 32 + src/object/sp-radial-gradient.cpp | 206 ++ src/object/sp-radial-gradient.h | 49 + src/object/sp-rect.cpp | 577 ++++ src/object/sp-rect.h | 88 + src/object/sp-root.cpp | 393 +++ src/object/sp-root.h | 78 + src/object/sp-script.cpp | 85 + src/object/sp-script.h | 47 + src/object/sp-shape.cpp | 1175 ++++++++ src/object/sp-shape.h | 96 + src/object/sp-solid-color.cpp | 86 + src/object/sp-solid-color.h | 48 + src/object/sp-spiral.cpp | 629 +++++ src/object/sp-spiral.h | 81 + src/object/sp-star.cpp | 627 +++++ src/object/sp-star.h | 68 + src/object/sp-stop.cpp | 265 ++ src/object/sp-stop.h | 73 + src/object/sp-string.cpp | 177 ++ src/object/sp-string.h | 31 + src/object/sp-style-elem.cpp | 533 ++++ src/object/sp-style-elem.h | 33 + src/object/sp-switch.cpp | 159 ++ src/object/sp-switch.h | 49 + src/object/sp-symbol.cpp | 169 ++ src/object/sp-symbol.h | 48 + src/object/sp-tag-use-reference.cpp | 137 + src/object/sp-tag-use-reference.h | 79 + src/object/sp-tag-use.cpp | 198 ++ src/object/sp-tag-use.h | 56 + src/object/sp-tag.cpp | 142 + src/object/sp-tag.h | 57 + src/object/sp-text.cpp | 1279 +++++++++ src/object/sp-text.h | 109 + src/object/sp-textpath.h | 51 + src/object/sp-title.cpp | 32 + src/object/sp-title.h | 28 + src/object/sp-tref-reference.cpp | 106 + src/object/sp-tref-reference.h | 79 + src/object/sp-tref.cpp | 532 ++++ src/object/sp-tref.h | 84 + src/object/sp-tspan.cpp | 499 ++++ src/object/sp-tspan.h | 50 + src/object/sp-use-reference.cpp | 241 ++ src/object/sp-use-reference.h | 78 + src/object/sp-use.cpp | 763 +++++ src/object/sp-use.h | 91 + src/object/uri-references.cpp | 268 ++ src/object/uri-references.h | 165 ++ src/object/uri.cpp | 249 ++ src/object/uri.h | 164 ++ src/object/viewbox.cpp | 277 ++ src/object/viewbox.h | 62 + 216 files changed, 53661 insertions(+) create mode 100644 src/object/CMakeLists.txt create mode 100644 src/object/README create mode 100644 src/object/box3d-side.cpp create mode 100644 src/object/box3d-side.h create mode 100644 src/object/box3d.cpp create mode 100644 src/object/box3d.h create mode 100644 src/object/color-profile.cpp create mode 100644 src/object/color-profile.h create mode 100644 src/object/filters/CMakeLists.txt create mode 100644 src/object/filters/blend.cpp create mode 100644 src/object/filters/blend.h create mode 100644 src/object/filters/colormatrix.cpp create mode 100644 src/object/filters/colormatrix.h create mode 100644 src/object/filters/componenttransfer-funcnode.cpp create mode 100644 src/object/filters/componenttransfer-funcnode.h create mode 100644 src/object/filters/componenttransfer.cpp create mode 100644 src/object/filters/componenttransfer.h create mode 100644 src/object/filters/composite.cpp create mode 100644 src/object/filters/composite.h create mode 100644 src/object/filters/convolvematrix.cpp create mode 100644 src/object/filters/convolvematrix.h create mode 100644 src/object/filters/diffuselighting.cpp create mode 100644 src/object/filters/diffuselighting.h create mode 100644 src/object/filters/displacementmap.cpp create mode 100644 src/object/filters/displacementmap.h create mode 100644 src/object/filters/distantlight.cpp create mode 100644 src/object/filters/distantlight.h create mode 100644 src/object/filters/flood.cpp create mode 100644 src/object/filters/flood.h create mode 100644 src/object/filters/gaussian-blur.cpp create mode 100644 src/object/filters/gaussian-blur.h create mode 100644 src/object/filters/image.cpp create mode 100644 src/object/filters/image.h create mode 100644 src/object/filters/merge.cpp create mode 100644 src/object/filters/merge.h create mode 100644 src/object/filters/mergenode.cpp create mode 100644 src/object/filters/mergenode.h create mode 100644 src/object/filters/morphology.cpp create mode 100644 src/object/filters/morphology.h create mode 100644 src/object/filters/offset.cpp create mode 100644 src/object/filters/offset.h create mode 100644 src/object/filters/pointlight.cpp create mode 100644 src/object/filters/pointlight.h create mode 100644 src/object/filters/sp-filter-primitive.cpp create mode 100644 src/object/filters/sp-filter-primitive.h create mode 100644 src/object/filters/specularlighting.cpp create mode 100644 src/object/filters/specularlighting.h create mode 100644 src/object/filters/spotlight.cpp create mode 100644 src/object/filters/spotlight.h create mode 100644 src/object/filters/tile.cpp create mode 100644 src/object/filters/tile.h create mode 100644 src/object/filters/turbulence.cpp create mode 100644 src/object/filters/turbulence.h create mode 100644 src/object/object-set.cpp create mode 100644 src/object/object-set.h create mode 100644 src/object/persp3d-reference.cpp create mode 100644 src/object/persp3d-reference.h create mode 100644 src/object/persp3d.cpp create mode 100644 src/object/persp3d.h create mode 100644 src/object/sp-anchor.cpp create mode 100644 src/object/sp-anchor.h create mode 100644 src/object/sp-clippath.cpp create mode 100644 src/object/sp-clippath.h create mode 100644 src/object/sp-conn-end-pair.cpp create mode 100644 src/object/sp-conn-end-pair.h create mode 100644 src/object/sp-conn-end.cpp create mode 100644 src/object/sp-conn-end.h create mode 100644 src/object/sp-defs.cpp create mode 100644 src/object/sp-defs.h create mode 100644 src/object/sp-desc.cpp create mode 100644 src/object/sp-desc.h create mode 100644 src/object/sp-dimensions.cpp create mode 100644 src/object/sp-dimensions.h create mode 100644 src/object/sp-ellipse.cpp create mode 100644 src/object/sp-ellipse.h create mode 100644 src/object/sp-factory.cpp create mode 100644 src/object/sp-factory.h create mode 100644 src/object/sp-filter-reference.cpp create mode 100644 src/object/sp-filter-reference.h create mode 100644 src/object/sp-filter-units.h create mode 100644 src/object/sp-filter.cpp create mode 100644 src/object/sp-filter.h create mode 100644 src/object/sp-flowdiv.cpp create mode 100644 src/object/sp-flowdiv.h create mode 100644 src/object/sp-flowregion.cpp create mode 100644 src/object/sp-flowregion.h create mode 100644 src/object/sp-flowtext.cpp create mode 100644 src/object/sp-flowtext.h create mode 100644 src/object/sp-font-face.cpp create mode 100644 src/object/sp-font-face.h create mode 100644 src/object/sp-font.cpp create mode 100644 src/object/sp-font.h create mode 100644 src/object/sp-glyph-kerning.cpp create mode 100644 src/object/sp-glyph-kerning.h create mode 100644 src/object/sp-glyph.cpp create mode 100644 src/object/sp-glyph.h create mode 100644 src/object/sp-gradient-reference.cpp create mode 100644 src/object/sp-gradient-reference.h create mode 100644 src/object/sp-gradient-spread.h create mode 100644 src/object/sp-gradient-units.h create mode 100644 src/object/sp-gradient-vector.h create mode 100644 src/object/sp-gradient.cpp create mode 100644 src/object/sp-gradient.h create mode 100644 src/object/sp-guide.cpp create mode 100644 src/object/sp-guide.h create mode 100644 src/object/sp-hatch-path.cpp create mode 100644 src/object/sp-hatch-path.h create mode 100644 src/object/sp-hatch.cpp create mode 100644 src/object/sp-hatch.h create mode 100644 src/object/sp-image.cpp create mode 100644 src/object/sp-image.h create mode 100644 src/object/sp-item-group.cpp create mode 100644 src/object/sp-item-group.h create mode 100644 src/object/sp-item-rm-unsatisfied-cns.cpp create mode 100644 src/object/sp-item-rm-unsatisfied-cns.h create mode 100644 src/object/sp-item-transform.cpp create mode 100644 src/object/sp-item-transform.h create mode 100644 src/object/sp-item-update-cns.cpp create mode 100644 src/object/sp-item-update-cns.h create mode 100644 src/object/sp-item.cpp create mode 100644 src/object/sp-item.h create mode 100644 src/object/sp-line.cpp create mode 100644 src/object/sp-line.h create mode 100644 src/object/sp-linear-gradient.cpp create mode 100644 src/object/sp-linear-gradient.h create mode 100644 src/object/sp-lpe-item.cpp create mode 100644 src/object/sp-lpe-item.h create mode 100644 src/object/sp-marker-loc.h create mode 100644 src/object/sp-marker.cpp create mode 100644 src/object/sp-marker.h create mode 100644 src/object/sp-mask.cpp create mode 100644 src/object/sp-mask.h create mode 100644 src/object/sp-mesh-array.cpp create mode 100644 src/object/sp-mesh-array.h create mode 100644 src/object/sp-mesh-gradient.cpp create mode 100644 src/object/sp-mesh-gradient.h create mode 100644 src/object/sp-mesh-patch.cpp create mode 100644 src/object/sp-mesh-patch.h create mode 100644 src/object/sp-mesh-row.cpp create mode 100644 src/object/sp-mesh-row.h create mode 100644 src/object/sp-metadata.cpp create mode 100644 src/object/sp-metadata.h create mode 100644 src/object/sp-missing-glyph.cpp create mode 100644 src/object/sp-missing-glyph.h create mode 100644 src/object/sp-namedview.cpp create mode 100644 src/object/sp-namedview.h create mode 100644 src/object/sp-object-group.cpp create mode 100644 src/object/sp-object-group.h create mode 100644 src/object/sp-object.cpp create mode 100644 src/object/sp-object.h create mode 100644 src/object/sp-offset.cpp create mode 100644 src/object/sp-offset.h create mode 100644 src/object/sp-paint-server-reference.h create mode 100644 src/object/sp-paint-server.cpp create mode 100644 src/object/sp-paint-server.h create mode 100644 src/object/sp-path.cpp create mode 100644 src/object/sp-path.h create mode 100644 src/object/sp-pattern.cpp create mode 100644 src/object/sp-pattern.h create mode 100644 src/object/sp-polygon.cpp create mode 100644 src/object/sp-polygon.h create mode 100644 src/object/sp-polyline.cpp create mode 100644 src/object/sp-polyline.h create mode 100644 src/object/sp-radial-gradient.cpp create mode 100644 src/object/sp-radial-gradient.h create mode 100644 src/object/sp-rect.cpp create mode 100644 src/object/sp-rect.h create mode 100644 src/object/sp-root.cpp create mode 100644 src/object/sp-root.h create mode 100644 src/object/sp-script.cpp create mode 100644 src/object/sp-script.h create mode 100644 src/object/sp-shape.cpp create mode 100644 src/object/sp-shape.h create mode 100644 src/object/sp-solid-color.cpp create mode 100644 src/object/sp-solid-color.h create mode 100644 src/object/sp-spiral.cpp create mode 100644 src/object/sp-spiral.h create mode 100644 src/object/sp-star.cpp create mode 100644 src/object/sp-star.h create mode 100644 src/object/sp-stop.cpp create mode 100644 src/object/sp-stop.h create mode 100644 src/object/sp-string.cpp create mode 100644 src/object/sp-string.h create mode 100644 src/object/sp-style-elem.cpp create mode 100644 src/object/sp-style-elem.h create mode 100644 src/object/sp-switch.cpp create mode 100644 src/object/sp-switch.h create mode 100644 src/object/sp-symbol.cpp create mode 100644 src/object/sp-symbol.h create mode 100644 src/object/sp-tag-use-reference.cpp create mode 100644 src/object/sp-tag-use-reference.h create mode 100644 src/object/sp-tag-use.cpp create mode 100644 src/object/sp-tag-use.h create mode 100644 src/object/sp-tag.cpp create mode 100644 src/object/sp-tag.h create mode 100644 src/object/sp-text.cpp create mode 100644 src/object/sp-text.h create mode 100644 src/object/sp-textpath.h create mode 100644 src/object/sp-title.cpp create mode 100644 src/object/sp-title.h create mode 100644 src/object/sp-tref-reference.cpp create mode 100644 src/object/sp-tref-reference.h create mode 100644 src/object/sp-tref.cpp create mode 100644 src/object/sp-tref.h create mode 100644 src/object/sp-tspan.cpp create mode 100644 src/object/sp-tspan.h create mode 100644 src/object/sp-use-reference.cpp create mode 100644 src/object/sp-use-reference.h create mode 100644 src/object/sp-use.cpp create mode 100644 src/object/sp-use.h create mode 100644 src/object/uri-references.cpp create mode 100644 src/object/uri-references.h create mode 100644 src/object/uri.cpp create mode 100644 src/object/uri.h create mode 100644 src/object/viewbox.cpp create mode 100644 src/object/viewbox.h (limited to 'src/object') diff --git a/src/object/CMakeLists.txt b/src/object/CMakeLists.txt new file mode 100644 index 000000000..3c347ce03 --- /dev/null +++ b/src/object/CMakeLists.txt @@ -0,0 +1,180 @@ + + +set(object_SRC + box3d-side.cpp + box3d.cpp + color-profile.cpp + object-set.cpp + persp3d-reference.cpp + persp3d.cpp + sp-anchor.cpp + sp-clippath.cpp + sp-conn-end-pair.cpp + sp-conn-end.cpp + sp-defs.cpp + sp-desc.cpp + sp-dimensions.cpp + sp-ellipse.cpp + sp-factory.cpp + sp-filter-reference.cpp + sp-filter.cpp + sp-flowdiv.cpp + sp-flowregion.cpp + sp-flowtext.cpp + sp-font-face.cpp + sp-font.cpp + sp-glyph-kerning.cpp + sp-glyph.cpp + sp-gradient-reference.cpp + sp-gradient.cpp + sp-guide.cpp + sp-hatch-path.cpp + sp-hatch.cpp + sp-image.cpp + sp-item-group.cpp + sp-item-rm-unsatisfied-cns.cpp + sp-item-transform.cpp + sp-item-update-cns.cpp + sp-item.cpp + sp-line.cpp + sp-linear-gradient.cpp + sp-lpe-item.cpp + sp-marker.cpp + sp-mask.cpp + sp-mesh-array.cpp + sp-mesh-gradient.cpp + sp-mesh-patch.cpp + sp-mesh-row.cpp + sp-metadata.cpp + sp-missing-glyph.cpp + sp-namedview.cpp + sp-object-group.cpp + sp-object.cpp + sp-offset.cpp + sp-paint-server.cpp + sp-path.cpp + sp-pattern.cpp + sp-polygon.cpp + sp-polyline.cpp + sp-radial-gradient.cpp + sp-rect.cpp + sp-root.cpp + sp-script.cpp + sp-shape.cpp + sp-solid-color.cpp + sp-spiral.cpp + sp-star.cpp + sp-stop.cpp + sp-string.cpp + sp-style-elem.cpp + sp-switch.cpp + sp-symbol.cpp + sp-tag-use-reference.cpp + sp-tag-use.cpp + sp-tag.cpp + sp-text.cpp + sp-title.cpp + sp-tref-reference.cpp + sp-tref.cpp + sp-tspan.cpp + sp-use-reference.cpp + sp-use.cpp + uri-references.cpp + uri.cpp + viewbox.cpp + + # ------- + # Headers + box3d-side.h + box3d.h + color-profile.h + object-set.h + persp3d-reference.h + persp3d.h + sp-anchor.h + sp-clippath.h + sp-conn-end-pair.h + sp-conn-end.h + sp-defs.h + sp-desc.h + sp-dimensions.h + sp-ellipse.h + sp-factory.h + sp-filter-reference.h + sp-filter-units.h + sp-filter.h + sp-flowdiv.h + sp-flowregion.h + sp-flowtext.h + sp-font-face.h + sp-font.h + sp-glyph-kerning.h + sp-glyph.h + sp-gradient-reference.h + sp-gradient-spread.h + sp-gradient-units.h + sp-gradient-vector.h + sp-gradient.h + sp-guide.h + sp-hatch-path.h + sp-hatch.h + sp-image.h + sp-item-group.h + sp-item-rm-unsatisfied-cns.h + sp-item-transform.h + sp-item-update-cns.h + sp-item.h + sp-line.h + sp-linear-gradient.h + sp-lpe-item.h + sp-marker-loc.h + sp-marker.h + sp-mask.h + sp-mesh-array.h + sp-mesh-gradient.h + sp-mesh-patch.h + sp-mesh-row.h + sp-metadata.h + sp-missing-glyph.h + sp-namedview.h + sp-object-group.h + sp-object.h + sp-offset.h + sp-paint-server-reference.h + sp-paint-server.h + sp-path.h + sp-pattern.h + sp-polygon.h + sp-polyline.h + sp-radial-gradient.h + sp-rect.h + sp-root.h + sp-script.h + sp-shape.h + sp-solid-color.h + sp-spiral.h + sp-star.h + sp-stop.h + sp-string.h + sp-style-elem.h + sp-switch.h + sp-symbol.h + sp-tag.h + sp-tag-use.h + sp-tag-use-reference.h + sp-text.h + sp-textpath.h + sp-title.h + sp-tref-reference.h + sp-tref.h + sp-tspan.h + sp-use-reference.h + sp-use.h + uri-references.h + uri.h + viewbox.h +) + +add_inkscape_source("${object_SRC}") + +add_subdirectory(filters) diff --git a/src/object/README b/src/object/README new file mode 100644 index 000000000..f0b8c5eb1 --- /dev/null +++ b/src/object/README @@ -0,0 +1,118 @@ + +This directory contains classes that are derived from SPObject as well as closely related code. + +The object tree implements an XML-to-display primitive mapping, and +provides an object hierarchy that can be modified using the +GUI. Changes in the XML tree are automatically propagated to the +object tree via observers, but not the other way around — a function +called updateRepr() must be explicitly called. Relevant nodes of the +object tree contains fully cascaded CSS style information. The object +tree also includes clones of objects that are referenced by the +element in the XML tree (this is needed as clones may have different +styling due to inheritance). + +See: http://wiki.inkscape.org/wiki/index.php/Object_tree + +Object class inheritance: + +sp-object.h: + color-profile.h: class ColorProfile + persp3d.h: class Persp3D + sp-defs.h: class SPDefs + sp-desc.h: class SPDesc + sp-filter.h: class SPFilter + sp-flowdiv.h: class SPFlowline + sp-flowdiv.h: class SPFlowregionbreak + sp-font-face.h: class SPFontFace + sp-font.h: class SPFont + sp-glyph.h: class SPGlyph + sp-glyph-kerning.h: class SPGlyphKerning + sp-glyph-kerning.h: class SPHkern + sp-glyph-kerning.h: class SPVkern + sp-guide.h: class SPGuide + sp-hatch-path.h: class SPHatchPath + sp-item.h: class SPItem + sp-flowdiv.h: class SPFlowdiv + sp-flowdiv.h: class SPFlowtspan + sp-flowdiv.h: class SPFlowpara + sp-flowregion.h: class SPFlowregion + sp-flowregion.h: class SPFlowregionExclude + sp-flowtext.h: class SPFlowtext + sp-image.h: class SPImage + sp-lpe-item.h: class SPLPEItem + sp-item-group.h: class SPGroup + box3d.h: class SPBox3D + sp-anchor.h: class SPAnchor + sp-marker.h: class SPMarker + sp-root.h: class SPRoot + sp-switch.h: class SPSwitch + sp-symbol.h: class SPSymbol + sp-shape.h: class SPShape + sp-ellipse.h: class SPGenericEllipse + sp-line.h: class SPLine + sp-offset.h: class SPOffset + sp-path.h: class SPPath + sp-polygon.h: class SPPolygon + sp-star.h: class SPStar + sp-polyline.h: class SPPolyLine + box3d-side.h: class Box3DSide + sp-rect.h: class SPRect + sp-spiral.h: class SPSpiral + sp-text.h: class SPText + sp-textpath.h: class SPTextPath + sp-tref.h: class SPTRef + sp-tspan.h: class SPTSpan + sp-use.h: class SPUse + sp-mesh-patch.h: class SPMeshpatch + sp-mesh-row.h: class SPMeshrow + sp-metadata.h: class SPMetadata + sp-missing-glyph.h: class SPMissingGlyph + sp-object-group.h: class SPObjectGroup + sp-clippath.h: class SPClipPath + sp-mask.h: class SPMask + sp-namedview.h: class SPNamedView + sp-paint-server.h: class SPPaintServer + sp-gradient.h: class SPGradient + sp-linear-gradient.h: class SPLinearGradient + sp-mesh-gradient.h: class SPMeshGradient + sp-radial-gradient.h: class SPRadialGradient + sp-hatch.h: class SPHatch + sp-pattern.h: class SPPattern + sp-solid-color.h: class SPSolidColor + sp-script.h: class SPScript + sp-stop.h: class SPStop + sp-string.h: class SPString + sp-style-elem.h: class SPStyleElem + sp-tag.h: class SPTag + sp-tag-use.h: class SPTagUse + sp-title.h: class SPTitle + +Other related files: + + object-set.h: + persp3d-reference.h + sp-conn-end-pair.h + sp-conn-end.h + sp-dimensions.h + sp-factory.h + sp-filter-reference.h + sp-filter-units.h + sp-gradient-reference.h + sp-gradient-spread.h + sp-gradient-units.h + sp-gradient-vector.h + sp-item-rm-unsatisfied-cns.h + sp-item-transform.h + sp-item-update-cns.h + sp-marker-loc.h + sp-mesh-array.h + sp-paint-server-reference.h + sp-tag-use-reference.h + sp-tref-reference.h + sp-use-reference.h + style-enums.h + style-internal.h + style.h + uri.h + uri-references.h + viewbox.h diff --git a/src/object/box3d-side.cpp b/src/object/box3d-side.cpp new file mode 100644 index 000000000..3eea8855c --- /dev/null +++ b/src/object/box3d-side.cpp @@ -0,0 +1,274 @@ +/* + * 3D box face implementation + * + * Authors: + * Maximilian Albert + * Abhishek Sharma + * Jon A. Cruz + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "box3d-side.h" +#include "document.h" +#include "xml/document.h" +#include "xml/repr.h" +#include "display/curve.h" +#include "svg/svg.h" +#include "attributes.h" +#include "inkscape.h" +#include "persp3d.h" +#include "persp3d-reference.h" +#include "ui/tools/box3d-tool.h" +#include "desktop-style.h" + +static void box3d_side_compute_corner_ids(Box3DSide *side, unsigned int corners[4]); + +Box3DSide::Box3DSide() : SPPolygon() { + this->dir1 = Box3D::NONE; + this->dir2 = Box3D::NONE; + this->front_or_rear = Box3D::FRONT; +} + +Box3DSide::~Box3DSide() { +} + +void Box3DSide::build(SPDocument * document, Inkscape::XML::Node * repr) { + SPPolygon::build(document, repr); + + this->readAttr( "inkscape:box3dsidetype" ); +} + + +Inkscape::XML::Node* Box3DSide::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + // this is where we end up when saving as plain SVG (also in other circumstances?) + // thus we don' set "sodipodi:type" so that the box is only saved as an ordinary svg:path + repr = xml_doc->createElement("svg:path"); + } + + if (flags & SP_OBJECT_WRITE_EXT) { + sp_repr_set_int(repr, "inkscape:box3dsidetype", this->dir1 ^ this->dir2 ^ this->front_or_rear); + } + + this->set_shape(); + + /* Duplicate the path */ + SPCurve const *curve = this->_curve; + + //Nulls might be possible if this called iteratively + if ( !curve ) { + return NULL; + } + + char *d = sp_svg_write_path ( curve->get_pathvector() ); + repr->setAttribute("d", d); + g_free (d); + + SPPolygon::write(xml_doc, repr, flags); + + return repr; +} + +void Box3DSide::set(unsigned int key, const gchar* value) { + // TODO: In case the box was recreated (by undo, e.g.) we need to recreate the path + // (along with other info?) from the parent box. + + /* fixme: we should really collect updates */ + switch (key) { + case SP_ATTR_INKSCAPE_BOX3D_SIDE_TYPE: + if (value) { + guint desc = atoi (value); + + if (!Box3D::is_face_id(desc)) { + g_print ("desc is not a face id: =%s=\n", value); + } + + g_return_if_fail (Box3D::is_face_id (desc)); + + Box3D::Axis plane = (Box3D::Axis) (desc & 0x7); + plane = (Box3D::is_plane(plane) ? plane : Box3D::orth_plane_or_axis(plane)); + this->dir1 = Box3D::extract_first_axis_direction(plane); + this->dir2 = Box3D::extract_second_axis_direction(plane); + this->front_or_rear = (Box3D::FrontOrRear) (desc & 0x8); + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } + break; + default: + SPPolygon::set(key, value); + break; + } +} + +void Box3DSide::update(SPCtx* ctx, guint flags) { + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore + } + + if (flags & (SP_OBJECT_MODIFIED_FLAG | + SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + + this->set_shape(); + } + + SPPolygon::update(ctx, flags); +} + +/* Create a new Box3DSide and append it to the parent box */ +Box3DSide * Box3DSide::createBox3DSide(SPBox3D *box) +{ + Box3DSide *box3d_side = 0; + Inkscape::XML::Document *xml_doc = box->document->rdoc; + Inkscape::XML::Node *repr_side = xml_doc->createElement("svg:path"); + repr_side->setAttribute("sodipodi:type", "inkscape:box3dside"); + box3d_side = static_cast(box->appendChildRepr(repr_side)); + return box3d_side; +} + +/* + * Function which return the type attribute for Box3D. + * Acts as a replacement for directly accessing the XML Tree directly. + */ +int Box3DSide::getFaceId() +{ + return this->getIntAttribute("inkscape:box3dsidetype", -1); +} + +void +box3d_side_position_set (Box3DSide *side) { + side->set_shape(); + + // This call is responsible for live update of the sides during the initial drag + side->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +void Box3DSide::set_shape() { + if (!this->document->getRoot()) { + // avoid a warning caused by sp_document_height() (which is called from sp_item_i2d_affine() below) + // when reading a file containing 3D boxes + return; + } + + SPObject *parent = this->parent; + + SPBox3D *box = dynamic_cast(parent); + if (!box) { + g_warning("Parent of 3D box side is not a 3D box.\n"); + return; + } + + Persp3D *persp = box3d_side_perspective(this); + + if (!persp) { + return; + } + + // TODO: Draw the correct quadrangle here + // To do this, determine the perspective of the box, the orientation of the side (e.g., XY-FRONT) + // compute the coordinates of the corners in P^3, project them onto the canvas, and draw the + // resulting path. + + unsigned int corners[4]; + box3d_side_compute_corner_ids(this, corners); + + SPCurve *c = new SPCurve(); + + if (!box3d_get_corner_screen(box, corners[0]).isFinite() || + !box3d_get_corner_screen(box, corners[1]).isFinite() || + !box3d_get_corner_screen(box, corners[2]).isFinite() || + !box3d_get_corner_screen(box, corners[3]).isFinite() ) + { + g_warning ("Trying to draw a 3D box side with invalid coordinates.\n"); + return; + } + + c->moveto(box3d_get_corner_screen(box, corners[0])); + c->lineto(box3d_get_corner_screen(box, corners[1])); + c->lineto(box3d_get_corner_screen(box, corners[2])); + c->lineto(box3d_get_corner_screen(box, corners[3])); + c->closepath(); + + /* Reset the this'scurve to the "original_curve" + * This is very important for LPEs to work properly! (the bbox might be recalculated depending on the curve in shape)*/ + this->setCurveInsync( c, TRUE); + + if (hasPathEffect() && pathEffectsEnabled()) { + SPCurve *c_lpe = c->copy(); + bool success = this->performPathEffect(c_lpe); + + if (success) { + this->setCurveInsync(c_lpe, TRUE); + } + + c_lpe->unref(); + } + + c->unref(); +} + +Glib::ustring box3d_side_axes_string(Box3DSide *side) +{ + Glib::ustring result(Box3D::string_from_axes((Box3D::Axis) (side->dir1 ^ side->dir2))); + + switch ((Box3D::Axis) (side->dir1 ^ side->dir2)) { + case Box3D::XY: + result += ((side->front_or_rear == Box3D::FRONT) ? "front" : "rear"); + break; + + case Box3D::XZ: + result += ((side->front_or_rear == Box3D::FRONT) ? "top" : "bottom"); + break; + + case Box3D::YZ: + result += ((side->front_or_rear == Box3D::FRONT) ? "right" : "left"); + break; + + default: + break; + } + + return result; +} + +static void +box3d_side_compute_corner_ids(Box3DSide *side, unsigned int corners[4]) { + Box3D::Axis orth = Box3D::third_axis_direction (side->dir1, side->dir2); + + corners[0] = (side->front_or_rear ? orth : 0); + corners[1] = corners[0] ^ side->dir1; + corners[2] = corners[0] ^ side->dir1 ^ side->dir2; + corners[3] = corners[0] ^ side->dir2; +} + +Persp3D * +box3d_side_perspective(Box3DSide *side) { + SPBox3D *box = side ? dynamic_cast(side->parent) : NULL; + return box ? box->persp_ref->getObject() : NULL; +} + +Inkscape::XML::Node *box3d_side_convert_to_path(Box3DSide *side) { + // TODO: Copy over all important attributes (see sp_selected_item_to_curved_repr() for an example) + SPDocument *doc = side->document; + Inkscape::XML::Document *xml_doc = doc->getReprDoc(); + + Inkscape::XML::Node *repr = xml_doc->createElement("svg:path"); + repr->setAttribute("d", side->getAttribute("d")); + repr->setAttribute("style", side->getAttribute("style")); + + return repr; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/box3d-side.h b/src/object/box3d-side.h new file mode 100644 index 000000000..29f17b8f3 --- /dev/null +++ b/src/object/box3d-side.h @@ -0,0 +1,64 @@ +#ifndef SEEN_BOX3D_SIDE_H +#define SEEN_BOX3D_SIDE_H + +/* + * 3D box face implementation + * + * Authors: + * Maximilian Albert + * Abhishek Sharma + * Jon A. Cruz + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-polygon.h" +#include "axis-manip.h" + + +class SPBox3D; +class Persp3D; + +// FIXME: Would it be better to inherit from SPPath instead? +class Box3DSide : public SPPolygon { +public: + Box3DSide(); + virtual ~Box3DSide(); + + Box3D::Axis dir1; + Box3D::Axis dir2; + Box3D::FrontOrRear front_or_rear; + int getFaceId(); + static Box3DSide * createBox3DSide(SPBox3D *box); + + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void set(unsigned int key, char const* value); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); + virtual void update(SPCtx *ctx, unsigned int flags); + + virtual void set_shape(); +}; + +void box3d_side_position_set (Box3DSide *side); // FIXME: Replace this by box3d_side_set_shape?? + +Glib::ustring box3d_side_axes_string(Box3DSide *side); + +Persp3D *box3d_side_perspective(Box3DSide *side); + + +Inkscape::XML::Node *box3d_side_convert_to_path(Box3DSide *side); + +#endif // SEEN_BOX3D_SIDE_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/box3d.cpp b/src/object/box3d.cpp new file mode 100644 index 000000000..af1d00b0f --- /dev/null +++ b/src/object/box3d.cpp @@ -0,0 +1,1359 @@ +/* + * SVG implementation + * + * Authors: + * Maximilian Albert + * Lauris Kaplinski + * bulia byak + * Abhishek Sharma + * Jon A. Cruz + * + * Copyright (C) 2007 Authors + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "box3d.h" + +#include +#include "attributes.h" +#include "xml/document.h" +#include "xml/repr.h" + +#include "bad-uri-exception.h" +#include "box3d-side.h" +#include "ui/tools/box3d-tool.h" +#include "perspective-line.h" +#include "persp3d-reference.h" +#include "uri.h" +#include <2geom/line.h> +#include "sp-guide.h" +#include "sp-namedview.h" + +#include "desktop.h" + +#include "macros.h" + +static void box3d_ref_changed(SPObject *old_ref, SPObject *ref, SPBox3D *box); + +static gint counter = 0; + +SPBox3D::SPBox3D() : SPGroup() { + this->my_counter = 0; + this->swapped = Box3D::NONE; + + this->persp_href = NULL; + this->persp_ref = new Persp3DReference(this); + + /* we initialize the z-orders to zero so that they are updated during dragging */ + for (int i = 0; i < 6; ++i) { + z_orders[i] = 0; + } +} + +SPBox3D::~SPBox3D() { +} + +void SPBox3D::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPGroup::build(document, repr); + + my_counter = counter++; + + /* we initialize the z-orders to zero so that they are updated during dragging */ + for (int i = 0; i < 6; ++i) { + z_orders[i] = 0; + } + + // TODO: Create/link to the correct perspective + + if ( document ) { + persp_ref->changedSignal().connect(sigc::bind(sigc::ptr_fun(box3d_ref_changed), this)); + + readAttr( "inkscape:perspectiveID" ); + readAttr( "inkscape:corner0" ); + readAttr( "inkscape:corner7" ); + } +} + +void SPBox3D::release() { + SPBox3D* object = this; + SPBox3D *box = object; + + if (box->persp_href) { + g_free(box->persp_href); + } + + // We have to store this here because the Persp3DReference gets destroyed below, but we need to + // access it to call persp3d_remove_box(), which cannot be called earlier because the reference + // needs to be destroyed first. + Persp3D *persp = box3d_get_perspective(box); + + if (box->persp_ref) { + box->persp_ref->detach(); + delete box->persp_ref; + box->persp_ref = NULL; + } + + if (persp) { + persp3d_remove_box (persp, box); + /* + // TODO: This deletes a perspective when the last box referring to it is gone. Eventually, + // it would be nice to have this but currently it crashes when undoing/redoing box deletion + // Reason: When redoing a box deletion, the associated perspective is deleted twice, first + // by the following code and then again by the redo mechanism! Perhaps we should perform + // deletion of the perspective from another location "outside" the undo/redo mechanism? + if (persp->perspective_impl->boxes.empty()) { + SPDocument *doc = box->document; + persp->deleteObject(); + doc->setCurrentPersp3D(persp3d_document_first_persp(doc)); + } + */ + } + + SPGroup::release(); +} + +void SPBox3D::set(unsigned int key, const gchar* value) { + SPBox3D* object = this; + SPBox3D *box = object; + + switch (key) { + case SP_ATTR_INKSCAPE_BOX3D_PERSPECTIVE_ID: + if ( value && box->persp_href && ( strcmp(value, box->persp_href) == 0 ) ) { + /* No change, do nothing. */ + } else { + if (box->persp_href) { + g_free(box->persp_href); + box->persp_href = NULL; + } + if (value) { + box->persp_href = g_strdup(value); + + // Now do the attaching, which emits the changed signal. + try { + box->persp_ref->attach(Inkscape::URI(value)); + } catch (Inkscape::BadURIException &e) { + g_warning("%s", e.what()); + box->persp_ref->detach(); + } + } else { + // Detach, which emits the changed signal. + box->persp_ref->detach(); + } + } + + // FIXME: Is the following update doubled by some call in either persp3d.cpp or vanishing_point_new.cpp? + box3d_position_set(box); + break; + case SP_ATTR_INKSCAPE_BOX3D_CORNER0: + if (value && strcmp(value, "0 : 0 : 0 : 0")) { + box->orig_corner0 = Proj::Pt3(value); + box->save_corner0 = box->orig_corner0; + box3d_position_set(box); + } + break; + case SP_ATTR_INKSCAPE_BOX3D_CORNER7: + if (value && strcmp(value, "0 : 0 : 0 : 0")) { + box->orig_corner7 = Proj::Pt3(value); + box->save_corner7 = box->orig_corner7; + box3d_position_set(box); + } + break; + default: + SPGroup::set(key, value); + break; + } +} + +/** + * Gets called when (re)attached to another perspective. + */ +static void +box3d_ref_changed(SPObject *old_ref, SPObject *ref, SPBox3D *box) +{ + if (old_ref) { + sp_signal_disconnect_by_data(old_ref, box); + Persp3D *oldPersp = dynamic_cast(old_ref); + if (oldPersp) { + persp3d_remove_box(oldPersp, box); + } + } + Persp3D *persp = dynamic_cast(ref); + if ( persp && (ref != box) ) // FIXME: Comparisons sane? + { + persp3d_add_box(persp, box); + } +} + +void SPBox3D::update(SPCtx *ctx, guint flags) { + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + + /* FIXME?: Perhaps the display updates of box sides should be instantiated from here, but this + causes evil update loops so it's all done from box3d_position_set, which is called from + various other places (like the handlers in shape-editor-knotholders.cpp, vanishing-point.cpp, etc. */ + + } + + // Invoke parent method + SPGroup::update(ctx, flags); +} + +Inkscape::XML::Node* SPBox3D::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + SPBox3D* object = this; + SPBox3D *box = object; + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + // this is where we end up when saving as plain SVG (also in other circumstances?) + // thus we don' set "sodipodi:type" so that the box is only saved as an ordinary svg:g + repr = xml_doc->createElement("svg:g"); + } + + if (flags & SP_OBJECT_WRITE_EXT) { + + if (box->persp_href) { + repr->setAttribute("inkscape:perspectiveID", box->persp_href); + } else { + /* box is not yet linked to a perspective; use the document's current perspective */ + SPDocument *doc = object->document; + if (box->persp_ref->getURI()) { + gchar *uri_string = box->persp_ref->getURI()->toString(); + repr->setAttribute("inkscape:perspectiveID", uri_string); + g_free(uri_string); + } else { + Glib::ustring href = "#"; + href += doc->getCurrentPersp3D()->getId(); + repr->setAttribute("inkscape:perspectiveID", href.c_str()); + } + } + + gchar *coordstr0 = box->orig_corner0.coord_string(); + gchar *coordstr7 = box->orig_corner7.coord_string(); + repr->setAttribute("inkscape:corner0", coordstr0); + repr->setAttribute("inkscape:corner7", coordstr7); + g_free(coordstr0); + g_free(coordstr7); + + box->orig_corner0.normalize(); + box->orig_corner7.normalize(); + + box->save_corner0 = box->orig_corner0; + box->save_corner7 = box->orig_corner7; + } + + SPGroup::write(xml_doc, repr, flags); + + return repr; +} + +const char* SPBox3D::display_name() { + return _("3D Box"); +} + +void box3d_position_set(SPBox3D *box) +{ + /* This draws the curve and calls requestDisplayUpdate() for each side (the latter is done in + box3d_side_position_set() to avoid update conflicts with the parent box) */ + for (auto& obj: box->children) { + Box3DSide *side = dynamic_cast(&obj); + if (side) { + box3d_side_position_set(side); + } + } +} + +Geom::Affine SPBox3D::set_transform(Geom::Affine const &xform) { + // We don't apply the transform to the box directly but instead to its perspective (which is + // done in sp_selection_apply_affine). Here we only adjust strokes, patterns, etc. + + Geom::Affine ret(Geom::Affine(xform).withoutTranslation()); + gdouble const sw = hypot(ret[0], ret[1]); + gdouble const sh = hypot(ret[2], ret[3]); + + for (auto& child: children) { + SPItem *childitem = dynamic_cast(&child); + if (childitem) { + // Adjust stroke width + childitem->adjust_stroke(sqrt(fabs(sw * sh))); + + // Adjust pattern fill + childitem->adjust_pattern(xform); + + // Adjust gradient fill + childitem->adjust_gradient(xform); + + // Adjust LPE + childitem->adjust_livepatheffect(xform); + } + } + + return Geom::identity(); +} + +static Proj::Pt3 +box3d_get_proj_corner (guint id, Proj::Pt3 const &c0, Proj::Pt3 const &c7) { + return Proj::Pt3 ((id & Box3D::X) ? c7[Proj::X] : c0[Proj::X], + (id & Box3D::Y) ? c7[Proj::Y] : c0[Proj::Y], + (id & Box3D::Z) ? c7[Proj::Z] : c0[Proj::Z], + 1.0); +} + +Proj::Pt3 +box3d_get_proj_corner (SPBox3D const *box, guint id) { + return Proj::Pt3 ((id & Box3D::X) ? box->orig_corner7[Proj::X] : box->orig_corner0[Proj::X], + (id & Box3D::Y) ? box->orig_corner7[Proj::Y] : box->orig_corner0[Proj::Y], + (id & Box3D::Z) ? box->orig_corner7[Proj::Z] : box->orig_corner0[Proj::Z], + 1.0); +} + +Geom::Point +box3d_get_corner_screen (SPBox3D const *box, guint id, bool item_coords) { + Proj::Pt3 proj_corner (box3d_get_proj_corner (box, id)); + if (!box3d_get_perspective(box)) { + return Geom::Point (Geom::infinity(), Geom::infinity()); + } + Geom::Affine const i2d(box->i2dt_affine ()); + if (item_coords) { + return box3d_get_perspective(box)->perspective_impl->tmat.image(proj_corner).affine() * i2d.inverse(); + } else { + return box3d_get_perspective(box)->perspective_impl->tmat.image(proj_corner).affine(); + } +} + +Proj::Pt3 +box3d_get_proj_center (SPBox3D *box) { + box->orig_corner0.normalize(); + box->orig_corner7.normalize(); + return Proj::Pt3 ((box->orig_corner0[Proj::X] + box->orig_corner7[Proj::X]) / 2, + (box->orig_corner0[Proj::Y] + box->orig_corner7[Proj::Y]) / 2, + (box->orig_corner0[Proj::Z] + box->orig_corner7[Proj::Z]) / 2, + 1.0); +} + +Geom::Point +box3d_get_center_screen (SPBox3D *box) { + Proj::Pt3 proj_center (box3d_get_proj_center (box)); + if (!box3d_get_perspective(box)) { + return Geom::Point (Geom::infinity(), Geom::infinity()); + } + Geom::Affine const i2d( box->i2dt_affine() ); + return box3d_get_perspective(box)->perspective_impl->tmat.image(proj_center).affine() * i2d.inverse(); +} + +/* + * To keep the snappoint from jumping randomly between the two lines when the mouse pointer is close to + * their intersection, we remember the last snapped line and keep snapping to this specific line as long + * as the distance from the intersection to the mouse pointer is less than remember_snap_threshold. + */ + +// Should we make the threshold settable in the preferences? +static double remember_snap_threshold = 30; +static guint remember_snap_index = 0; + +// constant for sizing the array of points to be considered: +static const int MAX_POINT_COUNT = 4; + +static Proj::Pt3 +box3d_snap (SPBox3D *box, int id, Proj::Pt3 const &pt_proj, Proj::Pt3 const &start_pt) { + double z_coord = start_pt[Proj::Z]; + double diff_x = box->save_corner7[Proj::X] - box->save_corner0[Proj::X]; + double diff_y = box->save_corner7[Proj::Y] - box->save_corner0[Proj::Y]; + double x_coord = start_pt[Proj::X]; + double y_coord = start_pt[Proj::Y]; + Proj::Pt3 A_proj (x_coord, y_coord, z_coord, 1.0); + Proj::Pt3 B_proj (x_coord + diff_x, y_coord, z_coord, 1.0); + Proj::Pt3 C_proj (x_coord + diff_x, y_coord + diff_y, z_coord, 1.0); + Proj::Pt3 D_proj (x_coord, y_coord + diff_y, z_coord, 1.0); + Proj::Pt3 E_proj (x_coord - diff_x, y_coord + diff_y, z_coord, 1.0); + + Persp3DImpl *persp_impl = box3d_get_perspective(box)->perspective_impl; + Geom::Point A = persp_impl->tmat.image(A_proj).affine(); + Geom::Point B = persp_impl->tmat.image(B_proj).affine(); + Geom::Point C = persp_impl->tmat.image(C_proj).affine(); + Geom::Point D = persp_impl->tmat.image(D_proj).affine(); + Geom::Point E = persp_impl->tmat.image(E_proj).affine(); + Geom::Point pt = persp_impl->tmat.image(pt_proj).affine(); + + // TODO: Replace these lines between corners with lines from a corner to a vanishing point + // (this might help to prevent rounding errors if the box is small) + Box3D::Line pl1(A, B); + Box3D::Line pl2(A, D); + Box3D::Line diag1(A, (id == -1 || (!(id & Box3D::X) == !(id & Box3D::Y))) ? C : E); + Box3D::Line diag2(A, E); // diag2 is only taken into account if id equals -1, i.e., if we are snapping the center + + int num_snap_lines = (id != -1) ? 3 : 4; + Geom::Point snap_pts[MAX_POINT_COUNT]; + + snap_pts[0] = pl1.closest_to (pt); + snap_pts[1] = pl2.closest_to (pt); + snap_pts[2] = diag1.closest_to (pt); + if (id == -1) { + snap_pts[3] = diag2.closest_to (pt); + } + + gdouble const zoom = SP_ACTIVE_DESKTOP->current_zoom(); + + // determine the distances to all potential snapping points + double snap_dists[MAX_POINT_COUNT]; + for (int i = 0; i < num_snap_lines; ++i) { + snap_dists[i] = Geom::L2 (snap_pts[i] - pt) * zoom; + } + + // while we are within a given tolerance of the starting point, + // keep snapping to the same point to avoid jumping + bool within_tolerance = true; + for (int i = 0; i < num_snap_lines; ++i) { + if (snap_dists[i] > remember_snap_threshold) { + within_tolerance = false; + break; + } + } + + // find the closest snapping point + int snap_index = -1; + double snap_dist = Geom::infinity(); + for (int i = 0; i < num_snap_lines; ++i) { + if (snap_dists[i] < snap_dist) { + snap_index = i; + snap_dist = snap_dists[i]; + } + } + + // snap to the closest point (or the previously remembered one + // if we are within tolerance of the starting point) + Geom::Point result; + if (within_tolerance) { + result = snap_pts[remember_snap_index]; + } else { + remember_snap_index = snap_index; + result = snap_pts[snap_index]; + } + return box3d_get_perspective(box)->perspective_impl->tmat.preimage (result, z_coord, Proj::Z); +} + +SPBox3D * SPBox3D::createBox3D(SPItem * parent) +{ + SPBox3D *box3d = 0; + Inkscape::XML::Document *xml_doc = parent->document->rdoc; + Inkscape::XML::Node *repr = xml_doc->createElement("svg:g"); + repr->setAttribute("sodipodi:type", "inkscape:box3d"); + box3d = reinterpret_cast(parent->appendChildRepr(repr)); + return box3d; +} + +void +box3d_set_corner (SPBox3D *box, const guint id, Geom::Point const &new_pos, const Box3D::Axis movement, bool constrained) { + g_return_if_fail ((movement != Box3D::NONE) && (movement != Box3D::XYZ)); + + box->orig_corner0.normalize(); + box->orig_corner7.normalize(); + + /* update corners 0 and 7 according to which handle was moved and to the axes of movement */ + if (!(movement & Box3D::Z)) { + Persp3DImpl *persp_impl = box3d_get_perspective(box)->perspective_impl; + Proj::Pt3 pt_proj (persp_impl->tmat.preimage (new_pos, (id < 4) ? box->orig_corner0[Proj::Z] : + box->orig_corner7[Proj::Z], Proj::Z)); + if (constrained) { + pt_proj = box3d_snap (box, id, pt_proj, box3d_get_proj_corner (id, box->save_corner0, box->save_corner7)); + } + + // normalizing pt_proj is essential because we want to mingle affine coordinates + pt_proj.normalize(); + box->orig_corner0 = Proj::Pt3 ((id & Box3D::X) ? box->save_corner0[Proj::X] : pt_proj[Proj::X], + (id & Box3D::Y) ? box->save_corner0[Proj::Y] : pt_proj[Proj::Y], + box->save_corner0[Proj::Z], + 1.0); + box->orig_corner7 = Proj::Pt3 ((id & Box3D::X) ? pt_proj[Proj::X] : box->save_corner7[Proj::X], + (id & Box3D::Y) ? pt_proj[Proj::Y] : box->save_corner7[Proj::Y], + box->save_corner7[Proj::Z], + 1.0); + } else { + Persp3D *persp = box3d_get_perspective(box); + Persp3DImpl *persp_impl = box3d_get_perspective(box)->perspective_impl; + Box3D::PerspectiveLine pl(persp_impl->tmat.image( + box3d_get_proj_corner (id, box->save_corner0, box->save_corner7)).affine(), + Proj::Z, persp); + Geom::Point new_pos_snapped(pl.closest_to(new_pos)); + Proj::Pt3 pt_proj (persp_impl->tmat.preimage (new_pos_snapped, + box3d_get_proj_corner (box, id)[(movement & Box3D::Y) ? Proj::X : Proj::Y], + (movement & Box3D::Y) ? Proj::X : Proj::Y)); + bool corner0_move_x = !(id & Box3D::X) && (movement & Box3D::X); + bool corner0_move_y = !(id & Box3D::Y) && (movement & Box3D::Y); + bool corner7_move_x = (id & Box3D::X) && (movement & Box3D::X); + bool corner7_move_y = (id & Box3D::Y) && (movement & Box3D::Y); + // normalizing pt_proj is essential because we want to mingle affine coordinates + pt_proj.normalize(); + box->orig_corner0 = Proj::Pt3 (corner0_move_x ? pt_proj[Proj::X] : box->orig_corner0[Proj::X], + corner0_move_y ? pt_proj[Proj::Y] : box->orig_corner0[Proj::Y], + (id & Box3D::Z) ? box->orig_corner0[Proj::Z] : pt_proj[Proj::Z], + 1.0); + box->orig_corner7 = Proj::Pt3 (corner7_move_x ? pt_proj[Proj::X] : box->orig_corner7[Proj::X], + corner7_move_y ? pt_proj[Proj::Y] : box->orig_corner7[Proj::Y], + (id & Box3D::Z) ? pt_proj[Proj::Z] : box->orig_corner7[Proj::Z], + 1.0); + } + // FIXME: Should we update the box here? If so, how? +} + +void box3d_set_center (SPBox3D *box, Geom::Point const &new_pos, Geom::Point const &old_pos, const Box3D::Axis movement, bool constrained) { + g_return_if_fail ((movement != Box3D::NONE) && (movement != Box3D::XYZ)); + + box->orig_corner0.normalize(); + box->orig_corner7.normalize(); + + Persp3D *persp = box3d_get_perspective(box); + if (!(movement & Box3D::Z)) { + double coord = (box->orig_corner0[Proj::Z] + box->orig_corner7[Proj::Z]) / 2; + double radx = (box->orig_corner7[Proj::X] - box->orig_corner0[Proj::X]) / 2; + double rady = (box->orig_corner7[Proj::Y] - box->orig_corner0[Proj::Y]) / 2; + + Proj::Pt3 pt_proj (persp->perspective_impl->tmat.preimage (new_pos, coord, Proj::Z)); + if (constrained) { + Proj::Pt3 old_pos_proj (persp->perspective_impl->tmat.preimage (old_pos, coord, Proj::Z)); + old_pos_proj.normalize(); + pt_proj = box3d_snap (box, -1, pt_proj, old_pos_proj); + } + // normalizing pt_proj is essential because we want to mingle affine coordinates + pt_proj.normalize(); + box->orig_corner0 = Proj::Pt3 ((movement & Box3D::X) ? pt_proj[Proj::X] - radx : box->orig_corner0[Proj::X], + (movement & Box3D::Y) ? pt_proj[Proj::Y] - rady : box->orig_corner0[Proj::Y], + box->orig_corner0[Proj::Z], + 1.0); + box->orig_corner7 = Proj::Pt3 ((movement & Box3D::X) ? pt_proj[Proj::X] + radx : box->orig_corner7[Proj::X], + (movement & Box3D::Y) ? pt_proj[Proj::Y] + rady : box->orig_corner7[Proj::Y], + box->orig_corner7[Proj::Z], + 1.0); + } else { + double coord = (box->orig_corner0[Proj::X] + box->orig_corner7[Proj::X]) / 2; + double radz = (box->orig_corner7[Proj::Z] - box->orig_corner0[Proj::Z]) / 2; + + Box3D::PerspectiveLine pl(old_pos, Proj::Z, persp); + Geom::Point new_pos_snapped(pl.closest_to(new_pos)); + Proj::Pt3 pt_proj (persp->perspective_impl->tmat.preimage (new_pos_snapped, coord, Proj::X)); + + /* normalizing pt_proj is essential because we want to mingle affine coordinates */ + pt_proj.normalize(); + box->orig_corner0 = Proj::Pt3 (box->orig_corner0[Proj::X], + box->orig_corner0[Proj::Y], + pt_proj[Proj::Z] - radz, + 1.0); + box->orig_corner7 = Proj::Pt3 (box->orig_corner7[Proj::X], + box->orig_corner7[Proj::Y], + pt_proj[Proj::Z] + radz, + 1.0); + } +} + +/* + * Manipulates corner1 through corner4 to contain the indices of the corners + * from which the perspective lines in the direction of 'axis' emerge + */ +void box3d_corners_for_PLs (const SPBox3D * box, Proj::Axis axis, + Geom::Point &corner1, Geom::Point &corner2, Geom::Point &corner3, Geom::Point &corner4) +{ + Persp3D *persp = box3d_get_perspective(box); + g_return_if_fail (persp); + Persp3DImpl *persp_impl = persp->perspective_impl; + //box->orig_corner0.normalize(); + //box->orig_corner7.normalize(); + double coord = (box->orig_corner0[axis] > box->orig_corner7[axis]) ? + box->orig_corner0[axis] : + box->orig_corner7[axis]; + + Proj::Pt3 c1, c2, c3, c4; + // FIXME: This can certainly be done more elegantly/efficiently than by a case-by-case analysis. + switch (axis) { + case Proj::X: + c1 = Proj::Pt3 (coord, box->orig_corner0[Proj::Y], box->orig_corner0[Proj::Z], 1.0); + c2 = Proj::Pt3 (coord, box->orig_corner7[Proj::Y], box->orig_corner0[Proj::Z], 1.0); + c3 = Proj::Pt3 (coord, box->orig_corner7[Proj::Y], box->orig_corner7[Proj::Z], 1.0); + c4 = Proj::Pt3 (coord, box->orig_corner0[Proj::Y], box->orig_corner7[Proj::Z], 1.0); + break; + case Proj::Y: + c1 = Proj::Pt3 (box->orig_corner0[Proj::X], coord, box->orig_corner0[Proj::Z], 1.0); + c2 = Proj::Pt3 (box->orig_corner7[Proj::X], coord, box->orig_corner0[Proj::Z], 1.0); + c3 = Proj::Pt3 (box->orig_corner7[Proj::X], coord, box->orig_corner7[Proj::Z], 1.0); + c4 = Proj::Pt3 (box->orig_corner0[Proj::X], coord, box->orig_corner7[Proj::Z], 1.0); + break; + case Proj::Z: + c1 = Proj::Pt3 (box->orig_corner7[Proj::X], box->orig_corner7[Proj::Y], coord, 1.0); + c2 = Proj::Pt3 (box->orig_corner7[Proj::X], box->orig_corner0[Proj::Y], coord, 1.0); + c3 = Proj::Pt3 (box->orig_corner0[Proj::X], box->orig_corner0[Proj::Y], coord, 1.0); + c4 = Proj::Pt3 (box->orig_corner0[Proj::X], box->orig_corner7[Proj::Y], coord, 1.0); + break; + default: + return; + } + corner1 = persp_impl->tmat.image(c1).affine(); + corner2 = persp_impl->tmat.image(c2).affine(); + corner3 = persp_impl->tmat.image(c3).affine(); + corner4 = persp_impl->tmat.image(c4).affine(); +} + +/* Auxiliary function: Checks whether the half-line from A to B crosses the line segment joining C and D */ +static bool +box3d_half_line_crosses_joining_line (Geom::Point const &A, Geom::Point const &B, + Geom::Point const &C, Geom::Point const &D) { + Geom::Point n0 = (B - A).ccw(); + double d0 = dot(n0,A); + + Geom::Point n1 = (D - C).ccw(); + double d1 = dot(n1,C); + + Geom::Line lineAB(A,B); + Geom::Line lineCD(C,D); + + Geom::OptCrossing inters = Geom::OptCrossing(); // empty by default + try + { + inters = Geom::intersection(lineAB, lineCD); + } + catch (Geom::InfiniteSolutions& e) + { + // We're probably dealing with parallel lines, so they don't really cross + return false; + } + + if (!inters) { + return false; + } + + Geom::Point E = lineAB.pointAt((*inters).ta); // the point of intersection + + if ((dot(C,n0) < d0) == (dot(D,n0) < d0)) { + // C and D lie on the same side of the line AB + return false; + } + if ((dot(A,n1) < d1) != (dot(B,n1) < d1)) { + // A and B lie on different sides of the line CD + return true; + } else if (Geom::distance(E,A) < Geom::distance(E,B)) { + // The line CD passes on the "wrong" side of A + return false; + } + + // The line CD passes on the "correct" side of A + return true; +} + +static bool +box3d_XY_axes_are_swapped (SPBox3D *box) { + Persp3D *persp = box3d_get_perspective(box); + g_return_val_if_fail(persp, false); + Box3D::PerspectiveLine l1(box3d_get_corner_screen(box, 3, false), Proj::X, persp); + Box3D::PerspectiveLine l2(box3d_get_corner_screen(box, 3, false), Proj::Y, persp); + Geom::Point v1(l1.direction()); + Geom::Point v2(l2.direction()); + v1.normalize(); + v2.normalize(); + + return (v1[Geom::X]*v2[Geom::Y] - v1[Geom::Y]*v2[Geom::X] > 0); +} + +static inline void +box3d_aux_set_z_orders (int z_orders[6], int a, int b, int c, int d, int e, int f) { + z_orders[0] = a; + z_orders[1] = b; + z_orders[2] = c; + z_orders[3] = d; + z_orders[4] = e; + z_orders[5] = f; +} + + +/* + * In standard perspective we have: + * 2 = front face + * 1 = top face + * 0 = left face + * 3 = right face + * 4 = bottom face + * 5 = rear face + */ + +/* All VPs infinite */ +static void +box3d_set_new_z_orders_case0 (SPBox3D *box, int z_orders[6], Box3D::Axis central_axis) { + bool swapped = box3d_XY_axes_are_swapped(box); + + switch(central_axis) { + case Box3D::X: + if (!swapped) { + box3d_aux_set_z_orders (z_orders, 2, 0, 4, 1, 3, 5); + } else { + box3d_aux_set_z_orders (z_orders, 3, 1, 5, 2, 4, 0); + } + break; + case Box3D::Y: + if (!swapped) { + box3d_aux_set_z_orders (z_orders, 2, 3, 1, 4, 0, 5); + } else { + box3d_aux_set_z_orders (z_orders, 5, 0, 4, 1, 3, 2); + } + break; + case Box3D::Z: + if (!swapped) { + box3d_aux_set_z_orders (z_orders, 2, 0, 1, 4, 3, 5); + } else { + box3d_aux_set_z_orders (z_orders, 5, 3, 4, 1, 0, 2); + } + break; + case Box3D::NONE: + if (!swapped) { + box3d_aux_set_z_orders (z_orders, 2, 3, 4, 1, 0, 5); + } else { + box3d_aux_set_z_orders (z_orders, 5, 0, 1, 4, 3, 2); + } + break; + default: + g_assert_not_reached(); + break; + } +} + +/* Precisely one finite VP */ +static void +box3d_set_new_z_orders_case1 (SPBox3D *box, int z_orders[6], Box3D::Axis central_axis, Box3D::Axis fin_axis) { + Persp3D *persp = box3d_get_perspective(box); + Geom::Point vp(persp3d_get_VP(persp, Box3D::toProj(fin_axis)).affine()); + + // note: in some of the case distinctions below we rely upon the fact that oaxis1 and oaxis2 are ordered + Box3D::Axis oaxis1 = Box3D::get_remaining_axes(fin_axis).first; + Box3D::Axis oaxis2 = Box3D::get_remaining_axes(fin_axis).second; + int inside1 = 0; + int inside2 = 0; + inside1 = box3d_pt_lies_in_PL_sector (box, vp, 3, 3 ^ oaxis2, oaxis1); + inside2 = box3d_pt_lies_in_PL_sector (box, vp, 3, 3 ^ oaxis1, oaxis2); + + bool swapped = box3d_XY_axes_are_swapped(box); + + switch(central_axis) { + case Box3D::X: + if (!swapped) { + box3d_aux_set_z_orders (z_orders, 2, 4, 0, 1, 3, 5); + } else { + box3d_aux_set_z_orders (z_orders, 5, 3, 1, 0, 2, 4); + } + break; + case Box3D::Y: + if (inside2 > 0) { + box3d_aux_set_z_orders (z_orders, 1, 2, 3, 0, 5, 4); + } else if (inside2 < 0) { + box3d_aux_set_z_orders (z_orders, 2, 3, 1, 4, 0, 5); + } else { + if (!swapped) { + box3d_aux_set_z_orders (z_orders, 2, 3, 1, 5, 0, 4); + } else { + box3d_aux_set_z_orders (z_orders, 5, 0, 4, 1, 3, 2); + } + } + break; + case Box3D::Z: + if (inside2) { + if (!swapped) { + box3d_aux_set_z_orders (z_orders, 2, 1, 3, 0, 4, 5); + } else { + box3d_aux_set_z_orders (z_orders, 5, 3, 4, 0, 1, 2); + } + } else if (inside1) { + if (!swapped) { + box3d_aux_set_z_orders (z_orders, 2, 0, 1, 4, 3, 5); + } else { + box3d_aux_set_z_orders (z_orders, 5, 3, 4, 1, 0, 2); + } + } else { + // "regular" case + if (!swapped) { + box3d_aux_set_z_orders (z_orders, 0, 1, 2, 5, 4, 3); + } else { + box3d_aux_set_z_orders (z_orders, 5, 3, 4, 0, 2, 1); + } + } + break; + case Box3D::NONE: + if (!swapped) { + box3d_aux_set_z_orders (z_orders, 2, 3, 4, 5, 0, 1); + } else { + box3d_aux_set_z_orders (z_orders, 5, 0, 1, 3, 2, 4); + } + break; + default: + g_assert_not_reached(); + } +} + +/* Precisely 2 finite VPs */ +static void +box3d_set_new_z_orders_case2 (SPBox3D *box, int z_orders[6], Box3D::Axis central_axis, Box3D::Axis /*infinite_axis*/) { + Geom::Point c3(box3d_get_corner_screen(box, 3, false)); + + bool swapped = box3d_XY_axes_are_swapped(box); + + int insidexy = box3d_VP_lies_in_PL_sector (box, Proj::X, 3, 3 ^ Box3D::Z, Box3D::Y); + //int insidexz = box3d_VP_lies_in_PL_sector (box, Proj::X, 3, 3 ^ Box3D::Y, Box3D::Z); + + int insideyx = box3d_VP_lies_in_PL_sector (box, Proj::Y, 3, 3 ^ Box3D::Z, Box3D::X); + int insideyz = box3d_VP_lies_in_PL_sector (box, Proj::Y, 3, 3 ^ Box3D::X, Box3D::Z); + + //int insidezx = box3d_VP_lies_in_PL_sector (box, Proj::Z, 3, 3 ^ Box3D::Y, Box3D::X); + int insidezy = box3d_VP_lies_in_PL_sector (box, Proj::Z, 3, 3 ^ Box3D::X, Box3D::Y); + + switch(central_axis) { + case Box3D::X: + if (!swapped) { + if (insidezy == -1) { + box3d_aux_set_z_orders (z_orders, 2, 4, 0, 1, 3, 5); + } else if (insidexy == 1) { + box3d_aux_set_z_orders (z_orders, 2, 4, 0, 5, 1, 3); + } else { + box3d_aux_set_z_orders (z_orders, 2, 4, 0, 1, 3, 5); + } + } else { + if (insideyz == -1) { + box3d_aux_set_z_orders (z_orders, 3, 1, 5, 0, 2, 4); + } else { + if (!swapped) { + box3d_aux_set_z_orders (z_orders, 3, 1, 5, 2, 4, 0); + } else { + if (insidexy == 0) { + box3d_aux_set_z_orders (z_orders, 3, 5, 1, 0, 2, 4); + } else { + box3d_aux_set_z_orders (z_orders, 3, 1, 5, 0, 2, 4); + } + } + } + } + break; + case Box3D::Y: + if (!swapped) { + if (insideyz == 1) { + box3d_aux_set_z_orders (z_orders, 2, 3, 1, 0, 5, 4); + } else { + box3d_aux_set_z_orders (z_orders, 2, 3, 1, 5, 0, 4); + } + } else { + if (insideyx == 1) { + box3d_aux_set_z_orders (z_orders, 4, 0, 5, 1, 3, 2); + } else { + box3d_aux_set_z_orders (z_orders, 5, 0, 4, 1, 3, 2); + } + } + break; + case Box3D::Z: + if (!swapped) { + if (insidezy == 1) { + box3d_aux_set_z_orders (z_orders, 2, 1, 0, 4, 3, 5); + } else if (insidexy == -1) { + box3d_aux_set_z_orders (z_orders, 2, 1, 0, 5, 4, 3); + } else { + box3d_aux_set_z_orders (z_orders, 2, 0, 1, 5, 3, 4); + } + } else { + box3d_aux_set_z_orders (z_orders, 3, 4, 5, 1, 0, 2); + } + break; + case Box3D::NONE: + if (!swapped) { + box3d_aux_set_z_orders (z_orders, 2, 3, 4, 1, 0, 5); + } else { + box3d_aux_set_z_orders (z_orders, 5, 0, 1, 4, 3, 2); + } + break; + default: + g_assert_not_reached(); + break; + } +} + +/* + * It can happen that during dragging the box is everted. + * In this case the opposite sides in this direction need to be swapped + */ +static Box3D::Axis +box3d_everted_directions (SPBox3D *box) { + Box3D::Axis ev = Box3D::NONE; + + box->orig_corner0.normalize(); + box->orig_corner7.normalize(); + + if (box->orig_corner0[Proj::X] < box->orig_corner7[Proj::X]) + ev = (Box3D::Axis) (ev ^ Box3D::X); + if (box->orig_corner0[Proj::Y] < box->orig_corner7[Proj::Y]) + ev = (Box3D::Axis) (ev ^ Box3D::Y); + if (box->orig_corner0[Proj::Z] > box->orig_corner7[Proj::Z]) // FIXME: Remove the need to distinguish signs among the cases + ev = (Box3D::Axis) (ev ^ Box3D::Z); + + return ev; +} + +static void +box3d_swap_sides(int z_orders[6], Box3D::Axis axis) { + int pos1 = -1; + int pos2 = -1; + + for (int i = 0; i < 6; ++i) { + if (!(Box3D::int_to_face(z_orders[i]) & axis)) { + if (pos1 == -1) { + pos1 = i; + } else { + pos2 = i; + break; + } + } + } + + if ((pos1 != -1) && (pos2 != -1)){ + int tmp = z_orders[pos1]; + z_orders[pos1] = z_orders[pos2]; + z_orders[pos2] = tmp; + } +} + + +bool +box3d_recompute_z_orders (SPBox3D *box) { + Persp3D *persp = box3d_get_perspective(box); + + if (!persp) + return false; + + int z_orders[6]; + + Geom::Point c3(box3d_get_corner_screen(box, 3, false)); + + // determine directions from corner3 to the VPs + int num_finite = 0; + Box3D::Axis axis_finite = Box3D::NONE; + Box3D::Axis axis_infinite = Box3D::NONE; + Geom::Point dirs[3]; + for (int i = 0; i < 3; ++i) { + dirs[i] = persp3d_get_PL_dir_from_pt(persp, c3, Box3D::toProj(Box3D::axes[i])); + if (persp3d_VP_is_finite(persp->perspective_impl, Proj::axes[i])) { + num_finite++; + axis_finite = Box3D::axes[i]; + } else { + axis_infinite = Box3D::axes[i]; + } + } + + // determine the "central" axis (if there is one) + Box3D::Axis central_axis = Box3D::NONE; + if(Box3D::lies_in_sector(dirs[0], dirs[1], dirs[2])) { + central_axis = Box3D::Z; + } else if(Box3D::lies_in_sector(dirs[1], dirs[2], dirs[0])) { + central_axis = Box3D::X; + } else if(Box3D::lies_in_sector(dirs[2], dirs[0], dirs[1])) { + central_axis = Box3D::Y; + } + + switch (num_finite) { + case 0: + // TODO: Remark: In this case (and maybe one of the others, too) the z-orders for all boxes + // coincide, hence only need to be computed once in a more central location. + box3d_set_new_z_orders_case0(box, z_orders, central_axis); + break; + case 1: + box3d_set_new_z_orders_case1(box, z_orders, central_axis, axis_finite); + break; + case 2: + case 3: + box3d_set_new_z_orders_case2(box, z_orders, central_axis, axis_infinite); + break; + default: + /* + * For each VP F, check whether the half-line from the corner3 to F crosses the line segment + * joining the other two VPs. If this is the case, it determines the "central" corner from + * which the visible sides can be deduced. Otherwise, corner3 is the central corner. + */ + // FIXME: We should eliminate the use of Geom::Point altogether + Box3D::Axis central_axis = Box3D::NONE; + Geom::Point vp_x = persp3d_get_VP(persp, Proj::X).affine(); + Geom::Point vp_y = persp3d_get_VP(persp, Proj::Y).affine(); + Geom::Point vp_z = persp3d_get_VP(persp, Proj::Z).affine(); + Geom::Point vpx(vp_x[Geom::X], vp_x[Geom::Y]); + Geom::Point vpy(vp_y[Geom::X], vp_y[Geom::Y]); + Geom::Point vpz(vp_z[Geom::X], vp_z[Geom::Y]); + + Geom::Point c3 = box3d_get_corner_screen(box, 3, false); + Geom::Point corner3(c3[Geom::X], c3[Geom::Y]); + + if (box3d_half_line_crosses_joining_line (corner3, vpx, vpy, vpz)) { + central_axis = Box3D::X; + } else if (box3d_half_line_crosses_joining_line (corner3, vpy, vpz, vpx)) { + central_axis = Box3D::Y; + } else if (box3d_half_line_crosses_joining_line (corner3, vpz, vpx, vpy)) { + central_axis = Box3D::Z; + } + + // FIXME: At present, this is not used. Why is it calculated? + /* + unsigned int central_corner = 3 ^ central_axis; + if (central_axis == Box3D::Z) { + central_corner = central_corner ^ Box3D::XYZ; + } + if (box3d_XY_axes_are_swapped(box)) { + central_corner = central_corner ^ Box3D::XYZ; + } + */ + + Geom::Point c1(box3d_get_corner_screen(box, 1, false)); + Geom::Point c2(box3d_get_corner_screen(box, 2, false)); + Geom::Point c7(box3d_get_corner_screen(box, 7, false)); + + Geom::Point corner1(c1[Geom::X], c1[Geom::Y]); + Geom::Point corner2(c2[Geom::X], c2[Geom::Y]); + Geom::Point corner7(c7[Geom::X], c7[Geom::Y]); + // FIXME: At present we don't use the information about central_corner computed above. + switch (central_axis) { + case Box3D::Y: + if (!box3d_half_line_crosses_joining_line(vpz, vpy, corner3, corner2)) { + box3d_aux_set_z_orders (z_orders, 2, 3, 1, 5, 0, 4); + } else { + // degenerate case + box3d_aux_set_z_orders (z_orders, 2, 1, 3, 0, 5, 4); + } + break; + + case Box3D::Z: + if (box3d_half_line_crosses_joining_line(vpx, vpz, corner3, corner1)) { + // degenerate case + box3d_aux_set_z_orders (z_orders, 2, 0, 1, 4, 3, 5); + } else if (box3d_half_line_crosses_joining_line(vpx, vpy, corner3, corner7)) { + // degenerate case + box3d_aux_set_z_orders (z_orders, 2, 1, 0, 5, 3, 4); + } else { + box3d_aux_set_z_orders (z_orders, 2, 1, 0, 3, 4, 5); + } + break; + + case Box3D::X: + if (box3d_half_line_crosses_joining_line(vpz, vpx, corner3, corner1)) { + // degenerate case + box3d_aux_set_z_orders (z_orders, 2, 1, 0, 4, 5, 3); + } else { + box3d_aux_set_z_orders (z_orders, 2, 4, 0, 5, 1, 3); + } + break; + + case Box3D::NONE: + box3d_aux_set_z_orders (z_orders, 2, 3, 4, 1, 0, 5); + break; + + default: + g_assert_not_reached(); + break; + } // end default case + } + + // TODO: If there are still errors in z-orders of everted boxes, we need to choose a variable corner + // instead of the hard-coded corner #3 in the computations above + Box3D::Axis ev = box3d_everted_directions(box); + for (int i = 0; i < 3; ++i) { + if (ev & Box3D::axes[i]) { + box3d_swap_sides(z_orders, Box3D::axes[i]); + } + } + + // Check whether anything actually changed + for (int i = 0; i < 6; ++i) { + if (box->z_orders[i] != z_orders[i]) { + for (int j = i; j < 6; ++j) { + box->z_orders[j] = z_orders[j]; + } + return true; + } + } + return false; +} + +static std::map box3d_get_sides(SPBox3D *box) +{ + std::map sides; + for (auto& obj: box->children) { + Box3DSide *side = dynamic_cast(&obj); + if (side) { + sides[Box3D::face_to_int(side->getFaceId())] = side; + } + } + sides.erase(-1); + return sides; +} + + +// TODO: Check whether the box is everted in any direction and swap the sides opposite to this direction +void +box3d_set_z_orders (SPBox3D *box) { + // For efficiency reasons, we only set the new z-orders if something really changed + if (box3d_recompute_z_orders (box)) { + std::map sides = box3d_get_sides(box); + std::map::iterator side; + for (unsigned int i = 0; i < 6; ++i) { + side = sides.find(box->z_orders[i]); + if (side != sides.end()) { + ((*side).second)->lowerToBottom(); + } + } + } +} + +/* + * Auxiliary function for z-order recomputing: + * Determines whether \a pt lies in the sector formed by the two PLs from the corners with IDs + * \a i21 and \a id2 to the VP in direction \a axis. If the VP is infinite, we say that \a pt + * lies in the sector if it lies between the two (parallel) PLs. + * \ret * 0 if \a pt doesn't lie in the sector + * * 1 if \a pt lies in the sector and either VP is finite of VP is infinite and the direction + * from the edge between the two corners to \a pt points towards the VP + * * -1 otherwise + */ +// TODO: Maybe it would be useful to have a similar method for projective points pt because then we +// can use it for VPs and perhaps merge the case distinctions during z-order recomputation. +int +box3d_pt_lies_in_PL_sector (SPBox3D const *box, Geom::Point const &pt, int id1, int id2, Box3D::Axis axis) { + Persp3D *persp = box3d_get_perspective(box); + + // the two corners + Geom::Point c1(box3d_get_corner_screen(box, id1, false)); + Geom::Point c2(box3d_get_corner_screen(box, id2, false)); + + int ret = 0; + if (persp3d_VP_is_finite(persp->perspective_impl, Box3D::toProj(axis))) { + Geom::Point vp(persp3d_get_VP(persp, Box3D::toProj(axis)).affine()); + Geom::Point v1(c1 - vp); + Geom::Point v2(c2 - vp); + Geom::Point w(pt - vp); + ret = static_cast(Box3D::lies_in_sector(v1, v2, w)); + } else { + Box3D::PerspectiveLine pl1(c1, Box3D::toProj(axis), persp); + Box3D::PerspectiveLine pl2(c2, Box3D::toProj(axis), persp); + if (pl1.lie_on_same_side(pt, c2) && pl2.lie_on_same_side(pt, c1)) { + // test whether pt lies "towards" or "away from" the VP + Box3D::Line edge(c1,c2); + Geom::Point c3(box3d_get_corner_screen(box, id1 ^ axis, false)); + if (edge.lie_on_same_side(pt, c3)) { + ret = 1; + } else { + ret = -1; + } + } + } + return ret; +} + +int +box3d_VP_lies_in_PL_sector (SPBox3D const *box, Proj::Axis vpdir, int id1, int id2, Box3D::Axis axis) { + Persp3D *persp = box3d_get_perspective(box); + + if (!persp3d_VP_is_finite(persp->perspective_impl, vpdir)) { + return 0; + } else { + return box3d_pt_lies_in_PL_sector(box, persp3d_get_VP(persp, vpdir).affine(), id1, id2, axis); + } +} + +/* swap the coordinates of corner0 and corner7 along the specified axis */ +static void +box3d_swap_coords(SPBox3D *box, Proj::Axis axis, bool smaller = true) { + box->orig_corner0.normalize(); + box->orig_corner7.normalize(); + if ((box->orig_corner0[axis] < box->orig_corner7[axis]) != smaller) { + double tmp = box->orig_corner0[axis]; + box->orig_corner0[axis] = box->orig_corner7[axis]; + box->orig_corner7[axis] = tmp; + } + // Should we also swap the coordinates of save_corner0 and save_corner7? +} + +/* ensure that the coordinates of corner0 and corner7 are in the correct order (to prevent everted boxes) */ +void +box3d_relabel_corners(SPBox3D *box) { + box3d_swap_coords(box, Proj::X, false); + box3d_swap_coords(box, Proj::Y, false); + box3d_swap_coords(box, Proj::Z, true); +} + +static void +box3d_check_for_swapped_coords(SPBox3D *box, Proj::Axis axis, bool smaller) { + box->orig_corner0.normalize(); + box->orig_corner7.normalize(); + + if ((box->orig_corner0[axis] < box->orig_corner7[axis]) != smaller) { + box->swapped = (Box3D::Axis) (box->swapped | Proj::toAffine(axis)); + } else { + box->swapped = (Box3D::Axis) (box->swapped & ~Proj::toAffine(axis)); + } +} + +static void +box3d_exchange_coords(SPBox3D *box) { + box->orig_corner0.normalize(); + box->orig_corner7.normalize(); + + for (int i = 0; i < 3; ++i) { + if (box->swapped & Box3D::axes[i]) { + double tmp = box->orig_corner0[i]; + box->orig_corner0[i] = box->orig_corner7[i]; + box->orig_corner7[i] = tmp; + } + } +} + +void +box3d_check_for_swapped_coords(SPBox3D *box) { + box3d_check_for_swapped_coords(box, Proj::X, false); + box3d_check_for_swapped_coords(box, Proj::Y, false); + box3d_check_for_swapped_coords(box, Proj::Z, true); + + box3d_exchange_coords(box); +} + +static void box3d_extract_boxes_rec(SPObject *obj, std::list &boxes) { + SPBox3D *box = dynamic_cast(obj); + if (box) { + boxes.push_back(box); + } else if (dynamic_cast(obj)) { + for (auto& child: obj->children) { + box3d_extract_boxes_rec(&child, boxes); + } + } +} + +std::list +box3d_extract_boxes(SPObject *obj) { + std::list boxes; + box3d_extract_boxes_rec(obj, boxes); + return boxes; +} + +Persp3D * +box3d_get_perspective(SPBox3D const *box) { + return box->persp_ref->getObject(); +} + +void +box3d_switch_perspectives(SPBox3D *box, Persp3D *old_persp, Persp3D *new_persp, bool recompute_corners) { + if (recompute_corners) { + box->orig_corner0.normalize(); + box->orig_corner7.normalize(); + double z0 = box->orig_corner0[Proj::Z]; + double z7 = box->orig_corner7[Proj::Z]; + Geom::Point corner0_screen = box3d_get_corner_screen(box, 0, false); + Geom::Point corner7_screen = box3d_get_corner_screen(box, 7, false); + + box->orig_corner0 = new_persp->perspective_impl->tmat.preimage(corner0_screen, z0, Proj::Z); + box->orig_corner7 = new_persp->perspective_impl->tmat.preimage(corner7_screen, z7, Proj::Z); + } + + persp3d_remove_box (old_persp, box); + persp3d_add_box (new_persp, box); + + Glib::ustring href = "#"; + href += new_persp->getId(); + box->setAttribute("inkscape:perspectiveID", href.c_str()); +} + +/* Converts the 3D box to an ordinary SPGroup, adds it to the XML tree at the same position as + the original box and deletes the latter */ +SPGroup *box3d_convert_to_group(SPBox3D *box) +{ + SPDocument *doc = box->document; + Inkscape::XML::Document *xml_doc = doc->getReprDoc(); + + // remember position of the box + int pos = box->getPosition(); + + // remember important attributes + gchar const *id = box->getAttribute("id"); + gchar const *style = box->getAttribute("style"); + gchar const *mask = box->getAttribute("mask"); + gchar const *clip_path = box->getAttribute("clip-path"); + + // create a new group and add the sides (converted to ordinary paths) as its children + Inkscape::XML::Node *grepr = xml_doc->createElement("svg:g"); + + for (auto& obj: box->children) { + Box3DSide *side = dynamic_cast(&obj); + if (side) { + Inkscape::XML::Node *repr = box3d_side_convert_to_path(side); + grepr->appendChild(repr); + } else { + g_warning("Non-side item encountered as child of a 3D box."); + } + } + + // add the new group to the box's parent and set remembered position + SPObject *parent = box->parent; + parent->appendChild(grepr); + grepr->setPosition(pos); + grepr->setAttribute("style", style); + if (mask) + grepr->setAttribute("mask", mask); + if (clip_path) + grepr->setAttribute("clip-path", clip_path); + + box->deleteObject(true); + + grepr->setAttribute("id", id); + + SPGroup *group = dynamic_cast(doc->getObjectByRepr(grepr)); + g_assert(group != NULL); + return group; +} + +const char *SPBox3D::displayName() const { + return _("3D Box"); +} + +gchar *SPBox3D::description() const { + // We could put more details about the 3d box here + return g_strdup(""); +} + +static inline void +box3d_push_back_corner_pair(SPBox3D const *box, std::list > &pts, int c1, int c2) { + pts.push_back(std::make_pair(box3d_get_corner_screen(box, c1, false), + box3d_get_corner_screen(box, c2, false))); +} + +void SPBox3D::convert_to_guides() const { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + if (!prefs->getBool("/tools/shapes/3dbox/convertguides", true)) { + this->convert_to_guides(); + return; + } + + std::list > pts; + + /* perspective lines in X direction */ + box3d_push_back_corner_pair(this, pts, 0, 1); + box3d_push_back_corner_pair(this, pts, 2, 3); + box3d_push_back_corner_pair(this, pts, 4, 5); + box3d_push_back_corner_pair(this, pts, 6, 7); + + /* perspective lines in Y direction */ + box3d_push_back_corner_pair(this, pts, 0, 2); + box3d_push_back_corner_pair(this, pts, 1, 3); + box3d_push_back_corner_pair(this, pts, 4, 6); + box3d_push_back_corner_pair(this, pts, 5, 7); + + /* perspective lines in Z direction */ + box3d_push_back_corner_pair(this, pts, 0, 4); + box3d_push_back_corner_pair(this, pts, 1, 5); + box3d_push_back_corner_pair(this, pts, 2, 6); + box3d_push_back_corner_pair(this, pts, 3, 7); + + sp_guide_pt_pairs_to_guides(this->document, pts); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/box3d.h b/src/object/box3d.h new file mode 100644 index 000000000..85f481e5b --- /dev/null +++ b/src/object/box3d.h @@ -0,0 +1,104 @@ +#ifndef SEEN_SP_BOX3D_H +#define SEEN_SP_BOX3D_H + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * Maximilian Albert + * Abhishek Sharma + * Jon A. Cruz box3d_extract_boxes(SPObject *obj); + +Persp3D *box3d_get_perspective(SPBox3D const *box); +void box3d_switch_perspectives(SPBox3D *box, Persp3D *old_persp, Persp3D *new_persp, bool recompute_corners = false); + +SPGroup *box3d_convert_to_group(SPBox3D *box); + + +#endif // SEEN_SP_BOX3D_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/color-profile.cpp b/src/object/color-profile.cpp new file mode 100644 index 000000000..7bdde9b6d --- /dev/null +++ b/src/object/color-profile.cpp @@ -0,0 +1,1376 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#define noDEBUG_LCMS + +#include + +#include +#include +#include + +#ifdef DEBUG_LCMS +#include +#endif // DEBUG_LCMS + +#include +#include +#include +#include + +#ifdef WIN32 +#ifndef _WIN32_WINDOWS // Allow use of features specific to Windows 98 or later. Required for correctly including icm.h +#define _WIN32_WINDOWS 0x0410 +#endif +#include +#endif + +#if HAVE_LIBLCMS2 +# include +#elif HAVE_LIBLCMS1 +# include +#endif // HAVE_LIBLCMS2 + +#include "xml/repr.h" +#include "color.h" +#include "color-profile.h" +#include "cms-system.h" +#include "color-profile-cms-fns.h" +#include "attributes.h" +#include "inkscape.h" +#include "document.h" +#include "preferences.h" +#include +#include +#include "uri.h" + +#ifdef WIN32 +#include +#endif // WIN32 + +using Inkscape::ColorProfile; +using Inkscape::ColorProfileImpl; + +namespace +{ +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) +cmsHPROFILE getSystemProfileHandle(); +cmsHPROFILE getProofProfileHandle(); +void loadProfiles(); +Glib::ustring getNameFromProfile(cmsHPROFILE profile); +#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) +} + +#ifdef DEBUG_LCMS +extern guint update_in_progress; +#define DEBUG_MESSAGE_SCISLAC(key, ...) \ +{\ + Inkscape::Preferences *prefs = Inkscape::Preferences::get();\ + bool dump = prefs->getBool(Glib::ustring("/options/scislac/") + #key);\ + bool dumpD = prefs->getBool(Glib::ustring("/options/scislac/") + #key"D");\ + bool dumpD2 = prefs->getBool(Glib::ustring("/options/scislac/") + #key"D2");\ + dumpD &= ( (update_in_progress == 0) || dumpD2 );\ + if ( dump )\ + {\ + g_message( __VA_ARGS__ );\ +\ + }\ + if ( dumpD )\ + {\ + GtkWidget *dialog = gtk_message_dialog_new(NULL,\ + GTK_DIALOG_DESTROY_WITH_PARENT, \ + GTK_MESSAGE_INFO, \ + GTK_BUTTONS_OK, \ + __VA_ARGS__ \ + );\ + g_signal_connect_swapped(dialog, "response",\ + G_CALLBACK(gtk_widget_destroy), \ + dialog); \ + gtk_widget_show_all( dialog );\ + }\ +} + + +#define DEBUG_MESSAGE(key, ...)\ +{\ + g_message( __VA_ARGS__ );\ +} + +#else +#define DEBUG_MESSAGE_SCISLAC(key, ...) +#define DEBUG_MESSAGE(key, ...) +#endif // DEBUG_LCMS + +namespace Inkscape { + +class ColorProfileImpl { +public: +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + static cmsHPROFILE _sRGBProf; + static cmsHPROFILE _NullProf; +#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + + ColorProfileImpl(); + +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + static cmsUInt32Number _getInputFormat( cmsColorSpaceSignature space ); + + static cmsHPROFILE getNULLProfile(); + static cmsHPROFILE getSRGBProfile(); + + void _clearProfile(); + + cmsHPROFILE _profHandle; + cmsProfileClassSignature _profileClass; + cmsColorSpaceSignature _profileSpace; + cmsHTRANSFORM _transf; + cmsHTRANSFORM _revTransf; + cmsHTRANSFORM _gamutTransf; +#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) +}; + +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) +cmsColorSpaceSignature asICColorSpaceSig(ColorSpaceSig const & sig) +{ + return ColorSpaceSigWrapper(sig); +} + +cmsProfileClassSignature asICColorProfileClassSig(ColorProfileClassSig const & sig) +{ + return ColorProfileClassSigWrapper(sig); +} +#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + +} // namespace Inkscape + +ColorProfileImpl::ColorProfileImpl() +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + : + _profHandle(0), + _profileClass(cmsSigInputClass), + _profileSpace(cmsSigRgbData), + _transf(0), + _revTransf(0), + _gamutTransf(0) +#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) +{ +} + +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + +cmsHPROFILE ColorProfileImpl::_sRGBProf = 0; + +cmsHPROFILE ColorProfileImpl::getSRGBProfile() { + if ( !_sRGBProf ) { + _sRGBProf = cmsCreate_sRGBProfile(); + } + return ColorProfileImpl::_sRGBProf; +} + +cmsHPROFILE ColorProfileImpl::_NullProf = 0; + +cmsHPROFILE ColorProfileImpl::getNULLProfile() { + if ( !_NullProf ) { + _NullProf = cmsCreateNULLProfile(); + } + return _NullProf; +} + +#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + +ColorProfile::FilePlusHome::FilePlusHome(Glib::ustring filename, bool isInHome) : filename(filename), isInHome(isInHome) { +} + +ColorProfile::FilePlusHome::FilePlusHome(const ColorProfile::FilePlusHome &filePlusHome) : FilePlusHome(filePlusHome.filename, filePlusHome.isInHome) { +} + +bool ColorProfile::FilePlusHome::operator<(FilePlusHome const &other) const { + // if one is from home folder, other from global folder, sort home folder first. cf bug 1457126 + bool result; + if (this->isInHome != other.isInHome) result = this->isInHome; + else result = this->filename < other.filename; + return result; +} + +ColorProfile::FilePlusHomeAndName::FilePlusHomeAndName(ColorProfile::FilePlusHome filePlusHome, Glib::ustring name) + : FilePlusHome(filePlusHome), name(name) { +} + +bool ColorProfile::FilePlusHomeAndName::operator<(ColorProfile::FilePlusHomeAndName const &other) const { + bool result; + if (this->isInHome != other.isInHome) result = this->isInHome; + else result = this->name < other.name; + return result; +} + + +ColorProfile::ColorProfile() : SPObject() { + this->impl = new ColorProfileImpl(); + + this->href = 0; + this->local = 0; + this->name = 0; + this->intentStr = 0; + this->rendering_intent = Inkscape::RENDERING_INTENT_UNKNOWN; +} + +ColorProfile::~ColorProfile() { +} + +bool ColorProfile::operator<(ColorProfile const &other) const { + gchar *a_name_casefold = g_utf8_casefold(this->name, -1 ); + gchar *b_name_casefold = g_utf8_casefold(other.name, -1 ); + int result = g_strcmp0(a_name_casefold, b_name_casefold); + g_free(a_name_casefold); + g_free(b_name_casefold); + return result < 0; +} + +/** + * Callback: free object + */ +void ColorProfile::release() { + // Unregister ourselves + if ( this->document ) { + this->document->removeResource("iccprofile", this); + } + + if ( this->href ) { + g_free( this->href ); + this->href = 0; + } + + if ( this->local ) { + g_free( this->local ); + this->local = 0; + } + + if ( this->name ) { + g_free( this->name ); + this->name = 0; + } + + if ( this->intentStr ) { + g_free( this->intentStr ); + this->intentStr = 0; + } + +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + this->impl->_clearProfile(); +#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + + delete this->impl; + this->impl = 0; +} + +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) +void ColorProfileImpl::_clearProfile() +{ + _profileSpace = cmsSigRgbData; + + if ( _transf ) { + cmsDeleteTransform( _transf ); + _transf = 0; + } + if ( _revTransf ) { + cmsDeleteTransform( _revTransf ); + _revTransf = 0; + } + if ( _gamutTransf ) { + cmsDeleteTransform( _gamutTransf ); + _gamutTransf = 0; + } + if ( _profHandle ) { + cmsCloseProfile( _profHandle ); + _profHandle = 0; + } +} +#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + +/** + * Callback: set attributes from associated repr. + */ +void ColorProfile::build(SPDocument *document, Inkscape::XML::Node *repr) { + g_assert(this->href == 0); + g_assert(this->local == 0); + g_assert(this->name == 0); + g_assert(this->intentStr == 0); + + SPObject::build(document, repr); + + this->readAttr( "xlink:href" ); + this->readAttr( "id" ); + this->readAttr( "local" ); + this->readAttr( "name" ); + this->readAttr( "rendering-intent" ); + + // Register + if ( document ) { + document->addResource( "iccprofile", this ); + } +} + + +/** + * Callback: set attribute. + */ +void ColorProfile::set(unsigned key, gchar const *value) { + switch (key) { + case SP_ATTR_XLINK_HREF: + if ( this->href ) { + g_free( this->href ); + this->href = 0; + } + if ( value ) { + this->href = g_strdup( value ); + if ( *this->href ) { +#if HAVE_LIBLCMS1 + cmsErrorAction( LCMS_ERROR_SHOW ); +#endif +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + + // TODO open filename and URIs properly + //FILE* fp = fopen_utf8name( filename, "r" ); + //LCMSAPI cmsHPROFILE LCMSEXPORT cmsOpenProfileFromMem(LPVOID MemPtr, cmsUInt32Number dwSize); + + // Try to open relative + SPDocument *doc = this->document; + if (!doc) { + doc = SP_ACTIVE_DOCUMENT; + g_warning("this has no document. using active"); + } + //# 1. Get complete URI of document + gchar const *docbase = doc->getURI(); + + gchar* escaped = g_uri_escape_string(this->href, "!*'();@=+$,/?#", TRUE); + + //g_message("docbase:%s\n", docbase); + //org::w3c::dom::URI docUri(docbase); + Inkscape::URI docUri(""); + if (docbase) { // The file has already been saved + docUri = Inkscape::URI::from_native_filename(docbase); + } + + //# 2. Get href of icc file. we don't care if it's rel or abs + //org::w3c::dom::URI hrefUri(escaped); + Inkscape::URI hrefUri(escaped); + //# 3. Resolve the href according the docBase. This follows + // the w3c specs. All absolute and relative issues are considered + std::string fullpath = hrefUri.getFullPath(docUri.getFullPath("")); + + gchar* fullname = g_uri_unescape_string(fullpath.c_str(), ""); + this->impl->_clearProfile(); + this->impl->_profHandle = cmsOpenProfileFromFile( fullname, "r" ); + if ( this->impl->_profHandle ) { + this->impl->_profileSpace = cmsGetColorSpace( this->impl->_profHandle ); + this->impl->_profileClass = cmsGetDeviceClass( this->impl->_profHandle ); + } + DEBUG_MESSAGE( lcmsOne, "cmsOpenProfileFromFile( '%s'...) = %p", fullname, (void*)this->impl->_profHandle ); + g_free(escaped); + escaped = 0; + g_free(fullname); +#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + } + } + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_LOCAL: + if ( this->local ) { + g_free( this->local ); + this->local = 0; + } + this->local = g_strdup( value ); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_NAME: + if ( this->name ) { + g_free( this->name ); + this->name = 0; + } + this->name = g_strdup( value ); + DEBUG_MESSAGE( lcmsTwo, " name set to '%s'", this->name ); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_RENDERING_INTENT: + if ( this->intentStr ) { + g_free( this->intentStr ); + this->intentStr = 0; + } + this->intentStr = g_strdup( value ); + + if ( value ) { + if ( strcmp( value, "auto" ) == 0 ) { + this->rendering_intent = RENDERING_INTENT_AUTO; + } else if ( strcmp( value, "perceptual" ) == 0 ) { + this->rendering_intent = RENDERING_INTENT_PERCEPTUAL; + } else if ( strcmp( value, "relative-colorimetric" ) == 0 ) { + this->rendering_intent = RENDERING_INTENT_RELATIVE_COLORIMETRIC; + } else if ( strcmp( value, "saturation" ) == 0 ) { + this->rendering_intent = RENDERING_INTENT_SATURATION; + } else if ( strcmp( value, "absolute-colorimetric" ) == 0 ) { + this->rendering_intent = RENDERING_INTENT_ABSOLUTE_COLORIMETRIC; + } else { + this->rendering_intent = RENDERING_INTENT_UNKNOWN; + } + } else { + this->rendering_intent = RENDERING_INTENT_UNKNOWN; + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + default: + SPObject::set(key, value); + break; + } +} + +/** + * Callback: write attributes to associated repr. + */ +Inkscape::XML::Node* ColorProfile::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:color-profile"); + } + + if ( (flags & SP_OBJECT_WRITE_ALL) || this->href ) { + repr->setAttribute( "xlink:href", this->href ); + } + + if ( (flags & SP_OBJECT_WRITE_ALL) || this->local ) { + repr->setAttribute( "local", this->local ); + } + + if ( (flags & SP_OBJECT_WRITE_ALL) || this->name ) { + repr->setAttribute( "name", this->name ); + } + + if ( (flags & SP_OBJECT_WRITE_ALL) || this->intentStr ) { + repr->setAttribute( "rendering-intent", this->intentStr ); + } + + SPObject::write(xml_doc, repr, flags); + + return repr; +} + + +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + +struct MapMap { + cmsColorSpaceSignature space; + cmsUInt32Number inForm; +}; + +cmsUInt32Number ColorProfileImpl::_getInputFormat( cmsColorSpaceSignature space ) +{ + MapMap possible[] = { + {cmsSigXYZData, TYPE_XYZ_16}, + {cmsSigLabData, TYPE_Lab_16}, + //cmsSigLuvData + {cmsSigYCbCrData, TYPE_YCbCr_16}, + {cmsSigYxyData, TYPE_Yxy_16}, + {cmsSigRgbData, TYPE_RGB_16}, + {cmsSigGrayData, TYPE_GRAY_16}, + {cmsSigHsvData, TYPE_HSV_16}, + {cmsSigHlsData, TYPE_HLS_16}, + {cmsSigCmykData, TYPE_CMYK_16}, + {cmsSigCmyData, TYPE_CMY_16}, + }; + + int index = 0; + for ( guint i = 0; i < G_N_ELEMENTS(possible); i++ ) { + if ( possible[i].space == space ) { + index = i; + break; + } + } + + return possible[index].inForm; +} + +static int getLcmsIntent( guint svgIntent ) +{ + int intent = INTENT_PERCEPTUAL; + switch ( svgIntent ) { + case Inkscape::RENDERING_INTENT_RELATIVE_COLORIMETRIC: + intent = INTENT_RELATIVE_COLORIMETRIC; + break; + case Inkscape::RENDERING_INTENT_SATURATION: + intent = INTENT_SATURATION; + break; + case Inkscape::RENDERING_INTENT_ABSOLUTE_COLORIMETRIC: + intent = INTENT_ABSOLUTE_COLORIMETRIC; + break; + case Inkscape::RENDERING_INTENT_PERCEPTUAL: + case Inkscape::RENDERING_INTENT_UNKNOWN: + case Inkscape::RENDERING_INTENT_AUTO: + default: + intent = INTENT_PERCEPTUAL; + } + return intent; +} + +static SPObject* bruteFind( SPDocument* document, gchar const* name ) +{ + SPObject* result = 0; + std::vector current = document->getResourceList("iccprofile"); + for (std::vector::const_iterator it = current.begin(); (!result) && (it != current.end()); ++it) { + if ( IS_COLORPROFILE(*it) ) { + ColorProfile* prof = COLORPROFILE(*it); + if ( prof ) { + if ( prof->name && (strcmp(prof->name, name) == 0) ) { + result = SP_OBJECT(*it); + break; + } + } + } + } + + return result; +} + +cmsHPROFILE Inkscape::CMSSystem::getHandle( SPDocument* document, guint* intent, gchar const* name ) +{ + cmsHPROFILE prof = 0; + + SPObject* thing = bruteFind( document, name ); + if ( thing ) { + prof = COLORPROFILE(thing)->impl->_profHandle; + } + + if ( intent ) { + *intent = thing ? COLORPROFILE(thing)->rendering_intent : (guint)RENDERING_INTENT_UNKNOWN; + } + + DEBUG_MESSAGE( lcmsThree, " queried for profile of '%s'. Returning %p with intent of %d", name, prof, (intent? *intent:0) ); + + return prof; +} + +Inkscape::ColorSpaceSig ColorProfile::getColorSpace() const { + return ColorSpaceSigWrapper(impl->_profileSpace); +} + +Inkscape::ColorProfileClassSig ColorProfile::getProfileClass() const { + return ColorProfileClassSigWrapper(impl->_profileClass); +} + +cmsHTRANSFORM ColorProfile::getTransfToSRGB8() +{ + if ( !impl->_transf && impl->_profHandle ) { + int intent = getLcmsIntent(rendering_intent); + impl->_transf = cmsCreateTransform( impl->_profHandle, ColorProfileImpl::_getInputFormat(impl->_profileSpace), ColorProfileImpl::getSRGBProfile(), TYPE_RGBA_8, intent, 0 ); + } + return impl->_transf; +} + +cmsHTRANSFORM ColorProfile::getTransfFromSRGB8() +{ + if ( !impl->_revTransf && impl->_profHandle ) { + int intent = getLcmsIntent(rendering_intent); + impl->_revTransf = cmsCreateTransform( ColorProfileImpl::getSRGBProfile(), TYPE_RGBA_8, impl->_profHandle, ColorProfileImpl::_getInputFormat(impl->_profileSpace), intent, 0 ); + } + return impl->_revTransf; +} + +cmsHTRANSFORM ColorProfile::getTransfGamutCheck() +{ + if ( !impl->_gamutTransf ) { + impl->_gamutTransf = cmsCreateProofingTransform(ColorProfileImpl::getSRGBProfile(), + TYPE_BGRA_8, + ColorProfileImpl::getNULLProfile(), + TYPE_GRAY_8, + impl->_profHandle, + INTENT_RELATIVE_COLORIMETRIC, + INTENT_RELATIVE_COLORIMETRIC, + (cmsFLAGS_GAMUTCHECK | cmsFLAGS_SOFTPROOFING)); + } + return impl->_gamutTransf; +} + +bool ColorProfile::GamutCheck(SPColor color) +{ + guint32 val = color.toRGBA32(0); + +#if HAVE_LIBLCMS1 + int alarm_r = 0; + int alarm_g = 0; + int alarm_b = 0; + cmsGetAlarmCodes(&alarm_r, &alarm_g, &alarm_b); + cmsSetAlarmCodes(255, 255, 255); +#elif HAVE_LIBLCMS2 + cmsUInt16Number oldAlarmCodes[cmsMAXCHANNELS] = {0}; + cmsGetAlarmCodes(oldAlarmCodes); + cmsUInt16Number newAlarmCodes[cmsMAXCHANNELS] = {0}; + newAlarmCodes[0] = ~0; + cmsSetAlarmCodes(newAlarmCodes); +#endif // HAVE_LIBLCMS1 + + cmsUInt8Number outofgamut = 0; + guchar check_color[4] = { + static_cast(SP_RGBA32_R_U(val)), + static_cast(SP_RGBA32_G_U(val)), + static_cast(SP_RGBA32_B_U(val)), + 255}; + + cmsHTRANSFORM gamutCheck = ColorProfile::getTransfGamutCheck(); + if (gamutCheck) { + cmsDoTransform(gamutCheck, &check_color, &outofgamut, 1); + } + +#if HAVE_LIBLCMS1 + cmsSetAlarmCodes(alarm_r, alarm_g, alarm_b); +#elif HAVE_LIBLCMS2 + cmsSetAlarmCodes(oldAlarmCodes); +#endif // HAVE_LIBLCMS1 + + return (outofgamut != 0); +} + +class ProfileInfo +{ +public: + ProfileInfo( cmsHPROFILE prof, Glib::ustring const & path ); + + Glib::ustring const& getName() {return _name;} + Glib::ustring const& getPath() {return _path;} + cmsColorSpaceSignature getSpace() {return _profileSpace;} + cmsProfileClassSignature getClass() {return _profileClass;} + +private: + Glib::ustring _path; + Glib::ustring _name; + cmsColorSpaceSignature _profileSpace; + cmsProfileClassSignature _profileClass; +}; + +ProfileInfo::ProfileInfo( cmsHPROFILE prof, Glib::ustring const & path ) : + _path( path ), + _name( getNameFromProfile(prof) ), + _profileSpace( cmsGetColorSpace( prof ) ), + _profileClass( cmsGetDeviceClass( prof ) ) +{ +} + + + +static std::vector knownProfiles; + +std::vector Inkscape::CMSSystem::getDisplayNames() +{ + loadProfiles(); + std::vector result; + + for ( std::vector::iterator it = knownProfiles.begin(); it != knownProfiles.end(); ++it ) { + if ( it->getClass() == cmsSigDisplayClass && it->getSpace() == cmsSigRgbData ) { + result.push_back( it->getName() ); + } + } + std::sort(result.begin(), result.end()); + + return result; +} + +std::vector Inkscape::CMSSystem::getSoftproofNames() +{ + loadProfiles(); + std::vector result; + + for ( std::vector::iterator it = knownProfiles.begin(); it != knownProfiles.end(); ++it ) { + if ( it->getClass() == cmsSigOutputClass ) { + result.push_back( it->getName() ); + } + } + std::sort(result.begin(), result.end()); + + return result; +} + +Glib::ustring Inkscape::CMSSystem::getPathForProfile(Glib::ustring const& name) +{ + loadProfiles(); + Glib::ustring result; + + for ( std::vector::iterator it = knownProfiles.begin(); it != knownProfiles.end(); ++it ) { + if ( name == it->getName() ) { + result = it->getPath(); + break; + } + } + + return result; +} + +void Inkscape::CMSSystem::doTransform(cmsHTRANSFORM transform, void *inBuf, void *outBuf, unsigned int size) +{ + cmsDoTransform(transform, inBuf, outBuf, size); +} + +bool Inkscape::CMSSystem::isPrintColorSpace(ColorProfile const *profile) +{ + bool isPrint = false; + if ( profile ) { + ColorSpaceSigWrapper colorspace = profile->getColorSpace(); + isPrint = (colorspace == cmsSigCmykData) || (colorspace == cmsSigCmyData); + } + return isPrint; +} + +gint Inkscape::CMSSystem::getChannelCount(ColorProfile const *profile) +{ + gint count = 0; + if ( profile ) { +#if HAVE_LIBLCMS1 + count = _cmsChannelsOf( asICColorSpaceSig(profile->getColorSpace()) ); +#elif HAVE_LIBLCMS2 + count = cmsChannelsOf( asICColorSpaceSig(profile->getColorSpace()) ); +#endif + } + return count; +} + +#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + +// the bool return value tells if it's a user's directory or a system location +// note that this will treat places under $HOME as system directories when they are found via $XDG_DATA_DIRS +std::set ColorProfile::getBaseProfileDirs() { +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + static bool warnSet = false; + if (!warnSet) { +#if HAVE_LIBLCMS1 + cmsErrorAction( LCMS_ERROR_SHOW ); +#endif + warnSet = true; + } +#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + std::set sources; + + // first try user's local dir + gchar* path = g_build_filename(g_get_user_data_dir(), "color", "icc", NULL); + sources.insert(FilePlusHome(path, true)); + g_free(path); + + // search colord ICC store paths + // (see https://github.com/hughsie/colord/blob/fe10f76536bb27614ced04e0ff944dc6fb4625c0/lib/colord/cd-icc-store.c#L590) + + // user store + path = g_build_filename(g_get_user_data_dir(), "icc", NULL); + sources.insert(FilePlusHome(path, true)); + g_free(path); + + path = g_build_filename(g_get_home_dir(), ".color", "icc", NULL); + sources.insert(FilePlusHome(path, true)); + g_free(path); + + // machine store + sources.insert(FilePlusHome("/var/lib/color/icc", false)); + sources.insert(FilePlusHome("/var/lib/colord/icc", false)); + + const gchar* const * dataDirs = g_get_system_data_dirs(); + for ( int i = 0; dataDirs[i]; i++ ) { + gchar* path = g_build_filename(dataDirs[i], "color", "icc", NULL); + sources.insert(FilePlusHome(path, false)); + g_free(path); + } + + // On OS X: + { + sources.insert(FilePlusHome("/System/Library/ColorSync/Profiles", false)); + sources.insert(FilePlusHome("/Library/ColorSync/Profiles", false)); + + gchar *path = g_build_filename(g_get_home_dir(), "Library", "ColorSync", "Profiles", NULL); + sources.insert(FilePlusHome(path, true)); + g_free(path); + } + +#ifdef WIN32 + wchar_t pathBuf[MAX_PATH + 1]; + pathBuf[0] = 0; + DWORD pathSize = sizeof(pathBuf); + g_assert(sizeof(wchar_t) == sizeof(gunichar2)); + if ( GetColorDirectoryW( NULL, pathBuf, &pathSize ) ) { + gchar * utf8Path = g_utf16_to_utf8( (gunichar2*)(&pathBuf[0]), -1, NULL, NULL, NULL ); + if ( !g_utf8_validate(utf8Path, -1, NULL) ) { + g_warning( "GetColorDirectoryW() resulted in invalid UTF-8" ); + } else { + sources.insert(FilePlusHome(utf8Path, false)); + } + g_free( utf8Path ); + } +#endif // WIN32 + + return sources; +} + +static bool isIccFile( gchar const *filepath ) +{ + bool isIccFile = false; + GStatBuf st; + if ( g_stat(filepath, &st) == 0 && (st.st_size > 128) ) { + //0-3 == size + //36-39 == 'acsp' 0x61637370 + int fd = g_open( filepath, O_RDONLY, S_IRWXU); + if ( fd != -1 ) { + guchar scratch[40] = {0}; + size_t len = sizeof(scratch); + + //size_t left = 40; + ssize_t got = read(fd, scratch, len); + if ( got != -1 ) { + size_t calcSize = (scratch[0] << 24) | (scratch[1] << 16) | (scratch[2] << 8) | scratch[3]; + if ( calcSize > 128 && calcSize <= static_cast(st.st_size) ) { + isIccFile = (scratch[36] == 'a') && (scratch[37] == 'c') && (scratch[38] == 's') && (scratch[39] == 'p'); + } + } + + close(fd); +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + if (isIccFile) { + cmsHPROFILE prof = cmsOpenProfileFromFile( filepath, "r" ); + if ( prof ) { + cmsProfileClassSignature profClass = cmsGetDeviceClass(prof); + if ( profClass == cmsSigNamedColorClass ) { + isIccFile = false; // Ignore named color profiles for now. + } + cmsCloseProfile( prof ); + } + } +#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + } + } + return isIccFile; +} + +std::set ColorProfile::getProfileFiles() +{ + std::set files; + using Inkscape::IO::Resource::get_filenames; + + for (auto &path: ColorProfile::getBaseProfileDirs()) { + for(auto &filename: get_filenames(path.filename, {".icc", ".icm"})) { + if ( isIccFile(filename.c_str()) ) { + files.insert(FilePlusHome(filename, path.isInHome)); + } + } + } + + return files; +} + +std::set ColorProfile::getProfileFilesWithNames() +{ + std::set result; + +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + for (auto &profile: getProfileFiles()) { + cmsHPROFILE hProfile = cmsOpenProfileFromFile(profile.filename.c_str(), "r"); + if ( hProfile ) { + Glib::ustring name = getNameFromProfile(hProfile); + result.insert( FilePlusHomeAndName(profile, name) ); + cmsCloseProfile(hProfile); + } + } +#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + + return result; +} + +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) +#if HAVE_LIBLCMS1 +int errorHandlerCB(int ErrorCode, const char *ErrorText) +{ + g_message("lcms: Error %d; %s", ErrorCode, ErrorText); + + return 1; +} +#elif HAVE_LIBLCMS2 +void errorHandlerCB(cmsContext /*contextID*/, cmsUInt32Number errorCode, char const *errorText) +{ + g_message("lcms: Error %d", errorCode); + g_message(" %p", errorText); + //g_message("lcms: Error %d; %s", errorCode, errorText); +} +#endif + +namespace +{ + +Glib::ustring getNameFromProfile(cmsHPROFILE profile) +{ + Glib::ustring nameStr; + if ( profile ) { +#if HAVE_LIBLCMS1 + gchar const *name = cmsTakeProductDesc(profile); + if ( !name ) { + name = cmsTakeProductName(profile); + } + if ( name && !g_utf8_validate(name, -1, NULL) ) { + name = _("(invalid UTF-8 string)"); + } + nameStr = (name) ? name : C_("Profile name", "None"); +#elif HAVE_LIBLCMS2 + cmsUInt32Number byteLen = cmsGetProfileInfo(profile, cmsInfoDescription, "en", "US", NULL, 0); + if (byteLen > 0) { + // TODO investigate wchar_t and cmsGetProfileInfo() + std::vector data(byteLen); + cmsUInt32Number readLen = cmsGetProfileInfoASCII(profile, cmsInfoDescription, + "en", "US", + data.data(), data.size()); + if (readLen < data.size()) { + data.resize(readLen); + } + nameStr = Glib::ustring(data.begin(), data.end()); + } + if (nameStr.empty() || !g_utf8_validate(nameStr.c_str(), -1, NULL)) { + nameStr = _("(invalid UTF-8 string)"); + } +#endif + } + return nameStr; +} + +/** + * This function loads or refreshes data in knownProfiles. + * Call it at the start of every call that requires this data. + */ +void loadProfiles() +{ + static bool error_handler_set = false; + if (!error_handler_set) { +#if HAVE_LIBLCMS1 + cmsSetErrorHandler(errorHandlerCB); +#elif HAVE_LIBLCMS2 + //cmsSetLogErrorHandler(errorHandlerCB); + //g_message("LCMS error handler set"); +#endif + error_handler_set = true; + } + + static bool profiles_searched = false; + if ( !profiles_searched ) { + knownProfiles.clear(); + + for (auto &profile: ColorProfile::getProfileFiles()) { + cmsHPROFILE prof = cmsOpenProfileFromFile( profile.filename.c_str(), "r" ); + if ( prof ) { + ProfileInfo info( prof, Glib::filename_to_utf8( profile.filename.c_str() ) ); + cmsCloseProfile( prof ); + prof = 0; + + bool sameName = false; + for(auto &knownProfile: knownProfiles) { + if ( knownProfile.getName() == info.getName() ) { + sameName = true; + break; + } + } + + if ( !sameName ) { + knownProfiles.push_back(info); + } + } + } + profiles_searched = true; + } +} +} // namespace + +static bool gamutWarn = false; + +static Gdk::RGBA lastGamutColor("#808080"); + +static bool lastBPC = false; +#if defined(cmsFLAGS_PRESERVEBLACK) +static bool lastPreserveBlack = false; +#endif // defined(cmsFLAGS_PRESERVEBLACK) +static int lastIntent = INTENT_PERCEPTUAL; +static int lastProofIntent = INTENT_PERCEPTUAL; +static cmsHTRANSFORM transf = 0; + +namespace { +cmsHPROFILE getSystemProfileHandle() +{ + static cmsHPROFILE theOne = 0; + static Glib::ustring lastURI; + + loadProfiles(); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Glib::ustring uri = prefs->getString("/options/displayprofile/uri"); + + if ( !uri.empty() ) { + if ( uri != lastURI ) { + lastURI.clear(); + if ( theOne ) { + cmsCloseProfile( theOne ); + } + if ( transf ) { + cmsDeleteTransform( transf ); + transf = 0; + } + theOne = cmsOpenProfileFromFile( uri.data(), "r" ); + if ( theOne ) { + // a display profile must have the proper stuff + cmsColorSpaceSignature space = cmsGetColorSpace(theOne); + cmsProfileClassSignature profClass = cmsGetDeviceClass(theOne); + + if ( profClass != cmsSigDisplayClass ) { + g_warning("Not a display profile"); + cmsCloseProfile( theOne ); + theOne = 0; + } else if ( space != cmsSigRgbData ) { + g_warning("Not an RGB profile"); + cmsCloseProfile( theOne ); + theOne = 0; + } else { + lastURI = uri; + } + } + } + } else if ( theOne ) { + cmsCloseProfile( theOne ); + theOne = 0; + lastURI.clear(); + if ( transf ) { + cmsDeleteTransform( transf ); + transf = 0; + } + } + + return theOne; +} + + +cmsHPROFILE getProofProfileHandle() +{ + static cmsHPROFILE theOne = 0; + static Glib::ustring lastURI; + + loadProfiles(); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool which = prefs->getBool( "/options/softproof/enable"); + Glib::ustring uri = prefs->getString("/options/softproof/uri"); + + if ( which && !uri.empty() ) { + if ( lastURI != uri ) { + lastURI.clear(); + if ( theOne ) { + cmsCloseProfile( theOne ); + } + if ( transf ) { + cmsDeleteTransform( transf ); + transf = 0; + } + theOne = cmsOpenProfileFromFile( uri.data(), "r" ); + if ( theOne ) { + // a display profile must have the proper stuff + cmsColorSpaceSignature space = cmsGetColorSpace(theOne); + cmsProfileClassSignature profClass = cmsGetDeviceClass(theOne); + + (void)space; + (void)profClass; +/* + if ( profClass != cmsSigDisplayClass ) { + g_warning("Not a display profile"); + cmsCloseProfile( theOne ); + theOne = 0; + } else if ( space != cmsSigRgbData ) { + g_warning("Not an RGB profile"); + cmsCloseProfile( theOne ); + theOne = 0; + } else { +*/ + lastURI = uri; +/* + } +*/ + } + } + } else if ( theOne ) { + cmsCloseProfile( theOne ); + theOne = 0; + lastURI.clear(); + if ( transf ) { + cmsDeleteTransform( transf ); + transf = 0; + } + } + + return theOne; +} +} // namespace + +static void free_transforms(); + +cmsHTRANSFORM Inkscape::CMSSystem::getDisplayTransform() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool fromDisplay = prefs->getBool( "/options/displayprofile/from_display"); + if ( fromDisplay ) { + if ( transf ) { + cmsDeleteTransform(transf); + transf = 0; + } + return 0; + } + + bool warn = prefs->getBool( "/options/softproof/gamutwarn"); + int intent = prefs->getIntLimited( "/options/displayprofile/intent", 0, 0, 3 ); + int proofIntent = prefs->getIntLimited( "/options/softproof/intent", 0, 0, 3 ); + bool bpc = prefs->getBool( "/options/softproof/bpc"); +#if defined(cmsFLAGS_PRESERVEBLACK) + bool preserveBlack = prefs->getBool( "/options/softproof/preserveblack"); +#endif //defined(cmsFLAGS_PRESERVEBLACK) + Glib::ustring colorStr = prefs->getString("/options/softproof/gamutcolor"); + Gdk::RGBA gamutColor( colorStr.empty() ? "#808080" : colorStr ); + + if ( (warn != gamutWarn) + || (lastIntent != intent) + || (lastProofIntent != proofIntent) + || (bpc != lastBPC) +#if defined(cmsFLAGS_PRESERVEBLACK) + || (preserveBlack != lastPreserveBlack) +#endif // defined(cmsFLAGS_PRESERVEBLACK) + || (gamutColor != lastGamutColor) + ) { + gamutWarn = warn; + free_transforms(); + lastIntent = intent; + lastProofIntent = proofIntent; + lastBPC = bpc; +#if defined(cmsFLAGS_PRESERVEBLACK) + lastPreserveBlack = preserveBlack; +#endif // defined(cmsFLAGS_PRESERVEBLACK) + lastGamutColor = gamutColor; + } + + // Fetch these now, as they might clear the transform as a side effect. + cmsHPROFILE hprof = getSystemProfileHandle(); + cmsHPROFILE proofProf = hprof ? getProofProfileHandle() : 0; + + if ( !transf ) { + if ( hprof && proofProf ) { + cmsUInt32Number dwFlags = cmsFLAGS_SOFTPROOFING; + if ( gamutWarn ) { + dwFlags |= cmsFLAGS_GAMUTCHECK; + + auto gamutColor_r = gamutColor.get_red_u(); + auto gamutColor_g = gamutColor.get_green_u(); + auto gamutColor_b = gamutColor.get_blue_u(); + +#if HAVE_LIBLCMS1 + cmsSetAlarmCodes(gamutColor_r >> 8, gamutColor_g >> 8, gamutColor_b >> 8); +#elif HAVE_LIBLCMS2 + cmsUInt16Number newAlarmCodes[cmsMAXCHANNELS] = {0}; + newAlarmCodes[0] = gamutColor_r; + newAlarmCodes[1] = gamutColor_g; + newAlarmCodes[2] = gamutColor_b; + newAlarmCodes[3] = ~0; + cmsSetAlarmCodes(newAlarmCodes); +#endif + } + if ( bpc ) { + dwFlags |= cmsFLAGS_BLACKPOINTCOMPENSATION; + } +#if defined(cmsFLAGS_PRESERVEBLACK) + if ( preserveBlack ) { + dwFlags |= cmsFLAGS_PRESERVEBLACK; + } +#endif // defined(cmsFLAGS_PRESERVEBLACK) + transf = cmsCreateProofingTransform( ColorProfileImpl::getSRGBProfile(), TYPE_BGRA_8, hprof, TYPE_BGRA_8, proofProf, intent, proofIntent, dwFlags ); + } else if ( hprof ) { + transf = cmsCreateTransform( ColorProfileImpl::getSRGBProfile(), TYPE_BGRA_8, hprof, TYPE_BGRA_8, intent, 0 ); + } + } + + return transf; +} + + +class MemProfile { +public: + MemProfile(); + ~MemProfile(); + + std::string id; + cmsHPROFILE hprof; + cmsHTRANSFORM transf; +}; + +MemProfile::MemProfile() : + id(), + hprof(0), + transf(0) +{ +} + +MemProfile::~MemProfile() +{ +} + +static std::vector perMonitorProfiles; + +void free_transforms() +{ + if ( transf ) { + cmsDeleteTransform(transf); + transf = 0; + } + + for ( auto profile : perMonitorProfiles ) { + if ( profile.transf ) { + cmsDeleteTransform(profile.transf); + profile.transf = 0; + } + } +} + +Glib::ustring Inkscape::CMSSystem::getDisplayId( int monitor ) +{ + Glib::ustring id; + + if ( monitor >= 0 && monitor < static_cast(perMonitorProfiles.size()) ) { + MemProfile& item = perMonitorProfiles[monitor]; + id = item.id; + } + + return id; +} + +Glib::ustring Inkscape::CMSSystem::setDisplayPer( gpointer buf, guint bufLen, int monitor ) +{ + while ( static_cast(perMonitorProfiles.size()) <= monitor ) { + MemProfile tmp; + perMonitorProfiles.push_back(tmp); + } + MemProfile& item = perMonitorProfiles[monitor]; + + if ( item.hprof ) { + cmsCloseProfile( item.hprof ); + item.hprof = 0; + } + + Glib::ustring id; + + if ( buf && bufLen ) { + gsize len = bufLen; // len is an inout parameter + id = Glib::Checksum::compute_checksum(Glib::Checksum::CHECKSUM_MD5, + reinterpret_cast(buf), len); + + // Note: if this is not a valid profile, item.hprof will be set to null. + item.hprof = cmsOpenProfileFromMem(buf, bufLen); + } + item.id = id; + + return id; +} + +cmsHTRANSFORM Inkscape::CMSSystem::getDisplayPer( Glib::ustring const& id ) +{ + cmsHTRANSFORM result = 0; + if ( id.empty() ) { + return 0; + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool found = false; + + for ( auto it2 = perMonitorProfiles.begin(); it2 != perMonitorProfiles.end() && !found; ++it2 ) { + if ( id == it2->id ) { + MemProfile& item = *it2; + + bool warn = prefs->getBool( "/options/softproof/gamutwarn"); + int intent = prefs->getIntLimited( "/options/displayprofile/intent", 0, 0, 3 ); + int proofIntent = prefs->getIntLimited( "/options/softproof/intent", 0, 0, 3 ); + bool bpc = prefs->getBool( "/options/softproof/bpc"); +#if defined(cmsFLAGS_PRESERVEBLACK) + bool preserveBlack = prefs->getBool( "/options/softproof/preserveblack"); +#endif //defined(cmsFLAGS_PRESERVEBLACK) + Glib::ustring colorStr = prefs->getString("/options/softproof/gamutcolor"); + Gdk::RGBA gamutColor( colorStr.empty() ? "#808080" : colorStr ); + + if ( (warn != gamutWarn) + || (lastIntent != intent) + || (lastProofIntent != proofIntent) + || (bpc != lastBPC) +#if defined(cmsFLAGS_PRESERVEBLACK) + || (preserveBlack != lastPreserveBlack) +#endif // defined(cmsFLAGS_PRESERVEBLACK) + || (gamutColor != lastGamutColor) + ) { + gamutWarn = warn; + free_transforms(); + lastIntent = intent; + lastProofIntent = proofIntent; + lastBPC = bpc; +#if defined(cmsFLAGS_PRESERVEBLACK) + lastPreserveBlack = preserveBlack; +#endif // defined(cmsFLAGS_PRESERVEBLACK) + lastGamutColor = gamutColor; + } + + // Fetch these now, as they might clear the transform as a side effect. + cmsHPROFILE proofProf = item.hprof ? getProofProfileHandle() : 0; + + if ( !item.transf ) { + if ( item.hprof && proofProf ) { + cmsUInt32Number dwFlags = cmsFLAGS_SOFTPROOFING; + if ( gamutWarn ) { + dwFlags |= cmsFLAGS_GAMUTCHECK; + auto gamutColor_r = gamutColor.get_red_u(); + auto gamutColor_g = gamutColor.get_green_u(); + auto gamutColor_b = gamutColor.get_blue_u(); + +#if HAVE_LIBLCMS1 + cmsSetAlarmCodes(gamutColor_r >> 8, gamutColor_g >> 8, gamutColor_b >> 8); +#elif HAVE_LIBLCMS2 + cmsUInt16Number newAlarmCodes[cmsMAXCHANNELS] = {0}; + newAlarmCodes[0] = gamutColor_r; + newAlarmCodes[1] = gamutColor_g; + newAlarmCodes[2] = gamutColor_b; + newAlarmCodes[3] = ~0; + cmsSetAlarmCodes(newAlarmCodes); +#endif + } + if ( bpc ) { + dwFlags |= cmsFLAGS_BLACKPOINTCOMPENSATION; + } +#if defined(cmsFLAGS_PRESERVEBLACK) + if ( preserveBlack ) { + dwFlags |= cmsFLAGS_PRESERVEBLACK; + } +#endif // defined(cmsFLAGS_PRESERVEBLACK) + item.transf = cmsCreateProofingTransform( ColorProfileImpl::getSRGBProfile(), TYPE_BGRA_8, item.hprof, TYPE_BGRA_8, proofProf, intent, proofIntent, dwFlags ); + } else if ( item.hprof ) { + item.transf = cmsCreateTransform( ColorProfileImpl::getSRGBProfile(), TYPE_BGRA_8, item.hprof, TYPE_BGRA_8, intent, 0 ); + } + } + + result = item.transf; + found = true; + } + } + + return result; +} + + + +#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/color-profile.h b/src/object/color-profile.h new file mode 100644 index 000000000..4c5222843 --- /dev/null +++ b/src/object/color-profile.h @@ -0,0 +1,110 @@ +#ifndef SEEN_COLOR_PROFILE_H +#define SEEN_COLOR_PROFILE_H + +#include +#include + +#include +#include "cms-color-types.h" + +#include "sp-object.h" + +struct SPColor; + +namespace Inkscape { + +enum { + RENDERING_INTENT_UNKNOWN = 0, + RENDERING_INTENT_AUTO = 1, + RENDERING_INTENT_PERCEPTUAL = 2, + RENDERING_INTENT_RELATIVE_COLORIMETRIC = 3, + RENDERING_INTENT_SATURATION = 4, + RENDERING_INTENT_ABSOLUTE_COLORIMETRIC = 5 +}; + +class ColorProfileImpl; + + +/** + * Color Profile. + */ +class ColorProfile : public SPObject { +public: + ColorProfile(); + virtual ~ColorProfile(); + + bool operator<(ColorProfile const &other) const; + + // we use std::set with pointers to ColorProfile, just having operator< isn't enough to sort these + struct pointerComparator { + bool operator()(const ColorProfile * const & a, const ColorProfile * const & b) { return (*a) < (*b); }; + }; + + friend cmsHPROFILE colorprofile_get_handle( SPDocument*, unsigned int*, char const* ); + friend class CMSSystem; + + class FilePlusHome { + public: + FilePlusHome(Glib::ustring filename, bool isInHome); + FilePlusHome(const FilePlusHome &filePlusHome); + bool operator<(FilePlusHome const &other) const; + Glib::ustring filename; + bool isInHome; + }; + class FilePlusHomeAndName: public FilePlusHome { + public: + FilePlusHomeAndName(FilePlusHome filePlusHome, Glib::ustring name); + bool operator<(FilePlusHomeAndName const &other) const; + Glib::ustring name; + }; + + static std::set getBaseProfileDirs(); + static std::set getProfileFiles(); + static std::set getProfileFilesWithNames(); +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + //icColorSpaceSignature getColorSpace() const; + ColorSpaceSig getColorSpace() const; + //icProfileClassSignature getProfileClass() const; + ColorProfileClassSig getProfileClass() const; + cmsHTRANSFORM getTransfToSRGB8(); + cmsHTRANSFORM getTransfFromSRGB8(); + cmsHTRANSFORM getTransfGamutCheck(); + bool GamutCheck(SPColor color); + +#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + + char* href; + char* local; + char* name; + char* intentStr; + unsigned int rendering_intent; // FIXME: type the enum and hold that instead + +protected: + ColorProfileImpl *impl; + + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void set(unsigned int key, char const* value); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); +}; + +} // namespace Inkscape + +//#define COLORPROFILE_TYPE (Inkscape::colorprofile_get_type()) +#define COLORPROFILE(obj) ((Inkscape::ColorProfile*)obj) +#define IS_COLORPROFILE(obj) (dynamic_cast((SPObject*)obj)) + +#endif // !SEEN_COLOR_PROFILE_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/CMakeLists.txt b/src/object/filters/CMakeLists.txt new file mode 100644 index 000000000..04c23abe7 --- /dev/null +++ b/src/object/filters/CMakeLists.txt @@ -0,0 +1,53 @@ + +set(filters_SRC + sp-filter-primitive.cpp + blend.cpp + colormatrix.cpp + componenttransfer-funcnode.cpp + componenttransfer.cpp + composite.cpp + convolvematrix.cpp + diffuselighting.cpp + displacementmap.cpp + distantlight.cpp + flood.cpp + gaussian-blur.cpp + image.cpp + merge.cpp + mergenode.cpp + morphology.cpp + offset.cpp + pointlight.cpp + specularlighting.cpp + spotlight.cpp + tile.cpp + turbulence.cpp + + + # ------- + # Headers + sp-filter-primitive.h + blend.h + colormatrix.h + componenttransfer-funcnode.h + componenttransfer.h + composite.h + convolvematrix.h + diffuselighting.h + displacementmap.h + distantlight.h + flood.h + gaussian-blur.h + image.h + merge.h + mergenode.h + morphology.h + offset.h + pointlight.h + specularlighting.h + spotlight.h + tile.h + turbulence.h +) + +add_inkscape_source("${filters_SRC}") diff --git a/src/object/filters/blend.cpp b/src/object/filters/blend.cpp new file mode 100644 index 000000000..e23b2aa57 --- /dev/null +++ b/src/object/filters/blend.cpp @@ -0,0 +1,291 @@ +/** \file + * SVG implementation. + * + */ +/* + * Authors: + * Hugo Rodrigues + * Niko Kiirala + * Abhishek Sharma + * + * Copyright (C) 2006,2007 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include "blend.h" + +#include "attributes.h" + +#include "display/nr-filter.h" + +#include "object/sp-filter.h" + +#include "xml/repr.h" + + +SPFeBlend::SPFeBlend() + : SPFilterPrimitive(), blend_mode(Inkscape::Filters::BLEND_NORMAL), + in2(Inkscape::Filters::NR_FILTER_SLOT_NOT_SET) +{ +} + +SPFeBlend::~SPFeBlend() { +} + +/** + * Reads the Inkscape::XML::Node, and initializes SPFeBlend variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPFeBlend::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPFilterPrimitive::build(document, repr); + + /*LOAD ATTRIBUTES FROM REPR HERE*/ + this->readAttr( "mode" ); + this->readAttr( "in2" ); + + /* Unlike normal in, in2 is required attribute. Make sure, we can call + * it by some name. */ + if (this->in2 == Inkscape::Filters::NR_FILTER_SLOT_NOT_SET || + this->in2 == Inkscape::Filters::NR_FILTER_UNNAMED_SLOT) + { + SPFilter *parent = SP_FILTER(this->parent); + this->in2 = sp_filter_primitive_name_previous_out(this); + repr->setAttribute("in2", sp_filter_name_for_image(parent, this->in2)); + } +} + +/** + * Drops any allocated memory. + */ +void SPFeBlend::release() { + SPFilterPrimitive::release(); +} + +static Inkscape::Filters::FilterBlendMode sp_feBlend_readmode(gchar const *value) { + if (!value) { + return Inkscape::Filters::BLEND_NORMAL; + } + + switch (value[0]) { + case 'n': + if (strncmp(value, "normal", 6) == 0) + return Inkscape::Filters::BLEND_NORMAL; + break; + case 'm': + if (strncmp(value, "multiply", 8) == 0) + return Inkscape::Filters::BLEND_MULTIPLY; + break; + case 's': + if (strncmp(value, "screen", 6) == 0) + return Inkscape::Filters::BLEND_SCREEN; + if (strncmp(value, "saturation", 10) == 0) + return Inkscape::Filters::BLEND_SATURATION; + break; + case 'd': + if (strncmp(value, "darken", 6) == 0) + return Inkscape::Filters::BLEND_DARKEN; + if (strncmp(value, "difference", 10) == 0) + return Inkscape::Filters::BLEND_DIFFERENCE; + break; + case 'l': + if (strncmp(value, "lighten", 7) == 0) + return Inkscape::Filters::BLEND_LIGHTEN; + if (strncmp(value, "luminosity", 10) == 0) + return Inkscape::Filters::BLEND_LUMINOSITY; + break; + case 'o': + if (strncmp(value, "overlay", 7) == 0) + return Inkscape::Filters::BLEND_OVERLAY; + break; + case 'c': + if (strncmp(value, "color-dodge", 11) == 0) + return Inkscape::Filters::BLEND_COLORDODGE; + if (strncmp(value, "color-burn", 10) == 0) + return Inkscape::Filters::BLEND_COLORBURN; + if (strncmp(value, "color", 5) == 0) + return Inkscape::Filters::BLEND_COLOR; + break; + case 'h': + if (strncmp(value, "hard-light", 10) == 0) + return Inkscape::Filters::BLEND_HARDLIGHT; + if (strncmp(value, "hue", 3) == 0) + return Inkscape::Filters::BLEND_HUE; + break; + case 'e': + if (strncmp(value, "exclusion", 10) == 0) + return Inkscape::Filters::BLEND_EXCLUSION; + default: + std::cout << "Inkscape::Filters::FilterBlendMode: Unimplemented mode: " << value << std::endl; + // do nothing by default + break; + } + + return Inkscape::Filters::BLEND_NORMAL; +} + +/** + * Sets a specific value in the SPFeBlend. + */ +void SPFeBlend::set(unsigned int key, gchar const *value) { + Inkscape::Filters::FilterBlendMode mode; + int input; + + switch(key) { + /*DEAL WITH SETTING ATTRIBUTES HERE*/ + case SP_ATTR_MODE: + mode = sp_feBlend_readmode(value); + + if (mode != this->blend_mode) { + this->blend_mode = mode; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_IN2: + input = sp_filter_primitive_read_in(this, value); + + if (input != this->in2) { + this->in2 = input; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + default: + SPFilterPrimitive::set(key, value); + break; + } +} + +/** + * Receives update notifications. + */ +void SPFeBlend::update(SPCtx *ctx, guint flags) { + if (flags & SP_OBJECT_MODIFIED_FLAG) { + this->readAttr( "mode" ); + this->readAttr( "in2" ); + } + + /* Unlike normal in, in2 is required attribute. Make sure, we can call + * it by some name. */ + /* This may not be true.... see issue at + * http://www.w3.org/TR/filter-effects/#feBlendElement (but it doesn't hurt). */ + if (this->in2 == Inkscape::Filters::NR_FILTER_SLOT_NOT_SET || + this->in2 == Inkscape::Filters::NR_FILTER_UNNAMED_SLOT) + { + SPFilter *parent = SP_FILTER(this->parent); + this->in2 = sp_filter_primitive_name_previous_out(this); + + // TODO: XML Tree being used directly here while it shouldn't be. + this->getRepr()->setAttribute("in2", sp_filter_name_for_image(parent, this->in2)); + } + + SPFilterPrimitive::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPFeBlend::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + SPFilter *parent = SP_FILTER(this->parent); + + if (!repr) { + repr = doc->createElement("svg:feBlend"); + } + + gchar const *in2_name = sp_filter_name_for_image(parent, this->in2); + + if( !in2_name ) { + + // This code is very similar to sp_filter_primtive_name_previous_out() + SPObject *i = parent->firstChild(); + + // Find previous filter primitive + while (i && i->getNext() != this) { + i = i->getNext(); + } + + if( i ) { + SPFilterPrimitive *i_prim = SP_FILTER_PRIMITIVE(i); + in2_name = sp_filter_name_for_image(parent, i_prim->image_out); + } + } + + if (in2_name) { + repr->setAttribute("in2", in2_name); + } else { + g_warning("Unable to set in2 for feBlend"); + } + + char const *mode; + switch(this->blend_mode) { + case Inkscape::Filters::BLEND_NORMAL: + mode = "normal"; break; + case Inkscape::Filters::BLEND_MULTIPLY: + mode = "multiply"; break; + case Inkscape::Filters::BLEND_SCREEN: + mode = "screen"; break; + case Inkscape::Filters::BLEND_DARKEN: + mode = "darken"; break; + case Inkscape::Filters::BLEND_LIGHTEN: + mode = "lighten"; break; + // New + case Inkscape::Filters::BLEND_OVERLAY: + mode = "overlay"; break; + case Inkscape::Filters::BLEND_COLORDODGE: + mode = "color-dodge"; break; + case Inkscape::Filters::BLEND_COLORBURN: + mode = "color-burn"; break; + case Inkscape::Filters::BLEND_HARDLIGHT: + mode = "hard-light"; break; + case Inkscape::Filters::BLEND_SOFTLIGHT: + mode = "soft-light"; break; + case Inkscape::Filters::BLEND_DIFFERENCE: + mode = "difference"; break; + case Inkscape::Filters::BLEND_EXCLUSION: + mode = "exclusion"; break; + case Inkscape::Filters::BLEND_HUE: + mode = "hue"; break; + case Inkscape::Filters::BLEND_SATURATION: + mode = "saturation"; break; + case Inkscape::Filters::BLEND_COLOR: + mode = "color"; break; + case Inkscape::Filters::BLEND_LUMINOSITY: + mode = "luminosity"; break; + default: + mode = 0; + } + + repr->setAttribute("mode", mode); + + SPFilterPrimitive::write(doc, repr, flags); + + return repr; +} + +void SPFeBlend::build_renderer(Inkscape::Filters::Filter* filter) { + g_assert(this != NULL); + g_assert(filter != NULL); + + int primitive_n = filter->add_primitive(Inkscape::Filters::NR_FILTER_BLEND); + Inkscape::Filters::FilterPrimitive *nr_primitive = filter->get_primitive(primitive_n); + Inkscape::Filters::FilterBlend *nr_blend = dynamic_cast(nr_primitive); + g_assert(nr_blend != NULL); + + sp_filter_primitive_renderer_common(this, nr_primitive); + + nr_blend->set_mode(this->blend_mode); + nr_blend->set_input(1, this->in2); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/blend.h b/src/object/filters/blend.h new file mode 100644 index 000000000..d5af9fe7d --- /dev/null +++ b/src/object/filters/blend.h @@ -0,0 +1,54 @@ +/** @file + * @brief SVG blend filter effect + *//* + * Authors: + * Hugo Rodrigues + * Niko Kiirala + * + * Copyright (C) 2006,2007 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SP_FEBLEND_H_SEEN +#define SP_FEBLEND_H_SEEN + +#include "sp-filter-primitive.h" +#include "display/nr-filter-blend.h" + +#define SP_FEBLEND(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FEBLEND(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPFeBlend : public SPFilterPrimitive { +public: + SPFeBlend(); + virtual ~SPFeBlend(); + + Inkscape::Filters::FilterBlendMode blend_mode; + int in2; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void set(unsigned int key, const gchar* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags); + + virtual void build_renderer(Inkscape::Filters::Filter* filter); +}; + +#endif /* !SP_FEBLEND_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/colormatrix.cpp b/src/object/filters/colormatrix.cpp new file mode 100644 index 000000000..0e8398ace --- /dev/null +++ b/src/object/filters/colormatrix.cpp @@ -0,0 +1,160 @@ +/** \file + * SVG implementation. + * + */ +/* + * Authors: + * Felipe Sanches + * hugo Rodrigues + * Abhishek Sharma + * + * Copyright (C) 2007 Felipe C. da S. Sanches + * Copyright (C) 2006 Hugo Rodrigues + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include "attributes.h" +#include "svg/svg.h" +#include "colormatrix.h" +#include "xml/repr.h" +#include "helper-fns.h" + +#include "display/nr-filter.h" + +SPFeColorMatrix::SPFeColorMatrix() + : SPFilterPrimitive(), type(Inkscape::Filters::COLORMATRIX_MATRIX), value(0) +{ +} + +SPFeColorMatrix::~SPFeColorMatrix() { +} + +/** + * Reads the Inkscape::XML::Node, and initializes SPFeColorMatrix variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPFeColorMatrix::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPFilterPrimitive::build(document, repr); + + /*LOAD ATTRIBUTES FROM REPR HERE*/ + this->readAttr( "type" ); + this->readAttr( "values" ); +} + +/** + * Drops any allocated memory. + */ +void SPFeColorMatrix::release() { + SPFilterPrimitive::release(); +} + +static Inkscape::Filters::FilterColorMatrixType sp_feColorMatrix_read_type(gchar const *value){ + if (!value) { + return Inkscape::Filters::COLORMATRIX_MATRIX; //matrix is default + } + + switch(value[0]){ + case 'm': + if (strcmp(value, "matrix") == 0) return Inkscape::Filters::COLORMATRIX_MATRIX; + break; + case 's': + if (strcmp(value, "saturate") == 0) return Inkscape::Filters::COLORMATRIX_SATURATE; + break; + case 'h': + if (strcmp(value, "hueRotate") == 0) return Inkscape::Filters::COLORMATRIX_HUEROTATE; + break; + case 'l': + if (strcmp(value, "luminanceToAlpha") == 0) return Inkscape::Filters::COLORMATRIX_LUMINANCETOALPHA; + break; + } + + return Inkscape::Filters::COLORMATRIX_MATRIX; //matrix is default +} + +/** + * Sets a specific value in the SPFeColorMatrix. + */ +void SPFeColorMatrix::set(unsigned int key, gchar const *str) { + Inkscape::Filters::FilterColorMatrixType read_type; + + /*DEAL WITH SETTING ATTRIBUTES HERE*/ + switch(key) { + case SP_ATTR_TYPE: + read_type = sp_feColorMatrix_read_type(str); + + if (this->type != read_type){ + this->type = read_type; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_VALUES: + if (str){ + this->values = helperfns_read_vector(str); + this->value = helperfns_read_number(str, HELPERFNS_NO_WARNING); + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + default: + SPFilterPrimitive::set(key, str); + break; + } +} + +/** + * Receives update notifications. + */ +void SPFeColorMatrix::update(SPCtx *ctx, guint flags) { + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + + /* do something to trigger redisplay, updates? */ + + } + + SPFilterPrimitive::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPFeColorMatrix::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + /* TODO: Don't just clone, but create a new repr node and write all + * relevant values into it */ + if (!repr) { + repr = this->getRepr()->duplicate(doc); + } + + SPFilterPrimitive::write(doc, repr, flags); + + return repr; +} + +void SPFeColorMatrix::build_renderer(Inkscape::Filters::Filter* filter) { + g_assert(this != NULL); + g_assert(filter != NULL); + + int primitive_n = filter->add_primitive(Inkscape::Filters::NR_FILTER_COLORMATRIX); + Inkscape::Filters::FilterPrimitive *nr_primitive = filter->get_primitive(primitive_n); + Inkscape::Filters::FilterColorMatrix *nr_colormatrix = dynamic_cast(nr_primitive); + g_assert(nr_colormatrix != NULL); + + sp_filter_primitive_renderer_common(this, nr_primitive); + nr_colormatrix->set_type(this->type); + nr_colormatrix->set_value(this->value); + nr_colormatrix->set_values(this->values); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/colormatrix.h b/src/object/filters/colormatrix.h new file mode 100644 index 000000000..2a1c403f1 --- /dev/null +++ b/src/object/filters/colormatrix.h @@ -0,0 +1,54 @@ +/** @file + * @brief SVG color matrix filter effect + *//* + * Authors: + * Hugo Rodrigues + * + * Copyright (C) 2006 Hugo Rodrigues + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#ifndef SP_FECOLORMATRIX_H_SEEN +#define SP_FECOLORMATRIX_H_SEEN + +#include +#include "sp-filter-primitive.h" +#include "display/nr-filter-colormatrix.h" + +#define SP_FECOLORMATRIX(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FECOLORMATRIX(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPFeColorMatrix : public SPFilterPrimitive { +public: + SPFeColorMatrix(); + virtual ~SPFeColorMatrix(); + + Inkscape::Filters::FilterColorMatrixType type; + gdouble value; + std::vector values; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void set(unsigned int key, const gchar* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags); + + virtual void build_renderer(Inkscape::Filters::Filter* filter); +}; + +#endif /* !SP_FECOLORMATRIX_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/componenttransfer-funcnode.cpp b/src/object/filters/componenttransfer-funcnode.cpp new file mode 100644 index 000000000..23c8dbd96 --- /dev/null +++ b/src/object/filters/componenttransfer-funcnode.cpp @@ -0,0 +1,216 @@ +/** \file + * SVG , , and implementations. + */ +/* + * Authors: + * Hugo Rodrigues + * Niko Kiirala + * Felipe Corrêa da Silva Sanches + * Abhishek Sharma + * + * Copyright (C) 2006, 2007, 2008 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include "attributes.h" +#include "document.h" +#include "componenttransfer.h" +#include "componenttransfer-funcnode.h" +#include "xml/repr.h" +#include "helper-fns.h" + +#define SP_MACROS_SILENT + + +/* FeFuncNode class */ +SPFeFuncNode::SPFeFuncNode(SPFeFuncNode::Channel channel) + : SPObject(), type(Inkscape::Filters::COMPONENTTRANSFER_TYPE_IDENTITY), + slope(1), intercept(0), amplitude(1), exponent(1), offset(0), channel(channel) { +} + +SPFeFuncNode::~SPFeFuncNode() { +} + +/** + * Reads the Inkscape::XML::Node, and initializes SPDistantLight variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPFeFuncNode::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPObject::build(document, repr); + + //Read values of key attributes from XML nodes into object. + this->readAttr( "type" ); + this->readAttr( "tableValues" ); + this->readAttr( "slope" ); + this->readAttr( "intercept" ); + this->readAttr( "amplitude" ); + this->readAttr( "exponent" ); + this->readAttr( "offset" ); + + +//is this necessary? + document->addResource("fefuncnode", this); //maybe feFuncR, fefuncG, feFuncB and fefuncA ? +} + +/** + * Drops any allocated memory. + */ +void SPFeFuncNode::release() { + if ( this->document ) { + // Unregister ourselves + this->document->removeResource("fefuncnode", this); + } + +//TODO: release resources here +} + +static Inkscape::Filters::FilterComponentTransferType sp_feComponenttransfer_read_type(gchar const *value){ + if (!value) { + return Inkscape::Filters::COMPONENTTRANSFER_TYPE_ERROR; //type attribute is REQUIRED. + } + + switch(value[0]){ + case 'i': + if (strncmp(value, "identity", 8) == 0) { + return Inkscape::Filters::COMPONENTTRANSFER_TYPE_IDENTITY; + } + break; + case 't': + if (strncmp(value, "table", 5) == 0) { + return Inkscape::Filters::COMPONENTTRANSFER_TYPE_TABLE; + } + break; + case 'd': + if (strncmp(value, "discrete", 8) == 0) { + return Inkscape::Filters::COMPONENTTRANSFER_TYPE_DISCRETE; + } + break; + case 'l': + if (strncmp(value, "linear", 6) == 0) { + return Inkscape::Filters::COMPONENTTRANSFER_TYPE_LINEAR; + } + break; + case 'g': + if (strncmp(value, "gamma", 5) == 0) { + return Inkscape::Filters::COMPONENTTRANSFER_TYPE_GAMMA; + } + break; + } + + return Inkscape::Filters::COMPONENTTRANSFER_TYPE_ERROR; //type attribute is REQUIRED. +} + +/** + * Sets a specific value in the SPFeFuncNode. + */ +void SPFeFuncNode::set(unsigned int key, gchar const *value) { + Inkscape::Filters::FilterComponentTransferType type; + double read_num; + + switch(key) { + case SP_ATTR_TYPE: + type = sp_feComponenttransfer_read_type(value); + + if(type != this->type) { + this->type = type; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_TABLEVALUES: + if (value){ + this->tableValues = helperfns_read_vector(value); + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_SLOPE: + read_num = value ? helperfns_read_number(value) : 1; + + if (read_num != this->slope) { + this->slope = read_num; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_INTERCEPT: + read_num = value ? helperfns_read_number(value) : 0; + + if (read_num != this->intercept) { + this->intercept = read_num; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_AMPLITUDE: + read_num = value ? helperfns_read_number(value) : 1; + + if (read_num != this->amplitude) { + this->amplitude = read_num; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_EXPONENT: + read_num = value ? helperfns_read_number(value) : 1; + + if (read_num != this->exponent) { + this->exponent = read_num; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_OFFSET: + read_num = value ? helperfns_read_number(value) : 0; + + if (read_num != this->offset) { + this->offset = read_num; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + default: + SPObject::set(key, value); + break; + } +} + +/** + * Receives update notifications. + */ +void SPFeFuncNode::update(SPCtx *ctx, guint flags) { + std::cout << "SPFeFuncNode::update" << std::endl; + if (flags & SP_OBJECT_MODIFIED_FLAG) { + this->readAttr( "type" ); + this->readAttr( "tableValues" ); + this->readAttr( "slope" ); + this->readAttr( "intercept" ); + this->readAttr( "amplitude" ); + this->readAttr( "exponent" ); + this->readAttr( "offset" ); + } + + SPObject::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPFeFuncNode::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + std::cout << "SPFeFuncNode::write" << std::endl; + if (!repr) { + repr = this->getRepr()->duplicate(doc); + } + + SPObject::write(doc, repr, flags); + + return repr; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/componenttransfer-funcnode.h b/src/object/filters/componenttransfer-funcnode.h new file mode 100644 index 000000000..f4bb88594 --- /dev/null +++ b/src/object/filters/componenttransfer-funcnode.h @@ -0,0 +1,63 @@ +#ifndef SP_FECOMPONENTTRANSFER_FUNCNODE_H_SEEN +#define SP_FECOMPONENTTRANSFER_FUNCNODE_H_SEEN + +/** \file + * SVG implementation, see sp-filter.cpp. + */ +/* + * Authors: + * Hugo Rodrigues + * Niko Kiirala + * Felipe Corrêa da Silva Sanches + * + * Copyright (C) 2006,2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "../sp-object.h" +#include "display/nr-filter-component-transfer.h" + +#define SP_FEFUNCNODE(obj) (dynamic_cast((SPObject*)obj)) + +class SPFeFuncNode : public SPObject { +public: + enum Channel { + R, G, B, A + }; + + SPFeFuncNode(Channel channel); + virtual ~SPFeFuncNode(); + + Inkscape::Filters::FilterComponentTransferType type; + std::vector tableValues; + double slope; + double intercept; + double amplitude; + double exponent; + double offset; + Channel channel; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void set(unsigned int key, const gchar* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags); +}; + +#endif /* !SP_FECOMPONENTTRANSFER_FUNCNODE_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/componenttransfer.cpp b/src/object/filters/componenttransfer.cpp new file mode 100644 index 000000000..dd13d85d1 --- /dev/null +++ b/src/object/filters/componenttransfer.cpp @@ -0,0 +1,187 @@ +/** \file + * SVG implementation. + * + */ +/* + * Authors: + * hugo Rodrigues + * Abhishek Sharma + * + * Copyright (C) 2006 Hugo Rodrigues + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "attributes.h" +#include "document.h" + +// In same directory +#include "componenttransfer.h" +#include "componenttransfer-funcnode.h" + +#include "display/nr-filter.h" + +#include "xml/repr.h" + +SPFeComponentTransfer::SPFeComponentTransfer() + : SPFilterPrimitive(), renderer(NULL) +{ +} + +SPFeComponentTransfer::~SPFeComponentTransfer() { +} + +/** + * Reads the Inkscape::XML::Node, and initializes SPFeComponentTransfer variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPFeComponentTransfer::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPFilterPrimitive::build(document, repr); + + /*LOAD ATTRIBUTES FROM REPR HERE*/ + + //do we need this? + //document->addResource("feComponentTransfer", object); +} + +static void sp_feComponentTransfer_children_modified(SPFeComponentTransfer *sp_componenttransfer) +{ + if (sp_componenttransfer->renderer) { + bool set[4] = {false, false, false, false}; + for(auto& node: sp_componenttransfer->children) { + int i = 4; + + SPFeFuncNode *funcNode = SP_FEFUNCNODE(&node); + + switch (funcNode->channel) { + case SPFeFuncNode::R: + i = 0; + break; + case SPFeFuncNode::G: + i = 1; + break; + case SPFeFuncNode::B: + i = 2; + break; + case SPFeFuncNode::A: + i = 3; + break; + } + + if (i == 4) { + g_warning("Unrecognized channel for component transfer."); + break; + } + sp_componenttransfer->renderer->type[i] = ((SPFeFuncNode *) &node)->type; + sp_componenttransfer->renderer->tableValues[i] = ((SPFeFuncNode *) &node)->tableValues; + sp_componenttransfer->renderer->slope[i] = ((SPFeFuncNode *) &node)->slope; + sp_componenttransfer->renderer->intercept[i] = ((SPFeFuncNode *) &node)->intercept; + sp_componenttransfer->renderer->amplitude[i] = ((SPFeFuncNode *) &node)->amplitude; + sp_componenttransfer->renderer->exponent[i] = ((SPFeFuncNode *) &node)->exponent; + sp_componenttransfer->renderer->offset[i] = ((SPFeFuncNode *) &node)->offset; + set[i] = true; + } + // Set any types not explicitly set to the identity transform + for(int i=0;i<4;i++) { + if (!set[i]) { + sp_componenttransfer->renderer->type[i] = Inkscape::Filters::COMPONENTTRANSFER_TYPE_IDENTITY; + } + } + } +} + +/** + * Callback for child_added event. + */ +void SPFeComponentTransfer::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) { + SPFilterPrimitive::child_added(child, ref); + + sp_feComponentTransfer_children_modified(this); + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/** + * Callback for remove_child event. + */ +void SPFeComponentTransfer::remove_child(Inkscape::XML::Node *child) { + SPFilterPrimitive::remove_child(child); + + sp_feComponentTransfer_children_modified(this); + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/** + * Drops any allocated memory. + */ +void SPFeComponentTransfer::release() { + SPFilterPrimitive::release(); +} + +/** + * Sets a specific value in the SPFeComponentTransfer. + */ +void SPFeComponentTransfer::set(unsigned int key, gchar const *value) { + switch(key) { + /*DEAL WITH SETTING ATTRIBUTES HERE*/ + default: + SPFilterPrimitive::set(key, value); + break; + } +} + +/** + * Receives update notifications. + */ +void SPFeComponentTransfer::update(SPCtx *ctx, guint flags) { + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + + /* do something to trigger redisplay, updates? */ + + } + + SPFilterPrimitive::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPFeComponentTransfer::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + /* TODO: Don't just clone, but create a new repr node and write all + * relevant values into it */ + if (!repr) { + repr = this->getRepr()->duplicate(doc); + } + + SPFilterPrimitive::write(doc, repr, flags); + + return repr; +} + +void SPFeComponentTransfer::build_renderer(Inkscape::Filters::Filter* filter) { + g_assert(this != NULL); + g_assert(filter != NULL); + + int primitive_n = filter->add_primitive(Inkscape::Filters::NR_FILTER_COMPONENTTRANSFER); + Inkscape::Filters::FilterPrimitive *nr_primitive = filter->get_primitive(primitive_n); + Inkscape::Filters::FilterComponentTransfer *nr_componenttransfer = dynamic_cast(nr_primitive); + g_assert(nr_componenttransfer != NULL); + + this->renderer = nr_componenttransfer; + sp_filter_primitive_renderer_common(this, nr_primitive); + + + sp_feComponentTransfer_children_modified(this); //do we need it?! +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/componenttransfer.h b/src/object/filters/componenttransfer.h new file mode 100644 index 000000000..8dbe91db1 --- /dev/null +++ b/src/object/filters/componenttransfer.h @@ -0,0 +1,58 @@ +/** @file + * @brief SVG component transferfilter effect + *//* + * Authors: + * Hugo Rodrigues + * + * Copyright (C) 2006 Hugo Rodrigues + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#ifndef SP_FECOMPONENTTRANSFER_H_SEEN +#define SP_FECOMPONENTTRANSFER_H_SEEN + +#include "sp-filter-primitive.h" + +#define SP_FECOMPONENTTRANSFER(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FECOMPONENTTRANSFER(obj) (dynamic_cast((SPObject*)obj) != NULL) + +namespace Inkscape { +namespace Filters { +class FilterComponentTransfer; +} } + +class SPFeComponentTransfer : public SPFilterPrimitive { +public: + SPFeComponentTransfer(); + virtual ~SPFeComponentTransfer(); + + Inkscape::Filters::FilterComponentTransfer *renderer; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref); + virtual void remove_child(Inkscape::XML::Node* child); + + virtual void set(unsigned int key, const gchar* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags); + + virtual void build_renderer(Inkscape::Filters::Filter* filter); +}; + +#endif /* !SP_FECOMPONENTTRANSFER_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/composite.cpp b/src/object/filters/composite.cpp new file mode 100644 index 000000000..bca67774b --- /dev/null +++ b/src/object/filters/composite.cpp @@ -0,0 +1,334 @@ +/** \file + * SVG implementation. + * + */ +/* + * Authors: + * hugo Rodrigues + * Abhishek Sharma + * + * Copyright (C) 2006 Hugo Rodrigues + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "composite.h" + +#include "attributes.h" +#include "helper-fns.h" + +#include "display/nr-filter.h" +#include "display/nr-filter-composite.h" + +#include "object/sp-filter.h" + +#include "svg/svg.h" + +#include "xml/repr.h" + +SPFeComposite::SPFeComposite() + : SPFilterPrimitive(), composite_operator(COMPOSITE_DEFAULT), + k1(0), k2(0), k3(0), k4(0), in2(Inkscape::Filters::NR_FILTER_SLOT_NOT_SET) +{ +} + +SPFeComposite::~SPFeComposite() { +} + +/** + * Reads the Inkscape::XML::Node, and initializes SPFeComposite variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPFeComposite::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPFilterPrimitive::build(document, repr); + + this->readAttr( "operator" ); + + if (this->composite_operator == COMPOSITE_ARITHMETIC) { + this->readAttr( "k1" ); + this->readAttr( "k2" ); + this->readAttr( "k3" ); + this->readAttr( "k4" ); + } + + this->readAttr( "in2" ); + + /* Unlike normal in, in2 is required attribute. Make sure, we can call + * it by some name. */ + if (this->in2 == Inkscape::Filters::NR_FILTER_SLOT_NOT_SET || + this->in2 == Inkscape::Filters::NR_FILTER_UNNAMED_SLOT) + { + SPFilter *parent = SP_FILTER(this->parent); + this->in2 = sp_filter_primitive_name_previous_out(this); + repr->setAttribute("in2", sp_filter_name_for_image(parent, this->in2)); + } +} + +/** + * Drops any allocated memory. + */ +void SPFeComposite::release() { + SPFilterPrimitive::release(); +} + +static FeCompositeOperator +sp_feComposite_read_operator(gchar const *value) { + if (!value) { + return COMPOSITE_DEFAULT; + } + + if (strcmp(value, "over") == 0) { + return COMPOSITE_OVER; + } else if (strcmp(value, "in") == 0) { + return COMPOSITE_IN; + } else if (strcmp(value, "out") == 0) { + return COMPOSITE_OUT; + } else if (strcmp(value, "atop") == 0) { + return COMPOSITE_ATOP; + } else if (strcmp(value, "xor") == 0) { + return COMPOSITE_XOR; + } else if (strcmp(value, "arithmetic") == 0) { + return COMPOSITE_ARITHMETIC; + } +#ifdef WITH_CSSCOMPOSITE + else if (strcmp(value, "clear") == 0) { + return COMPOSITE_CLEAR; + } else if (strcmp(value, "copy") == 0) { + return COMPOSITE_COPY; + } else if (strcmp(value, "destination") == 0) { + return COMPOSITE_DESTINATION; + } else if (strcmp(value, "destination-over") == 0) { + return COMPOSITE_DESTINATION_OVER; + } else if (strcmp(value, "destination-in") == 0) { + return COMPOSITE_DESTINATION_IN; + } else if (strcmp(value, "destination-out") == 0) { + return COMPOSITE_DESTINATION_OUT; + } else if (strcmp(value, "destination-atop") == 0) { + return COMPOSITE_DESTINATION_ATOP; + } else if (strcmp(value, "lighter") == 0) { + return COMPOSITE_LIGHTER; + } +#endif + std::cout << "Inkscape::Filters::FilterCompositeOperator: Unimplemented operator: " << value << std::endl; + + return COMPOSITE_DEFAULT; +} + +/** + * Sets a specific value in the SPFeComposite. + */ +void SPFeComposite::set(unsigned int key, gchar const *value) { + int input; + FeCompositeOperator op; + double k_n; + + switch(key) { + /*DEAL WITH SETTING ATTRIBUTES HERE*/ + case SP_ATTR_OPERATOR: + op = sp_feComposite_read_operator(value); + if (op != this->composite_operator) { + this->composite_operator = op; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + + case SP_ATTR_K1: + k_n = value ? helperfns_read_number(value) : 0; + if (k_n != this->k1) { + this->k1 = k_n; + if (this->composite_operator == COMPOSITE_ARITHMETIC) + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + + case SP_ATTR_K2: + k_n = value ? helperfns_read_number(value) : 0; + if (k_n != this->k2) { + this->k2 = k_n; + if (this->composite_operator == COMPOSITE_ARITHMETIC) + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + + case SP_ATTR_K3: + k_n = value ? helperfns_read_number(value) : 0; + if (k_n != this->k3) { + this->k3 = k_n; + if (this->composite_operator == COMPOSITE_ARITHMETIC) + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + + case SP_ATTR_K4: + k_n = value ? helperfns_read_number(value) : 0; + if (k_n != this->k4) { + this->k4 = k_n; + if (this->composite_operator == COMPOSITE_ARITHMETIC) + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + + case SP_ATTR_IN2: + input = sp_filter_primitive_read_in(this, value); + if (input != this->in2) { + this->in2 = input; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + + default: + SPFilterPrimitive::set(key, value); + break; + } +} + +/** + * Receives update notifications. + */ +void SPFeComposite::update(SPCtx *ctx, guint flags) { + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + + /* do something to trigger redisplay, updates? */ + + } + + /* Unlike normal in, in2 is required attribute. Make sure, we can call + * it by some name. */ + /* This may not be true.... see issue at + * http://www.w3.org/TR/filter-effects/#feBlendElement (but it doesn't hurt). */ + if (this->in2 == Inkscape::Filters::NR_FILTER_SLOT_NOT_SET || + this->in2 == Inkscape::Filters::NR_FILTER_UNNAMED_SLOT) + { + SPFilter *parent = SP_FILTER(this->parent); + this->in2 = sp_filter_primitive_name_previous_out(this); + + //XML Tree being used directly here while it shouldn't be. + this->getRepr()->setAttribute("in2", sp_filter_name_for_image(parent, this->in2)); + } + + SPFilterPrimitive::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPFeComposite::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + SPFilter *parent = SP_FILTER(this->parent); + + if (!repr) { + repr = doc->createElement("svg:feComposite"); + } + + gchar const *in2_name = sp_filter_name_for_image(parent, this->in2); + + if( !in2_name ) { + + // This code is very similar to sp_filter_primitive_name_previous_out() + SPObject *i = parent->firstChild(); + + // Find previous filter primitive + while (i && i->getNext() != this) { + i = i->getNext(); + } + + if( i ) { + SPFilterPrimitive *i_prim = SP_FILTER_PRIMITIVE(i); + in2_name = sp_filter_name_for_image(parent, i_prim->image_out); + } + } + + if (in2_name) { + repr->setAttribute("in2", in2_name); + } else { + g_warning("Unable to set in2 for feComposite"); + } + + char const *comp_op; + + switch (this->composite_operator) { + case COMPOSITE_OVER: + comp_op = "over"; break; + case COMPOSITE_IN: + comp_op = "in"; break; + case COMPOSITE_OUT: + comp_op = "out"; break; + case COMPOSITE_ATOP: + comp_op = "atop"; break; + case COMPOSITE_XOR: + comp_op = "xor"; break; + case COMPOSITE_ARITHMETIC: + comp_op = "arithmetic"; break; +#ifdef WITH_CSSCOMPOSITE + // New CSS operators + case COMPOSITE_CLEAR: + comp_op = "clear"; break; + case COMPOSITE_COPY: + comp_op = "copy"; break; + case COMPOSITE_DESTINATION: + comp_op = "destination"; break; + case COMPOSITE_DESTINATION_OVER: + comp_op = "destination-over"; break; + case COMPOSITE_DESTINATION_IN: + comp_op = "destination-in"; break; + case COMPOSITE_DESTINATION_OUT: + comp_op = "destination-out"; break; + case COMPOSITE_DESTINATION_ATOP: + comp_op = "destination-atop"; break; + case COMPOSITE_LIGHTER: + comp_op = "lighter"; break; +#endif + default: + comp_op = 0; + } + + repr->setAttribute("operator", comp_op); + + if (this->composite_operator == COMPOSITE_ARITHMETIC) { + sp_repr_set_svg_double(repr, "k1", this->k1); + sp_repr_set_svg_double(repr, "k2", this->k2); + sp_repr_set_svg_double(repr, "k3", this->k3); + sp_repr_set_svg_double(repr, "k4", this->k4); + } else { + repr->setAttribute("k1", 0); + repr->setAttribute("k2", 0); + repr->setAttribute("k3", 0); + repr->setAttribute("k4", 0); + } + + SPFilterPrimitive::write(doc, repr, flags); + + return repr; +} + +void SPFeComposite::build_renderer(Inkscape::Filters::Filter* filter) { + g_assert(this != NULL); + g_assert(filter != NULL); + + int primitive_n = filter->add_primitive(Inkscape::Filters::NR_FILTER_COMPOSITE); + Inkscape::Filters::FilterPrimitive *nr_primitive = filter->get_primitive(primitive_n); + Inkscape::Filters::FilterComposite *nr_composite = dynamic_cast(nr_primitive); + g_assert(nr_composite != NULL); + + sp_filter_primitive_renderer_common(this, nr_primitive); + + nr_composite->set_operator(this->composite_operator); + nr_composite->set_input(1, this->in2); + + if (this->composite_operator == COMPOSITE_ARITHMETIC) { + nr_composite->set_arithmetic(this->k1, this->k2, + this->k3, this->k4); + } +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/composite.h b/src/object/filters/composite.h new file mode 100644 index 000000000..12f7b5344 --- /dev/null +++ b/src/object/filters/composite.h @@ -0,0 +1,76 @@ +/** @file + * @brief SVG composite filter effect + *//* + * Authors: + * Hugo Rodrigues + * + * Copyright (C) 2006 Hugo Rodrigues + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#ifndef SP_FECOMPOSITE_H_SEEN +#define SP_FECOMPOSITE_H_SEEN + +#include "sp-filter-primitive.h" + +#define SP_FECOMPOSITE(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FECOMPOSITE(obj) (dynamic_cast((SPObject*)obj) != NULL) + +enum FeCompositeOperator { + // Default value is 'over', but let's distinquish specifying the + // default and implicitly using the default + COMPOSITE_DEFAULT, + COMPOSITE_OVER, /* Source Over */ + COMPOSITE_IN, /* Source In */ + COMPOSITE_OUT, /* Source Out */ + COMPOSITE_ATOP, /* Source Atop */ + COMPOSITE_XOR, + COMPOSITE_ARITHMETIC, /* Not a fundamental PorterDuff operator, nor Cairo */ +#ifdef WITH_CSSCOMPOSITE + // New in CSS + COMPOSITE_CLEAR, + COMPOSITE_COPY, /* Source */ + COMPOSITE_DESTINATION, + COMPOSITE_DESTINATION_OVER, + COMPOSITE_DESTINATION_IN, + COMPOSITE_DESTINATION_OUT, + COMPOSITE_DESTINATION_ATOP, + COMPOSITE_LIGHTER, /* Plus, Add (Not a fundamental PorterDuff operator */ +#endif + COMPOSITE_ENDOPERATOR /* Cairo Saturate is not included in CSS */ +}; + +class SPFeComposite : public SPFilterPrimitive { +public: + SPFeComposite(); + virtual ~SPFeComposite(); + + FeCompositeOperator composite_operator; + double k1, k2, k3, k4; + int in2; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void set(unsigned int key, const gchar* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags); + + virtual void build_renderer(Inkscape::Filters::Filter* filter); +}; + +#endif /* !SP_FECOMPOSITE_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/convolvematrix.cpp b/src/object/filters/convolvematrix.cpp new file mode 100644 index 000000000..e856690ff --- /dev/null +++ b/src/object/filters/convolvematrix.cpp @@ -0,0 +1,319 @@ +/** \file + * SVG implementation. + * + */ +/* + * Authors: + * Felipe Corrêa da Silva Sanches + * hugo Rodrigues + * Abhishek Sharma + * + * Copyright (C) 2006 Hugo Rodrigues + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include + +#include "convolvematrix.h" + +#include "attributes.h" +#include "helper-fns.h" + +#include "display/nr-filter.h" + +#include "xml/repr.h" + +SPFeConvolveMatrix::SPFeConvolveMatrix() : SPFilterPrimitive() { + this->bias = 0; + this->divisorIsSet = 0; + this->divisor = 0; + + //Setting default values: + this->order.set("3 3"); + this->targetX = 1; + this->targetY = 1; + this->edgeMode = Inkscape::Filters::CONVOLVEMATRIX_EDGEMODE_DUPLICATE; + this->preserveAlpha = false; + + //some helper variables: + this->targetXIsSet = false; + this->targetYIsSet = false; + this->kernelMatrixIsSet = false; +} + +SPFeConvolveMatrix::~SPFeConvolveMatrix() { +} + +/** + * Reads the Inkscape::XML::Node, and initializes SPFeConvolveMatrix variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPFeConvolveMatrix::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPFilterPrimitive::build(document, repr); + + /*LOAD ATTRIBUTES FROM REPR HERE*/ + this->readAttr( "order" ); + this->readAttr( "kernelMatrix" ); + this->readAttr( "divisor" ); + this->readAttr( "bias" ); + this->readAttr( "targetX" ); + this->readAttr( "targetY" ); + this->readAttr( "edgeMode" ); + this->readAttr( "kernelUnitLength" ); + this->readAttr( "preserveAlpha" ); +} + +/** + * Drops any allocated memory. + */ +void SPFeConvolveMatrix::release() { + SPFilterPrimitive::release(); +} + +static Inkscape::Filters::FilterConvolveMatrixEdgeMode sp_feConvolveMatrix_read_edgeMode(gchar const *value){ + if (!value) { + return Inkscape::Filters::CONVOLVEMATRIX_EDGEMODE_DUPLICATE; //duplicate is default + } + + switch (value[0]) { + case 'd': + if (strncmp(value, "duplicate", 9) == 0) { + return Inkscape::Filters::CONVOLVEMATRIX_EDGEMODE_DUPLICATE; + } + break; + case 'w': + if (strncmp(value, "wrap", 4) == 0) { + return Inkscape::Filters::CONVOLVEMATRIX_EDGEMODE_WRAP; + } + break; + case 'n': + if (strncmp(value, "none", 4) == 0) { + return Inkscape::Filters::CONVOLVEMATRIX_EDGEMODE_NONE; + } + break; + } + + return Inkscape::Filters::CONVOLVEMATRIX_EDGEMODE_DUPLICATE; //duplicate is default +} + +/** + * Sets a specific value in the SPFeConvolveMatrix. + */ +void SPFeConvolveMatrix::set(unsigned int key, gchar const *value) { + double read_num; + int read_int; + bool read_bool; + Inkscape::Filters::FilterConvolveMatrixEdgeMode read_mode; + + switch(key) { + /*DEAL WITH SETTING ATTRIBUTES HERE*/ + case SP_ATTR_ORDER: + this->order.set(value); + + //From SVG spec: If is not provided, it defaults to . + if (this->order.optNumIsSet() == false) { + this->order.setOptNumber(this->order.getNumber()); + } + + if (this->targetXIsSet == false) { + this->targetX = (int) floor(this->order.getNumber()/2); + } + + if (this->targetYIsSet == false) { + this->targetY = (int) floor(this->order.getOptNumber()/2); + } + + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_KERNELMATRIX: + if (value){ + this->kernelMatrixIsSet = true; + this->kernelMatrix = helperfns_read_vector(value); + + if (! this->divisorIsSet) { + this->divisor = 0; + + for (unsigned int i = 0; i< this->kernelMatrix.size(); i++) { + this->divisor += this->kernelMatrix[i]; + } + + if (this->divisor == 0) { + this->divisor = 1; + } + } + + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } else { + g_warning("For feConvolveMatrix you MUST pass a kernelMatrix parameter!"); + } + break; + case SP_ATTR_DIVISOR: + if (value) { + read_num = helperfns_read_number(value); + + if (read_num == 0) { + // This should actually be an error, but given our UI it is more useful to simply set divisor to the default. + if (this->kernelMatrixIsSet) { + for (unsigned int i = 0; i< this->kernelMatrix.size(); i++) { + read_num += this->kernelMatrix[i]; + } + } + + if (read_num == 0) { + read_num = 1; + } + + if (this->divisorIsSet || this->divisor!=read_num) { + this->divisorIsSet = false; + this->divisor = read_num; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + } else if (!this->divisorIsSet || this->divisor!=read_num) { + this->divisorIsSet = true; + this->divisor = read_num; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + } + break; + case SP_ATTR_BIAS: + read_num = 0; + if (value) { + read_num = helperfns_read_number(value); + } + + if (read_num != this->bias){ + this->bias = read_num; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_TARGETX: + if (value) { + read_int = (int) helperfns_read_number(value); + + if (read_int < 0 || read_int > this->order.getNumber()){ + g_warning("targetX must be a value between 0 and orderX! Assuming floor(orderX/2) as default value."); + read_int = (int) floor(this->order.getNumber()/2.0); + } + + this->targetXIsSet = true; + + if (read_int != this->targetX){ + this->targetX = read_int; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + } + break; + case SP_ATTR_TARGETY: + if (value) { + read_int = (int) helperfns_read_number(value); + + if (read_int < 0 || read_int > this->order.getOptNumber()){ + g_warning("targetY must be a value between 0 and orderY! Assuming floor(orderY/2) as default value."); + read_int = (int) floor(this->order.getOptNumber()/2.0); + } + + this->targetYIsSet = true; + + if (read_int != this->targetY){ + this->targetY = read_int; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + } + break; + case SP_ATTR_EDGEMODE: + read_mode = sp_feConvolveMatrix_read_edgeMode(value); + + if (read_mode != this->edgeMode){ + this->edgeMode = read_mode; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_KERNELUNITLENGTH: + this->kernelUnitLength.set(value); + + //From SVG spec: If the value is not specified, it defaults to the same value as . + if (this->kernelUnitLength.optNumIsSet() == false) { + this->kernelUnitLength.setOptNumber(this->kernelUnitLength.getNumber()); + } + + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_PRESERVEALPHA: + read_bool = helperfns_read_bool(value, false); + + if (read_bool != this->preserveAlpha){ + this->preserveAlpha = read_bool; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + default: + SPFilterPrimitive::set(key, value); + break; + } + +} + +/** + * Receives update notifications. + */ +void SPFeConvolveMatrix::update(SPCtx *ctx, guint flags) { + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + + /* do something to trigger redisplay, updates? */ + + } + + SPFilterPrimitive::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPFeConvolveMatrix::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + /* TODO: Don't just clone, but create a new repr node and write all + * relevant values into it */ + if (!repr) { + repr = this->getRepr()->duplicate(doc); + } + + + SPFilterPrimitive::write(doc, repr, flags); + + return repr; +} + +void SPFeConvolveMatrix::build_renderer(Inkscape::Filters::Filter* filter) { + g_assert(this != NULL); + g_assert(filter != NULL); + + int primitive_n = filter->add_primitive(Inkscape::Filters::NR_FILTER_CONVOLVEMATRIX); + Inkscape::Filters::FilterPrimitive *nr_primitive = filter->get_primitive(primitive_n); + Inkscape::Filters::FilterConvolveMatrix *nr_convolve = dynamic_cast(nr_primitive); + g_assert(nr_convolve != NULL); + + sp_filter_primitive_renderer_common(this, nr_primitive); + + nr_convolve->set_targetX(this->targetX); + nr_convolve->set_targetY(this->targetY); + nr_convolve->set_orderX( (int)this->order.getNumber() ); + nr_convolve->set_orderY( (int)this->order.getOptNumber() ); + nr_convolve->set_kernelMatrix(this->kernelMatrix); + nr_convolve->set_divisor(this->divisor); + nr_convolve->set_bias(this->bias); + nr_convolve->set_preserveAlpha(this->preserveAlpha); +} +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/convolvematrix.h b/src/object/filters/convolvematrix.h new file mode 100644 index 000000000..9783eaa47 --- /dev/null +++ b/src/object/filters/convolvematrix.h @@ -0,0 +1,66 @@ +/** @file + * @brief SVG matrix convolution filter effect + */ +/* + * Authors: + * Felipe Corrêa da Silva Sanches + * Hugo Rodrigues + * + * Copyright (C) 2006 Hugo Rodrigues + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#ifndef SP_FECONVOLVEMATRIX_H_SEEN +#define SP_FECONVOLVEMATRIX_H_SEEN + +#include +#include "sp-filter-primitive.h" +#include "number-opt-number.h" +#include "display/nr-filter-convolve-matrix.h" + +#define SP_FECONVOLVEMATRIX(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FECONVOLVEMATRIX(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPFeConvolveMatrix : public SPFilterPrimitive { +public: + SPFeConvolveMatrix(); + virtual ~SPFeConvolveMatrix(); + + NumberOptNumber order; + std::vector kernelMatrix; + double divisor, bias; + int targetX, targetY; + Inkscape::Filters::FilterConvolveMatrixEdgeMode edgeMode; + NumberOptNumber kernelUnitLength; + bool preserveAlpha; + + bool targetXIsSet; + bool targetYIsSet; + bool divisorIsSet; + bool kernelMatrixIsSet; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void set(unsigned int key, const gchar* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags); + + virtual void build_renderer(Inkscape::Filters::Filter* filter); +}; + +#endif /* !SP_FECONVOLVEMATRIX_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/diffuselighting.cpp b/src/object/filters/diffuselighting.cpp new file mode 100644 index 000000000..f23817993 --- /dev/null +++ b/src/object/filters/diffuselighting.cpp @@ -0,0 +1,327 @@ +/** \file + * SVG implementation. + * + */ +/* + * Authors: + * hugo Rodrigues + * Jean-Rene Reinhard + * Abhishek Sharma + * + * Copyright (C) 2006 Hugo Rodrigues + * 2007 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +// Same directory +#include "diffuselighting.h" +#include "distantlight.h" +#include "pointlight.h" +#include "spotlight.h" + +#include "strneq.h" +#include "attributes.h" + +#include "display/nr-filter.h" +#include "display/nr-filter-diffuselighting.h" + +#include "svg/svg.h" +#include "svg/svg-color.h" +#include "svg/svg-icc-color.h" + +#include "xml/repr.h" + +/* FeDiffuseLighting base class */ +static void sp_feDiffuseLighting_children_modified(SPFeDiffuseLighting *sp_diffuselighting); + +SPFeDiffuseLighting::SPFeDiffuseLighting() : SPFilterPrimitive() { + this->surfaceScale = 1; + this->diffuseConstant = 1; + this->lighting_color = 0xffffffff; + this->icc = NULL; + + //TODO kernelUnit + this->renderer = NULL; + + this->surfaceScale_set = FALSE; + this->diffuseConstant_set = FALSE; + this->lighting_color_set = FALSE; +} + +SPFeDiffuseLighting::~SPFeDiffuseLighting() { +} + +/** + * Reads the Inkscape::XML::Node, and initializes SPFeDiffuseLighting variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPFeDiffuseLighting::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPFilterPrimitive::build(document, repr); + + /*LOAD ATTRIBUTES FROM REPR HERE*/ + this->readAttr( "surfaceScale" ); + this->readAttr( "diffuseConstant" ); + this->readAttr( "kernelUnitLength" ); + this->readAttr( "lighting-color" ); +} + +/** + * Drops any allocated memory. + */ +void SPFeDiffuseLighting::release() { + SPFilterPrimitive::release(); +} + +/** + * Sets a specific value in the SPFeDiffuseLighting. + */ +void SPFeDiffuseLighting::set(unsigned int key, gchar const *value) { + gchar const *cend_ptr = NULL; + gchar *end_ptr = NULL; + + switch(key) { + /*DEAL WITH SETTING ATTRIBUTES HERE*/ + //TODO test forbidden values + case SP_ATTR_SURFACESCALE: + end_ptr = NULL; + + if (value) { + this->surfaceScale = g_ascii_strtod(value, &end_ptr); + + if (end_ptr) { + this->surfaceScale_set = TRUE; + } + } + + if (!value || !end_ptr) { + this->surfaceScale = 1; + this->surfaceScale_set = FALSE; + } + + if (this->renderer) { + this->renderer->surfaceScale = this->surfaceScale; + } + + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_DIFFUSECONSTANT: + end_ptr = NULL; + + if (value) { + this->diffuseConstant = g_ascii_strtod(value, &end_ptr); + + if (end_ptr && this->diffuseConstant >= 0) { + this->diffuseConstant_set = TRUE; + } else { + end_ptr = NULL; + g_warning("this: diffuseConstant should be a positive number ... defaulting to 1"); + } + } + + if (!value || !end_ptr) { + this->diffuseConstant = 1; + this->diffuseConstant_set = FALSE; + } + + if (this->renderer) { + this->renderer->diffuseConstant = this->diffuseConstant; + } + + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_KERNELUNITLENGTH: + //TODO kernelUnit + //this->kernelUnitLength.set(value); + /*TODOif (feDiffuseLighting->renderer) { + feDiffuseLighting->renderer->surfaceScale = feDiffuseLighting->renderer; + } + */ + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_PROP_LIGHTING_COLOR: + cend_ptr = NULL; + this->lighting_color = sp_svg_read_color(value, &cend_ptr, 0xffffffff); + + //if a value was read + if (cend_ptr) { + while (g_ascii_isspace(*cend_ptr)) { + ++cend_ptr; + } + + if (strneq(cend_ptr, "icc-color(", 10)) { + if (!this->icc) { + this->icc = new SVGICCColor(); + } + + if ( ! sp_svg_read_icc_color( cend_ptr, this->icc ) ) { + delete this->icc; + this->icc = NULL; + } + } + + this->lighting_color_set = TRUE; + } else { + //lighting_color already contains the default value + this->lighting_color_set = FALSE; + } + + if (this->renderer) { + this->renderer->lighting_color = this->lighting_color; + } + + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + default: + SPFilterPrimitive::set(key, value); + break; + } +} + +/** + * Receives update notifications. + */ +void SPFeDiffuseLighting::update(SPCtx *ctx, guint flags) { + if (flags & (SP_OBJECT_MODIFIED_FLAG)) { + this->readAttr( "surfaceScale" ); + this->readAttr( "diffuseConstant" ); + this->readAttr( "kernelUnit" ); + this->readAttr( "lighting-color" ); + } + + SPFilterPrimitive::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPFeDiffuseLighting::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + /* TODO: Don't just clone, but create a new repr node and write all + * relevant values _and children_ into it */ + if (!repr) { + repr = this->getRepr()->duplicate(doc); + //repr = doc->createElement("svg:feDiffuseLighting"); + } + + if (this->surfaceScale_set) { + sp_repr_set_css_double(repr, "surfaceScale", this->surfaceScale); + } else { + repr->setAttribute("surfaceScale", NULL); + } + + if (this->diffuseConstant_set) { + sp_repr_set_css_double(repr, "diffuseConstant", this->diffuseConstant); + } else { + repr->setAttribute("diffuseConstant", NULL); + } + + /*TODO kernelUnits */ + if (this->lighting_color_set) { + gchar c[64]; + sp_svg_write_color(c, sizeof(c), this->lighting_color); + repr->setAttribute("lighting-color", c); + } else { + repr->setAttribute("lighting-color", NULL); + } + + SPFilterPrimitive::write(doc, repr, flags); + + return repr; +} + +/** + * Callback for child_added event. + */ +void SPFeDiffuseLighting::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) { + SPFilterPrimitive::child_added(child, ref); + + sp_feDiffuseLighting_children_modified(this); + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/** + * Callback for remove_child event. + */ +void SPFeDiffuseLighting::remove_child(Inkscape::XML::Node *child) { + SPFilterPrimitive::remove_child(child); + + sp_feDiffuseLighting_children_modified(this); + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +void SPFeDiffuseLighting::order_changed(Inkscape::XML::Node *child, Inkscape::XML::Node *old_ref, Inkscape::XML::Node *new_ref) { + SPFilterPrimitive::order_changed(child, old_ref, new_ref); + + sp_feDiffuseLighting_children_modified(this); + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +static void sp_feDiffuseLighting_children_modified(SPFeDiffuseLighting *sp_diffuselighting) +{ + if (sp_diffuselighting->renderer) { + sp_diffuselighting->renderer->light_type = Inkscape::Filters::NO_LIGHT; + if (SP_IS_FEDISTANTLIGHT(sp_diffuselighting->firstChild())) { + sp_diffuselighting->renderer->light_type = Inkscape::Filters::DISTANT_LIGHT; + sp_diffuselighting->renderer->light.distant = SP_FEDISTANTLIGHT(sp_diffuselighting->firstChild()); + } + if (SP_IS_FEPOINTLIGHT(sp_diffuselighting->firstChild())) { + sp_diffuselighting->renderer->light_type = Inkscape::Filters::POINT_LIGHT; + sp_diffuselighting->renderer->light.point = SP_FEPOINTLIGHT(sp_diffuselighting->firstChild()); + } + if (SP_IS_FESPOTLIGHT(sp_diffuselighting->firstChild())) { + sp_diffuselighting->renderer->light_type = Inkscape::Filters::SPOT_LIGHT; + sp_diffuselighting->renderer->light.spot = SP_FESPOTLIGHT(sp_diffuselighting->firstChild()); + } + } +} + +void SPFeDiffuseLighting::build_renderer(Inkscape::Filters::Filter* filter) { + g_assert(this != NULL); + g_assert(filter != NULL); + + int primitive_n = filter->add_primitive(Inkscape::Filters::NR_FILTER_DIFFUSELIGHTING); + Inkscape::Filters::FilterPrimitive *nr_primitive = filter->get_primitive(primitive_n); + Inkscape::Filters::FilterDiffuseLighting *nr_diffuselighting = dynamic_cast(nr_primitive); + g_assert(nr_diffuselighting != NULL); + + this->renderer = nr_diffuselighting; + sp_filter_primitive_renderer_common(this, nr_primitive); + + nr_diffuselighting->diffuseConstant = this->diffuseConstant; + nr_diffuselighting->surfaceScale = this->surfaceScale; + nr_diffuselighting->lighting_color = this->lighting_color; + nr_diffuselighting->set_icc(this->icc); + + //We assume there is at most one child + nr_diffuselighting->light_type = Inkscape::Filters::NO_LIGHT; + + if (SP_IS_FEDISTANTLIGHT(this->firstChild())) { + nr_diffuselighting->light_type = Inkscape::Filters::DISTANT_LIGHT; + nr_diffuselighting->light.distant = SP_FEDISTANTLIGHT(this->firstChild()); + } + + if (SP_IS_FEPOINTLIGHT(this->firstChild())) { + nr_diffuselighting->light_type = Inkscape::Filters::POINT_LIGHT; + nr_diffuselighting->light.point = SP_FEPOINTLIGHT(this->firstChild()); + } + + if (SP_IS_FESPOTLIGHT(this->firstChild())) { + nr_diffuselighting->light_type = Inkscape::Filters::SPOT_LIGHT; + nr_diffuselighting->light.spot = SP_FESPOTLIGHT(this->firstChild()); + } + + //nr_offset->set_dx(sp_offset->dx); + //nr_offset->set_dy(sp_offset->dy); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/diffuselighting.h b/src/object/filters/diffuselighting.h new file mode 100644 index 000000000..f41c6c056 --- /dev/null +++ b/src/object/filters/diffuselighting.h @@ -0,0 +1,72 @@ +/** @file + * @brief SVG diffuse lighting filter effect + *//* + * Authors: + * Hugo Rodrigues + * Jean-Rene Reinhard + * + * Copyright (C) 2006-2007 Authors + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SP_FEDIFFUSELIGHTING_H_SEEN +#define SP_FEDIFFUSELIGHTING_H_SEEN + +#include "sp-filter-primitive.h" +#include "number-opt-number.h" + +#define SP_FEDIFFUSELIGHTING(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FEDIFFUSELIGHTING(obj) (dynamic_cast((SPObject*)obj) != NULL) + +struct SVGICCColor; + +namespace Inkscape { +namespace Filters { +class FilterDiffuseLighting; +} } + +class SPFeDiffuseLighting : public SPFilterPrimitive { +public: + SPFeDiffuseLighting(); + virtual ~SPFeDiffuseLighting(); + + gfloat surfaceScale; + guint surfaceScale_set : 1; + gfloat diffuseConstant; + guint diffuseConstant_set : 1; + NumberOptNumber kernelUnitLength; + guint32 lighting_color; + guint lighting_color_set : 1; + Inkscape::Filters::FilterDiffuseLighting *renderer; + SVGICCColor *icc; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref); + virtual void remove_child(Inkscape::XML::Node* child); + + virtual void order_changed(Inkscape::XML::Node* child, Inkscape::XML::Node* old_repr, Inkscape::XML::Node* new_repr); + + virtual void set(unsigned int key, const gchar* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags); + + virtual void build_renderer(Inkscape::Filters::Filter* filter); +}; + +#endif /* !SP_FEDIFFUSELIGHTING_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/displacementmap.cpp b/src/object/filters/displacementmap.cpp new file mode 100644 index 000000000..978fd517b --- /dev/null +++ b/src/object/filters/displacementmap.cpp @@ -0,0 +1,257 @@ +/** \file + * SVG implementation. + * + */ +/* + * Authors: + * hugo Rodrigues + * Abhishek Sharma + * + * Copyright (C) 2006 Hugo Rodrigues + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "displacementmap.h" + +#include "attributes.h" +#include "helper-fns.h" + +#include "display/nr-filter-displacement-map.h" +#include "display/nr-filter.h" + +#include "object/sp-filter.h" + +#include "svg/svg.h" + +#include "xml/repr.h" + +SPFeDisplacementMap::SPFeDisplacementMap() : SPFilterPrimitive() { + this->scale=0; + this->xChannelSelector = DISPLACEMENTMAP_CHANNEL_ALPHA; + this->yChannelSelector = DISPLACEMENTMAP_CHANNEL_ALPHA; + this->in2 = Inkscape::Filters::NR_FILTER_SLOT_NOT_SET; +} + +SPFeDisplacementMap::~SPFeDisplacementMap() { +} + +/** + * Reads the Inkscape::XML::Node, and initializes SPFeDisplacementMap variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPFeDisplacementMap::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPFilterPrimitive::build(document, repr); + + /*LOAD ATTRIBUTES FROM REPR HERE*/ + this->readAttr( "scale" ); + this->readAttr( "in2" ); + this->readAttr( "xChannelSelector" ); + this->readAttr( "yChannelSelector" ); + + /* Unlike normal in, in2 is required attribute. Make sure, we can call + * it by some name. */ + if (this->in2 == Inkscape::Filters::NR_FILTER_SLOT_NOT_SET || + this->in2 == Inkscape::Filters::NR_FILTER_UNNAMED_SLOT) + { + SPFilter *parent = SP_FILTER(this->parent); + this->in2 = sp_filter_primitive_name_previous_out(this); + repr->setAttribute("in2", sp_filter_name_for_image(parent, this->in2)); + } +} + +/** + * Drops any allocated memory. + */ +void SPFeDisplacementMap::release() { + SPFilterPrimitive::release(); +} + +static FilterDisplacementMapChannelSelector sp_feDisplacementMap_readChannelSelector(gchar const *value) +{ + if (!value) return DISPLACEMENTMAP_CHANNEL_ALPHA; + + switch (value[0]) { + case 'R': + return DISPLACEMENTMAP_CHANNEL_RED; + break; + case 'G': + return DISPLACEMENTMAP_CHANNEL_GREEN; + break; + case 'B': + return DISPLACEMENTMAP_CHANNEL_BLUE; + break; + case 'A': + return DISPLACEMENTMAP_CHANNEL_ALPHA; + break; + default: + // error + g_warning("Invalid attribute for Channel Selector. Valid modes are 'R', 'G', 'B' or 'A'"); + break; + } + + return DISPLACEMENTMAP_CHANNEL_ALPHA; //default is Alpha Channel +} + +/** + * Sets a specific value in the SPFeDisplacementMap. + */ +void SPFeDisplacementMap::set(unsigned int key, gchar const *value) { + int input; + double read_num; + FilterDisplacementMapChannelSelector read_selector; + + switch(key) { + /*DEAL WITH SETTING ATTRIBUTES HERE*/ + case SP_ATTR_XCHANNELSELECTOR: + read_selector = sp_feDisplacementMap_readChannelSelector(value); + + if (read_selector != this->xChannelSelector){ + this->xChannelSelector = read_selector; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_YCHANNELSELECTOR: + read_selector = sp_feDisplacementMap_readChannelSelector(value); + + if (read_selector != this->yChannelSelector){ + this->yChannelSelector = read_selector; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_SCALE: + read_num = value ? helperfns_read_number(value) : 0; + + if (read_num != this->scale) { + this->scale = read_num; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_IN2: + input = sp_filter_primitive_read_in(this, value); + + if (input != this->in2) { + this->in2 = input; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + default: + SPFilterPrimitive::set(key, value); + break; + } +} + +/** + * Receives update notifications. + */ +void SPFeDisplacementMap::update(SPCtx *ctx, guint flags) { + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + + /* do something to trigger redisplay, updates? */ + + } + + /* Unlike normal in, in2 is required attribute. Make sure, we can call + * it by some name. */ + if (this->in2 == Inkscape::Filters::NR_FILTER_SLOT_NOT_SET || + this->in2 == Inkscape::Filters::NR_FILTER_UNNAMED_SLOT) + { + SPFilter *parent = SP_FILTER(this->parent); + this->in2 = sp_filter_primitive_name_previous_out(this); + + //XML Tree being used directly here while it shouldn't be. + this->getRepr()->setAttribute("in2", sp_filter_name_for_image(parent, this->in2)); + } + + SPFilterPrimitive::update(ctx, flags); +} + +static char const * get_channelselector_name(FilterDisplacementMapChannelSelector selector) { + switch(selector) { + case DISPLACEMENTMAP_CHANNEL_RED: + return "R"; + case DISPLACEMENTMAP_CHANNEL_GREEN: + return "G"; + case DISPLACEMENTMAP_CHANNEL_BLUE: + return "B"; + case DISPLACEMENTMAP_CHANNEL_ALPHA: + return "A"; + default: + return 0; + } +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPFeDisplacementMap::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + SPFilter *parent = SP_FILTER(this->parent); + + if (!repr) { + repr = doc->createElement("svg:feDisplacementMap"); + } + + gchar const *in2_name = sp_filter_name_for_image(parent, this->in2); + + if( !in2_name ) { + + // This code is very similar to sp_filter_primtive_name_previous_out() + SPObject *i = parent->firstChild(); + + // Find previous filter primitive + while (i && i->getNext() != this) { + i = i->getNext(); + } + + if( i ) { + SPFilterPrimitive *i_prim = SP_FILTER_PRIMITIVE(i); + in2_name = sp_filter_name_for_image(parent, i_prim->image_out); + } + } + + if (in2_name) { + repr->setAttribute("in2", in2_name); + } else { + g_warning("Unable to set in2 for feDisplacementMap"); + } + + sp_repr_set_svg_double(repr, "scale", this->scale); + repr->setAttribute("xChannelSelector", + get_channelselector_name(this->xChannelSelector)); + repr->setAttribute("yChannelSelector", + get_channelselector_name(this->yChannelSelector)); + + SPFilterPrimitive::write(doc, repr, flags); + + return repr; +} + +void SPFeDisplacementMap::build_renderer(Inkscape::Filters::Filter* filter) { + g_assert(this != NULL); + g_assert(filter != NULL); + + int primitive_n = filter->add_primitive(Inkscape::Filters::NR_FILTER_DISPLACEMENTMAP); + Inkscape::Filters::FilterPrimitive *nr_primitive = filter->get_primitive(primitive_n); + Inkscape::Filters::FilterDisplacementMap *nr_displacement_map = dynamic_cast(nr_primitive); + g_assert(nr_displacement_map != NULL); + + sp_filter_primitive_renderer_common(this, nr_primitive); + + nr_displacement_map->set_input(1, this->in2); + nr_displacement_map->set_scale(this->scale); + nr_displacement_map->set_channel_selector(0, this->xChannelSelector); + nr_displacement_map->set_channel_selector(1, this->yChannelSelector); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/displacementmap.h b/src/object/filters/displacementmap.h new file mode 100644 index 000000000..85a6beaaa --- /dev/null +++ b/src/object/filters/displacementmap.h @@ -0,0 +1,62 @@ +/** \file + * SVG displacement map filter effect + *//* + * Authors: + * Hugo Rodrigues + * + * Copyright (C) 2006 Hugo Rodrigues + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SP_FEDISPLACEMENTMAP_H_SEEN +#define SP_FEDISPLACEMENTMAP_H_SEEN + +#include "sp-filter-primitive.h" + +#define SP_FEDISPLACEMENTMAP(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FEDISPLACEMENTMAP(obj) (dynamic_cast((SPObject*)obj) != NULL) + +enum FilterDisplacementMapChannelSelector { + DISPLACEMENTMAP_CHANNEL_RED, + DISPLACEMENTMAP_CHANNEL_GREEN, + DISPLACEMENTMAP_CHANNEL_BLUE, + DISPLACEMENTMAP_CHANNEL_ALPHA, + DISPLACEMENTMAP_CHANNEL_ENDTYPE +}; + +class SPFeDisplacementMap : public SPFilterPrimitive { +public: + SPFeDisplacementMap(); + virtual ~SPFeDisplacementMap(); + + int in2; + double scale; + FilterDisplacementMapChannelSelector xChannelSelector; + FilterDisplacementMapChannelSelector yChannelSelector; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void set(unsigned int key, const gchar* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags); + + virtual void build_renderer(Inkscape::Filters::Filter* filter); +}; + +#endif /* !SP_FEDISPLACEMENTMAP_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/distantlight.cpp b/src/object/filters/distantlight.cpp new file mode 100644 index 000000000..076a7aab5 --- /dev/null +++ b/src/object/filters/distantlight.cpp @@ -0,0 +1,167 @@ +/** \file + * SVG implementation. + */ +/* + * Authors: + * Hugo Rodrigues + * Niko Kiirala + * Jean-Rene Reinhard + * Abhishek Sharma + * + * Copyright (C) 2006,2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +// In same dirctory +#include "distantlight.h" +#include "diffuselighting.h" +#include "specularlighting.h" + +#include "attributes.h" +#include "document.h" + +#include "xml/repr.h" + +#define SP_MACROS_SILENT + + +SPFeDistantLight::SPFeDistantLight() + : SPObject(), azimuth(0), azimuth_set(FALSE), elevation(0), elevation_set(FALSE) { +} + +SPFeDistantLight::~SPFeDistantLight() { +} + +/** + * Reads the Inkscape::XML::Node, and initializes SPDistantLight variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPFeDistantLight::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPObject::build(document, repr); + + //Read values of key attributes from XML nodes into object. + this->readAttr( "azimuth" ); + this->readAttr( "elevation" ); + +//is this necessary? + document->addResource("fedistantlight", this); +} + +/** + * Drops any allocated memory. + */ +void SPFeDistantLight::release() { + if ( this->document ) { + // Unregister ourselves + this->document->removeResource("fedistantlight", this); + } + +//TODO: release resources here +} + +/** + * Sets a specific value in the SPFeDistantLight. + */ +void SPFeDistantLight::set(unsigned int key, gchar const *value) { + gchar *end_ptr; + + switch (key) { + case SP_ATTR_AZIMUTH: + end_ptr =NULL; + + if (value) { + this->azimuth = g_ascii_strtod(value, &end_ptr); + + if (end_ptr) { + this->azimuth_set = TRUE; + } + } + + if (!value || !end_ptr) { + this->azimuth_set = FALSE; + this->azimuth = 0; + } + + if (this->parent && + (SP_IS_FEDIFFUSELIGHTING(this->parent) || + SP_IS_FESPECULARLIGHTING(this->parent))) { + this->parent->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_ELEVATION: + end_ptr =NULL; + + if (value) { + this->elevation = g_ascii_strtod(value, &end_ptr); + + if (end_ptr) { + this->elevation_set = TRUE; + } + } + + if (!value || !end_ptr) { + this->elevation_set = FALSE; + this->elevation = 0; + } + + if (this->parent && + (SP_IS_FEDIFFUSELIGHTING(this->parent) || + SP_IS_FESPECULARLIGHTING(this->parent))) { + this->parent->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + default: + // See if any parents need this value. + SPObject::set(key, value); + break; + } +} + +/** + * * Receives update notifications. + * */ +void SPFeDistantLight::update(SPCtx *ctx, guint flags) { + if (flags & SP_OBJECT_MODIFIED_FLAG) { + /* do something to trigger redisplay, updates? */ + this->readAttr( "azimuth" ); + this->readAttr( "elevation" ); + } + + SPObject::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPFeDistantLight::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + if (!repr) { + repr = this->getRepr()->duplicate(doc); + } + + if (this->azimuth_set) { + sp_repr_set_css_double(repr, "azimuth", this->azimuth); + } + + if (this->elevation_set) { + sp_repr_set_css_double(repr, "elevation", this->elevation); + } + + SPObject::write(doc, repr, flags); + + return repr; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/distantlight.h b/src/object/filters/distantlight.h new file mode 100644 index 000000000..1aa68a1d0 --- /dev/null +++ b/src/object/filters/distantlight.h @@ -0,0 +1,58 @@ +#ifndef SP_FEDISTANTLIGHT_H_SEEN +#define SP_FEDISTANTLIGHT_H_SEEN + +/** \file + * SVG implementation, see sp-filter.cpp. + */ +/* + * Authors: + * Hugo Rodrigues + * Niko Kiirala + * Jean-Rene Reinhard + * + * Copyright (C) 2006,2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "../sp-object.h" + +#define SP_FEDISTANTLIGHT(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FEDISTANTLIGHT(obj) (dynamic_cast((SPObject*)obj) != NULL) + +/* Distant light class */ +class SPFeDistantLight : public SPObject { +public: + SPFeDistantLight(); + virtual ~SPFeDistantLight(); + + /** azimuth attribute */ + float azimuth; + unsigned int azimuth_set : 1; + /** elevation attribute */ + float elevation; + unsigned int elevation_set : 1; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void set(unsigned int key, char const* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); +}; + +#endif /* !SP_FEDISTANTLIGHT_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/flood.cpp b/src/object/filters/flood.cpp new file mode 100644 index 000000000..9132b2028 --- /dev/null +++ b/src/object/filters/flood.cpp @@ -0,0 +1,181 @@ +/** \file + * SVG implementation. + * + */ +/* + * Authors: + * hugo Rodrigues + * Abhishek Sharma + * + * Copyright (C) 2006 Hugo Rodrigues + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "flood.h" + +#include "strneq.h" +#include "attributes.h" + +#include "svg/svg.h" +#include "svg/svg-color.h" + +#include "display/nr-filter.h" +#include "display/nr-filter-flood.h" + +#include "xml/repr.h" + +SPFeFlood::SPFeFlood() : SPFilterPrimitive() { + this->color = 0; + + this->opacity = 1; + this->icc = NULL; +} + +SPFeFlood::~SPFeFlood() { +} + +/** + * Reads the Inkscape::XML::Node, and initializes SPFeFlood variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPFeFlood::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPFilterPrimitive::build(document, repr); + + /*LOAD ATTRIBUTES FROM REPR HERE*/ + this->readAttr( "flood-opacity" ); + this->readAttr( "flood-color" ); +} + +/** + * Drops any allocated memory. + */ +void SPFeFlood::release() { + SPFilterPrimitive::release(); +} + +/** + * Sets a specific value in the SPFeFlood. + */ +void SPFeFlood::set(unsigned int key, gchar const *value) { + gchar const *cend_ptr = NULL; + gchar *end_ptr = NULL; + guint32 read_color; + double read_num; + bool dirty = false; + + switch(key) { + /*DEAL WITH SETTING ATTRIBUTES HERE*/ + case SP_PROP_FLOOD_COLOR: + cend_ptr = NULL; + read_color = sp_svg_read_color(value, &cend_ptr, 0xffffffff); + + if (cend_ptr && read_color != this->color){ + this->color = read_color; + dirty=true; + } + + if (cend_ptr){ + while (g_ascii_isspace(*cend_ptr)) { + ++cend_ptr; + } + + if (strneq(cend_ptr, "icc-color(", 10)) { + if (!this->icc) { + this->icc = new SVGICCColor(); + } + + if ( ! sp_svg_read_icc_color( cend_ptr, this->icc ) ) { + delete this->icc; + this->icc = NULL; + } + + dirty = true; + } + } + + if (dirty) { + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_PROP_FLOOD_OPACITY: + if (value) { + read_num = g_ascii_strtod(value, &end_ptr); + + if (end_ptr != NULL) { + if (*end_ptr) { + g_warning("Unable to convert \"%s\" to number", value); + read_num = 1; + } + } + } else { + read_num = 1; + } + + if (read_num != this->opacity) { + this->opacity = read_num; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + default: + SPFilterPrimitive::set(key, value); + break; + } +} + +/** + * Receives update notifications. + */ +void SPFeFlood::update(SPCtx *ctx, guint flags) { + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + + /* do something to trigger redisplay, updates? */ + + } + + SPFilterPrimitive::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPFeFlood::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + /* TODO: Don't just clone, but create a new repr node and write all + * relevant values into it */ + if (!repr) { + repr = this->getRepr()->duplicate(doc); + } + + SPFilterPrimitive::write(doc, repr, flags); + + return repr; +} + +void SPFeFlood::build_renderer(Inkscape::Filters::Filter* filter) { + g_assert(this != NULL); + g_assert(filter != NULL); + + int primitive_n = filter->add_primitive(Inkscape::Filters::NR_FILTER_FLOOD); + Inkscape::Filters::FilterPrimitive *nr_primitive = filter->get_primitive(primitive_n); + Inkscape::Filters::FilterFlood *nr_flood = dynamic_cast(nr_primitive); + g_assert(nr_flood != NULL); + + sp_filter_primitive_renderer_common(this, nr_primitive); + + nr_flood->set_opacity(this->opacity); + nr_flood->set_color(this->color); + nr_flood->set_icc(this->icc); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/flood.h b/src/object/filters/flood.h new file mode 100644 index 000000000..75e332b73 --- /dev/null +++ b/src/object/filters/flood.h @@ -0,0 +1,54 @@ +/** @file + * @brief SVG flood filter effect + *//* + * Authors: + * Hugo Rodrigues + * + * Copyright (C) 2006 Hugo Rodrigues + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SP_FEFLOOD_H_SEEN +#define SP_FEFLOOD_H_SEEN + +#include "sp-filter-primitive.h" +#include "svg/svg-icc-color.h" + +#define SP_FEFLOOD(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FEFLOOD(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPFeFlood : public SPFilterPrimitive { +public: + SPFeFlood(); + virtual ~SPFeFlood(); + + guint32 color; + SVGICCColor *icc; + double opacity; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void set(unsigned int key, const gchar* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags); + + virtual void build_renderer(Inkscape::Filters::Filter* filter); +}; + +#endif /* !SP_FEFLOOD_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/gaussian-blur.cpp b/src/object/filters/gaussian-blur.cpp new file mode 100644 index 000000000..81addb8e1 --- /dev/null +++ b/src/object/filters/gaussian-blur.cpp @@ -0,0 +1,136 @@ +/** \file + * SVG implementation. + * + */ +/* + * Authors: + * Hugo Rodrigues + * Niko Kiirala + * Abhishek Sharma + * + * Copyright (C) 2006,2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gaussian-blur.h" + +#include "attributes.h" + +#include "display/nr-filter.h" +#include "display/nr-filter-gaussian.h" + +#include "svg/svg.h" + +#include "xml/repr.h" + +SPGaussianBlur::SPGaussianBlur() : SPFilterPrimitive() { +} + +SPGaussianBlur::~SPGaussianBlur() { +} + +/** + * Reads the Inkscape::XML::Node, and initializes SPGaussianBlur variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPGaussianBlur::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPFilterPrimitive::build(document, repr); + + this->readAttr( "stdDeviation" ); +} + +/** + * Drops any allocated memory. + */ +void SPGaussianBlur::release() { + SPFilterPrimitive::release(); +} + +/** + * Sets a specific value in the SPGaussianBlur. + */ +void SPGaussianBlur::set(unsigned int key, gchar const *value) { + switch(key) { + case SP_ATTR_STDDEVIATION: + this->stdDeviation.set(value); + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + default: + SPFilterPrimitive::set(key, value); + break; + } +} + +/** + * Receives update notifications. + */ +void SPGaussianBlur::update(SPCtx *ctx, guint flags) { + if (flags & SP_OBJECT_MODIFIED_FLAG) { + this->readAttr( "stdDeviation" ); + } + + SPFilterPrimitive::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPGaussianBlur::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + /* TODO: Don't just clone, but create a new repr node and write all + * relevant values into it */ + if (!repr) { + repr = this->getRepr()->duplicate(doc); + } + + SPFilterPrimitive::write(doc, repr, flags); + + return repr; +} + +void sp_gaussianBlur_setDeviation(SPGaussianBlur *blur, float num) +{ + blur->stdDeviation.setNumber(num); +} + +void sp_gaussianBlur_setDeviation(SPGaussianBlur *blur, float num, float optnum) +{ + blur->stdDeviation.setNumber(num); + blur->stdDeviation.setOptNumber(optnum); +} + +void SPGaussianBlur::build_renderer(Inkscape::Filters::Filter* filter) { + int handle = filter->add_primitive(Inkscape::Filters::NR_FILTER_GAUSSIANBLUR); + Inkscape::Filters::FilterPrimitive *nr_primitive = filter->get_primitive(handle); + Inkscape::Filters::FilterGaussian *nr_blur = dynamic_cast(nr_primitive); + + sp_filter_primitive_renderer_common(this, nr_primitive); + + gfloat num = this->stdDeviation.getNumber(); + + if (num >= 0.0) { + gfloat optnum = this->stdDeviation.getOptNumber(); + + if(optnum >= 0.0) { + nr_blur->set_deviation((double) num, (double) optnum); + } else { + nr_blur->set_deviation((double) num); + } + } +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/gaussian-blur.h b/src/object/filters/gaussian-blur.h new file mode 100644 index 000000000..00de8a95f --- /dev/null +++ b/src/object/filters/gaussian-blur.h @@ -0,0 +1,56 @@ +/** @file + * @brief SVG Gaussian blur filter effect + *//* + * Authors: + * Hugo Rodrigues + * + * Copyright (C) 2006 Hugo Rodrigues + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SP_GAUSSIANBLUR_H_SEEN +#define SP_GAUSSIANBLUR_H_SEEN + +#include "sp-filter-primitive.h" +#include "number-opt-number.h" + +#define SP_GAUSSIANBLUR(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_GAUSSIANBLUR(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPGaussianBlur : public SPFilterPrimitive { +public: + SPGaussianBlur(); + virtual ~SPGaussianBlur(); + + /** stdDeviation attribute */ + NumberOptNumber stdDeviation; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void set(unsigned int key, const gchar* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags); + + virtual void build_renderer(Inkscape::Filters::Filter* filter); +}; + +void sp_gaussianBlur_setDeviation(SPGaussianBlur *blur, float num); +void sp_gaussianBlur_setDeviation(SPGaussianBlur *blur, float num, float optnum); + +#endif /* !SP_GAUSSIANBLUR_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/image.cpp b/src/object/filters/image.cpp new file mode 100644 index 000000000..1eeb32111 --- /dev/null +++ b/src/object/filters/image.cpp @@ -0,0 +1,262 @@ +/** \file + * SVG implementation. + * + */ +/* + * Authors: + * Felipe Corrêa da Silva Sanches + * hugo Rodrigues + * Abhishek Sharma + * + * Copyright (C) 2007 Felipe Sanches + * Copyright (C) 2006 Hugo Rodrigues + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "image.h" + +#include + +#include "attributes.h" +#include "enums.h" + +#include "bad-uri-exception.h" + +#include "object/sp-image.h" +#include "object/uri.h" +#include "object/uri-references.h" + +#include "display/nr-filter-image.h" +#include "display/nr-filter.h" + +#include "xml/repr.h" + + +SPFeImage::SPFeImage() : SPFilterPrimitive() { + this->href = NULL; + this->from_element = 0; + this->SVGElemRef = NULL; + this->SVGElem = NULL; + + this->aspect_align = SP_ASPECT_XMID_YMID; // Default + this->aspect_clip = SP_ASPECT_MEET; // Default +} + +SPFeImage::~SPFeImage() { +} + +/** + * Reads the Inkscape::XML::Node, and initializes SPFeImage variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPFeImage::build(SPDocument *document, Inkscape::XML::Node *repr) +{ + SPFilterPrimitive::build(document, repr); + + /*LOAD ATTRIBUTES FROM REPR HERE*/ + + this->readAttr( "preserveAspectRatio" ); + this->readAttr( "xlink:href" ); +} + +/** + * Drops any allocated memory. + */ +void SPFeImage::release() { + this->_image_modified_connection.disconnect(); + this->_href_modified_connection.disconnect(); + + if (this->SVGElemRef) { + delete this->SVGElemRef; + } + + SPFilterPrimitive::release(); +} + +static void sp_feImage_elem_modified(SPObject* /*href*/, guint /*flags*/, SPObject* obj) +{ + obj->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +static void sp_feImage_href_modified(SPObject* /*old_elem*/, SPObject* new_elem, SPObject* obj) +{ + SPFeImage *feImage = SP_FEIMAGE(obj); + feImage->_image_modified_connection.disconnect(); + if (new_elem) { + feImage->SVGElem = SP_ITEM(new_elem); + feImage->_image_modified_connection = ((SPObject*) feImage->SVGElem)->connectModified(sigc::bind(sigc::ptr_fun(&sp_feImage_elem_modified), obj)); + } else { + feImage->SVGElem = 0; + } + + obj->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/** + * Sets a specific value in the SPFeImage. + */ +void SPFeImage::set(unsigned int key, gchar const *value) { + switch(key) { + /*DEAL WITH SETTING ATTRIBUTES HERE*/ + case SP_ATTR_XLINK_HREF: + if (this->href) { + g_free(this->href); + } + this->href = (value) ? g_strdup (value) : NULL; + if (!this->href) return; + delete this->SVGElemRef; + this->SVGElemRef = 0; + this->SVGElem = 0; + this->_image_modified_connection.disconnect(); + this->_href_modified_connection.disconnect(); + try{ + Inkscape::URI SVGElem_uri(this->href); + this->SVGElemRef = new Inkscape::URIReference(this->document); + this->SVGElemRef->attach(SVGElem_uri); + this->from_element = true; + this->_href_modified_connection = this->SVGElemRef->changedSignal().connect(sigc::bind(sigc::ptr_fun(&sp_feImage_href_modified), this)); + if (SPObject *elemref = this->SVGElemRef->getObject()) { + this->SVGElem = SP_ITEM(elemref); + this->_image_modified_connection = ((SPObject*) this->SVGElem)->connectModified(sigc::bind(sigc::ptr_fun(&sp_feImage_elem_modified), this)); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } else { + g_warning("SVG element URI was not found in the document while loading this: %s", value); + } + } + // catches either MalformedURIException or UnsupportedURIException + catch(const Inkscape::BadURIException & e) + { + this->from_element = false; + /* This occurs when using external image as the source */ + //g_warning("caught Inkscape::BadURIException in sp_feImage_set"); + break; + } + break; + + case SP_ATTR_PRESERVEASPECTRATIO: + /* Copied from sp-image.cpp */ + /* Do setup before, so we can use break to escape */ + this->aspect_align = SP_ASPECT_XMID_YMID; // Default + this->aspect_clip = SP_ASPECT_MEET; // Default + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + if (value) { + int len; + gchar c[256]; + const gchar *p, *e; + unsigned int align, clip; + p = value; + while (*p && *p == 32) p += 1; + if (!*p) break; + e = p; + while (*e && *e != 32) e += 1; + len = e - p; + if (len > 8) break; + memcpy (c, value, len); + c[len] = 0; + /* Now the actual part */ + if (!strcmp (c, "none")) { + align = SP_ASPECT_NONE; + } else if (!strcmp (c, "xMinYMin")) { + align = SP_ASPECT_XMIN_YMIN; + } else if (!strcmp (c, "xMidYMin")) { + align = SP_ASPECT_XMID_YMIN; + } else if (!strcmp (c, "xMaxYMin")) { + align = SP_ASPECT_XMAX_YMIN; + } else if (!strcmp (c, "xMinYMid")) { + align = SP_ASPECT_XMIN_YMID; + } else if (!strcmp (c, "xMidYMid")) { + align = SP_ASPECT_XMID_YMID; + } else if (!strcmp (c, "xMaxYMid")) { + align = SP_ASPECT_XMAX_YMID; + } else if (!strcmp (c, "xMinYMax")) { + align = SP_ASPECT_XMIN_YMAX; + } else if (!strcmp (c, "xMidYMax")) { + align = SP_ASPECT_XMID_YMAX; + } else if (!strcmp (c, "xMaxYMax")) { + align = SP_ASPECT_XMAX_YMAX; + } else { + g_warning("Illegal preserveAspectRatio: %s", c); + break; + } + clip = SP_ASPECT_MEET; + while (*e && *e == 32) e += 1; + if (*e) { + if (!strcmp (e, "meet")) { + clip = SP_ASPECT_MEET; + } else if (!strcmp (e, "slice")) { + clip = SP_ASPECT_SLICE; + } else { + break; + } + } + this->aspect_align = align; + this->aspect_clip = clip; + } + break; + + default: + SPFilterPrimitive::set(key, value); + break; + } +} + +/** + * Receives update notifications. + */ +void SPFeImage::update(SPCtx *ctx, guint flags) { + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + + /* do something to trigger redisplay, updates? */ + } + + SPFilterPrimitive::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPFeImage::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + /* TODO: Don't just clone, but create a new repr node and write all + * relevant values into it */ + if (!repr) { + repr = this->getRepr()->duplicate(doc); + } + + SPFilterPrimitive::write(doc, repr, flags); + + return repr; +} + +void SPFeImage::build_renderer(Inkscape::Filters::Filter* filter) { + g_assert(this != NULL); + g_assert(filter != NULL); + + int primitive_n = filter->add_primitive(Inkscape::Filters::NR_FILTER_IMAGE); + Inkscape::Filters::FilterPrimitive *nr_primitive = filter->get_primitive(primitive_n); + Inkscape::Filters::FilterImage *nr_image = dynamic_cast(nr_primitive); + g_assert(nr_image != NULL); + + sp_filter_primitive_renderer_common(this, nr_primitive); + + nr_image->from_element = this->from_element; + nr_image->SVGElem = this->SVGElem; + nr_image->set_align( this->aspect_align ); + nr_image->set_clip( this->aspect_clip ); + nr_image->set_href(this->href); + nr_image->set_document(this->document); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/image.h b/src/object/filters/image.h new file mode 100644 index 000000000..26524c166 --- /dev/null +++ b/src/object/filters/image.h @@ -0,0 +1,68 @@ +/** @file + * @brief SVG image filter effect + *//* + * Authors: + * Felipe Corrêa da Silva Sanches + * Hugo Rodrigues + * + * Copyright (C) 2006 Hugo Rodrigues + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SP_FEIMAGE_H_SEEN +#define SP_FEIMAGE_H_SEEN + +#include "sp-filter-primitive.h" + +#define SP_FEIMAGE(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FEIMAGE(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPItem; + +namespace Inkscape { +class URIReference; +} + +class SPFeImage : public SPFilterPrimitive { +public: + SPFeImage(); + virtual ~SPFeImage(); + + gchar *href; + + /* preserveAspectRatio */ + unsigned int aspect_align : 4; + unsigned int aspect_clip : 1; + + bool from_element; + SPItem* SVGElem; + Inkscape::URIReference* SVGElemRef; + sigc::connection _image_modified_connection; + sigc::connection _href_modified_connection; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void set(unsigned int key, const gchar* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags); + + virtual void build_renderer(Inkscape::Filters::Filter* filter); +}; + +#endif /* !SP_FEIMAGE_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/merge.cpp b/src/object/filters/merge.cpp new file mode 100644 index 000000000..8ec40cb46 --- /dev/null +++ b/src/object/filters/merge.cpp @@ -0,0 +1,116 @@ +/** \file + * SVG implementation. + * + */ +/* + * Authors: + * hugo Rodrigues + * + * Copyright (C) 2006 Hugo Rodrigues + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "attributes.h" +#include "svg/svg.h" +#include "xml/repr.h" + +#include "merge.h" +#include "mergenode.h" +#include "display/nr-filter.h" +#include "display/nr-filter-merge.h" + +SPFeMerge::SPFeMerge() : SPFilterPrimitive() { +} + +SPFeMerge::~SPFeMerge() { +} + +/** + * Reads the Inkscape::XML::Node, and initializes SPFeMerge variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPFeMerge::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPFilterPrimitive::build(document, repr); +} + +/** + * Drops any allocated memory. + */ +void SPFeMerge::release() { + SPFilterPrimitive::release(); +} + +/** + * Sets a specific value in the SPFeMerge. + */ +void SPFeMerge::set(unsigned int key, gchar const *value) { + switch(key) { + /*DEAL WITH SETTING ATTRIBUTES HERE*/ + default: + SPFilterPrimitive::set(key, value); + break; + } +} + +/** + * Receives update notifications. + */ +void SPFeMerge::update(SPCtx *ctx, guint flags) { + if (flags & SP_OBJECT_MODIFIED_FLAG) { + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + + SPFilterPrimitive::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPFeMerge::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + /* TODO: Don't just clone, but create a new repr node and write all + * relevant values into it. And child nodes, too! */ + if (!repr) { + repr = this->getRepr()->duplicate(doc); + } + + + SPFilterPrimitive::write(doc, repr, flags); + + return repr; +} + +void SPFeMerge::build_renderer(Inkscape::Filters::Filter* filter) { + g_assert(this != NULL); + g_assert(filter != NULL); + + int primitive_n = filter->add_primitive(Inkscape::Filters::NR_FILTER_MERGE); + Inkscape::Filters::FilterPrimitive *nr_primitive = filter->get_primitive(primitive_n); + Inkscape::Filters::FilterMerge *nr_merge = dynamic_cast(nr_primitive); + g_assert(nr_merge != NULL); + + sp_filter_primitive_renderer_common(this, nr_primitive); + + int in_nr = 0; + + for(auto& input: children) { + if (SP_IS_FEMERGENODE(&input)) { + SPFeMergeNode *node = SP_FEMERGENODE(&input); + nr_merge->set_input(in_nr, node->input); + in_nr++; + } + } +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/merge.h b/src/object/filters/merge.h new file mode 100644 index 000000000..68257c38e --- /dev/null +++ b/src/object/filters/merge.h @@ -0,0 +1,47 @@ +/** \file + * SVG merge filter effect + *//* + * Authors: + * Hugo Rodrigues + * + * Copyright (C) 2006 Hugo Rodrigues + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#ifndef SP_FEMERGE_H_SEEN +#define SP_FEMERGE_H_SEEN + +#include "sp-filter-primitive.h" + +#define SP_FEMERGE(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FEMERGE(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPFeMerge : public SPFilterPrimitive { +public: + SPFeMerge(); + virtual ~SPFeMerge(); + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void set(unsigned int key, const gchar* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags); + + virtual void build_renderer(Inkscape::Filters::Filter* filter); +}; + +#endif /* !SP_FEMERGE_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/mergenode.cpp b/src/object/filters/mergenode.cpp new file mode 100644 index 000000000..04ab2af95 --- /dev/null +++ b/src/object/filters/mergenode.cpp @@ -0,0 +1,106 @@ +/** \file + * feMergeNode implementation. A feMergeNode contains the name of one + * input image for feMerge. + */ +/* + * Authors: + * Kees Cook + * Niko Kiirala + * Abhishek Sharma + * + * Copyright (C) 2004,2007 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "mergenode.h" +#include "merge.h" + +#include "attributes.h" + +#include "display/nr-filter-types.h" + +#include "xml/repr.h" + +SPFeMergeNode::SPFeMergeNode() + : SPObject(), input(Inkscape::Filters::NR_FILTER_SLOT_NOT_SET) { +} + +SPFeMergeNode::~SPFeMergeNode() { +} + +/** + * Reads the Inkscape::XML::Node, and initializes SPFeMergeNode variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPFeMergeNode::build(SPDocument */*document*/, Inkscape::XML::Node */*repr*/) { + this->readAttr( "in" ); +} + +/** + * Drops any allocated memory. + */ +void SPFeMergeNode::release() { + SPObject::release(); +} + +/** + * Sets a specific value in the SPFeMergeNode. + */ +void SPFeMergeNode::set(unsigned int key, gchar const *value) { + SPFeMerge *parent = SP_FEMERGE(this->parent); + + if (key == SP_ATTR_IN) { + int input = sp_filter_primitive_read_in(parent, value); + if (input != this->input) { + this->input = input; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + } + + /* See if any parents need this value. */ + SPObject::set(key, value); +} + +/** + * Receives update notifications. + */ +void SPFeMergeNode::update(SPCtx *ctx, guint flags) { + if (flags & SP_OBJECT_MODIFIED_FLAG) { + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + + SPObject::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPFeMergeNode::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + // Inkscape-only this, not copied during an "plain SVG" dump: + if (flags & SP_OBJECT_WRITE_EXT) { + if (repr) { + // is this sane? + //repr->mergeFrom(object->getRepr(), "id"); + } else { + repr = this->getRepr()->duplicate(doc); + } + } + + SPObject::write(doc, repr, flags); + + return repr; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/mergenode.h b/src/object/filters/mergenode.h new file mode 100644 index 000000000..f2d204ad4 --- /dev/null +++ b/src/object/filters/mergenode.h @@ -0,0 +1,52 @@ +#ifndef SP_FEMERGENODE_H_SEEN +#define SP_FEMERGENODE_H_SEEN + +/** \file + * feMergeNode implementation. A feMergeNode stores information about one + * input image for feMerge filter primitive. + */ +/* + * Authors: + * Kees Cook + * Niko Kiirala + * + * Copyright (C) 2004,2007 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "object/sp-object.h" + +#define SP_FEMERGENODE(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FEMERGENODE(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPFeMergeNode : public SPObject { +public: + SPFeMergeNode(); + virtual ~SPFeMergeNode(); + + int input; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void set(unsigned int key, const gchar* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags); +}; + +#endif /* !SP_FEMERGENODE_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/morphology.cpp b/src/object/filters/morphology.cpp new file mode 100644 index 000000000..b3cfa0697 --- /dev/null +++ b/src/object/filters/morphology.cpp @@ -0,0 +1,162 @@ +/** \file + * SVG implementation. + * + */ +/* + * Authors: + * Felipe Sanches + * Hugo Rodrigues + * Abhishek Sharma + * + * Copyright (C) 2006 Hugo Rodrigues + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include "attributes.h" +#include "svg/svg.h" +#include "morphology.h" +#include "xml/repr.h" +#include "display/nr-filter.h" + +SPFeMorphology::SPFeMorphology() : SPFilterPrimitive() { + this->Operator = Inkscape::Filters::MORPHOLOGY_OPERATOR_ERODE; + + //Setting default values: + this->radius.set("0"); +} + +SPFeMorphology::~SPFeMorphology() { +} + +/** + * Reads the Inkscape::XML::Node, and initializes SPFeMorphology variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPFeMorphology::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPFilterPrimitive::build(document, repr); + + /*LOAD ATTRIBUTES FROM REPR HERE*/ + this->readAttr( "operator" ); + this->readAttr( "radius" ); +} + +/** + * Drops any allocated memory. + */ +void SPFeMorphology::release() { + SPFilterPrimitive::release(); +} + +static Inkscape::Filters::FilterMorphologyOperator sp_feMorphology_read_operator(gchar const *value){ + if (!value) { + return Inkscape::Filters::MORPHOLOGY_OPERATOR_ERODE; //erode is default + } + + switch(value[0]){ + case 'e': + if (strncmp(value, "erode", 5) == 0) { + return Inkscape::Filters::MORPHOLOGY_OPERATOR_ERODE; + } + break; + case 'd': + if (strncmp(value, "dilate", 6) == 0) { + return Inkscape::Filters::MORPHOLOGY_OPERATOR_DILATE; + } + break; + } + + return Inkscape::Filters::MORPHOLOGY_OPERATOR_ERODE; //erode is default +} + +/** + * Sets a specific value in the SPFeMorphology. + */ +void SPFeMorphology::set(unsigned int key, gchar const *value) { + Inkscape::Filters::FilterMorphologyOperator read_operator; + + switch(key) { + /*DEAL WITH SETTING ATTRIBUTES HERE*/ + case SP_ATTR_OPERATOR: + read_operator = sp_feMorphology_read_operator(value); + + if (read_operator != this->Operator){ + this->Operator = read_operator; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_RADIUS: + this->radius.set(value); + + //From SVG spec: If is not provided, it defaults to . + if (this->radius.optNumIsSet() == false) { + this->radius.setOptNumber(this->radius.getNumber()); + } + + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + default: + SPFilterPrimitive::set(key, value); + break; + } + +} + +/** + * Receives update notifications. + */ +void SPFeMorphology::update(SPCtx *ctx, guint flags) { + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + + /* do something to trigger redisplay, updates? */ + + } + + SPFilterPrimitive::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPFeMorphology::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + /* TODO: Don't just clone, but create a new repr node and write all + * relevant values into it */ + if (!repr) { + repr = this->getRepr()->duplicate(doc); + } + + SPFilterPrimitive::write(doc, repr, flags); + + return repr; +} + +void SPFeMorphology::build_renderer(Inkscape::Filters::Filter* filter) { + g_assert(this != NULL); + g_assert(filter != NULL); + + int primitive_n = filter->add_primitive(Inkscape::Filters::NR_FILTER_MORPHOLOGY); + Inkscape::Filters::FilterPrimitive *nr_primitive = filter->get_primitive(primitive_n); + Inkscape::Filters::FilterMorphology *nr_morphology = dynamic_cast(nr_primitive); + g_assert(nr_morphology != NULL); + + sp_filter_primitive_renderer_common(this, nr_primitive); + + nr_morphology->set_operator(this->Operator); + nr_morphology->set_xradius( this->radius.getNumber() ); + nr_morphology->set_yradius( this->radius.getOptNumber() ); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/morphology.h b/src/object/filters/morphology.h new file mode 100644 index 000000000..f84a7271e --- /dev/null +++ b/src/object/filters/morphology.h @@ -0,0 +1,54 @@ +/** \file + * @brief SVG morphology filter effect + *//* + * Authors: + * Hugo Rodrigues + * + * Copyright (C) 2006 Hugo Rodrigues + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SP_FEMORPHOLOGY_H_SEEN +#define SP_FEMORPHOLOGY_H_SEEN + +#include "sp-filter-primitive.h" +#include "number-opt-number.h" +#include "display/nr-filter-morphology.h" + +#define SP_FEMORPHOLOGY(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FEMORPHOLOGY(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPFeMorphology : public SPFilterPrimitive { +public: + SPFeMorphology(); + virtual ~SPFeMorphology(); + + Inkscape::Filters::FilterMorphologyOperator Operator; + NumberOptNumber radius; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void set(unsigned int key, const gchar* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags); + + virtual void build_renderer(Inkscape::Filters::Filter* filter); +}; + +#endif /* !SP_FEMORPHOLOGY_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/offset.cpp b/src/object/filters/offset.cpp new file mode 100644 index 000000000..a0057d722 --- /dev/null +++ b/src/object/filters/offset.cpp @@ -0,0 +1,138 @@ +/** \file + * SVG implementation. + * + */ +/* + * Authors: + * hugo Rodrigues + * Niko Kiirala + * Abhishek Sharma + * + * Copyright (C) 2006,2007 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "offset.h" + +#include "attributes.h" +#include "helper-fns.h" + +#include "display/nr-filter.h" +#include "display/nr-filter-offset.h" + +#include "svg/svg.h" + +#include "xml/repr.h" + +SPFeOffset::SPFeOffset() : SPFilterPrimitive() { + this->dx = 0; + this->dy = 0; +} + +SPFeOffset::~SPFeOffset() { +} + +/** + * Reads the Inkscape::XML::Node, and initializes SPFeOffset variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPFeOffset::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPFilterPrimitive::build(document, repr); + + this->readAttr( "dx" ); + this->readAttr( "dy" ); +} + +/** + * Drops any allocated memory. + */ +void SPFeOffset::release() { + SPFilterPrimitive::release(); +} + +/** + * Sets a specific value in the SPFeOffset. + */ +void SPFeOffset::set(unsigned int key, gchar const *value) { + double read_num; + + switch(key) { + case SP_ATTR_DX: + read_num = value ? helperfns_read_number(value) : 0; + + if (read_num != this->dx) { + this->dx = read_num; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_DY: + read_num = value ? helperfns_read_number(value) : 0; + + if (read_num != this->dy) { + this->dy = read_num; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + + /*DEAL WITH SETTING ATTRIBUTES HERE*/ + default: + SPFilterPrimitive::set(key, value); + break; + } +} + +/** + * Receives update notifications. + */ +void SPFeOffset::update(SPCtx *ctx, guint flags) { + if (flags & SP_OBJECT_MODIFIED_FLAG) { + this->readAttr( "dx" ); + this->readAttr( "dy" ); + } + + SPFilterPrimitive::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPFeOffset::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + /* TODO: Don't just clone, but create a new repr node and write all + * relevant values into it */ + if (!repr) { + repr = this->getRepr()->duplicate(doc); + } + + SPFilterPrimitive::write(doc, repr, flags); + + return repr; +} + +void SPFeOffset::build_renderer(Inkscape::Filters::Filter* filter) { + g_assert(this != NULL); + g_assert(filter != NULL); + + int primitive_n = filter->add_primitive(Inkscape::Filters::NR_FILTER_OFFSET); + Inkscape::Filters::FilterPrimitive *nr_primitive = filter->get_primitive(primitive_n); + Inkscape::Filters::FilterOffset *nr_offset = dynamic_cast(nr_primitive); + g_assert(nr_offset != NULL); + + sp_filter_primitive_renderer_common(this, nr_primitive); + + nr_offset->set_dx(this->dx); + nr_offset->set_dy(this->dy); +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/offset.h b/src/object/filters/offset.h new file mode 100644 index 000000000..0d26f6f90 --- /dev/null +++ b/src/object/filters/offset.h @@ -0,0 +1,51 @@ +/** @file + * @brief SVG offset filter effect + *//* + * Authors: + * Hugo Rodrigues + * + * Copyright (C) 2006 Hugo Rodrigues + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SP_FEOFFSET_H_SEEN +#define SP_FEOFFSET_H_SEEN + +#include "sp-filter-primitive.h" + +#define SP_FEOFFSET(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FEOFFSET(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPFeOffset : public SPFilterPrimitive { +public: + SPFeOffset(); + virtual ~SPFeOffset(); + + double dx, dy; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void set(unsigned int key, const gchar* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags); + + virtual void build_renderer(Inkscape::Filters::Filter* filter); +}; + +#endif /* !SP_FEOFFSET_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/pointlight.cpp b/src/object/filters/pointlight.cpp new file mode 100644 index 000000000..942140c1b --- /dev/null +++ b/src/object/filters/pointlight.cpp @@ -0,0 +1,192 @@ +/** \file + * SVG implementation. + */ +/* + * Authors: + * Hugo Rodrigues + * Niko Kiirala + * Jean-Rene Reinhard + * Abhishek Sharma + * + * Copyright (C) 2006,2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +// Same directory +#include "pointlight.h" +#include "diffuselighting.h" +#include "specularlighting.h" + +#include + +#include "attributes.h" +#include "document.h" + + +#include "xml/node.h" +#include "xml/repr.h" + +#define SP_MACROS_SILENT + +SPFePointLight::SPFePointLight() + : SPObject(), x(0), x_set(FALSE), y(0), y_set(FALSE), z(0), z_set(FALSE) { +} + +SPFePointLight::~SPFePointLight() { +} + + +/** + * Reads the Inkscape::XML::Node, and initializes SPPointLight variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPFePointLight::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPObject::build(document, repr); + + //Read values of key attributes from XML nodes into object. + this->readAttr( "x" ); + this->readAttr( "y" ); + this->readAttr( "z" ); + +//is this necessary? + document->addResource("fepointlight", this); +} + +/** + * Drops any allocated memory. + */ +void SPFePointLight::release() { + if ( this->document ) { + // Unregister ourselves + this->document->removeResource("fepointlight", this); + } + +//TODO: release resources here +} + +/** + * Sets a specific value in the SPFePointLight. + */ +void SPFePointLight::set(unsigned int key, gchar const *value) { + gchar *end_ptr; + + switch (key) { + case SP_ATTR_X: + end_ptr = NULL; + + if (value) { + this->x = g_ascii_strtod(value, &end_ptr); + + if (end_ptr) { + this->x_set = TRUE; + } + } + + if (!value || !end_ptr) { + this->x = 0; + this->x_set = FALSE; + } + + if (this->parent && + (SP_IS_FEDIFFUSELIGHTING(this->parent) || + SP_IS_FESPECULARLIGHTING(this->parent))) { + this->parent->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_Y: + end_ptr = NULL; + + if (value) { + this->y = g_ascii_strtod(value, &end_ptr); + + if (end_ptr) { + this->y_set = TRUE; + } + } + + if (!value || !end_ptr) { + this->y = 0; + this->y_set = FALSE; + } + + if (this->parent && + (SP_IS_FEDIFFUSELIGHTING(this->parent) || + SP_IS_FESPECULARLIGHTING(this->parent))) { + this->parent->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_Z: + end_ptr = NULL; + + if (value) { + this->z = g_ascii_strtod(value, &end_ptr); + + if (end_ptr) { + this->z_set = TRUE; + } + } + + if (!value || !end_ptr) { + this->z = 0; + this->z_set = FALSE; + } + + if (this->parent && + (SP_IS_FEDIFFUSELIGHTING(this->parent) || + SP_IS_FESPECULARLIGHTING(this->parent))) { + this->parent->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + default: + // See if any parents need this value. + SPObject::set(key, value); + break; + } +} + +/** + * * Receives update notifications. + * */ +void SPFePointLight::update(SPCtx *ctx, guint flags) { + if (flags & SP_OBJECT_MODIFIED_FLAG) { + /* do something to trigger redisplay, updates? */ + this->readAttr( "x" ); + this->readAttr( "y" ); + this->readAttr( "z" ); + } + + SPObject::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPFePointLight::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + if (!repr) { + repr = this->getRepr()->duplicate(doc); + } + + if (this->x_set) + sp_repr_set_css_double(repr, "x", this->x); + if (this->y_set) + sp_repr_set_css_double(repr, "y", this->y); + if (this->z_set) + sp_repr_set_css_double(repr, "z", this->z); + + SPObject::write(doc, repr, flags); + + return repr; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/pointlight.h b/src/object/filters/pointlight.h new file mode 100644 index 000000000..2bd5496e1 --- /dev/null +++ b/src/object/filters/pointlight.h @@ -0,0 +1,60 @@ +/** \file + * SVG implementation, see sp-filter.cpp. + */ +#ifndef SP_FEPOINTLIGHT_H_SEEN +#define SP_FEPOINTLIGHT_H_SEEN + +/* + * Authors: + * Hugo Rodrigues + * Niko Kiirala + * Jean-Rene Reinhard + * + * Copyright (C) 2006,2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "object/sp-object.h" + +#define SP_FEPOINTLIGHT(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FEPOINTLIGHT(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPFePointLight : public SPObject { +public: + SPFePointLight(); + virtual ~SPFePointLight(); + + /** x coordinate of the light source */ + float x; + unsigned int x_set : 1; + /** y coordinate of the light source */ + float y; + unsigned int y_set : 1; + /** z coordinate of the light source */ + float z; + unsigned int z_set : 1; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void set(unsigned int key, char const* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); +}; + +#endif /* !SP_FEPOINTLIGHT_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/sp-filter-primitive.cpp b/src/object/filters/sp-filter-primitive.cpp new file mode 100644 index 000000000..e5381373d --- /dev/null +++ b/src/object/filters/sp-filter-primitive.cpp @@ -0,0 +1,278 @@ +/** \file + * Superclass for all the filter primitives + * + */ +/* + * Authors: + * Kees Cook + * Niko Kiirala + * Abhishek Sharma + * + * Copyright (C) 2004-2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "sp-filter-primitive.h" + +#include "attributes.h" + +#include "display/nr-filter-primitive.h" + +#include "style.h" + + +// CPPIFY: Make pure virtual. +//void SPFilterPrimitive::build_renderer(Inkscape::Filters::Filter* filter) { +// throw; +//} + +SPFilterPrimitive::SPFilterPrimitive() : SPObject() { + this->image_in = Inkscape::Filters::NR_FILTER_SLOT_NOT_SET; + this->image_out = Inkscape::Filters::NR_FILTER_SLOT_NOT_SET; + + // We must keep track if a value is set or not, if not set then the region defaults to 0%, 0%, + // 100%, 100% ("x", "y", "width", "height") of the -> filter <- region. If set then + // percentages are in terms of bounding box or viewbox, depending on value of "primitiveUnits" + + // NB: SVGLength.set takes prescaled percent values: 1 means 100% + this->x.unset(SVGLength::PERCENT, 0, 0); + this->y.unset(SVGLength::PERCENT, 0, 0); + this->width.unset(SVGLength::PERCENT, 1, 0); + this->height.unset(SVGLength::PERCENT, 1, 0); +} + +SPFilterPrimitive::~SPFilterPrimitive() { +} + +/** + * Reads the Inkscape::XML::Node, and initializes SPFilterPrimitive variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPFilterPrimitive::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPFilterPrimitive* object = this; + + object->readAttr( "style" ); // struct not derived from SPItem, we need to do this ourselves. + object->readAttr( "in" ); + object->readAttr( "result" ); + object->readAttr( "x" ); + object->readAttr( "y" ); + object->readAttr( "width" ); + object->readAttr( "height" ); + + SPObject::build(document, repr); +} + +/** + * Drops any allocated memory. + */ +void SPFilterPrimitive::release() { + SPObject::release(); +} + +/** + * Sets a specific value in the SPFilterPrimitive. + */ +void SPFilterPrimitive::set(unsigned int key, gchar const *value) { + + int image_nr; + switch (key) { + case SP_ATTR_IN: + if (value) { + image_nr = sp_filter_primitive_read_in(this, value); + } else { + image_nr = Inkscape::Filters::NR_FILTER_SLOT_NOT_SET; + } + if (image_nr != this->image_in) { + this->image_in = image_nr; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_RESULT: + if (value) { + image_nr = sp_filter_primitive_read_result(this, value); + } else { + image_nr = Inkscape::Filters::NR_FILTER_SLOT_NOT_SET; + } + if (image_nr != this->image_out) { + this->image_out = image_nr; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + + /* Filter primitive sub-region */ + case SP_ATTR_X: + this->x.readOrUnset(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_Y: + this->y.readOrUnset(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_WIDTH: + this->width.readOrUnset(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_HEIGHT: + this->height.readOrUnset(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + + /* See if any parents need this value. */ + SPObject::set(key, value); +} + +/** + * Receives update notifications. + */ +void SPFilterPrimitive::update(SPCtx *ctx, guint flags) { + + SPItemCtx *ictx = (SPItemCtx *) ctx; + + // Do here since we know viewport (Bounding box case handled during rendering) + SPFilter *parent = SP_FILTER(this->parent); + + if( parent->primitiveUnits == SP_FILTER_UNITS_USERSPACEONUSE ) { + this->calcDimsFromParentViewport(ictx, true); + } + + SPObject::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPFilterPrimitive::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + SPFilterPrimitive* object = this; + + SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(object); + SPFilter *parent = SP_FILTER(object->parent); + + if (!repr) { + repr = object->getRepr()->duplicate(doc); + } + + gchar const *in_name = sp_filter_name_for_image(parent, prim->image_in); + repr->setAttribute("in", in_name); + + gchar const *out_name = sp_filter_name_for_image(parent, prim->image_out); + repr->setAttribute("result", out_name); + + /* Do we need to add x,y,width,height? */ + SPObject::write(doc, repr, flags); + + return repr; +} + +int sp_filter_primitive_read_in(SPFilterPrimitive *prim, gchar const *name) +{ + if (!name || !prim){ + return Inkscape::Filters::NR_FILTER_SLOT_NOT_SET; + } + // TODO: are these case sensitive or not? (assumed yes) + switch (name[0]) { + case 'S': + if (strcmp(name, "SourceGraphic") == 0) + return Inkscape::Filters::NR_FILTER_SOURCEGRAPHIC; + if (strcmp(name, "SourceAlpha") == 0) + return Inkscape::Filters::NR_FILTER_SOURCEALPHA; + if (strcmp(name, "StrokePaint") == 0) + return Inkscape::Filters::NR_FILTER_STROKEPAINT; + break; + case 'B': + if (strcmp(name, "BackgroundImage") == 0) + return Inkscape::Filters::NR_FILTER_BACKGROUNDIMAGE; + if (strcmp(name, "BackgroundAlpha") == 0) + return Inkscape::Filters::NR_FILTER_BACKGROUNDALPHA; + break; + case 'F': + if (strcmp(name, "FillPaint") == 0) + return Inkscape::Filters::NR_FILTER_FILLPAINT; + break; + } + + SPFilter *parent = SP_FILTER(prim->parent); + int ret = sp_filter_get_image_name(parent, name); + if (ret >= 0) return ret; + + return Inkscape::Filters::NR_FILTER_SLOT_NOT_SET; +} + +int sp_filter_primitive_read_result(SPFilterPrimitive *prim, gchar const *name) +{ + SPFilter *parent = SP_FILTER(prim->parent); + int ret = sp_filter_get_image_name(parent, name); + if (ret >= 0) return ret; + + ret = sp_filter_set_image_name(parent, name); + if (ret >= 0) return ret; + + return Inkscape::Filters::NR_FILTER_SLOT_NOT_SET; +} + +/** + * Gives name for output of previous filter. Makes things clearer when prim + * is a filter with two or more inputs. Returns the slot number of result + * of previous primitive, or NR_FILTER_SOURCEGRAPHIC if this is the first + * primitive. + */ +int sp_filter_primitive_name_previous_out(SPFilterPrimitive *prim) { + SPFilter *parent = SP_FILTER(prim->parent); + SPObject *i = parent->firstChild(); + while (i && i->getNext() != prim) { + i = i->getNext(); + } + if (i) { + SPFilterPrimitive *i_prim = SP_FILTER_PRIMITIVE(i); + if (i_prim->image_out < 0) { + Glib::ustring name = sp_filter_get_new_result_name(parent); + int slot = sp_filter_set_image_name(parent, name.c_str()); + i_prim->image_out = slot; + //XML Tree is being directly used while it shouldn't be. + i_prim->getRepr()->setAttribute("result", name.c_str()); + return slot; + } else { + return i_prim->image_out; + } + } + return Inkscape::Filters::NR_FILTER_SOURCEGRAPHIC; +} + +/* Common initialization for filter primitives */ +void sp_filter_primitive_renderer_common(SPFilterPrimitive *sp_prim, Inkscape::Filters::FilterPrimitive *nr_prim) +{ + g_assert(sp_prim != NULL); + g_assert(nr_prim != NULL); + + + nr_prim->set_input(sp_prim->image_in); + nr_prim->set_output(sp_prim->image_out); + + /* TODO: place here code to handle input images, filter area etc. */ + // We don't know current viewport or bounding box, this is wrong approach. + nr_prim->set_subregion( sp_prim->x, sp_prim->y, sp_prim->width, sp_prim->height ); + + // Give renderer access to filter properties + nr_prim->setStyle( sp_prim->style ); +} + + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/sp-filter-primitive.h b/src/object/filters/sp-filter-primitive.h new file mode 100644 index 000000000..cebac8b1c --- /dev/null +++ b/src/object/filters/sp-filter-primitive.h @@ -0,0 +1,67 @@ +#ifndef SEEN_SP_FILTER_PRIMITIVE_H +#define SEEN_SP_FILTER_PRIMITIVE_H + +/** \file + * Document level base class for all SVG filter primitives. + */ +/* + * Authors: + * Hugo Rodrigues + * Niko Kiirala + * + * Copyright (C) 2006,2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "../sp-object.h" +#include "../sp-dimensions.h" + +#define SP_FILTER_PRIMITIVE(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FILTER_PRIMITIVE(obj) (dynamic_cast((SPObject*)obj) != NULL) + +namespace Inkscape { +namespace Filters { +class Filter; +class FilterPrimitive; +} } + +class SPFilterPrimitive : public SPObject, public SPDimensions { +public: + SPFilterPrimitive(); + virtual ~SPFilterPrimitive(); + + int image_in, image_out; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void set(unsigned int key, char const* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); + +public: + virtual void build_renderer(Inkscape::Filters::Filter* filter) = 0; +}; + +/* Common initialization for filter primitives */ +void sp_filter_primitive_renderer_common(SPFilterPrimitive *sp_prim, Inkscape::Filters::FilterPrimitive *nr_prim); + +int sp_filter_primitive_name_previous_out(SPFilterPrimitive *prim); +int sp_filter_primitive_read_in(SPFilterPrimitive *prim, char const *name); +int sp_filter_primitive_read_result(SPFilterPrimitive *prim, char const *name); + +#endif +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/specularlighting.cpp b/src/object/filters/specularlighting.cpp new file mode 100644 index 000000000..c46a21080 --- /dev/null +++ b/src/object/filters/specularlighting.cpp @@ -0,0 +1,341 @@ +/** \file + * SVG implementation. + * + */ +/* + * Authors: + * hugo Rodrigues + * Jean-Rene Reinhard + * Abhishek Sharma + * + * Copyright (C) 2006 Hugo Rodrigues + * 2007 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +// Same directory +#include "specularlighting.h" +#include "distantlight.h" +#include "pointlight.h" +#include "spotlight.h" + +#include "attributes.h" +#include "strneq.h" + +#include "display/nr-filter.h" +#include "display/nr-filter-specularlighting.h" + +#include "object/sp-object.h" + +#include "svg/svg.h" +#include "svg/svg-color.h" +#include "svg/svg-icc-color.h" + +#include "xml/repr.h" + +/* FeSpecularLighting base class */ +static void sp_feSpecularLighting_children_modified(SPFeSpecularLighting *sp_specularlighting); + +SPFeSpecularLighting::SPFeSpecularLighting() : SPFilterPrimitive() { + this->surfaceScale = 1; + this->specularConstant = 1; + this->specularExponent = 1; + this->lighting_color = 0xffffffff; + this->icc = NULL; + + //TODO kernelUnit + this->renderer = NULL; + + this->surfaceScale_set = FALSE; + this->specularConstant_set = FALSE; + this->specularExponent_set = FALSE; + this->lighting_color_set = FALSE; +} + +SPFeSpecularLighting::~SPFeSpecularLighting() { +} + +/** + * Reads the Inkscape::XML::Node, and initializes SPFeSpecularLighting variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPFeSpecularLighting::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPFilterPrimitive::build(document, repr); + + /*LOAD ATTRIBUTES FROM REPR HERE*/ + this->readAttr( "surfaceScale" ); + this->readAttr( "specularConstant" ); + this->readAttr( "specularExponent" ); + this->readAttr( "kernelUnitLength" ); + this->readAttr( "lighting-color" ); +} + +/** + * Drops any allocated memory. + */ +void SPFeSpecularLighting::release() { + SPFilterPrimitive::release(); +} + +/** + * Sets a specific value in the SPFeSpecularLighting. + */ +void SPFeSpecularLighting::set(unsigned int key, gchar const *value) { + gchar const *cend_ptr = NULL; + gchar *end_ptr = NULL; + + switch(key) { + /*DEAL WITH SETTING ATTRIBUTES HERE*/ +//TODO test forbidden values + case SP_ATTR_SURFACESCALE: + end_ptr = NULL; + if (value) { + this->surfaceScale = g_ascii_strtod(value, &end_ptr); + if (end_ptr) { + this->surfaceScale_set = TRUE; + } else { + g_warning("this: surfaceScale should be a number ... defaulting to 1"); + } + + } + //if the attribute is not set or has an unreadable value + if (!value || !end_ptr) { + this->surfaceScale = 1; + this->surfaceScale_set = FALSE; + } + if (this->renderer) { + this->renderer->surfaceScale = this->surfaceScale; + } + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SPECULARCONSTANT: + end_ptr = NULL; + if (value) { + this->specularConstant = g_ascii_strtod(value, &end_ptr); + if (end_ptr && this->specularConstant >= 0) { + this->specularConstant_set = TRUE; + } else { + end_ptr = NULL; + g_warning("this: specularConstant should be a positive number ... defaulting to 1"); + } + } + if (!value || !end_ptr) { + this->specularConstant = 1; + this->specularConstant_set = FALSE; + } + if (this->renderer) { + this->renderer->specularConstant = this->specularConstant; + } + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SPECULAREXPONENT: + end_ptr = NULL; + if (value) { + this->specularExponent = g_ascii_strtod(value, &end_ptr); + if (this->specularExponent >= 1 && this->specularExponent <= 128) { + this->specularExponent_set = TRUE; + } else { + end_ptr = NULL; + g_warning("this: specularExponent should be a number in range [1, 128] ... defaulting to 1"); + } + } + if (!value || !end_ptr) { + this->specularExponent = 1; + this->specularExponent_set = FALSE; + } + if (this->renderer) { + this->renderer->specularExponent = this->specularExponent; + } + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_KERNELUNITLENGTH: + //TODO kernelUnit + //this->kernelUnitLength.set(value); + /*TODOif (feSpecularLighting->renderer) { + feSpecularLighting->renderer->surfaceScale = feSpecularLighting->renderer; + } + */ + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_PROP_LIGHTING_COLOR: + cend_ptr = NULL; + this->lighting_color = sp_svg_read_color(value, &cend_ptr, 0xffffffff); + //if a value was read + if (cend_ptr) { + while (g_ascii_isspace(*cend_ptr)) { + ++cend_ptr; + } + if (strneq(cend_ptr, "icc-color(", 10)) { + if (!this->icc) this->icc = new SVGICCColor(); + if ( ! sp_svg_read_icc_color( cend_ptr, this->icc ) ) { + delete this->icc; + this->icc = NULL; + } + } + this->lighting_color_set = TRUE; + } else { + //lighting_color already contains the default value + this->lighting_color_set = FALSE; + } + if (this->renderer) { + this->renderer->lighting_color = this->lighting_color; + } + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + default: + SPFilterPrimitive::set(key, value); + break; + } +} + +/** + * Receives update notifications. + */ +void SPFeSpecularLighting::update(SPCtx *ctx, guint flags) { + if (flags & (SP_OBJECT_MODIFIED_FLAG)) { + this->readAttr( "surfaceScale" ); + this->readAttr( "specularConstant" ); + this->readAttr( "specularExponent" ); + this->readAttr( "kernelUnitLength" ); + this->readAttr( "lighting-color" ); + } + + SPFilterPrimitive::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPFeSpecularLighting::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + /* TODO: Don't just clone, but create a new repr node and write all + * relevant values _and children_ into it */ + if (!repr) { + repr = this->getRepr()->duplicate(doc); + //repr = doc->createElement("svg:feSpecularLighting"); + } + + if (this->surfaceScale_set) { + sp_repr_set_css_double(repr, "surfaceScale", this->surfaceScale); + } + + if (this->specularConstant_set) { + sp_repr_set_css_double(repr, "specularConstant", this->specularConstant); + } + + if (this->specularExponent_set) { + sp_repr_set_css_double(repr, "specularExponent", this->specularExponent); + } + + /*TODO kernelUnits */ + if (this->lighting_color_set) { + gchar c[64]; + sp_svg_write_color(c, sizeof(c), this->lighting_color); + repr->setAttribute("lighting-color", c); + } + + SPFilterPrimitive::write(doc, repr, flags); + + return repr; +} + +/** + * Callback for child_added event. + */ +void SPFeSpecularLighting::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) { + SPFilterPrimitive::child_added(child, ref); + + sp_feSpecularLighting_children_modified(this); + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/** + * Callback for remove_child event. + */ +void SPFeSpecularLighting::remove_child(Inkscape::XML::Node *child) { + SPFilterPrimitive::remove_child(child); + + sp_feSpecularLighting_children_modified(this); + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +void SPFeSpecularLighting::order_changed(Inkscape::XML::Node *child, Inkscape::XML::Node *old_ref, Inkscape::XML::Node *new_ref) { + SPFilterPrimitive::order_changed(child, old_ref, new_ref); + + sp_feSpecularLighting_children_modified(this); + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +static void sp_feSpecularLighting_children_modified(SPFeSpecularLighting *sp_specularlighting) { + if (sp_specularlighting->renderer) { + sp_specularlighting->renderer->light_type = Inkscape::Filters::NO_LIGHT; + + if (SP_IS_FEDISTANTLIGHT(sp_specularlighting->firstChild())) { + sp_specularlighting->renderer->light_type = Inkscape::Filters::DISTANT_LIGHT; + sp_specularlighting->renderer->light.distant = SP_FEDISTANTLIGHT(sp_specularlighting->firstChild()); + } + + if (SP_IS_FEPOINTLIGHT(sp_specularlighting->firstChild())) { + sp_specularlighting->renderer->light_type = Inkscape::Filters::POINT_LIGHT; + sp_specularlighting->renderer->light.point = SP_FEPOINTLIGHT(sp_specularlighting->firstChild()); + } + + if (SP_IS_FESPOTLIGHT(sp_specularlighting->firstChild())) { + sp_specularlighting->renderer->light_type = Inkscape::Filters::SPOT_LIGHT; + sp_specularlighting->renderer->light.spot = SP_FESPOTLIGHT(sp_specularlighting->firstChild()); + } + } +} + +void SPFeSpecularLighting::build_renderer(Inkscape::Filters::Filter* filter) { + g_assert(this != NULL); + g_assert(filter != NULL); + + int primitive_n = filter->add_primitive(Inkscape::Filters::NR_FILTER_SPECULARLIGHTING); + Inkscape::Filters::FilterPrimitive *nr_primitive = filter->get_primitive(primitive_n); + Inkscape::Filters::FilterSpecularLighting *nr_specularlighting = dynamic_cast(nr_primitive); + g_assert(nr_specularlighting != NULL); + + this->renderer = nr_specularlighting; + sp_filter_primitive_renderer_common(this, nr_primitive); + + nr_specularlighting->specularConstant = this->specularConstant; + nr_specularlighting->specularExponent = this->specularExponent; + nr_specularlighting->surfaceScale = this->surfaceScale; + nr_specularlighting->lighting_color = this->lighting_color; + nr_specularlighting->set_icc(this->icc); + + //We assume there is at most one child + nr_specularlighting->light_type = Inkscape::Filters::NO_LIGHT; + + if (SP_IS_FEDISTANTLIGHT(this->firstChild())) { + nr_specularlighting->light_type = Inkscape::Filters::DISTANT_LIGHT; + nr_specularlighting->light.distant = SP_FEDISTANTLIGHT(this->firstChild()); + } + + if (SP_IS_FEPOINTLIGHT(this->firstChild())) { + nr_specularlighting->light_type = Inkscape::Filters::POINT_LIGHT; + nr_specularlighting->light.point = SP_FEPOINTLIGHT(this->firstChild()); + } + + if (SP_IS_FESPOTLIGHT(this->firstChild())) { + nr_specularlighting->light_type = Inkscape::Filters::SPOT_LIGHT; + nr_specularlighting->light.spot = SP_FESPOTLIGHT(this->firstChild()); + } + + //nr_offset->set_dx(sp_offset->dx); + //nr_offset->set_dy(sp_offset->dy); +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/specularlighting.h b/src/object/filters/specularlighting.h new file mode 100644 index 000000000..1de32ec58 --- /dev/null +++ b/src/object/filters/specularlighting.h @@ -0,0 +1,78 @@ +/** @file + * @brief SVG specular lighting filter effect + *//* + * Authors: + * Hugo Rodrigues + * Jean-Rene Reinhard + * + * Copyright (C) 2006 Hugo Rodrigues + * 2007 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SP_FESPECULARLIGHTING_H_SEEN +#define SP_FESPECULARLIGHTING_H_SEEN + +#include "sp-filter-primitive.h" +#include "number-opt-number.h" + +#define SP_FESPECULARLIGHTING(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FESPECULARLIGHTING(obj) (dynamic_cast((SPObject*)obj) != NULL) + +struct SVGICCColor; + +namespace Inkscape { +namespace Filters { +class FilterSpecularLighting; +} +} + +class SPFeSpecularLighting : public SPFilterPrimitive { +public: + SPFeSpecularLighting(); + virtual ~SPFeSpecularLighting(); + + gfloat surfaceScale; + guint surfaceScale_set : 1; + gfloat specularConstant; + guint specularConstant_set : 1; + gfloat specularExponent; + guint specularExponent_set : 1; + NumberOptNumber kernelUnitLength; + guint32 lighting_color; + guint lighting_color_set : 1; + SVGICCColor *icc; + + Inkscape::Filters::FilterSpecularLighting *renderer; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref); + virtual void remove_child(Inkscape::XML::Node* child); + + virtual void order_changed(Inkscape::XML::Node* child, Inkscape::XML::Node* old_repr, Inkscape::XML::Node* new_repr); + + virtual void set(unsigned int key, const gchar* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags); + + virtual void build_renderer(Inkscape::Filters::Filter* filter); +}; + +#endif /* !SP_FESPECULARLIGHTING_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/spotlight.cpp b/src/object/filters/spotlight.cpp new file mode 100644 index 000000000..a05691196 --- /dev/null +++ b/src/object/filters/spotlight.cpp @@ -0,0 +1,322 @@ +/** \file + * SVG implementation. + */ +/* + * Authors: + * Hugo Rodrigues + * Niko Kiirala + * Jean-Rene Reinhard + * Abhishek Sharma + * + * Copyright (C) 2006,2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +// Same directory +#include "spotlight.h" +#include "diffuselighting.h" +#include "specularlighting.h" + +#include "attributes.h" +#include "document.h" + +#include "xml/repr.h" + +#define SP_MACROS_SILENT + +SPFeSpotLight::SPFeSpotLight() + : SPObject(), x(0), x_set(FALSE), y(0), y_set(FALSE), z(0), z_set(FALSE), pointsAtX(0), pointsAtX_set(FALSE), + pointsAtY(0), pointsAtY_set(FALSE), pointsAtZ(0), pointsAtZ_set(FALSE), + specularExponent(1), specularExponent_set(FALSE), limitingConeAngle(90), + limitingConeAngle_set(FALSE) +{ +} + +SPFeSpotLight::~SPFeSpotLight() { +} + + +/** + * Reads the Inkscape::XML::Node, and initializes SPPointLight variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPFeSpotLight::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPObject::build(document, repr); + + //Read values of key attributes from XML nodes into object. + this->readAttr( "x" ); + this->readAttr( "y" ); + this->readAttr( "z" ); + this->readAttr( "pointsAtX" ); + this->readAttr( "pointsAtY" ); + this->readAttr( "pointsAtZ" ); + this->readAttr( "specularExponent" ); + this->readAttr( "limitingConeAngle" ); + +//is this necessary? + document->addResource("fespotlight", this); +} + +/** + * Drops any allocated memory. + */ +void SPFeSpotLight::release() { + if ( this->document ) { + // Unregister ourselves + this->document->removeResource("fespotlight", this); + } + +//TODO: release resources here +} + +/** + * Sets a specific value in the SPFeSpotLight. + */ +void SPFeSpotLight::set(unsigned int key, gchar const *value) { + gchar *end_ptr; + + switch (key) { + case SP_ATTR_X: + end_ptr = NULL; + + if (value) { + this->x = g_ascii_strtod(value, &end_ptr); + + if (end_ptr) { + this->x_set = TRUE; + } + } + + if(!value || !end_ptr) { + this->x = 0; + this->x_set = FALSE; + } + + if (this->parent && + (SP_IS_FEDIFFUSELIGHTING(this->parent) || + SP_IS_FESPECULARLIGHTING(this->parent))) { + this->parent->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_Y: + end_ptr = NULL; + + if (value) { + this->y = g_ascii_strtod(value, &end_ptr); + + if (end_ptr) { + this->y_set = TRUE; + } + } + + if(!value || !end_ptr) { + this->y = 0; + this->y_set = FALSE; + } + + if (this->parent && + (SP_IS_FEDIFFUSELIGHTING(this->parent) || + SP_IS_FESPECULARLIGHTING(this->parent))) { + this->parent->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_Z: + end_ptr = NULL; + + if (value) { + this->z = g_ascii_strtod(value, &end_ptr); + + if (end_ptr) { + this->z_set = TRUE; + } + } + + if(!value || !end_ptr) { + this->z = 0; + this->z_set = FALSE; + } + + if (this->parent && + (SP_IS_FEDIFFUSELIGHTING(this->parent) || + SP_IS_FESPECULARLIGHTING(this->parent))) { + this->parent->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_POINTSATX: + end_ptr = NULL; + + if (value) { + this->pointsAtX = g_ascii_strtod(value, &end_ptr); + + if (end_ptr) { + this->pointsAtX_set = TRUE; + } + } + + if(!value || !end_ptr) { + this->pointsAtX = 0; + this->pointsAtX_set = FALSE; + } + + if (this->parent && + (SP_IS_FEDIFFUSELIGHTING(this->parent) || + SP_IS_FESPECULARLIGHTING(this->parent))) { + this->parent->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_POINTSATY: + end_ptr = NULL; + + if (value) { + this->pointsAtY = g_ascii_strtod(value, &end_ptr); + + if (end_ptr) { + this->pointsAtY_set = TRUE; + } + } + + if(!value || !end_ptr) { + this->pointsAtY = 0; + this->pointsAtY_set = FALSE; + } + + if (this->parent && + (SP_IS_FEDIFFUSELIGHTING(this->parent) || + SP_IS_FESPECULARLIGHTING(this->parent))) { + this->parent->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_POINTSATZ: + end_ptr = NULL; + + if (value) { + this->pointsAtZ = g_ascii_strtod(value, &end_ptr); + + if (end_ptr) { + this->pointsAtZ_set = TRUE; + } + } + + if(!value || !end_ptr) { + this->pointsAtZ = 0; + this->pointsAtZ_set = FALSE; + } + + if (this->parent && + (SP_IS_FEDIFFUSELIGHTING(this->parent) || + SP_IS_FESPECULARLIGHTING(this->parent))) { + this->parent->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_SPECULAREXPONENT: + end_ptr = NULL; + + if (value) { + this->specularExponent = g_ascii_strtod(value, &end_ptr); + + if (end_ptr) { + this->specularExponent_set = TRUE; + } + } + + if(!value || !end_ptr) { + this->specularExponent = 1; + this->specularExponent_set = FALSE; + } + + if (this->parent && + (SP_IS_FEDIFFUSELIGHTING(this->parent) || + SP_IS_FESPECULARLIGHTING(this->parent))) { + this->parent->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_LIMITINGCONEANGLE: + end_ptr = NULL; + + if (value) { + this->limitingConeAngle = g_ascii_strtod(value, &end_ptr); + + if (end_ptr) { + this->limitingConeAngle_set = TRUE; + } + } + + if(!value || !end_ptr) { + this->limitingConeAngle = 90; + this->limitingConeAngle_set = FALSE; + } + + if (this->parent && + (SP_IS_FEDIFFUSELIGHTING(this->parent) || + SP_IS_FESPECULARLIGHTING(this->parent))) { + this->parent->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + default: + // See if any parents need this value. + SPObject::set(key, value); + break; + } +} + +/** + * * Receives update notifications. + * */ +void SPFeSpotLight::update(SPCtx *ctx, guint flags) { + if (flags & SP_OBJECT_MODIFIED_FLAG) { + /* do something to trigger redisplay, updates? */ + this->readAttr( "x" ); + this->readAttr( "y" ); + this->readAttr( "z" ); + this->readAttr( "pointsAtX" ); + this->readAttr( "pointsAtY" ); + this->readAttr( "pointsAtZ" ); + this->readAttr( "specularExponent" ); + this->readAttr( "limitingConeAngle" ); + } + + SPObject::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPFeSpotLight::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + if (!repr) { + repr = this->getRepr()->duplicate(doc); + } + + if (this->x_set) + sp_repr_set_css_double(repr, "x", this->x); + if (this->y_set) + sp_repr_set_css_double(repr, "y", this->y); + if (this->z_set) + sp_repr_set_css_double(repr, "z", this->z); + if (this->pointsAtX_set) + sp_repr_set_css_double(repr, "pointsAtX", this->pointsAtX); + if (this->pointsAtY_set) + sp_repr_set_css_double(repr, "pointsAtY", this->pointsAtY); + if (this->pointsAtZ_set) + sp_repr_set_css_double(repr, "pointsAtZ", this->pointsAtZ); + if (this->specularExponent_set) + sp_repr_set_css_double(repr, "specularExponent", this->specularExponent); + if (this->limitingConeAngle_set) + sp_repr_set_css_double(repr, "limitingConeAngle", this->limitingConeAngle); + + SPObject::write(doc, repr, flags); + + return repr; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/spotlight.h b/src/object/filters/spotlight.h new file mode 100644 index 000000000..c82d24301 --- /dev/null +++ b/src/object/filters/spotlight.h @@ -0,0 +1,76 @@ +#ifndef SP_FESPOTLIGHT_H_SEEN +#define SP_FESPOTLIGHT_H_SEEN + +/** \file + * SVG implementation, see sp-filter.cpp. + */ +/* + * Authors: + * Hugo Rodrigues + * Niko Kiirala + * Jean-Rene Reinhard + * + * Copyright (C) 2006,2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "object/sp-object.h" + +#define SP_FESPOTLIGHT(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FESPOTLIGHT(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPFeSpotLight : public SPObject { +public: + SPFeSpotLight(); + virtual ~SPFeSpotLight(); + + /** x coordinate of the light source */ + float x; + unsigned int x_set : 1; + /** y coordinate of the light source */ + float y; + unsigned int y_set : 1; + /** z coordinate of the light source */ + float z; + unsigned int z_set : 1; + /** x coordinate of the point the source is pointing at */ + float pointsAtX; + unsigned int pointsAtX_set : 1; + /** y coordinate of the point the source is pointing at */ + float pointsAtY; + unsigned int pointsAtY_set : 1; + /** z coordinate of the point the source is pointing at */ + float pointsAtZ; + unsigned int pointsAtZ_set : 1; + /** specular exponent (focus of the light) */ + float specularExponent; + unsigned int specularExponent_set : 1; + /** limiting cone angle */ + float limitingConeAngle; + unsigned int limitingConeAngle_set : 1; + //other fields + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void set(unsigned int key, char const* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); +}; + +#endif /* !SP_FESPOTLIGHT_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/tile.cpp b/src/object/filters/tile.cpp new file mode 100644 index 000000000..82e63c220 --- /dev/null +++ b/src/object/filters/tile.cpp @@ -0,0 +1,109 @@ +/** \file + * SVG implementation. + * + */ +/* + * Authors: + * hugo Rodrigues + * + * Copyright (C) 2006 Hugo Rodrigues + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "tile.h" + +#include "attributes.h" + +#include "display/nr-filter.h" +#include "display/nr-filter-tile.h" + +#include "svg/svg.h" + +#include "xml/repr.h" + +SPFeTile::SPFeTile() : SPFilterPrimitive() { +} + +SPFeTile::~SPFeTile() { +} + +/** + * Reads the Inkscape::XML::Node, and initializes SPFeTile variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPFeTile::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPFilterPrimitive::build(document, repr); +} + +/** + * Drops any allocated memory. + */ +void SPFeTile::release() { + SPFilterPrimitive::release(); +} + +/** + * Sets a specific value in the SPFeTile. + */ +void SPFeTile::set(unsigned int key, gchar const *value) { + switch(key) { + /*DEAL WITH SETTING ATTRIBUTES HERE*/ + default: + SPFilterPrimitive::set(key, value); + break; + } +} + +/** + * Receives update notifications. + */ +void SPFeTile::update(SPCtx *ctx, guint flags) { + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + + /* do something to trigger redisplay, updates? */ + + } + + SPFilterPrimitive::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPFeTile::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + /* TODO: Don't just clone, but create a new repr node and write all + * relevant values into it */ + if (!repr) { + repr = this->getRepr()->duplicate(doc); + } + + SPFilterPrimitive::write(doc, repr, flags); + + return repr; +} + +void SPFeTile::build_renderer(Inkscape::Filters::Filter* filter) { + g_assert(this != NULL); + g_assert(filter != NULL); + + int primitive_n = filter->add_primitive(Inkscape::Filters::NR_FILTER_TILE); + Inkscape::Filters::FilterPrimitive *nr_primitive = filter->get_primitive(primitive_n); + Inkscape::Filters::FilterTile *nr_tile = dynamic_cast(nr_primitive); + g_assert(nr_tile != NULL); + + sp_filter_primitive_renderer_common(this, nr_primitive); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/tile.h b/src/object/filters/tile.h new file mode 100644 index 000000000..cc1a006dd --- /dev/null +++ b/src/object/filters/tile.h @@ -0,0 +1,50 @@ +/** @file + * @brief SVG tile filter effect + *//* + * Authors: + * Hugo Rodrigues + * + * Copyright (C) 2006 Hugo Rodrigues + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SP_FETILE_H_SEEN +#define SP_FETILE_H_SEEN + +#include "sp-filter-primitive.h" + +#define SP_FETILE(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FETILE(obj) (dynamic_cast((SPObject*)obj) != NULL) + +/* FeTile base class */ +class SPFeTile : public SPFilterPrimitive { +public: + SPFeTile(); + virtual ~SPFeTile(); + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void set(unsigned int key, const gchar* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags); + + virtual void build_renderer(Inkscape::Filters::Filter* filter); +}; + +#endif /* !SP_FETILE_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/turbulence.cpp b/src/object/filters/turbulence.cpp new file mode 100644 index 000000000..9af51892e --- /dev/null +++ b/src/object/filters/turbulence.cpp @@ -0,0 +1,230 @@ +/** \file + * SVG implementation. + * + */ +/* + * Authors: + * Felipe Corrêa da Silva Sanches + * hugo Rodrigues + * Abhishek Sharma + * + * Copyright (C) 2007 Felipe Sanches + * Copyright (C) 2006 Hugo Rodrigues + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "attributes.h" +#include "svg/svg.h" +#include "turbulence.h" +#include "helper-fns.h" +#include "xml/repr.h" + +#include "display/nr-filter.h" + +SPFeTurbulence::SPFeTurbulence() : SPFilterPrimitive() { + this->stitchTiles = 0; + this->seed = 0; + this->numOctaves = 0; + this->type = Inkscape::Filters::TURBULENCE_FRACTALNOISE; + + this->updated=false; +} + +SPFeTurbulence::~SPFeTurbulence() { +} + +/** + * Reads the Inkscape::XML::Node, and initializes SPFeTurbulence variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPFeTurbulence::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPFilterPrimitive::build(document, repr); + + /*LOAD ATTRIBUTES FROM REPR HERE*/ + this->readAttr( "baseFrequency" ); + this->readAttr( "numOctaves" ); + this->readAttr( "seed" ); + this->readAttr( "stitchTiles" ); + this->readAttr( "type" ); +} + +/** + * Drops any allocated memory. + */ +void SPFeTurbulence::release() { + SPFilterPrimitive::release(); +} + +static bool sp_feTurbulence_read_stitchTiles(gchar const *value){ + if (!value) { + return false; // 'noStitch' is default + } + + switch(value[0]){ + case 's': + if (strncmp(value, "stitch", 6) == 0) { + return true; + } + break; + case 'n': + if (strncmp(value, "noStitch", 8) == 0) { + return false; + } + break; + } + + return false; // 'noStitch' is default +} + +static Inkscape::Filters::FilterTurbulenceType sp_feTurbulence_read_type(gchar const *value){ + if (!value) { + return Inkscape::Filters::TURBULENCE_TURBULENCE; // 'turbulence' is default + } + + switch(value[0]){ + case 'f': + if (strncmp(value, "fractalNoise", 12) == 0) { + return Inkscape::Filters::TURBULENCE_FRACTALNOISE; + } + break; + case 't': + if (strncmp(value, "turbulence", 10) == 0) { + return Inkscape::Filters::TURBULENCE_TURBULENCE; + } + break; + } + + return Inkscape::Filters::TURBULENCE_TURBULENCE; // 'turbulence' is default +} + +/** + * Sets a specific value in the SPFeTurbulence. + */ +void SPFeTurbulence::set(unsigned int key, gchar const *value) { + int read_int; + double read_num; + bool read_bool; + Inkscape::Filters::FilterTurbulenceType read_type; + + switch(key) { + /*DEAL WITH SETTING ATTRIBUTES HERE*/ + case SP_ATTR_BASEFREQUENCY: + this->baseFrequency.set(value); + + // From SVG spec: If two s are provided, the first number represents + // a base frequency in the X direction and the second value represents a base + // frequency in the Y direction. If one number is provided, then that value is + // used for both X and Y. + if (this->baseFrequency.optNumIsSet() == false) { + this->baseFrequency.setOptNumber(this->baseFrequency.getNumber()); + } + + this->updated = false; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_NUMOCTAVES: + read_int = value ? (int)floor(helperfns_read_number(value)) : 1; + + if (read_int != this->numOctaves){ + this->numOctaves = read_int; + this->updated = false; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_SEED: + read_num = value ? helperfns_read_number(value) : 0; + + if (read_num != this->seed){ + this->seed = read_num; + this->updated = false; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_STITCHTILES: + read_bool = sp_feTurbulence_read_stitchTiles(value); + + if (read_bool != this->stitchTiles){ + this->stitchTiles = read_bool; + this->updated = false; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case SP_ATTR_TYPE: + read_type = sp_feTurbulence_read_type(value); + + if (read_type != this->type){ + this->type = read_type; + this->updated = false; + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + default: + SPFilterPrimitive::set(key, value); + break; + } +} + +/** + * Receives update notifications. + */ +void SPFeTurbulence::update(SPCtx *ctx, guint flags) { + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + + /* do something to trigger redisplay, updates? */ + + } + + SPFilterPrimitive::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPFeTurbulence::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + /* TODO: Don't just clone, but create a new repr node and write all + * relevant values into it */ + if (!repr) { + repr = this->getRepr()->duplicate(doc); + } + + SPFilterPrimitive::write(doc, repr, flags); + + /* turbulence doesn't take input */ + repr->setAttribute("in", 0); + + return repr; +} + +void SPFeTurbulence::build_renderer(Inkscape::Filters::Filter* filter) { + g_assert(this != NULL); + g_assert(filter != NULL); + + int primitive_n = filter->add_primitive(Inkscape::Filters::NR_FILTER_TURBULENCE); + Inkscape::Filters::FilterPrimitive *nr_primitive = filter->get_primitive(primitive_n); + Inkscape::Filters::FilterTurbulence *nr_turbulence = dynamic_cast(nr_primitive); + g_assert(nr_turbulence != NULL); + + sp_filter_primitive_renderer_common(this, nr_primitive); + + nr_turbulence->set_baseFrequency(0, this->baseFrequency.getNumber()); + nr_turbulence->set_baseFrequency(1, this->baseFrequency.getOptNumber()); + nr_turbulence->set_numOctaves(this->numOctaves); + nr_turbulence->set_seed(this->seed); + nr_turbulence->set_stitchTiles(this->stitchTiles); + nr_turbulence->set_type(this->type); + nr_turbulence->set_updated(this->updated); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/filters/turbulence.h b/src/object/filters/turbulence.h new file mode 100644 index 000000000..89e6d4a19 --- /dev/null +++ b/src/object/filters/turbulence.h @@ -0,0 +1,63 @@ +/** @file + * @brief SVG turbulence filter effect + *//* + * Authors: + * Felipe Corrêa da Silva Sanches + * Hugo Rodrigues + * + * Copyright (C) 2006 Hugo Rodrigues + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SP_FETURBULENCE_H_SEEN +#define SP_FETURBULENCE_H_SEEN + +#include "sp-filter-primitive.h" +#include "number-opt-number.h" +#include "display/nr-filter-turbulence.h" + +#define SP_FETURBULENCE(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FETURBULENCE(obj) (dynamic_cast((SPObject*)obj) != NULL) + +/* FeTurbulence base class */ + +class SPFeTurbulence : public SPFilterPrimitive { +public: + SPFeTurbulence(); + virtual ~SPFeTurbulence(); + + /** TURBULENCE ATTRIBUTES HERE */ + NumberOptNumber baseFrequency; + int numOctaves; + double seed; + bool stitchTiles; + Inkscape::Filters::FilterTurbulenceType type; + SVGLength x, y, height, width; + bool updated; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void set(unsigned int key, const gchar* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags); + + virtual void build_renderer(Inkscape::Filters::Filter* filter); +}; + +#endif /* !SP_FETURBULENCE_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/object-set.cpp b/src/object/object-set.cpp new file mode 100644 index 000000000..36ddac350 --- /dev/null +++ b/src/object/object-set.cpp @@ -0,0 +1,392 @@ +/* + * Multiindex container for selection + * + * Authors: + * Adrian Boguszewski + * + * Copyright (C) 2016 Adrian Boguszewski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include "object-set.h" +#include "box3d.h" +#include "persp3d.h" +#include "preferences.h" +#include +#include + +namespace Inkscape { + +bool ObjectSet::add(SPObject* object, bool nosignal) { + g_return_val_if_fail(object != NULL, false); + g_return_val_if_fail(SP_IS_OBJECT(object), false); + + // any ancestor is in the set - do nothing + if (_anyAncestorIsInSet(object)) { + return false; + } + + // very nice function, but changes selection behavior (probably needs new selection option to deal with it) + // check if there is mutual ancestor for some elements, which can replace all of them in the set +// object = _getMutualAncestor(object); + + // remove all descendants from the set + _removeDescendantsFromSet(object); + + _add(object); + if (!nosignal) + _emitSignals(); + return true; +} + +bool ObjectSet::remove(SPObject* object) { + g_return_val_if_fail(object != NULL, false); + g_return_val_if_fail(SP_IS_OBJECT(object), false); + + // object is the top of subtree + if (includes(object)) { + _remove(object); + _emitSignals(); + return true; + } + + // any ancestor of object is in the set + if (_anyAncestorIsInSet(object)) { + _removeAncestorsFromSet(object); + _emitSignals(); + return true; + } + + // no object nor any parent in the set + return false; +} + +bool ObjectSet::includes(SPObject *object) { + g_return_val_if_fail(object != NULL, false); + g_return_val_if_fail(SP_IS_OBJECT(object), false); + + return _container.get().find(object) != _container.get().end(); +} + +void ObjectSet::clear() { + _clear(); + _emitSignals(); +} + +int ObjectSet::size() { + return _container.size(); +} + +bool ObjectSet::_anyAncestorIsInSet(SPObject *object) { + SPObject* o = object; + while (o != nullptr) { + if (includes(o)) { + return true; + } + o = o->parent; + } + + return false; +} + +void ObjectSet::_removeDescendantsFromSet(SPObject *object) { + for (auto& child: object->children) { + if (includes(&child)) { + _remove(&child); + // there is certainly no children of this child in the set + continue; + } + + _removeDescendantsFromSet(&child); + } +} + +void ObjectSet::_disconnect(SPObject *object) { + _releaseConnections[object].disconnect(); + _releaseConnections.erase(object); + _remove3DBoxesRecursively(object); + _releaseSignals(object); +} + +void ObjectSet::_remove(SPObject *object) { + _disconnect(object); + _container.get().erase(object); +} + +void ObjectSet::_add(SPObject *object) { + _releaseConnections[object] = object->connectRelease(sigc::hide_return(sigc::mem_fun(*this, &ObjectSet::remove))); + _container.push_back(object); + _add3DBoxesRecursively(object); + _connectSignals(object); +} + +void ObjectSet::_clear() { + for (auto object: _container) + _disconnect(object); + _container.clear(); +} + +SPObject *ObjectSet::_getMutualAncestor(SPObject *object) { + SPObject *o = object; + + bool flag = true; + while (o->parent != nullptr) { + for (auto &child: o->parent->children) { + if(&child != o && !includes(&child)) { + flag = false; + break; + } + } + if (!flag) { + break; + } + o = o->parent; + } + return o; +} + +void ObjectSet::_removeAncestorsFromSet(SPObject *object) { + SPObject* o = object; + while (o->parent != nullptr) { + for (auto &child: o->parent->children) { + if (&child != o) { + _add(&child); + } + } + if (includes(o->parent)) { + _remove(o->parent); + break; + } + o = o->parent; + } +} + +ObjectSet::~ObjectSet() { + _clear(); +} + +void ObjectSet::toggle(SPObject *obj) { + if (includes(obj)) { + remove(obj); + } else { + add(obj); + } +} + +bool ObjectSet::isEmpty() { + return _container.size() == 0; +} + +SPObject *ObjectSet::single() { + return _container.size() == 1 ? *_container.begin() : nullptr; +} + +SPItem *ObjectSet::singleItem() { + if (_container.size() == 1) { + SPObject* obj = *_container.begin(); + if (SP_IS_ITEM(obj)) { + return SP_ITEM(obj); + } + } + + return nullptr; +} + +SPItem *ObjectSet::smallestItem(CompareSize compare) { + return _sizeistItem(true, compare); +} + +SPItem *ObjectSet::largestItem(CompareSize compare) { + return _sizeistItem(false, compare); +} + +SPItem *ObjectSet::_sizeistItem(bool sml, CompareSize compare) { + auto items = this->items(); + gdouble max = sml ? 1e18 : 0; + SPItem *ist = NULL; + + for (auto i = items.begin(); i != items.end(); ++i) { + Geom::OptRect obox = SP_ITEM(*i)->documentPreferredBounds(); + if (!obox || obox.empty()) { + continue; + } + + Geom::Rect bbox = *obox; + + gdouble size = compare == AREA ? bbox.area() : + (compare == VERTICAL ? bbox.height() : bbox.width()); + size = sml ? size : size * -1; + if (size < max) { + max = size; + ist = SP_ITEM(*i); + } + } + + return ist; +} + +SPObjectRange ObjectSet::objects() { + return SPObjectRange(_container.get().begin(), _container.get().end()); +} + +Inkscape::XML::Node *ObjectSet::singleRepr() { + SPObject *obj = single(); + return obj ? obj->getRepr() : nullptr; +} + +void ObjectSet::set(SPObject *object, bool persist_selection_context) { + _clear(); + _add(object); + if(dynamic_cast(this)) + return dynamic_cast(this)->_emitChanged(persist_selection_context); +} + +void ObjectSet::setReprList(std::vector const &list) { + if(!document()) + return; + clear(); + for (auto iter = list.rbegin(); iter != list.rend(); ++iter) { + SPObject *obj = document()->getObjectById((*iter)->attribute("id")); + if (obj) { + add(obj, true); + } + } + _emitSignals(); + if(dynamic_cast(this)) + return dynamic_cast(this)->_emitChanged();// +} + + + +Geom::OptRect ObjectSet::bounds(SPItem::BBoxType type) const +{ + return (type == SPItem::GEOMETRIC_BBOX) ? + geometricBounds() : visualBounds(); +} + +Geom::OptRect ObjectSet::geometricBounds() const +{ + auto items = const_cast(this)->items(); + + Geom::OptRect bbox; + for (auto iter = items.begin(); iter != items.end(); ++iter) { + bbox.unionWith(SP_ITEM(*iter)->desktopGeometricBounds()); + } + return bbox; +} + +Geom::OptRect ObjectSet::visualBounds() const +{ + auto items = const_cast(this)->items(); + + Geom::OptRect bbox; + for (auto iter = items.begin(); iter != items.end(); ++iter) { + bbox.unionWith(SP_ITEM(*iter)->desktopVisualBounds()); + } + return bbox; +} + +Geom::OptRect ObjectSet::preferredBounds() const +{ + if (Inkscape::Preferences::get()->getInt("/tools/bounding_box") == 0) { + return bounds(SPItem::VISUAL_BBOX); + } else { + return bounds(SPItem::GEOMETRIC_BBOX); + } +} + +Geom::OptRect ObjectSet::documentBounds(SPItem::BBoxType type) const +{ + Geom::OptRect bbox; + auto items = const_cast(this)->items(); + if (items.empty()) return bbox; + + for (auto iter = items.begin(); iter != items.end(); ++iter) { + SPItem *item = SP_ITEM(*iter); + bbox |= item->documentBounds(type); + } + + return bbox; +} + +// If we have a selection of multiple items, then the center of the first item +// will be returned; this is also the case in SelTrans::centerRequest() +boost::optional ObjectSet::center() const { + auto items = const_cast(this)->items(); + if (!items.empty()) { + SPItem *first = items.back(); // from the first item in selection + if (first->isCenterSet()) { // only if set explicitly + return first->getCenter(); + } + } + Geom::OptRect bbox = preferredBounds(); + if (bbox) { + return bbox->midpoint(); + } else { + return boost::optional(); + } +} + +std::list const ObjectSet::perspList() { + std::list pl; + for (std::list::iterator i = _3dboxes.begin(); i != _3dboxes.end(); ++i) { + Persp3D *persp = box3d_get_perspective(*i); + if (std::find(pl.begin(), pl.end(), persp) == pl.end()) + pl.push_back(persp); + } + return pl; +} + +std::list const ObjectSet::box3DList(Persp3D *persp) { + std::list boxes; + if (persp) { + for (std::list::iterator i = _3dboxes.begin(); i != _3dboxes.end(); ++i) { + SPBox3D *box = *i; + if (persp == box3d_get_perspective(box)) { + boxes.push_back(box); + } + } + } else { + boxes = _3dboxes; + } + return boxes; +} + +void ObjectSet::_add3DBoxesRecursively(SPObject *obj) { + std::list boxes = box3d_extract_boxes(obj); + + for (std::list::iterator i = boxes.begin(); i != boxes.end(); ++i) { + SPBox3D *box = *i; + _3dboxes.push_back(box); + } +} + +void ObjectSet::_remove3DBoxesRecursively(SPObject *obj) { + std::list boxes = box3d_extract_boxes(obj); + + for (std::list::iterator i = boxes.begin(); i != boxes.end(); ++i) { + SPBox3D *box = *i; + std::list::iterator b = std::find(_3dboxes.begin(), _3dboxes.end(), box); + if (b == _3dboxes.end()) { + g_print ("Warning! Trying to remove unselected box from selection.\n"); + return; + } + _3dboxes.erase(b); + } +} + +} // namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/object-set.h b/src/object/object-set.h new file mode 100644 index 000000000..f9f02a213 --- /dev/null +++ b/src/object/object-set.h @@ -0,0 +1,500 @@ +/* + * Multiindex container for selection + * + * Authors: + * Adrian Boguszewski + * Marc Jeanmougin + * + * Copyright (C) 2016 Adrian Boguszewski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef INKSCAPE_PROTOTYPE_OBJECTSET_H +#define INKSCAPE_PROTOTYPE_OBJECTSET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sp-object.h" +#include "sp-item.h" +#include "sp-item-group.h" +#include "desktop.h" +#include "document.h" +#include "verbs.h" + +enum BoolOpErrors { + DONE, + DONE_NO_PATH, + DONE_NO_ACTION, + ERR_TOO_LESS_PATHS_1, + ERR_TOO_LESS_PATHS_2, + ERR_NO_PATHS, + ERR_Z_ORDER +}; + +// boolean operation +enum bool_op +{ + bool_op_union, // A OR B + bool_op_inters, // A AND B + bool_op_diff, // A \ B + bool_op_symdiff, // A XOR B + bool_op_cut, // coupure (pleines) + bool_op_slice // coupure (contour) +}; +typedef enum bool_op BooleanOp; + +class SPBox3D; +class Persp3D; + +namespace Inkscape { + +namespace XML { +class Node; +} + +struct hashed{}; +struct random_access{}; + +struct is_item { + bool operator()(SPObject* obj) { + return SP_IS_ITEM(obj); + } +}; + +struct is_group { + bool operator()(SPObject* obj) { + return SP_IS_GROUP(obj); + } +}; + +struct object_to_item { + typedef SPItem* result_type; + SPItem* operator()(SPObject* obj) const { + return SP_ITEM(obj); + } +}; + +struct object_to_node { + typedef XML::Node* result_type; + XML::Node* operator()(SPObject* obj) const { + return obj->getRepr(); + } +}; + +struct object_to_group { + typedef SPGroup* result_type; + SPGroup* operator()(SPObject* obj) const { + return SP_GROUP(obj); + } +}; + +typedef boost::multi_index_container< + SPObject*, + boost::multi_index::indexed_by< + boost::multi_index::sequenced<>, + boost::multi_index::random_access< + boost::multi_index::tag>, + boost::multi_index::hashed_unique< + boost::multi_index::tag, + boost::multi_index::identity> + >> MultiIndexContainer; + +typedef boost::any_range< + SPObject*, + boost::random_access_traversal_tag, + SPObject* const&, + std::ptrdiff_t> SPObjectRange; + +class ObjectSet { +public: + enum CompareSize {HORIZONTAL, VERTICAL, AREA}; + typedef decltype(MultiIndexContainer().get() | boost::adaptors::filtered(is_item()) | boost::adaptors::transformed(object_to_item())) SPItemRange; + typedef decltype(MultiIndexContainer().get() | boost::adaptors::filtered(is_group()) | boost::adaptors::transformed(object_to_group())) SPGroupRange; + typedef decltype(MultiIndexContainer().get() | boost::adaptors::filtered(is_item()) | boost::adaptors::transformed(object_to_node())) XMLNodeRange; + + ObjectSet(SPDesktop* desktop): _desktop(desktop) { + if (desktop) + _document = desktop->getDocument(); + }; + ObjectSet(SPDocument* doc): _desktop(nullptr), _document(doc) {}; + ObjectSet(): _desktop(nullptr), _document(nullptr) {}; + virtual ~ObjectSet(); + + void setDocument(SPDocument* doc){ + _document = doc; + } + + + /** + * Add an SPObject to the set of selected objects. + * + * @param obj the SPObject to add + * @param nosignal true if no signals should be sent + */ + bool add(SPObject* object, bool nosignal = false); + + /** + * Add an XML node's SPObject to the set of selected objects. + * + * @param the xml node of the item to add + */ + void add(XML::Node *repr) { + if(document() && repr) + add(document()->getObjectById(repr->attribute("id"))); + } + + /** Add items from an STL iterator range to the selection. + * \param from the begin iterator + * \param to the end iterator + */ + template + void add(InputIterator from, InputIterator to) { + for(auto it = from; it != to; ++it) { + _add(*it); + } + _emitSignals(); + } + + /** + * Removes an item from the set of selected objects. + * + * It is ok to call this method for an unselected item. + * + * @param item the item to unselect + * + * @return is success + */ + bool remove(SPObject* object); + + /** + * Returns true if the given object is selected. + */ + bool includes(SPObject *object); + + /** + * Set the selection to a single specific object. + * + * @param obj the object to select + */ + void set(SPObject *object, bool persist_selection_context = false); + void set(XML::Node *repr) { + if(document() && repr) + set(document()->getObjectById(repr->attribute("id"))); + } + /** + * Unselects all selected objects. + */ + void clear(); + + /** + * Returns size of the selection. + */ + int size(); + + /** + * Returns true if no items are selected. + */ + bool isEmpty(); + + /** + * Removes an item if selected, adds otherwise. + * + * @param item the item to unselect + */ + void toggle(SPObject *obj); + + /** + * Returns a single selected object. + * + * @return NULL unless exactly one object is selected + */ + SPObject *single(); + + /** + * Returns a single selected item. + * + * @return NULL unless exactly one object is selected + */ + SPItem *singleItem(); + + /** + * Returns the smallest item from this selection. + */ + SPItem *smallestItem(CompareSize compare); + + /** + * Returns the largest item from this selection. + */ + SPItem *largestItem(CompareSize compare); + + /** Returns the list of selected objects. */ + SPObjectRange objects(); + + /** Returns a range of selected SPItems. */ + SPItemRange items() { + return SPItemRange(_container.get() + | boost::adaptors::filtered(is_item()) + | boost::adaptors::transformed(object_to_item())); + }; + + /** Returns a range of selected groups. */ + SPGroupRange groups() { + return SPGroupRange (_container.get() + | boost::adaptors::filtered(is_group()) + | boost::adaptors::transformed(object_to_group())); + } + + /** Returns a range of the xml nodes of all selected objects. */ + XMLNodeRange xmlNodes() { + return XMLNodeRange(_container.get() + | boost::adaptors::filtered(is_item()) + | boost::adaptors::transformed(object_to_node())); + } + + /** + * Returns a single selected object's xml node. + * + * @return NULL unless exactly one object is selected + */ + XML::Node *singleRepr(); + + /** + * Selects exactly the specified objects. + * + * @param objs the objects to select + */ + template + typename boost::enable_if, void>::type + setList(const std::vector &objs) { + _clear(); + addList(objs); + } + + /** + * Selects exactly the specified objects. + * + * @param list the repr list to add + */ + void setReprList(std::vector const &list); + + /** + * Adds the specified objects to selection, without deselecting first. + * + * @param objs the objects to select + */ + template + typename boost::enable_if, void>::type + addList(const std::vector &objs) { + for (auto obj: objs) { + if (!includes(obj)) { + add(obj, true); + } + } + _emitSignals(); + } + + /** Returns the bounding rectangle of the selection. */ + Geom::OptRect bounds(SPItem::BBoxType type) const; + Geom::OptRect visualBounds() const; + Geom::OptRect geometricBounds() const; + + /** + * Returns either the visual or geometric bounding rectangle of the selection, based on the + * preferences specified for the selector tool + */ + Geom::OptRect preferredBounds() const; + + /* Returns the bounding rectangle of the selectionin document coordinates.*/ + Geom::OptRect documentBounds(SPItem::BBoxType type) const; + + /** + * Returns the rotation/skew center of the selection. + */ + boost::optional center() const; + + /** Returns a list of all perspectives which have a 3D box in the current selection. + (these may also be nested in groups) */ + std::list const perspList(); + + /** + * Returns a list of all 3D boxes in the current selection which are associated to @c + * persp. If @c pers is @c NULL, return all selected boxes. + */ + std::list const box3DList(Persp3D *persp = NULL); + + /** + * Returns the desktop the selection is bound to + * + * @return the desktop the selection is bound to, or NULL if in console mode + */ + SPDesktop *desktop() { return _desktop; } + + /** + * Returns the document the selection is bound to + * + * @return the document the selection is bound to, or NULL if in console mode + */ + SPDocument *document() { return _document; } + + //item groups operations + //in selection-chemistry.cpp + void deleteItems(); + void duplicate(bool suppressDone = false, bool duplicateLayer = false); + void clone(); + + /** + * @brief Unlink all directly selected clones. + * @param skip_undo If this is set to true the call to DocumentUndo::done is omitted. + * @return True if anything was unlinked, otherwise false. + */ + bool unlink(const bool skip_undo = false); + /** + * @brief Recursively unlink any clones present in the current selection, + * including clones which are used to clip other objects, groups of clones etc. + * @return true if anything was unlinked, otherwise false. + */ + bool unlinkRecursive(const bool skip_undo = false); + void relink(); + void cloneOriginal(); + void cloneOriginalPathLPE(bool allow_transforms = false); + Inkscape::XML::Node* group(); + void popFromGroup(); + void ungroup(); + + //z-order management + //in selection-chemistry.cpp + void stackUp(bool skip_undo = false); + void raise(bool skip_undo = false); + void raiseToTop(bool skip_undo = false); + void stackDown(bool skip_undo = false); + void lower(bool skip_undo = false); + void lowerToBottom(bool skip_undo = false); + void toNextLayer(bool skip_undo = false); + void toPrevLayer(bool skip_undo = false); + void toLayer(SPObject *layer, bool skip_undo = false); + + //clipboard management + //in selection-chemistry.cpp + void copy(); + void cut(); + void pasteStyle(); + void pasteSize(bool apply_x, bool apply_y); + void pasteSizeSeparately(bool apply_x, bool apply_y); + void pastePathEffect(); + + //path operations + //in path-chemistry.cpp + void combine(bool skip_undo = false); + void breakApart(bool skip_undo = false); + void toCurves(bool skip_undo = false); + void toLPEItems(); + void pathReverse(); + + // Boolean operations + // in splivarot.cpp + bool pathUnion(const bool skip_undo = false); + bool pathIntersect(const bool skip_undo = false); + bool pathDiff(const bool skip_undo = false); + bool pathSymDiff(const bool skip_undo = false); + bool pathCut(const bool skip_undo = false); + bool pathSlice(const bool skip_undo = false); + + //Other path operations + //in selection-chemistry.cpp + void toMarker(bool apply = true); + void toGuides(); + void toSymbol(); + void unSymbol(); + void tile(bool apply = true); //"Object to Pattern" + void untile(); + void createBitmapCopy(); + void setMask(bool apply_clip_path, bool apply_to_layer = false, bool skip_undo = false); + void editMask(bool clip); + void unsetMask(const bool apply_clip_path, const bool skip_undo = false); + void setClipGroup(); + + // moves + // in selection-chemistry.cpp + void removeLPE(); + void removeFilter(); + void applyAffine(Geom::Affine const &affine, bool set_i2d=true,bool compensate=true, bool adjust_transf_center=true); + void removeTransform(); + void setScaleAbsolute(double, double, double, double); + void setScaleRelative(const Geom::Point&, const Geom::Scale&); + void rotateRelative(const Geom::Point&, double); + void skewRelative(const Geom::Point&, double, double); + void moveRelative(const Geom::Point &move, bool compensate = true); + void moveRelative(double dx, double dy); + void rotate90(bool ccw); + void rotate(double); + void rotateScreen(double); + void scale(double); + void scaleScreen(double); + void scaleTimes(double); + void move(double dx, double dy); + void moveScreen(double dx, double dy); + + // various + void getExportHints(Glib::ustring &filename, float *xdpi, float *ydpi); + bool fitCanvas(bool with_margins, bool skip_undo = false); + void swapFillStroke(); + +protected: + virtual void _connectSignals(SPObject* object) {}; + virtual void _releaseSignals(SPObject* object) {}; + virtual void _emitSignals() {}; + void _add(SPObject* object); + void _clear(); + void _remove(SPObject* object); + bool _anyAncestorIsInSet(SPObject *object); + void _removeDescendantsFromSet(SPObject *object); + void _removeAncestorsFromSet(SPObject *object); + SPItem *_sizeistItem(bool sml, CompareSize compare); + SPObject *_getMutualAncestor(SPObject *object); + virtual void _add3DBoxesRecursively(SPObject *obj); + virtual void _remove3DBoxesRecursively(SPObject *obj); + + MultiIndexContainer _container; + GC::soft_ptr _desktop; + GC::soft_ptr _document; + std::list _3dboxes; + std::unordered_map _releaseConnections; + +private: + BoolOpErrors pathBoolOp(bool_op bop, const bool skip_undo, const bool checked = false, const unsigned int verb = SP_VERB_NONE, const Glib::ustring description = ""); + void _disconnect(SPObject* object); + +}; + +typedef ObjectSet::SPItemRange SPItemRange; +typedef ObjectSet::SPGroupRange SPGroupRange; +typedef ObjectSet::XMLNodeRange XMLNodeRange; + +} // namespace Inkscape + +#endif //INKSCAPE_PROTOTYPE_OBJECTSET_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/persp3d-reference.cpp b/src/object/persp3d-reference.cpp new file mode 100644 index 000000000..49510764e --- /dev/null +++ b/src/object/persp3d-reference.cpp @@ -0,0 +1,110 @@ +/* + * The reference corresponding to the inkscape:perspectiveID attribute + * + * Copyright (C) 2007 Johan Engelen + * Copyright (C) 2007 Maximilian Albert + * + * Released under GNU GPL, read the file 'COPYING' for more information. + */ + +#include "persp3d-reference.h" +#include "uri.h" + +static void persp3dreference_href_changed(SPObject *old_ref, SPObject *ref, Persp3DReference *persp3dref); +static void persp3dreference_delete_self(SPObject *deleted, Persp3DReference *persp3dref); +static void persp3dreference_source_modified(SPObject *iSource, guint flags, Persp3DReference *persp3dref); + +Persp3DReference::Persp3DReference(SPObject* i_owner) : URIReference(i_owner) +{ + owner=i_owner; + persp_href = NULL; + persp_repr = NULL; + persp = NULL; + _changed_connection = changedSignal().connect(sigc::bind(sigc::ptr_fun(persp3dreference_href_changed), this)); // listening to myself, this should be virtual instead +} + +Persp3DReference::~Persp3DReference(void) +{ + _changed_connection.disconnect(); // to do before unlinking + + quit_listening(); + unlink(); +} + +bool +Persp3DReference::_acceptObject(SPObject *obj) const +{ + return SP_IS_PERSP3D(obj) && URIReference::_acceptObject(obj); +; + /* effic: Don't bother making this an inline function: _acceptObject is a virtual function, + typically called from a context where the runtime type is not known at compile time. */ +} + +void +Persp3DReference::unlink(void) +{ + g_free(persp_href); + persp_href = NULL; + detach(); +} + +void +Persp3DReference::start_listening(Persp3D* to) +{ + if ( to == NULL ) { + return; + } + persp = to; + persp_repr = to->getRepr(); + _delete_connection = to->connectDelete(sigc::bind(sigc::ptr_fun(&persp3dreference_delete_self), this)); + _modified_connection = to->connectModified(sigc::bind<2>(sigc::ptr_fun(&persp3dreference_source_modified), this)); +} + +void +Persp3DReference::quit_listening(void) +{ + if ( persp == NULL ) { + return; + } + _modified_connection.disconnect(); + _delete_connection.disconnect(); + persp_repr = NULL; + persp = NULL; +} + +static void +persp3dreference_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, Persp3DReference *persp3dref) +{ + persp3dref->quit_listening(); + Persp3D *refobj = SP_PERSP3D(persp3dref->getObject()); + if ( refobj ) { + persp3dref->start_listening(refobj); + } + + persp3dref->owner->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +static void +persp3dreference_delete_self(SPObject */*deleted*/, Persp3DReference *persp3dref) +{ + g_return_if_fail(persp3dref->owner); + persp3dref->owner->deleteObject(); +} + +static void +persp3dreference_source_modified(SPObject */*iSource*/, guint /*flags*/, Persp3DReference *persp3dref) +{ + persp3dref->owner->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/persp3d-reference.h b/src/object/persp3d-reference.h new file mode 100644 index 000000000..871b29623 --- /dev/null +++ b/src/object/persp3d-reference.h @@ -0,0 +1,68 @@ +#ifndef SEEN_PERSP3D_REFERENCE_H +#define SEEN_PERSP3D_REFERENCE_H + +/* + * The reference corresponding to the inkscape:perspectiveID attribute + * + * Copyright (C) 2007 Johan Engelen + * Copyright (C) 2007 Maximilian Albert + * + * Released under GNU GPL, read the file 'COPYING' for more information. + */ + +#include +#include + +#include "uri-references.h" +#include "persp3d.h" + +class SPObject; + +namespace Inkscape { +namespace XML { +class Node; +} +} + +class Persp3DReference : public Inkscape::URIReference { +public: + Persp3DReference(SPObject *obj); + ~Persp3DReference(); + + Persp3D *getObject() const { + return SP_PERSP3D(URIReference::getObject()); + } + + SPObject *owner; + + // concerning the Persp3D (we only use SPBox3D) that is referred to: + char *persp_href; + Inkscape::XML::Node *persp_repr; + Persp3D *persp; + + sigc::connection _changed_connection; + sigc::connection _modified_connection; + sigc::connection _delete_connection; + + void link(char* to); + void unlink(void); + void start_listening(Persp3D* to); + void quit_listening(void); + +protected: + virtual bool _acceptObject(SPObject *obj) const; +}; + + +#endif /* !SEEN_PERSP3D_REFERENCE_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/persp3d.cpp b/src/object/persp3d.cpp new file mode 100644 index 000000000..ca39447a1 --- /dev/null +++ b/src/object/persp3d.cpp @@ -0,0 +1,581 @@ +/* + * Class modelling a 3D perspective as an SPObject + * + * Authors: + * Maximilian Albert + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 2007 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "persp3d.h" +#include "perspective-line.h" +#include "sp-root.h" +#include "sp-defs.h" + +#include "attributes.h" +#include "document-private.h" +#include "document-undo.h" +#include "vanishing-point.h" +#include "ui/tools/box3d-tool.h" +#include "svg/stringstream.h" +#include "xml/node-event-vector.h" +#include "desktop.h" + +#include +#include "verbs.h" +#include "util/units.h" + +using Inkscape::DocumentUndo; + +static void persp3d_on_repr_attr_changed (Inkscape::XML::Node * repr, const gchar *key, const gchar *oldval, const gchar *newval, bool is_interactive, void * data); + +static int global_counter = 0; + +/* Constructor/destructor for the internal class */ + +Persp3DImpl::Persp3DImpl() : + tmat (Proj::TransfMat3x4 ()), + document (NULL) +{ + my_counter = global_counter++; +} + +static Inkscape::XML::NodeEventVector const persp3d_repr_events = { + NULL, /* child_added */ + NULL, /* child_removed */ + persp3d_on_repr_attr_changed, + NULL, /* content_changed */ + NULL /* order_changed */ +}; + + +Persp3D::Persp3D() : SPObject() { + this->perspective_impl = new Persp3DImpl(); +} + +Persp3D::~Persp3D() { +} + + +/** + * Virtual build: set persp3d attributes from its associated XML node. + */ +void Persp3D::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPObject::build(document, repr); + + this->readAttr( "inkscape:vp_x" ); + this->readAttr( "inkscape:vp_y" ); + this->readAttr( "inkscape:vp_z" ); + this->readAttr( "inkscape:persp3d-origin" ); + + if (repr) { + repr->addListener (&persp3d_repr_events, this); + } +} + +/** + * Virtual release of Persp3D members before destruction. + */ +void Persp3D::release() { + delete this->perspective_impl; + this->getRepr()->removeListenerByData(this); +} + + +/** + * Virtual set: set attribute to value. + */ +// FIXME: Currently we only read the finite positions of vanishing points; +// should we move VPs into their own repr (as it's done for SPStop, e.g.)? +void Persp3D::set(unsigned key, gchar const *value) { + + // Read values are in 'user units'. + double scale_x = 1.0; + double scale_y = 1.0; + SPRoot *root = document->getRoot(); + if( root->viewBox_set ) { + scale_x = root->width.computed / root->viewBox.width(); + scale_y = root->height.computed / root->viewBox.height(); + } + + switch (key) { + case SP_ATTR_INKSCAPE_PERSP3D_VP_X: { + if (value) { + Proj::Pt2 pt (value); + Proj::Pt2 ptn ( pt[0]*scale_x, pt[1]*scale_y, pt[2] ); + perspective_impl->tmat.set_image_pt( Proj::X, ptn ); + } + break; + } + case SP_ATTR_INKSCAPE_PERSP3D_VP_Y: { + if (value) { + Proj::Pt2 pt (value); + Proj::Pt2 ptn ( pt[0]*scale_x, pt[1]*scale_y, pt[2] ); + perspective_impl->tmat.set_image_pt( Proj::Y, ptn ); + } + break; + } + case SP_ATTR_INKSCAPE_PERSP3D_VP_Z: { + if (value) { + Proj::Pt2 pt (value); + Proj::Pt2 ptn ( pt[0]*scale_x, pt[1]*scale_y, pt[2] ); + perspective_impl->tmat.set_image_pt( Proj::Z, ptn ); + } + break; + } + case SP_ATTR_INKSCAPE_PERSP3D_ORIGIN: { + if (value) { + Proj::Pt2 pt (value); + Proj::Pt2 ptn ( pt[0]*scale_x, pt[1]*scale_y, pt[2] ); + perspective_impl->tmat.set_image_pt( Proj::W, ptn ); + } + break; + } + default: { + SPObject::set(key, value); + break; + } + } + + // FIXME: Is this the right place for resetting the draggers? + Inkscape::UI::Tools::ToolBase *ec = INKSCAPE.active_event_context(); + if (SP_IS_BOX3D_CONTEXT(ec)) { + Inkscape::UI::Tools::Box3dTool *bc = SP_BOX3D_CONTEXT(ec); + bc->_vpdrag->updateDraggers(); + bc->_vpdrag->updateLines(); + bc->_vpdrag->updateBoxHandles(); + bc->_vpdrag->updateBoxReprs(); + } +} + +void Persp3D::update(SPCtx *ctx, guint flags) { + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + + /* TODO: Should we update anything here? */ + + } + + SPObject::update(ctx, flags); +} + +Persp3D *persp3d_create_xml_element(SPDocument *document, Persp3DImpl *dup) {// if dup is given, copy the attributes over + SPDefs *defs = document->getDefs(); + Inkscape::XML::Document *xml_doc = document->getReprDoc(); + Inkscape::XML::Node *repr; + + /* if no perspective is given, create a default one */ + repr = xml_doc->createElement("inkscape:perspective"); + repr->setAttribute("sodipodi:type", "inkscape:persp3d"); + + // Use 'user-units' + double width = document->getWidth().value("px"); + double height = document->getHeight().value("px"); + if( document->getRoot()->viewBox_set ) { + Geom::Rect vb = document->getRoot()->viewBox; + width = vb.width(); + height = vb.height(); + } + + Proj::Pt2 proj_vp_x = Proj::Pt2 (0.0, height/2.0, 1.0); + Proj::Pt2 proj_vp_y = Proj::Pt2 (0.0, 1000.0, 0.0); + Proj::Pt2 proj_vp_z = Proj::Pt2 (width, height/2.0, 1.0); + Proj::Pt2 proj_origin = Proj::Pt2 (width/2.0, height/3.0, 1.0 ); + + if (dup) { + proj_vp_x = dup->tmat.column (Proj::X); + proj_vp_y = dup->tmat.column (Proj::Y); + proj_vp_z = dup->tmat.column (Proj::Z); + proj_origin = dup->tmat.column (Proj::W); + } + + gchar *str = NULL; + str = proj_vp_x.coord_string(); + repr->setAttribute("inkscape:vp_x", str); + g_free (str); + str = proj_vp_y.coord_string(); + repr->setAttribute("inkscape:vp_y", str); + g_free (str); + str = proj_vp_z.coord_string(); + repr->setAttribute("inkscape:vp_z", str); + g_free (str); + str = proj_origin.coord_string(); + repr->setAttribute("inkscape:persp3d-origin", str); + g_free (str); + + /* Append the new persp3d to defs */ + defs->getRepr()->addChild(repr, NULL); + Inkscape::GC::release(repr); + + return reinterpret_cast( defs->get_child_by_repr(repr) ); +} + +Persp3D *persp3d_document_first_persp(SPDocument *document) +{ + Persp3D *first = 0; + for (auto& child: document->getDefs()->children) { + if (SP_IS_PERSP3D(&child)) { + first = SP_PERSP3D(&child); + break; + } + } + return first; +} + +/** + * Virtual write: write object attributes to repr. + */ +Inkscape::XML::Node* Persp3D::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + + if ((flags & SP_OBJECT_WRITE_BUILD & SP_OBJECT_WRITE_EXT) && !repr) { + // this is where we end up when saving as plain SVG (also in other circumstances?); + // hence we don't set the sodipodi:type attribute + repr = xml_doc->createElement("inkscape:perspective"); + } + + if (flags & SP_OBJECT_WRITE_EXT) { + + // Written values are in 'user units'. + double scale_x = 1.0; + double scale_y = 1.0; + SPRoot *root = document->getRoot(); + if( root->viewBox_set ) { + scale_x = root->viewBox.width() / root->width.computed; + scale_y = root->viewBox.height() / root->height.computed; + } + { + Proj::Pt2 pt = perspective_impl->tmat.column( Proj::X ); + Inkscape::SVGOStringStream os; + os << pt[0] * scale_x << " : " << pt[1] * scale_y << " : " << pt[2]; + repr->setAttribute("inkscape:vp_x", os.str().c_str()); + } + { + Proj::Pt2 pt = perspective_impl->tmat.column( Proj::Y ); + Inkscape::SVGOStringStream os; + os << pt[0] * scale_x << " : " << pt[1] * scale_y << " : " << pt[2]; + repr->setAttribute("inkscape:vp_y", os.str().c_str()); + } + { + Proj::Pt2 pt = perspective_impl->tmat.column( Proj::Z ); + Inkscape::SVGOStringStream os; + os << pt[0] * scale_x << " : " << pt[1] * scale_y << " : " << pt[2]; + repr->setAttribute("inkscape:vp_z", os.str().c_str()); + } + { + Proj::Pt2 pt = perspective_impl->tmat.column( Proj::W ); + Inkscape::SVGOStringStream os; + os << pt[0] * scale_x << " : " << pt[1] * scale_y << " : " << pt[2]; + repr->setAttribute("inkscape:persp3d-origin", os.str().c_str()); + } + } + + SPObject::write(xml_doc, repr, flags); + + return repr; +} + +/* convenience wrapper around persp3d_get_finite_dir() and persp3d_get_infinite_dir() */ +Geom::Point persp3d_get_PL_dir_from_pt (Persp3D *persp, Geom::Point const &pt, Proj::Axis axis) { + if (persp3d_VP_is_finite(persp->perspective_impl, axis)) { + return persp3d_get_finite_dir(persp, pt, axis); + } else { + return persp3d_get_infinite_dir(persp, axis); + } +} + +Geom::Point +persp3d_get_finite_dir (Persp3D *persp, Geom::Point const &pt, Proj::Axis axis) { + Box3D::PerspectiveLine pl(pt, axis, persp); + return pl.direction(); +} + +Geom::Point +persp3d_get_infinite_dir (Persp3D *persp, Proj::Axis axis) { + Proj::Pt2 vp(persp3d_get_VP(persp, axis)); + if (vp[2] != 0.0) { + g_print ("VP should be infinite but is (%f : %f : %f)\n", vp[0], vp[1], vp[2]); + g_return_val_if_fail(vp[2] != 0.0, Geom::Point(0.0, 0.0)); + } + return Geom::Point(vp[0], vp[1]); +} + +double +persp3d_get_infinite_angle (Persp3D *persp, Proj::Axis axis) { + return persp->perspective_impl->tmat.get_infinite_angle(axis); +} + +bool +persp3d_VP_is_finite (Persp3DImpl *persp_impl, Proj::Axis axis) { + return persp_impl->tmat.has_finite_image(axis); +} + +void +persp3d_toggle_VP (Persp3D *persp, Proj::Axis axis, bool set_undo) { + persp->perspective_impl->tmat.toggle_finite(axis); + // FIXME: Remove this repr update and rely on vp_drag_sel_modified() to do this for us + // On the other hand, vp_drag_sel_modified() would update all boxes; + // here we can confine ourselves to the boxes of this particular perspective. + persp3d_update_box_reprs (persp); + persp->updateRepr(SP_OBJECT_WRITE_EXT); + if (set_undo) { + DocumentUndo::done(SP_ACTIVE_DESKTOP->getDocument(), SP_VERB_CONTEXT_3DBOX, + _("Toggle vanishing point")); + } +} + +/* toggle VPs for the same axis in all perspectives of a given list */ +void +persp3d_toggle_VPs (std::list p, Proj::Axis axis) { + for (std::list::iterator i = p.begin(); i != p.end(); ++i) { + persp3d_toggle_VP((*i), axis, false); + } + DocumentUndo::done(SP_ACTIVE_DESKTOP->getDocument(), SP_VERB_CONTEXT_3DBOX, + _("Toggle multiple vanishing points")); +} + +void +persp3d_set_VP_state (Persp3D *persp, Proj::Axis axis, Proj::VPState state) { + if (persp3d_VP_is_finite(persp->perspective_impl, axis) != (state == Proj::VP_FINITE)) { + persp3d_toggle_VP(persp, axis); + } +} + +void +persp3d_rotate_VP (Persp3D *persp, Proj::Axis axis, double angle, bool alt_pressed) { // angle is in degrees + // FIXME: Most of this functionality should be moved to trans_mat_3x4.(h|cpp) + if (persp->perspective_impl->tmat.has_finite_image(axis)) { + // don't rotate anything for finite VPs + return; + } + Proj::Pt2 v_dir_proj (persp->perspective_impl->tmat.column(axis)); + Geom::Point v_dir (v_dir_proj[0], v_dir_proj[1]); + double a = Geom::atan2 (v_dir) * 180/M_PI; + a += alt_pressed ? 0.5 * ((angle > 0 ) - (angle < 0)) : angle; // the r.h.s. yields +/-0.5 or angle + persp->perspective_impl->tmat.set_infinite_direction (axis, a); + + persp3d_update_box_reprs (persp); + persp->updateRepr(SP_OBJECT_WRITE_EXT); +} + +void +persp3d_apply_affine_transformation (Persp3D *persp, Geom::Affine const &xform) { + persp->perspective_impl->tmat *= xform; + persp3d_update_box_reprs(persp); + persp->updateRepr(SP_OBJECT_WRITE_EXT); +} + +void +persp3d_add_box (Persp3D *persp, SPBox3D *box) { + Persp3DImpl *persp_impl = persp->perspective_impl; + + if (!box) { + return; + } + if (std::find (persp_impl->boxes.begin(), persp_impl->boxes.end(), box) != persp_impl->boxes.end()) { + return; + } + persp_impl->boxes.push_back(box); +} + +void +persp3d_remove_box (Persp3D *persp, SPBox3D *box) { + Persp3DImpl *persp_impl = persp->perspective_impl; + + std::vector::iterator i = std::find (persp_impl->boxes.begin(), persp_impl->boxes.end(), box); + if (i != persp_impl->boxes.end()) + persp_impl->boxes.erase(i); +} + +bool +persp3d_has_box (Persp3D *persp, SPBox3D *box) { + Persp3DImpl *persp_impl = persp->perspective_impl; + + // FIXME: For some reason, std::find() does not seem to compare pointers "correctly" (or do we need to + // provide a proper comparison function?), so we manually traverse the list. + for (std::vector::iterator i = persp_impl->boxes.begin(); i != persp_impl->boxes.end(); ++i) { + if ((*i) == box) { + return true; + } + } + return false; +} + +void +persp3d_update_box_displays (Persp3D *persp) { + Persp3DImpl *persp_impl = persp->perspective_impl; + + if (persp_impl->boxes.empty()) + return; + for (std::vector::iterator i = persp_impl->boxes.begin(); i != persp_impl->boxes.end(); ++i) { + box3d_position_set(*i); + } +} + +void +persp3d_update_box_reprs (Persp3D *persp) { + if (!persp) { + // Hmm, is it an error if this happens? + return; + } + Persp3DImpl *persp_impl = persp->perspective_impl; + + if (persp_impl->boxes.empty()) + return; + for (std::vector::iterator i = persp_impl->boxes.begin(); i != persp_impl->boxes.end(); ++i) { + (*i)->updateRepr(SP_OBJECT_WRITE_EXT); + box3d_set_z_orders(*i); + } +} + +void +persp3d_update_z_orders (Persp3D *persp) { + Persp3DImpl *persp_impl = persp->perspective_impl; + + if (persp_impl->boxes.empty()) + return; + for (std::vector::iterator i = persp_impl->boxes.begin(); i != persp_impl->boxes.end(); ++i) { + box3d_set_z_orders(*i); + } +} + +// FIXME: For some reason we seem to require a vector instead of a list in Persp3D, but in vp_knot_moved_handler() +// we need a list of boxes. If we can store a list in Persp3D right from the start, this function becomes +// obsolete. We should do this. +std::list +persp3d_list_of_boxes(Persp3D *persp) { + Persp3DImpl *persp_impl = persp->perspective_impl; + + std::list bx_lst; + for (std::vector::iterator i = persp_impl->boxes.begin(); i != persp_impl->boxes.end(); ++i) { + bx_lst.push_back(*i); + } + return bx_lst; +} + +bool +persp3d_perspectives_coincide(const Persp3D *lhs, const Persp3D *rhs) +{ + return lhs->perspective_impl->tmat == rhs->perspective_impl->tmat; +} + +void +persp3d_absorb(Persp3D *persp1, Persp3D *persp2) { + /* double check if we are called in sane situations */ + g_return_if_fail (persp3d_perspectives_coincide(persp1, persp2) && persp1 != persp2); + + std::vector::iterator boxes; + + // Note: We first need to copy the boxes of persp2 into a separate list; + // otherwise the loop below gets confused when perspectives are reattached. + std::list boxes_of_persp2 = persp3d_list_of_boxes(persp2); + + for (std::list::iterator i = boxes_of_persp2.begin(); i != boxes_of_persp2.end(); ++i) { + box3d_switch_perspectives((*i), persp2, persp1, true); + (*i)->updateRepr(SP_OBJECT_WRITE_EXT); // so that undo/redo can do its job properly + } +} + +static void +persp3d_on_repr_attr_changed ( Inkscape::XML::Node * /*repr*/, + const gchar */*key*/, + const gchar */*oldval*/, + const gchar */*newval*/, + bool /*is_interactive*/, + void * data ) +{ + if (!data) + return; + + Persp3D *persp = (Persp3D*) data; + persp3d_update_box_displays (persp); +} + +/* checks whether all boxes linked to this perspective are currently selected */ +bool +persp3d_has_all_boxes_in_selection (Persp3D *persp, Inkscape::ObjectSet *set) { + Persp3DImpl *persp_impl = persp->perspective_impl; + + std::list selboxes = set->box3DList(); + + for (std::vector::iterator i = persp_impl->boxes.begin(); i != persp_impl->boxes.end(); ++i) { + if (std::find(selboxes.begin(), selboxes.end(), *i) == selboxes.end()) { + // we have an unselected box in the perspective + return false; + } + } + return true; +} + +/* some debugging stuff follows */ + +void +persp3d_print_debugging_info (Persp3D *persp) { + Persp3DImpl *persp_impl = persp->perspective_impl; + g_print ("=== Info for Persp3D %d ===\n", persp_impl->my_counter); + gchar * cstr; + for (int i = 0; i < 4; ++i) { + cstr = persp3d_get_VP(persp, Proj::axes[i]).coord_string(); + g_print (" VP %s: %s\n", Proj::string_from_axis(Proj::axes[i]), cstr); + g_free(cstr); + } + cstr = persp3d_get_VP(persp, Proj::W).coord_string(); + g_print (" Origin: %s\n", cstr); + g_free(cstr); + + g_print (" Boxes: "); + for (std::vector::iterator i = persp_impl->boxes.begin(); i != persp_impl->boxes.end(); ++i) { + g_print ("%d (%d) ", (*i)->my_counter, box3d_get_perspective(*i)->perspective_impl->my_counter); + } + g_print ("\n"); + g_print ("========================\n"); +} + +void persp3d_print_debugging_info_all(SPDocument *document) +{ + for (auto& child: document->getDefs()->children) { + if (SP_IS_PERSP3D(&child)) { + persp3d_print_debugging_info(SP_PERSP3D(&child)); + } + } + persp3d_print_all_selected(); +} + +void +persp3d_print_all_selected() { + g_print ("\n======================================\n"); + g_print ("Selected perspectives and their boxes:\n"); + + std::list sel_persps = SP_ACTIVE_DESKTOP->getSelection()->perspList(); + + for (std::list::iterator j = sel_persps.begin(); j != sel_persps.end(); ++j) { + Persp3D *persp = SP_PERSP3D(*j); + Persp3DImpl *persp_impl = persp->perspective_impl; + g_print (" %s (%d): ", persp->getRepr()->attribute("id"), persp->perspective_impl->my_counter); + for (std::vector::iterator i = persp_impl->boxes.begin(); + i != persp_impl->boxes.end(); ++i) { + g_print ("%d ", (*i)->my_counter); + } + g_print ("\n"); + } + g_print ("======================================\n\n"); + } + +void print_current_persp3d(gchar *func_name, Persp3D *persp) { + g_print ("%s: current_persp3d is now %s\n", + func_name, + persp ? persp->getRepr()->attribute("id") : "NULL"); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/persp3d.h b/src/object/persp3d.h new file mode 100644 index 000000000..a6ca43177 --- /dev/null +++ b/src/object/persp3d.h @@ -0,0 +1,130 @@ +#ifndef SEEN_PERSP3D_H +#define SEEN_PERSP3D_H + +/* + * Implementation of 3D perspectives as SPObjects + * + * Authors: + * Maximilian Albert + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define SP_PERSP3D(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_PERSP3D(obj) (dynamic_cast((SPObject*)obj) != NULL) + +#include +#include +#include + +#include "transf_mat_3x4.h" +#include "document.h" +#include "inkscape.h" // for SP_ACTIVE_DOCUMENT + +#include "sp-object.h" + +class SPBox3D; + +namespace Inkscape { +namespace UI { +namespace Tools { + +class Box3dTool; + +} +} +} + + +class Persp3DImpl { +public: + Persp3DImpl(); + +//private: + Proj::TransfMat3x4 tmat; + + // Also write the list of boxes into the xml repr and vice versa link boxes to their persp3d? + std::vector boxes; + SPDocument *document; + + // for debugging only + int my_counter; + +// friend class Persp3D; +}; + +class Persp3D : public SPObject { +public: + Persp3D(); + virtual ~Persp3D(); + + Persp3DImpl *perspective_impl; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void set(unsigned int key, char const* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); +}; + + +// FIXME: Make more of these inline! +inline Persp3D * persp3d_get_from_repr (Inkscape::XML::Node *repr) { + return SP_PERSP3D(SP_ACTIVE_DOCUMENT->getObjectByRepr(repr)); +} +inline Proj::Pt2 persp3d_get_VP (Persp3D *persp, Proj::Axis axis) { + return persp->perspective_impl->tmat.column(axis); +} +Geom::Point persp3d_get_PL_dir_from_pt (Persp3D *persp, Geom::Point const &pt, Proj::Axis axis); // convenience wrapper around the following two +Geom::Point persp3d_get_finite_dir (Persp3D *persp, Geom::Point const &pt, Proj::Axis axis); +Geom::Point persp3d_get_infinite_dir (Persp3D *persp, Proj::Axis axis); +double persp3d_get_infinite_angle (Persp3D *persp, Proj::Axis axis); +bool persp3d_VP_is_finite (Persp3DImpl *persp_impl, Proj::Axis axis); +void persp3d_toggle_VP (Persp3D *persp, Proj::Axis axis, bool set_undo = true); +void persp3d_toggle_VPs (std::list, Proj::Axis axis); +void persp3d_set_VP_state (Persp3D *persp, Proj::Axis axis, Proj::VPState state); +void persp3d_rotate_VP (Persp3D *persp, Proj::Axis axis, double angle, bool alt_pressed); // angle is in degrees +void persp3d_apply_affine_transformation (Persp3D *persp, Geom::Affine const &xform); + +void persp3d_add_box (Persp3D *persp, SPBox3D *box); +void persp3d_remove_box (Persp3D *persp, SPBox3D *box); +bool persp3d_has_box (Persp3D *persp, SPBox3D *box); + +void persp3d_update_box_displays (Persp3D *persp); +void persp3d_update_box_reprs (Persp3D *persp); +void persp3d_update_z_orders (Persp3D *persp); +inline unsigned int persp3d_num_boxes (Persp3D *persp) { return persp->perspective_impl->boxes.size(); } +std::list persp3d_list_of_boxes(Persp3D *persp); + +bool persp3d_perspectives_coincide(Persp3D const *lhs, Persp3D const *rhs); +void persp3d_absorb(Persp3D *persp1, Persp3D *persp2); + +Persp3D * persp3d_create_xml_element (SPDocument *document, Persp3DImpl *dup = NULL); +Persp3D * persp3d_document_first_persp (SPDocument *document); + +bool persp3d_has_all_boxes_in_selection (Persp3D *persp, Inkscape::ObjectSet *set); + +void persp3d_print_debugging_info (Persp3D *persp); +void persp3d_print_debugging_info_all(SPDocument *doc); +void persp3d_print_all_selected(); + +void print_current_persp3d(char *func_name, Persp3D *persp); + +#endif /* __PERSP3D_H__ */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-anchor.cpp b/src/object/sp-anchor.cpp new file mode 100644 index 000000000..b40f53ee1 --- /dev/null +++ b/src/object/sp-anchor.cpp @@ -0,0 +1,189 @@ +/* + * SVG element implementation + * + * Author: + * Lauris Kaplinski + * Abhishek Sharma + * + * Copyright (C) 2017 Martin Owens + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define noSP_ANCHOR_VERBOSE + +#include +#include "xml/quote.h" +#include "xml/repr.h" +#include "attributes.h" +#include "sp-anchor.h" +#include "ui/view/view.h" +#include "document.h" + +SPAnchor::SPAnchor() : SPGroup() { + this->href = NULL; + this->type = NULL; + this->title = NULL; + this->page = NULL; +} + +SPAnchor::~SPAnchor() { +} + +void SPAnchor::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPGroup::build(document, repr); + + this->readAttr( "xlink:type" ); + this->readAttr( "xlink:role" ); + this->readAttr( "xlink:arcrole" ); + this->readAttr( "xlink:title" ); + this->readAttr( "xlink:show" ); + this->readAttr( "xlink:actuate" ); + this->readAttr( "xlink:href" ); + this->readAttr( "target" ); +} + +void SPAnchor::release() { + if (this->href) { + g_free(this->href); + this->href = NULL; + } + if (this->type) { + g_free(this->type); + this->type = NULL; + } + if (this->title) { + g_free(this->title); + this->title = NULL; + } + if (this->page) { + g_free(this->page); + this->page = NULL; + } + + SPGroup::release(); +} + +void SPAnchor::set(unsigned int key, const gchar* value) { + switch (key) { + case SP_ATTR_XLINK_HREF: + g_free(this->href); + this->href = g_strdup(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + this->updatePageAnchor(); + break; + case SP_ATTR_XLINK_TYPE: + g_free(this->type); + this->type = g_strdup(value); + this->updatePageAnchor(); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_XLINK_ROLE: + case SP_ATTR_XLINK_ARCROLE: + case SP_ATTR_XLINK_TITLE: + g_free(this->title); + this->title = g_strdup(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_XLINK_SHOW: + case SP_ATTR_XLINK_ACTUATE: + case SP_ATTR_TARGET: + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + default: + SPGroup::set(key, value); + break; + } +} + +/* + * Detect if this anchor qualifies as a page link and append + * the new page document to this document. + */ +void SPAnchor::updatePageAnchor() { + if (this->type && !strcmp(this->type, "page")) { + if (this->href && !this->page) { + this->page = this->document->createChildDoc(this->href); + } + } +} + +#define COPY_ATTR(rd,rs,key) (rd)->setAttribute((key), rs->attribute(key)); + +Inkscape::XML::Node* SPAnchor::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:a"); + } + + repr->setAttribute("xlink:href", this->href); + if (this->type) repr->setAttribute("xlink:type", this->type); + if (this->title) repr->setAttribute("xlink:title", this->title); + + if (repr != this->getRepr()) { + // XML Tree being directly used while it shouldn't be in the + // below COPY_ATTR lines + COPY_ATTR(repr, this->getRepr(), "xlink:role"); + COPY_ATTR(repr, this->getRepr(), "xlink:arcrole"); + COPY_ATTR(repr, this->getRepr(), "xlink:show"); + COPY_ATTR(repr, this->getRepr(), "xlink:actuate"); + COPY_ATTR(repr, this->getRepr(), "target"); + } + + SPGroup::write(xml_doc, repr, flags); + + return repr; +} + +const char* SPAnchor::displayName() const { + return _("Link"); +} + +gchar* SPAnchor::description() const { + if (this->href) { + char *quoted_href = xml_quote_strdup(this->href); + char *ret = g_strdup_printf(_("to %s"), quoted_href); + g_free(quoted_href); + return ret; + } else { + return g_strdup (_("without URI")); + } +} + +/* fixme: We should forward event to appropriate container/view */ +gint SPAnchor::event(SPEvent* event) { + switch (event->type) { + case SP_EVENT_ACTIVATE: + if (this->href) { + g_print("Activated xlink:href=\"%s\"\n", this->href); + return TRUE; + } + break; + + case SP_EVENT_MOUSEOVER: + (static_cast(event->data))->mouseover(); + break; + + case SP_EVENT_MOUSEOUT: + (static_cast(event->data))->mouseout(); + break; + + default: + break; + } + + return FALSE; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-anchor.h b/src/object/sp-anchor.h new file mode 100644 index 000000000..2dd81f74c --- /dev/null +++ b/src/object/sp-anchor.h @@ -0,0 +1,42 @@ +#ifndef SEEN_SP_ANCHOR_H +#define SEEN_SP_ANCHOR_H + +/* + * SVG element implementation + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-item-group.h" + +#define SP_ANCHOR(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_ANCHOR(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPAnchor : public SPGroup { +public: + SPAnchor(); + virtual ~SPAnchor(); + + char *href; + char *type; + char *title; + SPDocument *page; + + virtual void build(SPDocument *document, Inkscape::XML::Node *repr); + virtual void release(); + virtual void set(unsigned int key, char const* value); + virtual void updatePageAnchor(); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); + + virtual const char* displayName() const; + virtual char* description() const; + virtual int event(SPEvent *event); +}; + +#endif diff --git a/src/object/sp-clippath.cpp b/src/object/sp-clippath.cpp new file mode 100644 index 000000000..4f69bd026 --- /dev/null +++ b/src/object/sp-clippath.cpp @@ -0,0 +1,316 @@ +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 2001-2002 authors + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include + +#include "display/drawing.h" +#include "display/drawing-group.h" +#include "xml/repr.h" + +#include "enums.h" +#include "attributes.h" +#include "document.h" +#include "document-private.h" +#include "style.h" + +#include <2geom/transforms.h> + +#include "sp-clippath.h" +#include "sp-item.h" +#include "sp-defs.h" + +struct SPClipPathView { + SPClipPathView *next; + unsigned int key; + Inkscape::DrawingItem *arenaitem; + Geom::OptRect bbox; +}; + +static SPClipPathView* sp_clippath_view_new_prepend(SPClipPathView *list, unsigned int key, Inkscape::DrawingItem *arenaitem); +static SPClipPathView* sp_clippath_view_list_remove(SPClipPathView *list, SPClipPathView *view); + +SPClipPath::SPClipPath() : SPObjectGroup() { + this->clipPathUnits_set = FALSE; + this->clipPathUnits = SP_CONTENT_UNITS_USERSPACEONUSE; + + this->display = NULL; +} + +SPClipPath::~SPClipPath() { +} + +void SPClipPath::build(SPDocument* doc, Inkscape::XML::Node* repr) { + SPObjectGroup::build(doc, repr); + + this->readAttr( "style" ); + this->readAttr( "clipPathUnits" ); + + /* Register ourselves */ + doc->addResource("clipPath", this); +} + +void SPClipPath::release() { + if (this->document) { + // Unregister ourselves + this->document->removeResource("clipPath", this); + } + + while (this->display) { + /* We simply unref and let item manage this in handler */ + this->display = sp_clippath_view_list_remove(this->display, this->display); + } + + SPObjectGroup::release(); +} + +void SPClipPath::set(unsigned int key, const gchar* value) { + switch (key) { + case SP_ATTR_CLIPPATHUNITS: + this->clipPathUnits = SP_CONTENT_UNITS_USERSPACEONUSE; + this->clipPathUnits_set = FALSE; + + if (value) { + if (!strcmp(value, "userSpaceOnUse")) { + this->clipPathUnits_set = TRUE; + } else if (!strcmp(value, "objectBoundingBox")) { + this->clipPathUnits = SP_CONTENT_UNITS_OBJECTBOUNDINGBOX; + this->clipPathUnits_set = TRUE; + } + } + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + default: + if (SP_ATTRIBUTE_IS_CSS(key)) { + this->style->readFromObject( this ); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + } else { + SPObjectGroup::set(key, value); + } + break; + } +} + +void SPClipPath::child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref) { + /* Invoke SPObjectGroup implementation */ + SPObjectGroup::child_added(child, ref); + + /* Show new object */ + SPObject *ochild = this->document->getObjectByRepr(child); + + if (SP_IS_ITEM(ochild)) { + for (SPClipPathView *v = this->display; v != NULL; v = v->next) { + Inkscape::DrawingItem *ac = SP_ITEM(ochild)->invoke_show(v->arenaitem->drawing(), v->key, SP_ITEM_REFERENCE_FLAGS); + + if (ac) { + v->arenaitem->prependChild(ac); + } + } + } +} + +void SPClipPath::update(SPCtx* ctx, unsigned int flags) { + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + std::vector l; + for (auto& child: children) { + sp_object_ref(&child); + l.push_back(&child); + } + + for (auto child:l) { + if (flags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->updateDisplay(ctx, flags); + } + + sp_object_unref(child); + } + + for (SPClipPathView *v = this->display; v != NULL; v = v->next) { + Inkscape::DrawingGroup *g = dynamic_cast(v->arenaitem); + + if (this->clipPathUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX && v->bbox) { + Geom::Affine t = Geom::Scale(v->bbox->dimensions()); + t.setTranslation(v->bbox->min()); + g->setChildTransform(t); + } else { + g->setChildTransform(Geom::identity()); + } + } +} + +void SPClipPath::modified(unsigned int flags) { + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + std::vector l; + for (auto& child: children) { + sp_object_ref(&child); + l.push_back(&child); + } + + for (auto child:l) { + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + sp_object_unref(child); + } +} + +Inkscape::XML::Node* SPClipPath::write(Inkscape::XML::Document* xml_doc, Inkscape::XML::Node* repr, guint flags) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:clipPath"); + } + + SPObjectGroup::write(xml_doc, repr, flags); + + return repr; +} + +Inkscape::DrawingItem *SPClipPath::show(Inkscape::Drawing &drawing, unsigned int key) { + Inkscape::DrawingGroup *ai = new Inkscape::DrawingGroup(drawing); + display = sp_clippath_view_new_prepend(display, key, ai); + + for (auto& child: children) { + if (SP_IS_ITEM(&child)) { + Inkscape::DrawingItem *ac = SP_ITEM(&child)->invoke_show(drawing, key, SP_ITEM_REFERENCE_FLAGS); + + if (ac) { + /* The order is not important in clippath */ + ai->appendChild(ac); + } + } + } + + if (clipPathUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX && display->bbox) { + Geom::Affine t = Geom::Scale(display->bbox->dimensions()); + t.setTranslation(display->bbox->min()); + ai->setChildTransform(t); + } + + ai->setStyle(this->style); + + return ai; +} + +void SPClipPath::hide(unsigned int key) { + for (auto& child: children) { + if (SP_IS_ITEM(&child)) { + SP_ITEM(&child)->invoke_hide(key); + } + } + for (SPClipPathView *v = display; v != NULL; v = v->next) { + if (v->key == key) { + /* We simply unref and let item to manage this in handler */ + display = sp_clippath_view_list_remove(display, v); + return; + } + } +} + +void SPClipPath::setBBox(unsigned int key, Geom::OptRect const &bbox) { + for (SPClipPathView *v = display; v != NULL; v = v->next) { + if (v->key == key) { + v->bbox = bbox; + break; + } + } +} + +Geom::OptRect SPClipPath::geometricBounds(Geom::Affine const &transform) { + Geom::OptRect bbox; + + for (auto& i: children) { + if (SP_IS_ITEM(&i)) { + Geom::OptRect tmp = SP_ITEM(&i)->geometricBounds(Geom::Affine(SP_ITEM(&i)->transform) * transform); + bbox.unionWith(tmp); + } + } + + return bbox; +} + +/* ClipPath views */ + +SPClipPathView * +sp_clippath_view_new_prepend(SPClipPathView *list, unsigned int key, Inkscape::DrawingItem *arenaitem) +{ + SPClipPathView *new_path_view = g_new(SPClipPathView, 1); + + new_path_view->next = list; + new_path_view->key = key; + new_path_view->arenaitem = arenaitem; + new_path_view->bbox = Geom::OptRect(); + + return new_path_view; +} + +SPClipPathView * +sp_clippath_view_list_remove(SPClipPathView *list, SPClipPathView *view) +{ + if (view == list) { + list = list->next; + } else { + SPClipPathView *prev; + prev = list; + while (prev->next != view) prev = prev->next; + prev->next = view->next; + } + + delete view->arenaitem; + g_free(view); + + return list; +} + +// Create a mask element (using passed elements), add it to +const gchar *SPClipPath::create (std::vector &reprs, SPDocument *document) +{ + Inkscape::XML::Node *defsrepr = document->getDefs()->getRepr(); + + Inkscape::XML::Document *xml_doc = document->getReprDoc(); + Inkscape::XML::Node *repr = xml_doc->createElement("svg:clipPath"); + repr->setAttribute("clipPathUnits", "userSpaceOnUse"); + + defsrepr->appendChild(repr); + const gchar *id = repr->attribute("id"); + SPObject *clip_path_object = document->getObjectById(id); + + for (std::vector::const_iterator it = reprs.begin(); it != reprs.end(); ++it) { + Inkscape::XML::Node *node = (*it); + clip_path_object->appendChildRepr(node); + } + + Inkscape::GC::release(repr); + return id; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-clippath.h b/src/object/sp-clippath.h new file mode 100644 index 000000000..87b5be92c --- /dev/null +++ b/src/object/sp-clippath.h @@ -0,0 +1,127 @@ +#ifndef SEEN_SP_CLIPPATH_H +#define SEEN_SP_CLIPPATH_H + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * Abhishek Sharma + * Jon A. Cruz + * + * Copyright (C) 2001-2002 authors + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define SP_CLIPPATH(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_CLIPPATH(obj) (dynamic_cast((SPObject*)obj) != NULL) + +struct SPClipPathView; + +#include +#include "sp-object-group.h" +#include "uri-references.h" +#include "xml/node.h" + +namespace Inkscape { + +class Drawing; +class DrawingItem; + +} // namespace Inkscape + +class SPClipPath : public SPObjectGroup { +public: + SPClipPath(); + virtual ~SPClipPath(); + + class Reference; + + unsigned int clipPathUnits_set : 1; + unsigned int clipPathUnits : 1; + + SPClipPathView *display; + static char const *create(std::vector &reprs, SPDocument *document); + //static GType sp_clippath_get_type(void); + + Inkscape::DrawingItem *show(Inkscape::Drawing &drawing, unsigned int key); + void hide(unsigned int key); + + void setBBox(unsigned int key, Geom::OptRect const &bbox); + Geom::OptRect geometricBounds(Geom::Affine const &transform); + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref); + + virtual void set(unsigned int key, char const* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + virtual void modified(unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); +}; + + +class SPClipPathReference : public Inkscape::URIReference { +public: + SPClipPathReference(SPObject *obj) : URIReference(obj) {} + SPClipPath *getObject() const { + return static_cast(URIReference::getObject()); + } + +protected: + /** + * If the owner element of this reference (the element with <... clippath="...">) + * is a child of the clippath it refers to, return false. + * \return false if obj is not a clippath or if obj is a parent of this + * reference's owner element. True otherwise. + */ + virtual bool _acceptObject(SPObject *obj) const { + if (!SP_IS_CLIPPATH(obj)) { + return false; + } + SPObject * const owner = this->getOwner(); + if (!URIReference::_acceptObject(obj)) { + //XML Tree being used directly here while it shouldn't be... + Inkscape::XML::Node * const owner_repr = owner->getRepr(); + //XML Tree being used directly here while it shouldn't be... + Inkscape::XML::Node * const obj_repr = obj->getRepr(); + char const * owner_name = ""; + char const * owner_clippath = ""; + char const * obj_name = ""; + char const * obj_id = ""; + if (owner_repr != NULL) { + owner_name = owner_repr->name(); + owner_clippath = owner_repr->attribute("clippath"); + } + if (obj_repr != NULL) { + obj_name = obj_repr->name(); + obj_id = obj_repr->attribute("id"); + } + printf("WARNING: Ignoring recursive clippath reference " + "<%s clippath=\"%s\"> in <%s id=\"%s\">", + owner_name, owner_clippath, + obj_name, obj_id); + return false; + } + return true; + } +}; + +#endif // SEEN_SP_CLIPPATH_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-conn-end-pair.cpp b/src/object/sp-conn-end-pair.cpp new file mode 100644 index 000000000..daadd0cdd --- /dev/null +++ b/src/object/sp-conn-end-pair.cpp @@ -0,0 +1,345 @@ +/* + * A class for handling connector endpoint movement and libavoid interaction. + * + * Authors: + * Peter Moulder + * Michael Wybrow + * Abhishek Sharma + * + * * Copyright (C) 2004-2005 Monash University + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include + +#include "attributes.h" +#include "sp-conn-end.h" +#include "uri.h" +#include "display/curve.h" +#include "xml/repr.h" +#include "sp-path.h" +#include "libavoid/router.h" +#include "document.h" +#include "sp-item-group.h" + + +SPConnEndPair::SPConnEndPair(SPPath *const owner) + : _path(owner) + , _connRef(NULL) + , _connType(SP_CONNECTOR_NOAVOID) + , _connCurvature(0.0) + , _transformed_connection() +{ + for (unsigned handle_ix = 0; handle_ix <= 1; ++handle_ix) { + this->_connEnd[handle_ix] = new SPConnEnd(SP_OBJECT(owner)); + this->_connEnd[handle_ix]->_changed_connection + = this->_connEnd[handle_ix]->ref.changedSignal() + .connect(sigc::bind(sigc::ptr_fun(sp_conn_end_href_changed), + this->_connEnd[handle_ix], owner, handle_ix)); + } +} + +SPConnEndPair::~SPConnEndPair() +{ + for (unsigned handle_ix = 0; handle_ix < 2; ++handle_ix) { + delete this->_connEnd[handle_ix]; + this->_connEnd[handle_ix] = NULL; + } +} + +void SPConnEndPair::release() +{ + for (unsigned handle_ix = 0; handle_ix < 2; ++handle_ix) { + this->_connEnd[handle_ix]->_changed_connection.disconnect(); + this->_connEnd[handle_ix]->_delete_connection.disconnect(); + this->_connEnd[handle_ix]->_transformed_connection.disconnect(); + g_free(this->_connEnd[handle_ix]->href); + this->_connEnd[handle_ix]->href = NULL; + this->_connEnd[handle_ix]->ref.detach(); + } + + // If the document is being destroyed then the router instance + // and the ConnRefs will have been destroyed with it. + const bool routerInstanceExists = (_path->document->router != NULL); + + if (_connRef && routerInstanceExists) { + _connRef->router()->deleteConnector(_connRef); + } + _connRef = NULL; + + _transformed_connection.disconnect(); +} + +void sp_conn_end_pair_build(SPObject *object) +{ + object->readAttr( "inkscape:connector-type" ); + object->readAttr( "inkscape:connection-start" ); + object->readAttr( "inkscape:connection-end" ); + object->readAttr( "inkscape:connector-curvature" ); +} + + +static void avoid_conn_transformed(Geom::Affine const */*mp*/, SPItem *moved_item) +{ + SPPath *path = SP_PATH(moved_item); + if (path->connEndPair.isAutoRoutingConn()) { + path->connEndPair.tellLibavoidNewEndpoints(); + } +} + + +void SPConnEndPair::setAttr(unsigned const key, gchar const *const value) +{ + switch (key) { + case SP_ATTR_CONNECTOR_TYPE: + if (value && (strcmp(value, "polyline") == 0 || strcmp(value, "orthogonal") == 0)) { + int new_conn_type = strcmp(value, "polyline") ? SP_CONNECTOR_ORTHOGONAL : SP_CONNECTOR_POLYLINE; + + if (!_connRef) { + _connType = new_conn_type; + Avoid::Router *router = _path->document->router; + _connRef = new Avoid::ConnRef(router); + _connRef->setRoutingType(new_conn_type == SP_CONNECTOR_POLYLINE ? + Avoid::ConnType_PolyLine : Avoid::ConnType_Orthogonal); + _transformed_connection = _path->connectTransformed(sigc::ptr_fun(&avoid_conn_transformed)); + } else if (new_conn_type != _connType) { + _connType = new_conn_type; + _connRef->setRoutingType(new_conn_type == SP_CONNECTOR_POLYLINE ? + Avoid::ConnType_PolyLine : Avoid::ConnType_Orthogonal); + sp_conn_reroute_path(_path); + } + } else { + _connType = SP_CONNECTOR_NOAVOID; + + if (_connRef) { + _connRef->router()->deleteConnector(_connRef); + _connRef = NULL; + _transformed_connection.disconnect(); + } + } + break; + case SP_ATTR_CONNECTOR_CURVATURE: + if (value) { + _connCurvature = g_strtod(value, NULL); + if (_connRef && _connRef->isInitialised()) { + // Redraw the connector, but only if it has been initialised. + sp_conn_reroute_path(_path); + } + } + break; + case SP_ATTR_CONNECTION_START: + case SP_ATTR_CONNECTION_END: + this->_connEnd[(key == SP_ATTR_CONNECTION_START ? 0 : 1)]->setAttacherHref(value, _path); + break; + } +} + +void SPConnEndPair::writeRepr(Inkscape::XML::Node *const repr) const +{ + char const * const attr_strs[] = {"inkscape:connection-start", "inkscape:connection-end"}; + for (unsigned handle_ix = 0; handle_ix < 2; ++handle_ix) { + const Inkscape::URI* U = this->_connEnd[handle_ix]->ref.getURI(); + if (U) { + gchar *str = U->toString(); + repr->setAttribute(attr_strs[handle_ix], str); + g_free(str); + } + } + repr->setAttribute("inkscape:connector-curvature", Glib::Ascii::dtostr(_connCurvature).c_str()); + if (_connType == SP_CONNECTOR_POLYLINE || _connType == SP_CONNECTOR_ORTHOGONAL) + repr->setAttribute("inkscape:connector-type", _connType == SP_CONNECTOR_POLYLINE ? "polyline" : "orthogonal" ); +} + +void SPConnEndPair::getAttachedItems(SPItem *h2attItem[2]) const { + for (unsigned h = 0; h < 2; ++h) { + h2attItem[h] = this->_connEnd[h]->ref.getObject(); + + // Deal with the case of the attached object being an empty group. + // A group containing no items does not have a valid bbox, so + // causes problems for the auto-routing code. Also, since such a + // group no longer has an onscreen representation and can only be + // selected through the XML editor, it makes sense just to detach + // connectors from them. + if (SP_IS_GROUP(h2attItem[h])) { + if (SP_GROUP(h2attItem[h])->getItemCount() == 0) { + // This group is empty, so detach. + sp_conn_end_detach(_path, h); + h2attItem[h] = NULL; + } + } + } +} + +void SPConnEndPair::getEndpoints(Geom::Point endPts[]) const +{ + SPCurve const *curve = _path->get_curve_reference(); + SPItem *h2attItem[2] = {0}; + getAttachedItems(h2attItem); + Geom::Affine i2d = _path->i2doc_affine(); + + for (unsigned h = 0; h < 2; ++h) { + if (h2attItem[h]) { + g_assert(h2attItem[h]->avoidRef); + endPts[h] = h2attItem[h]->avoidRef->getConnectionPointPos(); + } else if (!curve->is_empty()) { + if (h == 0) { + endPts[h] = *(curve->first_point()) * i2d; + } else { + endPts[h] = *(curve->last_point()) * i2d; + } + } + } +} + +gdouble SPConnEndPair::getCurvature() const +{ + return _connCurvature; +} + +SPConnEnd** SPConnEndPair::getConnEnds() +{ + return _connEnd; +} + +bool SPConnEndPair::isOrthogonal() const +{ + return _connType == SP_CONNECTOR_ORTHOGONAL; +} + + +static void redrawConnectorCallback(void *ptr) +{ + SPPath *path = SP_PATH(ptr); + if (path->document == NULL) { + // This can happen when the document is being destroyed. + return; + } + sp_conn_redraw_path(path); +} + +void SPConnEndPair::rerouteFromManipulation() +{ + sp_conn_reroute_path_immediate(_path); +} + + +// Called from SPPath::update to initialise the endpoints. +void SPConnEndPair::update() +{ + if (_connType != SP_CONNECTOR_NOAVOID) { + g_assert(_connRef != NULL); + if (!_connRef->isInitialised()) { + _updateEndPoints(); + _connRef->setCallback(&redrawConnectorCallback, _path); + } + } +} + +void SPConnEndPair::_updateEndPoints() +{ + Geom::Point endPt[2]; + getEndpoints(endPt); + + Avoid::Point src(endPt[0][Geom::X], endPt[0][Geom::Y]); + Avoid::Point dst(endPt[1][Geom::X], endPt[1][Geom::Y]); + + _connRef->setEndpoints(src, dst); +} + + +bool SPConnEndPair::isAutoRoutingConn() +{ + return _connType != SP_CONNECTOR_NOAVOID; +} + +void SPConnEndPair::makePathInvalid() +{ + _connRef->makePathInvalid(); +} + + +// Redraws the curve along the recalculated route +// Straight or curved +void recreateCurve(SPCurve *curve, Avoid::ConnRef *connRef, const gdouble curvature) +{ + bool straight = curvature<1e-3; + + Avoid::PolyLine route = connRef->displayRoute(); + if (!straight) route = route.curvedPolyline(curvature); + connRef->calcRouteDist(); + + curve->reset(); + + curve->moveto( Geom::Point(route.ps[0].x, route.ps[0].y) ); + int pn = route.size(); + for (int i = 1; i < pn; ++i) { + Geom::Point p(route.ps[i].x, route.ps[i].y); + if (straight) { + curve->lineto( p ); + } else { + switch (route.ts[i]) { + case 'M': + curve->moveto( p ); + break; + case 'L': + curve->lineto( p ); + break; + case 'C': + g_assert( i+2curveto( p, Geom::Point(route.ps[i+1].x, route.ps[i+1].y), + Geom::Point(route.ps[i+2].x, route.ps[i+2].y) ); + i+=2; + break; + } + } + } +} + + +void SPConnEndPair::tellLibavoidNewEndpoints(bool const processTransaction) +{ + if (!isAutoRoutingConn()) { + // Do nothing + return; + } + makePathInvalid(); + + _updateEndPoints(); + if (processTransaction) { + _connRef->router()->processTransaction(); + } + return; +} + + +bool SPConnEndPair::reroutePathFromLibavoid() +{ + if (!isAutoRoutingConn()) { + // Do nothing + return false; + } + + SPCurve *curve = _path->get_curve(); + + recreateCurve(curve, _connRef, _connCurvature); + + Geom::Affine doc2item = _path->i2doc_affine().inverse(); + curve->transform(doc2item); + + return true; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-conn-end-pair.h b/src/object/sp-conn-end-pair.h new file mode 100644 index 000000000..a1ee4f885 --- /dev/null +++ b/src/object/sp-conn-end-pair.h @@ -0,0 +1,96 @@ +#ifndef SEEN_SP_CONN_END_PAIR +#define SEEN_SP_CONN_END_PAIR + +/* + * A class for handling connector endpoint movement and libavoid interaction. + * + * Authors: + * Peter Moulder + * + * * Copyright (C) 2004 Monash University + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include + +#include "libavoid/connector.h" + + +class SPConnEnd; +class SPCurve; +class SPPath; +class SPItem; +class SPObject; + +namespace Geom { class Point; } +namespace Inkscape { +namespace XML { +class Node; +} +} + +extern void recreateCurve(SPCurve *curve, Avoid::ConnRef *connRef, double curvature); + +class SPConnEndPair { +public: + SPConnEndPair(SPPath *); + ~SPConnEndPair(); + void release(); + void setAttr(unsigned const key, char const *const value); + void writeRepr(Inkscape::XML::Node *const repr) const; + void getAttachedItems(SPItem *[2]) const; + void getEndpoints(Geom::Point endPts[]) const; + double getCurvature() const; + SPConnEnd** getConnEnds(); + bool isOrthogonal() const; + friend void recreateCurve(SPCurve *curve, Avoid::ConnRef *connRef, double curvature); + void tellLibavoidNewEndpoints(bool const processTransaction = false); + bool reroutePathFromLibavoid(); + void makePathInvalid(); + void update(); + bool isAutoRoutingConn(); + void rerouteFromManipulation(); + +private: + void _updateEndPoints(); + + SPConnEnd *_connEnd[2]; + + SPPath *_path; + + // libavoid's internal representation of the item. + Avoid::ConnRef *_connRef; + + int _connType; + double _connCurvature; + + // A sigc connection for transformed signal. + sigc::connection _transformed_connection; +}; + + +void sp_conn_end_pair_build(SPObject *object); + + +// _connType options: +enum { + SP_CONNECTOR_NOAVOID, // Basic connector - a straight line. + SP_CONNECTOR_POLYLINE, // Object avoiding polyline. + SP_CONNECTOR_ORTHOGONAL // Object avoiding orthogonal polyline (only horizontal and verical segments). +}; + + +#endif /* !SEEN_SP_CONN_END_PAIR */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-conn-end.cpp b/src/object/sp-conn-end.cpp new file mode 100644 index 000000000..996d8499a --- /dev/null +++ b/src/object/sp-conn-end.cpp @@ -0,0 +1,304 @@ +#include "sp-conn-end.h" + +#include +#include +#include + +#include "bad-uri-exception.h" +#include "display/curve.h" +#include "xml/repr.h" +#include "sp-path.h" +#include "uri.h" +#include "document.h" +#include "sp-item-group.h" +#include "2geom/path-intersection.h" + + +static void change_endpts(SPCurve *const curve, double const endPos[2]); + +SPConnEnd::SPConnEnd(SPObject *const owner) + : ref(owner) + , href(NULL) + // Default to center connection endpoint + , _changed_connection() + , _delete_connection() + , _transformed_connection() + , _group_connection() +{ +} + +static SPObject const *get_nearest_common_ancestor(SPObject const *const obj, SPItem const *const objs[2]) +{ + SPObject const *anc_sofar = obj; + for (unsigned i = 0; i < 2; ++i) { + if ( objs[i] != NULL ) { + anc_sofar = anc_sofar->nearestCommonAncestor(objs[i]); + } + } + return anc_sofar; +} + + +static bool try_get_intersect_point_with_item_recursive(Geom::PathVector& conn_pv, SPItem* item, + const Geom::Affine& item_transform, double& intersect_pos) +{ + double initial_pos = intersect_pos; + // if this is a group... + if (SP_IS_GROUP(item)) { + SPGroup* group = SP_GROUP(item); + + // consider all first-order children + double child_pos = 0.0; + std::vector g = sp_item_group_item_list(group); + for (std::vector::const_iterator i = g.begin();i!=g.end();++i) { + SPItem* child_item = *i; + try_get_intersect_point_with_item_recursive(conn_pv, child_item, + item_transform * child_item->transform, child_pos); + if (intersect_pos < child_pos) + intersect_pos = child_pos; + } + return intersect_pos != initial_pos; + } + + // if this is not a shape, nothing to be done + if (!SP_IS_SHAPE(item)) return false; + + // make sure it has an associated curve + SPCurve* item_curve = SP_SHAPE(item)->getCurve(); + if (!item_curve) return false; + + // apply transformations (up to common ancestor) + item_curve->transform(item_transform); + + const Geom::PathVector& curve_pv = item_curve->get_pathvector(); + Geom::CrossingSet cross = crossings(conn_pv, curve_pv); + // iterate over all Crossings + //TODO: check correctness of the following code: inner loop uses loop variable + // with a name identical to the loop variable of the outer loop. Then rename. + for (Geom::CrossingSet::const_iterator i = cross.begin(); i != cross.end(); ++i) { + const Geom::Crossings& cr = *i; + + for (Geom::Crossings::const_iterator i = cr.begin(); i != cr.end(); ++i) { + const Geom::Crossing& cr_pt = *i; + if ( intersect_pos < cr_pt.ta) + intersect_pos = cr_pt.ta; + } + } + + item_curve->unref(); + + return intersect_pos != initial_pos; +} + + +// This function returns the outermost intersection point between the path (a connector) +// and the item given. If the item is a group, then the component items are considered. +// The transforms given should be to a common ancestor of both the path and item. +// +static bool try_get_intersect_point_with_item(SPPath* conn, SPItem* item, + const Geom::Affine& item_transform, const Geom::Affine& conn_transform, + const bool at_start, double& intersect_pos) +{ + // Copy the curve and apply transformations up to common ancestor. + SPCurve* conn_curve = conn->_curve->copy(); + conn_curve->transform(conn_transform); + + Geom::PathVector conn_pv = conn_curve->get_pathvector(); + + // If this is not the starting point, use Geom::Path::reverse() to reverse the path + if (!at_start) { + // connectors are actually a single path, so consider the first element from a Geom::PathVector + conn_pv[0] = conn_pv[0].reversed(); + } + + // We start with the intersection point at the beginning of the path + intersect_pos = 0.0; + + // Find the intersection. + bool result = try_get_intersect_point_with_item_recursive(conn_pv, item, item_transform, intersect_pos); + + if (!result) { + // No intersection point has been found (why?) + // just default to connector end + intersect_pos = 0; + } + // If not at the starting point, recompute position with respect to original path + if (!at_start) { + intersect_pos = conn_pv[0].size() - intersect_pos; + } + // Free the curve copy. + conn_curve->unref(); + + return result; +} + + +static void sp_conn_get_route_and_redraw(SPPath *const path, const bool updatePathRepr = true) +{ + // Get the new route around obstacles. + bool rerouted = path->connEndPair.reroutePathFromLibavoid(); + if (!rerouted) { + return; + } + + SPItem *h2attItem[2] = {0}; + path->connEndPair.getAttachedItems(h2attItem); + + SPObject const *const ancestor = get_nearest_common_ancestor(path, h2attItem); + Geom::Affine const path2anc(i2anc_affine(path, ancestor)); + + // Set sensible values in case there the connector ends are not + // attached to any shapes. + Geom::PathVector conn_pv = path->_curve->get_pathvector(); + double endPos[2] = { 0.0, static_cast(conn_pv[0].size()) }; + + for (unsigned h = 0; h < 2; ++h) { + // Assume center point for all + if (h2attItem[h]) { + Geom::Affine h2i2anc = i2anc_affine(h2attItem[h], ancestor); + try_get_intersect_point_with_item(path, h2attItem[h], h2i2anc, path2anc, + (h == 0), endPos[h]); + } + } + change_endpts(path->_curve, endPos); + if (updatePathRepr) { + path->updateRepr(); + path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } +} + + +static void sp_conn_end_shape_move(Geom::Affine const */*mp*/, SPItem */*moved_item*/, SPPath *const path) +{ + if (path->connEndPair.isAutoRoutingConn()) { + path->connEndPair.tellLibavoidNewEndpoints(); + } +} + + +void sp_conn_reroute_path(SPPath *const path) +{ + if (path->connEndPair.isAutoRoutingConn()) { + path->connEndPair.tellLibavoidNewEndpoints(); + } +} + + +void sp_conn_reroute_path_immediate(SPPath *const path) +{ + if (path->connEndPair.isAutoRoutingConn()) { + bool processTransaction = true; + path->connEndPair.tellLibavoidNewEndpoints(processTransaction); + } + // Don't update the path repr or else connector dragging is slowed by + // constant update of values to the xml editor, and each step is also + // needlessly remembered by undo/redo. + bool const updatePathRepr = false; + sp_conn_get_route_and_redraw(path, updatePathRepr); +} + +void sp_conn_redraw_path(SPPath *const path) +{ + sp_conn_get_route_and_redraw(path); +} + + +static void change_endpts(SPCurve *const curve, double const endPos[2]) +{ + // Use Geom::Path::portion to cut the curve at the end positions + if (endPos[0] > endPos[1]) { + // Path is "negative", reset the curve and return + curve->reset(); + return; + } + const Geom::Path& old_path = curve->get_pathvector()[0]; + Geom::PathVector new_path_vector; + new_path_vector.push_back(old_path.portion(endPos[0], endPos[1])); + curve->set_pathvector(new_path_vector); +} + +static void sp_conn_end_deleted(SPObject *, SPObject *const owner, unsigned const handle_ix) +{ + char const * const attrs[] = { + "inkscape:connection-start", "inkscape:connection-end"}; + owner->getRepr()->setAttribute(attrs[handle_ix], NULL); + /* I believe this will trigger sp_conn_end_href_changed. */ +} + +void sp_conn_end_detach(SPObject *const owner, unsigned const handle_ix) +{ + sp_conn_end_deleted(NULL, owner, handle_ix); +} + +void SPConnEnd::setAttacherHref(gchar const *value, SPPath* /*path*/) +{ + bool validRef = true; + + if (value && href && strcmp(value, href) == 0) { + /* No change, do nothing. */ + } else if (!value) { + validRef = false; + } else { + href = g_strdup(value); + // Now do the attaching, which emits the changed signal. + try { + ref.attach(Inkscape::URI(value)); + } catch (Inkscape::BadURIException &e) { + /* TODO: Proper error handling as per + * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing. (Also needed for + * sp-use.) */ + g_warning("%s", e.what()); + validRef = false; + } + } + + if (!validRef) { + ref.detach(); + g_free(href); + href = NULL; + } +} + + +void sp_conn_end_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, + SPConnEnd *connEndPtr, SPPath *const path, unsigned const handle_ix) +{ + g_return_if_fail(connEndPtr != NULL); + SPConnEnd &connEnd = *connEndPtr; + connEnd._delete_connection.disconnect(); + connEnd._transformed_connection.disconnect(); + connEnd._group_connection.disconnect(); + + if (connEnd.href) { + SPObject *refobj = connEnd.ref.getObject(); + if (refobj) { + connEnd._delete_connection + = refobj->connectDelete(sigc::bind(sigc::ptr_fun(&sp_conn_end_deleted), + path, handle_ix)); + // This allows the connector tool to dive into a group's children + // And connect to their children's centers. + SPObject *parent = refobj->parent; + if (SP_IS_GROUP(parent) && ! SP_IS_LAYER(parent)) { + connEnd._group_connection + = SP_ITEM(parent)->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_conn_end_shape_move), + path)); + } + connEnd._transformed_connection + = SP_ITEM(refobj)->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_conn_end_shape_move), + path)); + } + } +} + + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-conn-end.h b/src/object/sp-conn-end.h new file mode 100644 index 000000000..2b89a159d --- /dev/null +++ b/src/object/sp-conn-end.h @@ -0,0 +1,59 @@ +#ifndef SEEN_SP_CONN_END +#define SEEN_SP_CONN_END + +#include +#include + +#include "sp-use-reference.h" +#include "conn-avoid-ref.h" + +class SPPath; + +class SPConnEnd { +public: + SPConnEnd(SPObject *owner); + + SPUseReference ref; + char *href; + + /** Change of href string (not a modification of the attributes of the referrent). */ + sigc::connection _changed_connection; + + /** Called when the attached object gets deleted. */ + sigc::connection _delete_connection; + + /** A sigc connection for transformed signal, used to do move compensation. */ + sigc::connection _transformed_connection; + + /** A sigc connection for owning group transformed, used to do move compensation. */ + sigc::connection _group_connection; + + void setAttacherHref(char const * value, SPPath * unused); + //void setAttacherEndpoint(char const *, SPPath *); // not defined + + +private: + SPConnEnd(SPConnEnd const &); // no copy + SPConnEnd &operator=(SPConnEnd const &); // no assign +}; + +void sp_conn_end_href_changed(SPObject *old_ref, SPObject *ref, + SPConnEnd *connEnd, SPPath *path, unsigned const handle_ix); +void sp_conn_reroute_path(SPPath *const path); +void sp_conn_reroute_path_immediate(SPPath *const path); +void sp_conn_redraw_path(SPPath *const path); +void sp_conn_end_detach(SPObject *const owner, unsigned const handle_ix); + + +#endif /* !SEEN_SP_CONN_END */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-defs.cpp b/src/object/sp-defs.cpp new file mode 100644 index 000000000..619a27c0f --- /dev/null +++ b/src/object/sp-defs.cpp @@ -0,0 +1,107 @@ +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 2000-2002 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +/* + * fixme: We should really check childrens validity - currently everything + * flips in + */ + +#include "sp-defs.h" +#include "xml/repr.h" +#include "document.h" + +SPDefs::SPDefs() : SPObject() { +} + +SPDefs::~SPDefs() { +} + +void SPDefs::release() { + SPObject::release(); +} + +void SPDefs::update(SPCtx *ctx, guint flags) { + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + std::vector l(this->childList(true)); + for(std::vector::const_iterator i=l.begin();i!=l.end();++i){ + SPObject *child = *i; + if (flags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->updateDisplay(ctx, flags); + } + sp_object_unref(child); + } +} + +void SPDefs::modified(unsigned int flags) { + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + std::vector l; + for (auto& child: children) { + sp_object_ref(&child); + l.push_back(&child); + } + + for (auto child:l) { + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + sp_object_unref(child); + } +} + +Inkscape::XML::Node* SPDefs::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if (flags & SP_OBJECT_WRITE_BUILD) { + + if (!repr) { + repr = xml_doc->createElement("svg:defs"); + } + + std::vector l; + for (auto& child: children) { + Inkscape::XML::Node *crepr = child.updateRepr(xml_doc, NULL, flags); + if (crepr) { + l.push_back(crepr); + } + } + for (auto i=l.rbegin();i!=l.rend();++i) { + repr->addChild(*i, NULL); + Inkscape::GC::release(*i); + } + } else { + for (auto& child: children) { + child.updateRepr(flags); + } + } + + SPObject::write(xml_doc, repr, flags); + + return repr; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-defs.h b/src/object/sp-defs.h new file mode 100644 index 000000000..c122cb2a9 --- /dev/null +++ b/src/object/sp-defs.h @@ -0,0 +1,44 @@ +#ifndef SEEN_SP_DEFS_H +#define SEEN_SP_DEFS_H + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * Abhishek Sharma + * + * Copyright (C) 2000-2002 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-object.h" + +#define SP_DEFS(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_DEFS(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPDefs : public SPObject { +public: + SPDefs(); + virtual ~SPDefs(); + +protected: + virtual void release(); + virtual void update(SPCtx* ctx, unsigned int flags); + virtual void modified(unsigned int flags); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); +}; + +#endif // !SEEN_SP_DEFS_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-desc.cpp b/src/object/sp-desc.cpp new file mode 100644 index 000000000..3c75d087a --- /dev/null +++ b/src/object/sp-desc.cpp @@ -0,0 +1,32 @@ +/* + * SVG implementation + * + * Authors: + * Jeff Schiller + * + * Copyright (C) 2008 Jeff Schiller + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-desc.h" +#include "xml/repr.h" + +SPDesc::SPDesc() : SPObject() { +} + +SPDesc::~SPDesc() { +} + +/** + * Writes it's settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPDesc::write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags) { + if (!repr) { + repr = this->getRepr()->duplicate(doc); + } + + SPObject::write(doc, repr, flags); + + return repr; +} diff --git a/src/object/sp-desc.h b/src/object/sp-desc.h new file mode 100644 index 000000000..40888bee4 --- /dev/null +++ b/src/object/sp-desc.h @@ -0,0 +1,29 @@ +#ifndef SEEN_SP_DESC_H +#define SEEN_SP_DESC_H + +/* + * SVG implementation + * + * Authors: + * Jeff Schiller + * + * Copyright (C) 2008 Jeff Schiller + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-object.h" + +#define SP_DESC(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_DESC(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPDesc : public SPObject { +public: + SPDesc(); + virtual ~SPDesc(); + +protected: + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); +}; + +#endif diff --git a/src/object/sp-dimensions.cpp b/src/object/sp-dimensions.cpp new file mode 100644 index 000000000..f39b98945 --- /dev/null +++ b/src/object/sp-dimensions.cpp @@ -0,0 +1,55 @@ +/* + * SVG dimensions implementation + * + * Authors: + * Lauris Kaplinski + * Edward Flick (EAF) + * Abhishek Sharma + * Jon A. Cruz + * + * Copyright (C) 1999-2005 Authors + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "sp-dimensions.h" +#include "sp-item.h" + +void SPDimensions::calcDimsFromParentViewport(const SPItemCtx *ictx, bool assign_to_set) +{ +#define ASSIGN(field) { if (assign_to_set) { field._set = true; } } + if (this->x.unit == SVGLength::PERCENT) { + ASSIGN(x); + this->x.computed = this->x.value * ictx->viewport.width(); + } + + if (this->y.unit == SVGLength::PERCENT) { + ASSIGN(y); + this->y.computed = this->y.value * ictx->viewport.height(); + } + + if (this->width.unit == SVGLength::PERCENT) { + ASSIGN(width); + this->width.computed = this->width.value * ictx->viewport.width(); + } + + if (this->height.unit == SVGLength::PERCENT) { + ASSIGN(height); + this->height.computed = this->height.value * ictx->viewport.height(); + } +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-dimensions.h b/src/object/sp-dimensions.h new file mode 100644 index 000000000..eb76df739 --- /dev/null +++ b/src/object/sp-dimensions.h @@ -0,0 +1,41 @@ +#ifndef SP_DIMENSIONS_H__ +#define SP_DIMENSIONS_H__ + +/* + * dimensions helper class, common code used by root, image and others + * + * Authors: + * Shlomi Fish + * Copyright (C) 2017 Shlomi Fish, authors + * + * Released under dual Expat and GNU GPL, read the file 'COPYING' for more information + * + */ + +#include "svg/svg-length.h" + +class SPItemCtx; + +class SPDimensions { + +public: + SVGLength x; + SVGLength y; + SVGLength width; + SVGLength height; + void calcDimsFromParentViewport(const SPItemCtx *ictx, bool assign_to_set = false); +}; + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-basic-offset:2 + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=2:tabstop=8:softtabstop=2:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-ellipse.cpp b/src/object/sp-ellipse.cpp new file mode 100644 index 000000000..c32e3012c --- /dev/null +++ b/src/object/sp-ellipse.cpp @@ -0,0 +1,792 @@ +/* + * SVG and related implementations + * + * Authors: + * Lauris Kaplinski + * Mitsuru Oka + * bulia byak + * Abhishek Sharma + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2013 Tavmjong Bah + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include + +#include "live_effects/effect.h" + +#include <2geom/angle.h> +#include <2geom/circle.h> +#include <2geom/ellipse.h> +#include <2geom/path-sink.h> + +#include "attributes.h" +#include "display/curve.h" +#include "document.h" +#include "preferences.h" +#include "snap-candidate.h" +#include "sp-ellipse.h" +#include "style.h" +#include "svg/svg.h" +#include "svg/path-string.h" + +#define SP_2PI (2 * M_PI) + +SPGenericEllipse::SPGenericEllipse() + : SPShape() + , start(0) + , end(SP_2PI) + , type(SP_GENERIC_ELLIPSE_UNDEFINED) + , arc_type(SP_GENERIC_ELLIPSE_ARC_TYPE_ARC) +{ +} + +SPGenericEllipse::~SPGenericEllipse() +{ +} + +void SPGenericEllipse::build(SPDocument *document, Inkscape::XML::Node *repr) +{ + // std::cout << "SPGenericEllipse::build: Entrance: " << this->type + // << " (" << g_quark_to_string(repr->code()) << ")" << std::endl; + + switch ( type ) { + case SP_GENERIC_ELLIPSE_ARC: + this->readAttr("sodipodi:cx"); + this->readAttr("sodipodi:cy"); + this->readAttr("sodipodi:rx"); + this->readAttr("sodipodi:ry"); + this->readAttr("sodipodi:start"); + this->readAttr("sodipodi:end"); + this->readAttr("sodipodi:open"); + this->readAttr("sodipodi:arc-type"); + break; + + case SP_GENERIC_ELLIPSE_CIRCLE: + this->readAttr("cx"); + this->readAttr("cy"); + this->readAttr("r"); + break; + + case SP_GENERIC_ELLIPSE_ELLIPSE: + this->readAttr("cx"); + this->readAttr("cy"); + this->readAttr("rx"); + this->readAttr("ry"); + break; + + default: + std::cerr << "SPGenericEllipse::build() unknown defined type." << std::endl; + } + + // std::cout << " cx: " << cx.write() << std::endl; + // std::cout << " cy: " << cy.write() << std::endl; + // std::cout << " rx: " << rx.write() << std::endl; + // std::cout << " ry: " << ry.write() << std::endl; + SPShape::build(document, repr); +} + +void SPGenericEllipse::set(unsigned int key, gchar const *value) +{ + // There are multiple ways to set internal cx, cy, rx, and ry (via SVG attributes or Sodipodi + // attributes) thus we don't want to unset them if a read fails (e.g., when we explicitly clear + // an attribute by setting it to NULL). + + // We must update the SVGLengths immediately or nodes may be misplaced after they are moved. + double const w = viewport.width(); + double const h = viewport.height(); + double const d = hypot(w, h) / sqrt(2); // diagonal + double const em = style->font_size.computed; + double const ex = em * 0.5; + + SVGLength t; + switch (key) { + case SP_ATTR_CX: + case SP_ATTR_SODIPODI_CX: + if( t.read(value) ) cx = t; + cx.update( em, ex, w ); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_CY: + case SP_ATTR_SODIPODI_CY: + if( t.read(value) ) cy = t; + cy.update( em, ex, h ); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_RX: + case SP_ATTR_SODIPODI_RX: + if( t.read(value) && t.value > 0.0 ) rx = t; + rx.update( em, ex, w ); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_RY: + case SP_ATTR_SODIPODI_RY: + if( t.read(value) && t.value > 0.0 ) ry = t; + ry.update( em, ex, h ); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_R: + if( t.read(value) && t.value > 0.0 ) { + this->ry = this->rx = t; + } + rx.update( em, ex, d ); + ry.update( em, ex, d ); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_SODIPODI_START: + if (value) { + sp_svg_number_read_d(value, &this->start); + } else { + this->start = 0; + } + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_SODIPODI_END: + if (value) { + sp_svg_number_read_d(value, &this->end); + } else { + this->end = 2 * M_PI; + } + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_SODIPODI_OPEN: + // This is only for reading in old files so rely on constructor to set default. + if (!value) { // Only set if not "true" + this->arc_type = SP_GENERIC_ELLIPSE_ARC_TYPE_SLICE; + } + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_SODIPODI_ARC_TYPE: + // To read in old files that use 'open', we need to not set if value is null. + // We could also check inkscape version. + if (value) { + if (!strcmp(value,"arc")) { + this->arc_type = SP_GENERIC_ELLIPSE_ARC_TYPE_ARC; + } else if (!strcmp(value,"chord")) { + this->arc_type = SP_GENERIC_ELLIPSE_ARC_TYPE_CHORD; + } else { + this->arc_type = SP_GENERIC_ELLIPSE_ARC_TYPE_SLICE; + } + } + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + default: + SPShape::set(key, value); + break; + } +} + +void SPGenericEllipse::update(SPCtx *ctx, guint flags) +{ + // std::cout << "\nSPGenericEllipse::update: Entrance" << std::endl; + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + Geom::Rect const &viewbox = ((SPItemCtx const *) ctx)->viewport; + + double const dx = viewbox.width(); + double const dy = viewbox.height(); + double const dr = hypot(dx, dy) / sqrt(2); + double const em = this->style->font_size.computed; + double const ex = em * 0.5; // fixme: get from pango or libnrtype + + this->cx.update(em, ex, dx); + this->cy.update(em, ex, dy); + this->rx.update(em, ex, dr); + this->ry.update(em, ex, dr); + + this->set_shape(); + } + + SPShape::update(ctx, flags); + // std::cout << "SPGenericEllipse::update: Exit\n" << std::endl; +} + +Inkscape::XML::Node *SPGenericEllipse::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) +{ + // std::cout << "\nSPGenericEllipse::write: Entrance (" + // << (repr == NULL ? " NULL" : g_quark_to_string(repr->code())) + // << ")" << std::endl; + + GenericEllipseType new_type = SP_GENERIC_ELLIPSE_UNDEFINED; + if (_isSlice() || hasPathEffect() ) { + new_type = SP_GENERIC_ELLIPSE_ARC; + } else if ( rx.computed == ry.computed ) { + new_type = SP_GENERIC_ELLIPSE_CIRCLE; + } else { + new_type = SP_GENERIC_ELLIPSE_ELLIPSE; + } + // std::cout << " new_type: " << new_type << std::endl; + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + + switch ( new_type ) { + + case SP_GENERIC_ELLIPSE_ARC: + repr = xml_doc->createElement("svg:path"); + break; + case SP_GENERIC_ELLIPSE_CIRCLE: + repr = xml_doc->createElement("svg:circle"); + break; + case SP_GENERIC_ELLIPSE_ELLIPSE: + repr = xml_doc->createElement("svg:ellipse"); + break; + case SP_GENERIC_ELLIPSE_UNDEFINED: + default: + std::cerr << "SPGenericEllipse::write(): unknown type." << std::endl; + } + } + + if( type != new_type ) { + switch( new_type ) { + case SP_GENERIC_ELLIPSE_ARC: + repr->setCodeUnsafe(g_quark_from_string("svg:path")); + break; + case SP_GENERIC_ELLIPSE_CIRCLE: + repr->setCodeUnsafe(g_quark_from_string("svg:circle")); + break; + case SP_GENERIC_ELLIPSE_ELLIPSE: + repr->setCodeUnsafe(g_quark_from_string("svg:ellipse")); + break; + default: + std::cerr << "SPGenericEllipse::write(): unknown type." << std::endl; + } + type = new_type; + + // FIXME: The XML dialog won't update the element name. We need + // a notifyElementNameChanged callback added to the XML observers + // to trigger a refresh. + } + + // std::cout << " type: " << g_quark_to_string( repr->code() ) << std::endl; + // std::cout << " cx: " << cx.write() << " " << cx.computed + // << " cy: " << cy.write() << " " << cy.computed + // << " rx: " << rx.write() << " " << rx.computed + // << " ry: " << ry.write() << " " << ry.computed << std::endl; + + switch ( type ) { + case SP_GENERIC_ELLIPSE_UNDEFINED: + case SP_GENERIC_ELLIPSE_ARC: + + repr->setAttribute("cx", NULL ); + repr->setAttribute("cy", NULL ); + repr->setAttribute("rx", NULL ); + repr->setAttribute("ry", NULL ); + repr->setAttribute("r", NULL ); + + if (flags & SP_OBJECT_WRITE_EXT) { + + repr->setAttribute("sodipodi:type", "arc"); + sp_repr_set_svg_length(repr, "sodipodi:cx", cx); + sp_repr_set_svg_length(repr, "sodipodi:cy", cy); + sp_repr_set_svg_length(repr, "sodipodi:rx", rx); + sp_repr_set_svg_length(repr, "sodipodi:ry", ry); + + // write start and end only if they are non-trivial; otherwise remove + if (_isSlice()) { + sp_repr_set_svg_double(repr, "sodipodi:start", start); + sp_repr_set_svg_double(repr, "sodipodi:end", end); + + switch ( arc_type ) { + case SP_GENERIC_ELLIPSE_ARC_TYPE_SLICE: + repr->setAttribute("sodipodi:open", NULL); // For backwards compat. + repr->setAttribute("sodipodi:arc-type", "slice"); + break; + case SP_GENERIC_ELLIPSE_ARC_TYPE_CHORD: + // A chord's path isn't "open" but its fill most closely resembles an arc. + repr->setAttribute("sodipodi:open", "true"); // For backwards compat. + repr->setAttribute("sodipodi:arc-type", "chord"); + break; + case SP_GENERIC_ELLIPSE_ARC_TYPE_ARC: + repr->setAttribute("sodipodi:open", "true"); // For backwards compat. + repr->setAttribute("sodipodi:arc-type", "arc"); + break; + default: + std::cerr << "SPGenericEllipse::write: unknown arc-type." << std::endl; + } + } else { + repr->setAttribute("sodipodi:end", NULL); + repr->setAttribute("sodipodi:start", NULL); + repr->setAttribute("sodipodi:open", NULL); + repr->setAttribute("sodipodi:arc-type", NULL); + } + } + + // write d= + set_elliptical_path_attribute(repr); + break; + + case SP_GENERIC_ELLIPSE_CIRCLE: + sp_repr_set_svg_length(repr, "cx", cx); + sp_repr_set_svg_length(repr, "cy", cy); + sp_repr_set_svg_length(repr, "r", rx); + repr->setAttribute("rx", NULL ); + repr->setAttribute("ry", NULL ); + repr->setAttribute("sodipodi:cx", NULL ); + repr->setAttribute("sodipodi:cy", NULL ); + repr->setAttribute("sodipodi:rx", NULL ); + repr->setAttribute("sodipodi:ry", NULL ); + repr->setAttribute("sodipodi:end", NULL ); + repr->setAttribute("sodipodi:start", NULL ); + repr->setAttribute("sodipodi:open", NULL ); + repr->setAttribute("sodipodi:arc-type", NULL); + repr->setAttribute("sodipodi:type", NULL ); + repr->setAttribute("d", NULL ); + break; + + case SP_GENERIC_ELLIPSE_ELLIPSE: + sp_repr_set_svg_length(repr, "cx", cx); + sp_repr_set_svg_length(repr, "cy", cy); + sp_repr_set_svg_length(repr, "rx", rx); + sp_repr_set_svg_length(repr, "ry", ry); + repr->setAttribute("r", NULL ); + repr->setAttribute("sodipodi:cx", NULL ); + repr->setAttribute("sodipodi:cy", NULL ); + repr->setAttribute("sodipodi:rx", NULL ); + repr->setAttribute("sodipodi:ry", NULL ); + repr->setAttribute("sodipodi:end", NULL ); + repr->setAttribute("sodipodi:start", NULL ); + repr->setAttribute("sodipodi:open", NULL ); + repr->setAttribute("sodipodi:arc-type", NULL); + repr->setAttribute("sodipodi:type", NULL ); + repr->setAttribute("d", NULL ); + break; + + default: + std::cerr << "SPGenericEllipse::write: unknown type." << std::endl; + } + + set_shape(); // evaluate SPCurve + + SPShape::write(xml_doc, repr, flags); + + return repr; +} + +const char *SPGenericEllipse::displayName() const +{ + + switch ( type ) { + case SP_GENERIC_ELLIPSE_UNDEFINED: + case SP_GENERIC_ELLIPSE_ARC: + + if (_isSlice()) { + switch ( arc_type ) { + case SP_GENERIC_ELLIPSE_ARC_TYPE_SLICE: + return _("Slice"); + break; + case SP_GENERIC_ELLIPSE_ARC_TYPE_CHORD: + return _("Chord"); + break; + case SP_GENERIC_ELLIPSE_ARC_TYPE_ARC: + return _("Arc"); + break; + } + } else { + return _("Ellipse"); + } + + case SP_GENERIC_ELLIPSE_CIRCLE: + return _("Circle"); + + case SP_GENERIC_ELLIPSE_ELLIPSE: + return _("Ellipse"); + + default: + return "Unknown ellipse: ERROR"; + } + return ("Shouldn't be here"); +} + +// Create path for rendering shape on screen +void SPGenericEllipse::set_shape(bool force) +{ + // std::cout << "SPGenericEllipse::set_shape: Entrance" << std::endl; + if (hasBrokenPathEffect()) { + g_warning("The ellipse shape has unknown LPE on it! Convert to path to make it editable preserving the appearance; editing it as ellipse will remove the bad LPE"); + + if (this->getRepr()->attribute("d")) { + // unconditionally read the curve from d, if any, to preserve appearance + Geom::PathVector pv = sp_svg_read_pathv(this->getRepr()->attribute("d")); + SPCurve *cold = new SPCurve(pv); + this->setCurveInsync(cold, TRUE); + cold->unref(); + } + + return; + } + if (Geom::are_near(this->rx.computed, 0) || Geom::are_near(this->ry.computed, 0)) { + return; + } + + this->normalize(); + + SPCurve *curve = NULL; + + // For simplicity, we use a circle with center (0, 0) and radius 1 for our calculations. + Geom::Circle circle(0, 0, 1); + + if (!this->_isSlice()) { + start = 0.0; + end = 2.0*M_PI; + } + double incr = end - start; // arc angle + if (incr < 0.0) incr += 2.0*M_PI; + + int numsegs = 1 + int(incr*2.0/M_PI); // number of arc segments + if (numsegs > 4) numsegs = 4; + + incr = incr/numsegs; // limit arc angle to less than 90 degrees + Geom::Path path(Geom::Point::polar(start)); + Geom::EllipticalArc* arc; + for (int seg = 0; seg < numsegs; seg++) { + arc = circle.arc(Geom::Point::polar(start + seg*incr), Geom::Point::polar(start + (seg + 0.5)*incr), Geom::Point::polar(start + (seg + 1.0)*incr)); + path.append(*arc); + delete arc; + } + Geom::PathBuilder pb; + pb.append(path); + if (this->_isSlice() && this->arc_type == SP_GENERIC_ELLIPSE_ARC_TYPE_SLICE) { + pb.lineTo(Geom::Point(0, 0)); + } + if ( !(this->arc_type == SP_GENERIC_ELLIPSE_ARC_TYPE_ARC) ) { + pb.closePath(); + } else { + pb.flush(); + } + curve = new SPCurve(pb.peek()); + + // gchar *str = sp_svg_write_path(curve->get_pathvector()); + // std::cout << " path: " << str << std::endl; + // g_free(str); + + // Stretching / moving the calculated shape to fit the actual dimensions. + Geom::Affine aff = Geom::Scale(rx.computed, ry.computed) * Geom::Translate(cx.computed, cy.computed); + curve->transform(aff); + + /* Reset the shape's curve to the "original_curve" + * This is very important for LPEs to work properly! (the bbox might be recalculated depending on the curve in shape)*/ + if(this->getCurveBeforeLPE()) { + if(!force && this->getCurveBeforeLPE()->get_pathvector() == curve->get_pathvector()) { + curve->unref(); + return; + } + } + this->setCurveInsync(curve, TRUE); + this->setCurveBeforeLPE(curve); + + if (hasPathEffect() && pathEffectsEnabled()) { + SPCurve *c_lpe = curve->copy(); + bool success = this->performPathEffect(c_lpe); + + if (success) { + this->setCurveInsync(c_lpe, TRUE); + } + + c_lpe->unref(); + } + + curve->unref(); + // std::cout << "SPGenericEllipse::set_shape: Exit" << std::endl; +} + +Geom::Affine SPGenericEllipse::set_transform(Geom::Affine const &xform) +{ + if (hasPathEffect() && pathEffectsEnabled() && + (this->hasPathEffectOfType(Inkscape::LivePathEffect::CLONE_ORIGINAL) || + this->hasPathEffectOfType(Inkscape::LivePathEffect::BEND_PATH) || + this->hasPathEffectOfType(Inkscape::LivePathEffect::FILL_BETWEEN_MANY) || + this->hasPathEffectOfType(Inkscape::LivePathEffect::FILL_BETWEEN_STROKES) ) ) + { + // if path has this LPE applied, don't write the transform to the pathdata, but write it 'unoptimized' + // also if the effect is type BEND PATH to fix bug #179842 + this->adjust_livepatheffect(xform); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + return xform; + } + /* Calculate ellipse start in parent coords. */ + Geom::Point pos(Geom::Point(this->cx.computed, this->cy.computed) * xform); + + /* This function takes care of translation and scaling, we return whatever parts we can't + handle. */ + Geom::Affine ret(Geom::Affine(xform).withoutTranslation()); + gdouble const sw = hypot(ret[0], ret[1]); + gdouble const sh = hypot(ret[2], ret[3]); + + if (sw > 1e-9) { + ret[0] /= sw; + ret[1] /= sw; + } else { + ret[0] = 1.0; + ret[1] = 0.0; + } + + if (sh > 1e-9) { + ret[2] /= sh; + ret[3] /= sh; + } else { + ret[2] = 0.0; + ret[3] = 1.0; + } + + if (this->rx._set) { + this->rx.scale( sw ); + } + + if (this->ry._set) { + this->ry.scale( sh ); + } + + /* Find start in item coords */ + pos = pos * ret.inverse(); + this->cx = pos[Geom::X]; + this->cy = pos[Geom::Y]; + + this->set_shape(); + + // Adjust stroke width + this->adjust_stroke(sqrt(fabs(sw * sh))); + + // Adjust pattern fill + this->adjust_pattern(xform * ret.inverse()); + + // Adjust gradient fill + this->adjust_gradient(xform * ret.inverse()); + + // Adjust livepatheffect + this->adjust_livepatheffect(xform); + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + + return ret; +} + +void SPGenericEllipse::snappoints(std::vector &p, Inkscape::SnapPreferences const *snapprefs) const +{ + // CPPIFY: is this call necessary? + const_cast(this)->normalize(); + + Geom::Affine const i2dt = this->i2dt_affine(); + + // Snap to the 4 quadrant points of the ellipse, but only if the arc + // spans far enough to include them + if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_ELLIPSE_QUADRANT_POINT)) { + for (double angle = 0; angle < SP_2PI; angle += M_PI_2) { + if (Geom::AngleInterval(this->start, this->end, true).contains(angle)) { + Geom::Point pt = this->getPointAtAngle(angle) * i2dt; + p.push_back(Inkscape::SnapCandidatePoint(pt, Inkscape::SNAPSOURCE_ELLIPSE_QUADRANT_POINT, Inkscape::SNAPTARGET_ELLIPSE_QUADRANT_POINT)); + } + } + } + + double cx = this->cx.computed; + double cy = this->cy.computed; + + + bool slice = this->_isSlice(); + + // Add the centre, if we have a closed slice or when explicitly asked for + if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_NODE_CUSP) && slice && + this->arc_type == SP_GENERIC_ELLIPSE_ARC_TYPE_SLICE) { + Geom::Point pt = Geom::Point(cx, cy) * i2dt; + p.push_back(Inkscape::SnapCandidatePoint(pt, Inkscape::SNAPSOURCE_NODE_CUSP, Inkscape::SNAPTARGET_NODE_CUSP)); + } + + if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_OBJECT_MIDPOINT)) { + Geom::Point pt = Geom::Point(cx, cy) * i2dt; + p.push_back(Inkscape::SnapCandidatePoint(pt, Inkscape::SNAPSOURCE_OBJECT_MIDPOINT, Inkscape::SNAPTARGET_OBJECT_MIDPOINT)); + } + + // And if we have a slice, also snap to the endpoints + if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_NODE_CUSP) && slice) { + // Add the start point, if it's not coincident with a quadrant point + if (!Geom::are_near(std::fmod(this->start, M_PI_2), 0)) { + Geom::Point pt = this->getPointAtAngle(this->start) * i2dt; + p.push_back(Inkscape::SnapCandidatePoint(pt, Inkscape::SNAPSOURCE_NODE_CUSP, Inkscape::SNAPTARGET_NODE_CUSP)); + } + + // Add the end point, if it's not coincident with a quadrant point + if (!Geom::are_near(std::fmod(this->end, M_PI_2), 0)) { + Geom::Point pt = this->getPointAtAngle(this->end) * i2dt; + p.push_back(Inkscape::SnapCandidatePoint(pt, Inkscape::SNAPSOURCE_NODE_CUSP, Inkscape::SNAPTARGET_NODE_CUSP)); + } + } +} + +void SPGenericEllipse::modified(guint flags) +{ + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + this->set_shape(); + } + + SPShape::modified(flags); +} + +void SPGenericEllipse::update_patheffect(bool write) +{ + this->set_shape(true); + + if (write) { + Inkscape::XML::Node *repr = this->getRepr(); + + if (this->_curve != NULL && type == SP_GENERIC_ELLIPSE_ARC) { + gchar *str = sp_svg_write_path(this->_curve->get_pathvector()); + repr->setAttribute("d", str); + g_free(str); + } else { + repr->setAttribute("d", NULL); + } + } + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +void SPGenericEllipse::normalize() +{ + Geom::AngleInterval a(this->start, this->end, true); + + this->start = a.initialAngle().radians0(); + this->end = a.finalAngle().radians0(); +} + +Geom::Point SPGenericEllipse::getPointAtAngle(double arg) const +{ + return Geom::Point::polar(arg) * Geom::Scale(rx.computed, ry.computed) * Geom::Translate(cx.computed, cy.computed); +} + +/* + * set_elliptical_path_attribute: + * + * Convert center to endpoint parameterization and set it to repr. + * + * See SVG 1.0 Specification W3C Recommendation + * ``F.6 Ellptical arc implementation notes'' for more detail. + */ +bool SPGenericEllipse::set_elliptical_path_attribute(Inkscape::XML::Node *repr) +{ + // Make sure our pathvector is up to date. + this->set_shape(); + + if (_curve != NULL) { + gchar* d = sp_svg_write_path(_curve->get_pathvector()); + + repr->setAttribute("d", d); + + g_free(d); + } else { + repr->setAttribute("d", NULL); + } + + return true; +} + +void SPGenericEllipse::position_set(gdouble x, gdouble y, gdouble rx, gdouble ry) +{ + this->cx = x; + this->cy = y; + this->rx = rx; + this->ry = ry; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + // those pref values are in degrees, while we want radians + if (prefs->getDouble("/tools/shapes/arc/start", 0.0) != 0) { + this->start = Geom::Angle::from_degrees(prefs->getDouble("/tools/shapes/arc/start", 0.0)).radians0(); + } + + if (prefs->getDouble("/tools/shapes/arc/end", 0.0) != 0) { + this->end = Geom::Angle::from_degrees(prefs->getDouble("/tools/shapes/arc/end", 0.0)).radians0(); + } + + this->arc_type = (GenericEllipseArcType)prefs->getInt("/tools/shapes/arc/arc_type", 0); + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +bool SPGenericEllipse::_isSlice() const +{ + Geom::AngleInterval a(this->start, this->end, true); + + return !(Geom::are_near(a.extent(), 0) || Geom::are_near(a.extent(), SP_2PI)); +} + +/** +Returns the ratio in which the vector from p0 to p1 is stretched by transform + */ +gdouble SPGenericEllipse::vectorStretch(Geom::Point p0, Geom::Point p1, Geom::Affine xform) { + if (p0 == p1) { + return 0; + } + + return (Geom::distance(p0 * xform, p1 * xform) / Geom::distance(p0, p1)); +} + +void SPGenericEllipse::setVisibleRx(gdouble rx) { + if (rx == 0) { + this->rx.unset(); + } else { + this->rx = rx / SPGenericEllipse::vectorStretch( + Geom::Point(this->cx.computed + 1, this->cy.computed), + Geom::Point(this->cx.computed, this->cy.computed), + this->i2doc_affine()); + } + + this->updateRepr(); +} + +void SPGenericEllipse::setVisibleRy(gdouble ry) { + if (ry == 0) { + this->ry.unset(); + } else { + this->ry = ry / SPGenericEllipse::vectorStretch( + Geom::Point(this->cx.computed, this->cy.computed + 1), + Geom::Point(this->cx.computed, this->cy.computed), + this->i2doc_affine()); + } + + this->updateRepr(); +} + +gdouble SPGenericEllipse::getVisibleRx() const { + if (!this->rx._set) { + return 0; + } + + return this->rx.computed * SPGenericEllipse::vectorStretch( + Geom::Point(this->cx.computed + 1, this->cy.computed), + Geom::Point(this->cx.computed, this->cy.computed), + this->i2doc_affine()); +} + +gdouble SPGenericEllipse::getVisibleRy() const { + if (!this->ry._set) { + return 0; + } + + return this->ry.computed * SPGenericEllipse::vectorStretch( + Geom::Point(this->cx.computed, this->cy.computed + 1), + Geom::Point(this->cx.computed, this->cy.computed), + this->i2doc_affine()); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 : diff --git a/src/object/sp-ellipse.h b/src/object/sp-ellipse.h new file mode 100644 index 000000000..a31b571d8 --- /dev/null +++ b/src/object/sp-ellipse.h @@ -0,0 +1,113 @@ +/** + * SVG and related implementations + * + * Authors: + * Lauris Kaplinski + * Mitsuru Oka + * Tavmjong Bah + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2013 Tavmjong Bah + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_SP_ELLIPSE_H +#define SEEN_SP_ELLIPSE_H + +#include "svg/svg-length.h" +#include "sp-shape.h" + +/* Common parent class */ +#define SP_GENERICELLIPSE(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_GENERICELLIPSE(obj) (dynamic_cast((SPObject*)obj) != NULL) + +enum GenericEllipseType { + SP_GENERIC_ELLIPSE_UNDEFINED, // FIXME shouldn't exist + SP_GENERIC_ELLIPSE_ARC, + SP_GENERIC_ELLIPSE_CIRCLE, + SP_GENERIC_ELLIPSE_ELLIPSE +}; + +enum GenericEllipseArcType { + SP_GENERIC_ELLIPSE_ARC_TYPE_SLICE, // Default + SP_GENERIC_ELLIPSE_ARC_TYPE_CHORD, + SP_GENERIC_ELLIPSE_ARC_TYPE_ARC +}; + +class SPGenericEllipse : public SPShape { +public: + SPGenericEllipse(); + virtual ~SPGenericEllipse(); + + // Regardless of type, the ellipse/circle/arc is stored + // internally with these variables. (Circle radius is rx). + SVGLength cx; + SVGLength cy; + SVGLength rx; + SVGLength ry; + + // Return slice, chord, or arc. + GenericEllipseArcType arcType() { return arc_type; }; + void setArcType(GenericEllipseArcType type) { arc_type = type; }; + + double start, end; + GenericEllipseType type; + GenericEllipseArcType arc_type; + + virtual void build(SPDocument *document, Inkscape::XML::Node *repr); + + virtual void set(unsigned int key, char const *value); + virtual void update(SPCtx *ctx, unsigned int flags); + + virtual Inkscape::XML::Node *write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); + virtual const char *displayName() const; + + virtual void set_shape(bool force = false); + virtual Geom::Affine set_transform(Geom::Affine const &xform); + + virtual void snappoints(std::vector &p, Inkscape::SnapPreferences const *snapprefs) const; + + virtual void modified(unsigned int flags); + + virtual void update_patheffect(bool write); + + /** + * @brief Makes sure that start and end lie between 0 and 2 * PI. + */ + void normalize(); + + Geom::Point getPointAtAngle(double arg) const; + + bool set_elliptical_path_attribute(Inkscape::XML::Node *repr); + void position_set(double x, double y, double rx, double ry); + + double getVisibleRx() const; + void setVisibleRx(double rx); + + double getVisibleRy() const; + void setVisibleRy(double ry); + +protected: + /** + * @brief Determines whether the shape is a part of an ellipse. + */ + bool _isSlice() const; + +private: + static double vectorStretch(Geom::Point p0, Geom::Point p1, Geom::Affine xform); +}; + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 : diff --git a/src/object/sp-factory.cpp b/src/object/sp-factory.cpp new file mode 100644 index 000000000..a540399c7 --- /dev/null +++ b/src/object/sp-factory.cpp @@ -0,0 +1,364 @@ +/* + * Factory for SPObject tree + * + * Authors: + * Markus Engel + * + * Copyright (C) 2013 Authors + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-factory.h" + +// primary +#include "box3d.h" +#include "box3d-side.h" +#include "color-profile.h" +#include "persp3d.h" +#include "sp-anchor.h" +#include "sp-clippath.h" +#include "sp-defs.h" +#include "sp-desc.h" +#include "sp-ellipse.h" +#include "sp-filter.h" +#include "sp-flowdiv.h" +#include "sp-flowregion.h" +#include "sp-flowtext.h" +#include "sp-font.h" +#include "sp-font-face.h" +#include "sp-glyph.h" +#include "sp-glyph-kerning.h" +#include "sp-guide.h" +#include "sp-hatch.h" +#include "sp-hatch-path.h" +#include "sp-image.h" +#include "sp-line.h" +#include "sp-linear-gradient.h" +#include "sp-marker.h" +#include "sp-mask.h" +#include "sp-mesh-gradient.h" +#include "sp-mesh-patch.h" +#include "sp-mesh-row.h" +#include "sp-metadata.h" +#include "sp-missing-glyph.h" +#include "sp-namedview.h" +#include "sp-offset.h" +#include "sp-path.h" +#include "sp-pattern.h" +#include "sp-polyline.h" +#include "sp-radial-gradient.h" +#include "sp-rect.h" +#include "sp-root.h" +#include "sp-script.h" +#include "sp-solid-color.h" +#include "sp-spiral.h" +#include "sp-star.h" +#include "sp-stop.h" +#include "sp-string.h" +#include "sp-style-elem.h" +#include "sp-switch.h" +#include "sp-symbol.h" +#include "sp-tag.h" +#include "sp-tag-use.h" +#include "sp-text.h" +#include "sp-textpath.h" +#include "sp-title.h" +#include "sp-tref.h" +#include "sp-tspan.h" +#include "sp-use.h" +#include "live_effects/lpeobject.h" + +// filters +#include "filters/blend.h" +#include "filters/colormatrix.h" +#include "filters/componenttransfer.h" +#include "filters/componenttransfer-funcnode.h" +#include "filters/composite.h" +#include "filters/convolvematrix.h" +#include "filters/diffuselighting.h" +#include "filters/displacementmap.h" +#include "filters/distantlight.h" +#include "filters/flood.h" +#include "filters/gaussian-blur.h" +#include "filters/image.h" +#include "filters/merge.h" +#include "filters/mergenode.h" +#include "filters/morphology.h" +#include "filters/offset.h" +#include "filters/pointlight.h" +#include "filters/specularlighting.h" +#include "filters/spotlight.h" +#include "filters/tile.h" +#include "filters/turbulence.h" + +SPObject *SPFactory::createObject(std::string const& id) +{ + SPObject *ret = NULL; + + if (id == "inkscape:box3d") + ret = new SPBox3D; + else if (id == "inkscape:box3dside") + ret = new Box3DSide; + else if (id == "svg:color-profile") + ret = new Inkscape::ColorProfile; + else if (id == "inkscape:persp3d") + ret = new Persp3D; + else if (id == "svg:a") + ret = new SPAnchor; + else if (id == "svg:clipPath") + ret = new SPClipPath; + else if (id == "svg:defs") + ret = new SPDefs; + else if (id == "svg:desc") + ret = new SPDesc; + else if (id == "svg:ellipse") { + SPGenericEllipse *e = new SPGenericEllipse; + e->type = SP_GENERIC_ELLIPSE_ELLIPSE; + ret = e; + } else if (id == "svg:circle") { + SPGenericEllipse *c = new SPGenericEllipse; + c->type = SP_GENERIC_ELLIPSE_CIRCLE; + ret = c; + } else if (id == "arc") { + SPGenericEllipse *a = new SPGenericEllipse; + a->type = SP_GENERIC_ELLIPSE_ARC; + ret = a; + } + else if (id == "svg:filter") + ret = new SPFilter; + else if (id == "svg:flowDiv") + ret = new SPFlowdiv; + else if (id == "svg:flowSpan") + ret = new SPFlowtspan; + else if (id == "svg:flowPara") + ret = new SPFlowpara; + else if (id == "svg:flowLine") + ret = new SPFlowline; + else if (id == "svg:flowRegionBreak") + ret = new SPFlowregionbreak; + else if (id == "svg:flowRegion") + ret = new SPFlowregion; + else if (id == "svg:flowRegionExclude") + ret = new SPFlowregionExclude; + else if (id == "svg:flowRoot") + ret = new SPFlowtext; + else if (id == "svg:font") + ret = new SPFont; + else if (id == "svg:font-face") + ret = new SPFontFace; + else if (id == "svg:glyph") + ret = new SPGlyph; + else if (id == "svg:hkern") + ret = new SPHkern; + else if (id == "svg:vkern") + ret = new SPVkern; + else if (id == "sodipodi:guide") + ret = new SPGuide; + else if (id == "svg:hatch") + ret = new SPHatch; + else if (id == "svg:hatchpath") + ret = new SPHatchPath; + else if (id == "svg:hatchPath") { + std::cerr << "Warning: has been renamed " << std::endl; + ret = new SPHatchPath; + } + else if (id == "svg:image") + ret = new SPImage; + else if (id == "svg:g") + ret = new SPGroup; + else if (id == "svg:line") + ret = new SPLine; + else if (id == "svg:linearGradient") + ret = new SPLinearGradient; + else if (id == "svg:marker") + ret = new SPMarker; + else if (id == "svg:mask") + ret = new SPMask; + else if (id == "svg:mesh") { // SVG 2 old + ret = new SPMeshGradient; + std::cerr << "Warning: has been renamed ." << std::endl; + std::cerr << "Warning: has been repurposed as a shape that tightly wraps a ." << std::endl; + } + else if (id == "svg:meshGradient") { // SVG 2 old + ret = new SPMeshGradient; + std::cerr << "Warning: has been renamed " << std::endl; + } + else if (id == "svg:meshgradient") // SVG 2 + ret = new SPMeshGradient; + else if (id == "svg:meshPatch") { + ret = new SPMeshpatch; + std::cerr << "Warning: and have been renamed and " << std::endl; + } + else if (id == "svg:meshpatch") + ret = new SPMeshpatch; + else if (id == "svg:meshRow") + ret = new SPMeshrow; + else if (id == "svg:meshrow") + ret = new SPMeshrow; + else if (id == "svg:metadata") + ret = new SPMetadata; + else if (id == "svg:missing-glyph") + ret = new SPMissingGlyph; + else if (id == "sodipodi:namedview") + ret = new SPNamedView; + else if (id == "inkscape:offset") + ret = new SPOffset; + else if (id == "svg:path") + ret = new SPPath; + else if (id == "svg:pattern") + ret = new SPPattern; + else if (id == "svg:polygon") + ret = new SPPolygon; + else if (id == "svg:polyline") + ret = new SPPolyLine; + else if (id == "svg:radialGradient") + ret = new SPRadialGradient; + else if (id == "svg:rect") + ret = new SPRect; + else if (id == "svg:svg") + ret = new SPRoot; + else if (id == "svg:script") + ret = new SPScript; + else if (id == "svg:solidColor") { + ret = new SPSolidColor; + std::cerr << "Warning: has been renamed " << std::endl; + } + else if (id == "svg:solidcolor") + ret = new SPSolidColor; + else if (id == "spiral") + ret = new SPSpiral; + else if (id == "star") + ret = new SPStar; + else if (id == "svg:stop") + ret = new SPStop; + else if (id == "string") + ret = new SPString; + else if (id == "svg:style") + ret = new SPStyleElem; + else if (id == "svg:switch") + ret = new SPSwitch; + else if (id == "svg:symbol") + ret = new SPSymbol; + else if (id == "inkscape:tag") + ret = new SPTag; + else if (id == "inkscape:tagref") + ret = new SPTagUse; + else if (id == "svg:text") + ret = new SPText; + else if (id == "svg:title") + ret = new SPTitle; + else if (id == "svg:tref") + ret = new SPTRef; + else if (id == "svg:tspan") + ret = new SPTSpan; + else if (id == "svg:textPath") + ret = new SPTextPath; + else if (id == "svg:use") + ret = new SPUse; + else if (id == "inkscape:path-effect") + ret = new LivePathEffectObject; + + + // filters + else if (id == "svg:feBlend") + ret = new SPFeBlend; + else if (id == "svg:feColorMatrix") + ret = new SPFeColorMatrix; + else if (id == "svg:feComponentTransfer") + ret = new SPFeComponentTransfer; + else if (id == "svg:feFuncR") + ret = new SPFeFuncNode(SPFeFuncNode::R); + else if (id == "svg:feFuncG") + ret = new SPFeFuncNode(SPFeFuncNode::G); + else if (id == "svg:feFuncB") + ret = new SPFeFuncNode(SPFeFuncNode::B); + else if (id == "svg:feFuncA") + ret = new SPFeFuncNode(SPFeFuncNode::A); + else if (id == "svg:feComposite") + ret = new SPFeComposite; + else if (id == "svg:feConvolveMatrix") + ret = new SPFeConvolveMatrix; + else if (id == "svg:feDiffuseLighting") + ret = new SPFeDiffuseLighting; + else if (id == "svg:feDisplacementMap") + ret = new SPFeDisplacementMap; + else if (id == "svg:feDistantLight") + ret = new SPFeDistantLight; + else if (id == "svg:feFlood") + ret = new SPFeFlood; + else if (id == "svg:feGaussianBlur") + ret = new SPGaussianBlur; + else if (id == "svg:feImage") + ret = new SPFeImage; + else if (id == "svg:feMerge") + ret = new SPFeMerge; + else if (id == "svg:feMergeNode") + ret = new SPFeMergeNode; + else if (id == "svg:feMorphology") + ret = new SPFeMorphology; + else if (id == "svg:feOffset") + ret = new SPFeOffset; + else if (id == "svg:fePointLight") + ret = new SPFePointLight; + else if (id == "svg:feSpecularLighting") + ret = new SPFeSpecularLighting; + else if (id == "svg:feSpotLight") + ret = new SPFeSpotLight; + else if (id == "svg:feTile") + ret = new SPFeTile; + else if (id == "svg:feTurbulence") + ret = new SPFeTurbulence; + else if (id == "inkscape:grid") + ret = new SPObject; // TODO wtf + else if (id == "rdf:RDF") // no SP node yet + {} + else if (id == "inkscape:clipboard") // SP node not necessary + {} + else if (id == "inkscape:_templateinfo") // ? + {} + else if (id.empty()) // comments + {} + else { + fprintf(stderr, "WARNING: unknown type: %s\n", id.c_str()); + } + + return ret; +} + +std::string NodeTraits::get_type_string(Inkscape::XML::Node const &node) +{ + std::string name; + + switch (node.type()) { + case Inkscape::XML::TEXT_NODE: + name = "string"; + break; + + case Inkscape::XML::ELEMENT_NODE: { + char const *const sptype = node.attribute("sodipodi:type"); + + if (sptype) { + name = sptype; + } else { + name = node.name(); + } + break; + } + default: + name = ""; + break; + } + + return name; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-factory.h b/src/object/sp-factory.h new file mode 100644 index 000000000..040fd14ae --- /dev/null +++ b/src/object/sp-factory.h @@ -0,0 +1,43 @@ +/* + * Factory for SPObject tree + * + * Authors: + * Markus Engel + * + * Copyright (C) 2013 Authors + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SP_FACTORY_SEEN +#define SP_FACTORY_SEEN + +#include + +class SPObject; + +namespace Inkscape { +namespace XML { +class Node; +} +} + +struct SPFactory { + static SPObject *createObject(std::string const& id); +}; + +struct NodeTraits { + static std::string get_type_string(Inkscape::XML::Node const &node); +}; + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-filter-reference.cpp b/src/object/sp-filter-reference.cpp new file mode 100644 index 000000000..afb014820 --- /dev/null +++ b/src/object/sp-filter-reference.cpp @@ -0,0 +1,22 @@ +#include "sp-filter.h" +#include "sp-filter-reference.h" + +bool +SPFilterReference::_acceptObject(SPObject *obj) const +{ + return SP_IS_FILTER(obj) && URIReference::_acceptObject(obj); + /* effic: Don't bother making this an inline function: _acceptObject is a virtual function, + typically called from a context where the runtime type is not known at compile time. */ +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-filter-reference.h b/src/object/sp-filter-reference.h new file mode 100644 index 000000000..5901dca07 --- /dev/null +++ b/src/object/sp-filter-reference.h @@ -0,0 +1,34 @@ +#ifndef SEEN_SP_FILTER_REFERENCE_H +#define SEEN_SP_FILTER_REFERENCE_H + +#include "uri-references.h" + +class SPObject; +class SPDocument; +class SPFilter; + +class SPFilterReference : public Inkscape::URIReference { +public: + SPFilterReference(SPObject *obj) : URIReference(obj) {} + SPFilterReference(SPDocument *doc) : URIReference(doc) {} + + SPFilter *getObject() const { + return static_cast(URIReference::getObject()); + } + +protected: + virtual bool _acceptObject(SPObject *obj) const; +}; + +#endif /* !SEEN_SP_FILTER_REFERENCE_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-filter-units.h b/src/object/sp-filter-units.h new file mode 100644 index 000000000..415b89365 --- /dev/null +++ b/src/object/sp-filter-units.h @@ -0,0 +1,21 @@ +#ifndef SEEN_SP_FILTER_UNITS_H +#define SEEN_SP_FILTER_UNITS_H + +enum SPFilterUnits { + SP_FILTER_UNITS_OBJECTBOUNDINGBOX, + SP_FILTER_UNITS_USERSPACEONUSE +}; + + +#endif /* !SEEN_SP_FILTER_UNITS_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-filter.cpp b/src/object/sp-filter.cpp new file mode 100644 index 000000000..6cb4f8e5d --- /dev/null +++ b/src/object/sp-filter.cpp @@ -0,0 +1,532 @@ +/** \file + * SVG implementation. + */ +/* + * Authors: + * Hugo Rodrigues + * Niko Kiirala + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 2006,2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "sp-filter.h" + +#include +#include + +#include + +#include "bad-uri-exception.h" +#include "attributes.h" +#include "display/nr-filter.h" +#include "document.h" +#include "sp-filter-reference.h" +#include "filters/sp-filter-primitive.h" +#include "uri.h" +#include "xml/repr.h" + +using std::map; +using std::pair; + +#define SP_MACROS_SILENT + +static void filter_ref_changed(SPObject *old_ref, SPObject *ref, SPFilter *filter); +static void filter_ref_modified(SPObject *href, guint flags, SPFilter *filter); + + +SPFilter::SPFilter() + : SPObject(), filterUnits(SP_FILTER_UNITS_OBJECTBOUNDINGBOX), filterUnits_set(FALSE), + primitiveUnits(SP_FILTER_UNITS_USERSPACEONUSE), primitiveUnits_set(FALSE), + filterRes(NumberOptNumber()), + _renderer(NULL), _image_name(new std::map), _image_number_next(0) +{ + this->href = new SPFilterReference(this); + this->href->changedSignal().connect(sigc::bind(sigc::ptr_fun(filter_ref_changed), this)); + + this->x = 0; + this->y = 0; + this->width = 0; + this->height = 0; + + this->_image_name->clear(); +} + +SPFilter::~SPFilter() { +} + + +/** + * Reads the Inkscape::XML::Node, and initializes SPFilter variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPFilter::build(SPDocument *document, Inkscape::XML::Node *repr) { + //Read values of key attributes from XML nodes into object. + this->readAttr( "style" ); // struct not derived from SPItem, we need to do this ourselves. + this->readAttr( "filterUnits" ); + this->readAttr( "primitiveUnits" ); + this->readAttr( "x" ); + this->readAttr( "y" ); + this->readAttr( "width" ); + this->readAttr( "height" ); + this->readAttr( "filterRes" ); + this->readAttr( "xlink:href" ); + this->_refcount = 0; + + SPObject::build(document, repr); + +//is this necessary? + document->addResource("filter", this); +} + +/** + * Drops any allocated memory. + */ +void SPFilter::release() { + if (this->document) { + // Unregister ourselves + this->document->removeResource("filter", this); + } + +//TODO: release resources here + + //release href + if (this->href) { + this->modified_connection.disconnect(); + this->href->detach(); + delete this->href; + this->href = NULL; + } + + for (map::const_iterator i = this->_image_name->begin() ; i != this->_image_name->end() ; ++i) { + g_free(i->first); + } + + delete this->_image_name; + + SPObject::release(); +} + +/** + * Sets a specific value in the SPFilter. + */ +void SPFilter::set(unsigned int key, gchar const *value) { + switch (key) { + case SP_ATTR_FILTERUNITS: + if (value) { + if (!strcmp(value, "userSpaceOnUse")) { + this->filterUnits = SP_FILTER_UNITS_USERSPACEONUSE; + } else { + this->filterUnits = SP_FILTER_UNITS_OBJECTBOUNDINGBOX; + } + + this->filterUnits_set = TRUE; + } else { + this->filterUnits = SP_FILTER_UNITS_OBJECTBOUNDINGBOX; + this->filterUnits_set = FALSE; + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_PRIMITIVEUNITS: + if (value) { + if (!strcmp(value, "objectBoundingBox")) { + this->primitiveUnits = SP_FILTER_UNITS_OBJECTBOUNDINGBOX; + } else { + this->primitiveUnits = SP_FILTER_UNITS_USERSPACEONUSE; + } + + this->primitiveUnits_set = TRUE; + } else { + this->primitiveUnits = SP_FILTER_UNITS_USERSPACEONUSE; + this->primitiveUnits_set = FALSE; + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_X: + this->x.readOrUnset(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_Y: + this->y.readOrUnset(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_WIDTH: + this->width.readOrUnset(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_HEIGHT: + this->height.readOrUnset(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_FILTERRES: + this->filterRes.set(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_XLINK_HREF: + if (value) { + try { + this->href->attach(Inkscape::URI(value)); + } catch (Inkscape::BadURIException &e) { + g_warning("%s", e.what()); + this->href->detach(); + } + } else { + this->href->detach(); + } + break; + default: + // See if any parents need this value. + SPObject::set(key, value); + break; + } +} + + +/** + * Returns the number of references to the filter. + */ +guint SPFilter::getRefCount() { + // NOTE: this is currently updated by sp_style_filter_ref_changed() in style.cpp + return _refcount; +} + +/** + * Receives update notifications. + */ +void SPFilter::update(SPCtx *ctx, guint flags) { + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + + SPItemCtx *ictx = (SPItemCtx *) ctx; + + // Do here since we know viewport (Bounding box case handled during rendering) + // Note: This only works for root viewport since this routine is not called after + // setting a new viewport. A true fix requires a strategy like SPItemView or SPMarkerView. + if(this->filterUnits == SP_FILTER_UNITS_USERSPACEONUSE) { + this->calcDimsFromParentViewport(ictx, true); + } + /* do something to trigger redisplay, updates? */ + + } + + // Update filter primitives in order to update filter primitive area + // (SPObject::ActionUpdate is not actually used) + unsigned childflags = flags; + + if (flags & SP_OBJECT_MODIFIED_FLAG) { + childflags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + childflags &= SP_OBJECT_MODIFIED_CASCADE; + std::vector l(this->childList(true, SPObject::ActionUpdate)); + for(std::vector::const_iterator i=l.begin();i!=l.end();++i){ + SPObject *child = *i; + if( SP_IS_FILTER_PRIMITIVE( child ) ) { + child->updateDisplay(ctx, childflags); + } + sp_object_unref(child); + } + + SPObject::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPFilter::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + // Original from sp-item-group.cpp + if (flags & SP_OBJECT_WRITE_BUILD) { + if (!repr) { + repr = doc->createElement("svg:filter"); + } + + std::vector l; + for (auto& child: children) { + Inkscape::XML::Node *crepr = child.updateRepr(doc, NULL, flags); + + if (crepr) { + l.push_back(crepr); + } + } + + for (auto i=l.rbegin();i!=l.rend();++i) { + repr->addChild(*i, NULL); + Inkscape::GC::release(*i); + } + } else { + for (auto& child: children) { + child.updateRepr(flags); + } + } + + if ((flags & SP_OBJECT_WRITE_ALL) || this->filterUnits_set) { + switch (this->filterUnits) { + case SP_FILTER_UNITS_USERSPACEONUSE: + repr->setAttribute("filterUnits", "userSpaceOnUse"); + break; + default: + repr->setAttribute("filterUnits", "objectBoundingBox"); + break; + } + } + + if ((flags & SP_OBJECT_WRITE_ALL) || this->primitiveUnits_set) { + switch (this->primitiveUnits) { + case SP_FILTER_UNITS_OBJECTBOUNDINGBOX: + repr->setAttribute("primitiveUnits", "objectBoundingBox"); + break; + default: + repr->setAttribute("primitiveUnits", "userSpaceOnUse"); + break; + } + } + + if (this->x._set) { + sp_repr_set_svg_double(repr, "x", this->x.computed); + } else { + repr->setAttribute("x", NULL); + } + + if (this->y._set) { + sp_repr_set_svg_double(repr, "y", this->y.computed); + } else { + repr->setAttribute("y", NULL); + } + + if (this->width._set) { + sp_repr_set_svg_double(repr, "width", this->width.computed); + } else { + repr->setAttribute("width", NULL); + } + + if (this->height._set) { + sp_repr_set_svg_double(repr, "height", this->height.computed); + } else { + repr->setAttribute("height", NULL); + } + + if (this->filterRes.getNumber()>=0) { + gchar *tmp = this->filterRes.getValueString(); + repr->setAttribute("filterRes", tmp); + g_free(tmp); + } else { + repr->setAttribute("filterRes", NULL); + } + + if (this->href->getURI()) { + gchar *uri_string = this->href->getURI()->toString(); + repr->setAttribute("xlink:href", uri_string); + g_free(uri_string); + } + + SPObject::write(doc, repr, flags); + + return repr; +} + + +/** + * Gets called when the filter is (re)attached to another filter. + */ +static void +filter_ref_changed(SPObject *old_ref, SPObject *ref, SPFilter *filter) +{ + if (old_ref) { + filter->modified_connection.disconnect(); + } + + if ( SP_IS_FILTER(ref) + && ref != filter ) + { + filter->modified_connection = + ref->connectModified(sigc::bind(sigc::ptr_fun(&filter_ref_modified), filter)); + } + + filter_ref_modified(ref, 0, filter); +} + +static void filter_ref_modified(SPObject */*href*/, guint /*flags*/, SPFilter *filter) +{ + filter->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/** + * Callback for child_added event. + */ +void SPFilter::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) { + SPObject::child_added(child, ref); + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/** + * Callback for remove_child event. + */ +void SPFilter::remove_child(Inkscape::XML::Node *child) { + SPObject::remove_child(child); + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +void sp_filter_build_renderer(SPFilter *sp_filter, Inkscape::Filters::Filter *nr_filter) +{ + g_assert(sp_filter != NULL); + g_assert(nr_filter != NULL); + + sp_filter->_renderer = nr_filter; + + nr_filter->set_filter_units(sp_filter->filterUnits); + nr_filter->set_primitive_units(sp_filter->primitiveUnits); + nr_filter->set_x(sp_filter->x); + nr_filter->set_y(sp_filter->y); + nr_filter->set_width(sp_filter->width); + nr_filter->set_height(sp_filter->height); + + if (sp_filter->filterRes.getNumber() >= 0) { + if (sp_filter->filterRes.getOptNumber() >= 0) { + nr_filter->set_resolution(sp_filter->filterRes.getNumber(), + sp_filter->filterRes.getOptNumber()); + } else { + nr_filter->set_resolution(sp_filter->filterRes.getNumber()); + } + } + + nr_filter->clear_primitives(); + for(auto& primitive_obj: sp_filter->children) { + if (SP_IS_FILTER_PRIMITIVE(&primitive_obj)) { + SPFilterPrimitive *primitive = SP_FILTER_PRIMITIVE(&primitive_obj); + g_assert(primitive != NULL); + +// if (((SPFilterPrimitiveClass*) G_OBJECT_GET_CLASS(primitive))->build_renderer) { +// ((SPFilterPrimitiveClass *) G_OBJECT_GET_CLASS(primitive))->build_renderer(primitive, nr_filter); +// } else { +// g_warning("Cannot build filter renderer: missing builder"); +// } // CPPIFY: => FilterPrimitive should be abstract. + primitive->build_renderer(nr_filter); + } + } +} + +int sp_filter_primitive_count(SPFilter *filter) { + g_assert(filter != NULL); + int count = 0; + + for(auto& primitive_obj: filter->children) { + if (SP_IS_FILTER_PRIMITIVE(&primitive_obj)) { + count++; + } + } + + return count; +} + +int sp_filter_get_image_name(SPFilter *filter, gchar const *name) { + gchar *name_copy = strdup(name); + map::iterator result = filter->_image_name->find(name_copy); + free(name_copy); + if (result == filter->_image_name->end()) return -1; + else return (*result).second; +} + +int sp_filter_set_image_name(SPFilter *filter, gchar const *name) { + int value = filter->_image_number_next; + filter->_image_number_next++; + gchar *name_copy = strdup(name); + pair new_pair(name_copy, value); + pair::iterator,bool> ret = filter->_image_name->insert(new_pair); + if (ret.second == false) { + // The element is not inserted (because an element with the same key was already in the map) + // Therefore, free the memory allocated for the new entry: + free(name_copy); + + return (*ret.first).second; + } + return value; +} + +gchar const *sp_filter_name_for_image(SPFilter const *filter, int const image) { + switch (image) { + case Inkscape::Filters::NR_FILTER_SOURCEGRAPHIC: + return "SourceGraphic"; + break; + case Inkscape::Filters::NR_FILTER_SOURCEALPHA: + return "SourceAlpha"; + break; + case Inkscape::Filters::NR_FILTER_BACKGROUNDIMAGE: + return "BackgroundImage"; + break; + case Inkscape::Filters::NR_FILTER_BACKGROUNDALPHA: + return "BackgroundAlpha"; + break; + case Inkscape::Filters::NR_FILTER_STROKEPAINT: + return "StrokePaint"; + break; + case Inkscape::Filters::NR_FILTER_FILLPAINT: + return "FillPaint"; + break; + case Inkscape::Filters::NR_FILTER_SLOT_NOT_SET: + case Inkscape::Filters::NR_FILTER_UNNAMED_SLOT: + return 0; + break; + default: + for (map::const_iterator i + = filter->_image_name->begin() ; + i != filter->_image_name->end() ; ++i) { + if (i->second == image) { + return i->first; + } + } + } + return 0; +} + +Glib::ustring sp_filter_get_new_result_name(SPFilter *filter) { + g_assert(filter != NULL); + int largest = 0; + + for(auto& primitive_obj: filter->children) { + if (SP_IS_FILTER_PRIMITIVE(&primitive_obj)) { + Inkscape::XML::Node *repr = primitive_obj.getRepr(); + char const *result = repr->attribute("result"); + int index; + if (result) + { + if (sscanf(result, "result%5d", &index) == 1) + { + if (index > largest) + { + largest = index; + } + } + } + } + } + + return "result" + Glib::Ascii::dtostr(largest + 1); +} + +bool ltstr::operator()(const char* s1, const char* s2) const +{ + return strcmp(s1, s2) < 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-filter.h b/src/object/sp-filter.h new file mode 100644 index 000000000..054562d39 --- /dev/null +++ b/src/object/sp-filter.h @@ -0,0 +1,113 @@ +/** \file + * SVG element + *//* + * Authors: + * Hugo Rodrigues + * Niko Kiirala + * + * Copyright (C) 2006,2007 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#ifndef SP_FILTER_H_SEEN +#define SP_FILTER_H_SEEN + +#include +#include + +#include "number-opt-number.h" +#include "sp-dimensions.h" +#include "sp-object.h" +#include "sp-filter-units.h" +#include "svg/svg-length.h" + +#define SP_FILTER(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FILTER(obj) (dynamic_cast((SPObject*)obj) != NULL) + +#define SP_FILTER_FILTER_UNITS(f) (SP_FILTER(f)->filterUnits) +#define SP_FILTER_PRIMITIVE_UNITS(f) (SP_FILTER(f)->primitiveUnits) + +namespace Inkscape { +namespace Filters { +class Filter; +} } + +class SPFilterReference; +class SPFilterPrimitive; + +struct ltstr { + bool operator()(const char* s1, const char* s2) const; +}; + +class SPFilter : public SPObject, public SPDimensions { +public: + SPFilter(); + virtual ~SPFilter(); + + SPFilterUnits filterUnits; + unsigned int filterUnits_set : 1; + SPFilterUnits primitiveUnits; + unsigned int primitiveUnits_set : 1; + NumberOptNumber filterRes; + SPFilterReference *href; + sigc::connection modified_connection; + + guint getRefCount(); + guint _refcount; + + Inkscape::Filters::Filter *_renderer; + + std::map* _image_name; + int _image_number_next; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref); + virtual void remove_child(Inkscape::XML::Node* child); + + virtual void set(unsigned int key, const char* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); +}; + +void sp_filter_set_filter_units(SPFilter *filter, SPFilterUnits filterUnits); +void sp_filter_set_primitive_units(SPFilter *filter, SPFilterUnits filterUnits); +SPFilterPrimitive *add_primitive(SPFilter *filter, SPFilterPrimitive *primitive); +SPFilterPrimitive *get_primitive(SPFilter *filter, int index); + +/* Initializes the given Inkscape::Filters::Filter object as a renderer for this + * SPFilter object. */ +void sp_filter_build_renderer(SPFilter *sp_filter, Inkscape::Filters::Filter *nr_filter); + +/// Returns the number of filter primitives in this SPFilter object. +int sp_filter_primitive_count(SPFilter *filter); + +/// Returns a slot number for given image name, or -1 for unknown name. +int sp_filter_get_image_name(SPFilter *filter, char const *name); + +/// Returns slot number for given image name, even if it's unknown. +int sp_filter_set_image_name(SPFilter *filter, char const *name); + +/** Finds image name based on it's slot number. Returns 0 for unknown slot + * numbers. */ +char const *sp_filter_name_for_image(SPFilter const *filter, int const image); + +/// Returns a result image name that is not in use inside this filter. +Glib::ustring sp_filter_get_new_result_name(SPFilter *filter); + +#endif /* !SP_FILTER_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-flowdiv.cpp b/src/object/sp-flowdiv.cpp new file mode 100644 index 000000000..002fcff85 --- /dev/null +++ b/src/object/sp-flowdiv.cpp @@ -0,0 +1,463 @@ +/* + */ + +#include "xml/repr.h" +#include "sp-flowdiv.h" +#include "sp-string.h" +#include "document.h" + +SPFlowdiv::SPFlowdiv() : SPItem() { +} + +SPFlowdiv::~SPFlowdiv() { +} + +void SPFlowdiv::release() { + SPItem::release(); +} + +void SPFlowdiv::update(SPCtx *ctx, unsigned int flags) { + SPItemCtx *ictx = reinterpret_cast(ctx); + SPItemCtx cctx = *ictx; + + unsigned childflags = flags; + if (flags & SP_OBJECT_MODIFIED_FLAG) { + childflags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + childflags &= SP_OBJECT_MODIFIED_CASCADE; + + std::vector l; + for (auto& child: children) { + sp_object_ref(&child); + l.push_back(&child); + } + + for(auto child:l) { + if (childflags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + if (SP_IS_ITEM(child)) { + SPItem const &chi = *SP_ITEM(child); + cctx.i2doc = chi.transform * ictx->i2doc; + cctx.i2vp = chi.transform * ictx->i2vp; + child->updateDisplay((SPCtx *)&cctx, childflags); + } else { + child->updateDisplay(ctx, childflags); + } + } + + sp_object_unref(child); + } + + SPItem::update(ctx, flags); +} + +void SPFlowdiv::modified(unsigned int flags) { + SPItem::modified(flags); + + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + std::vector l; + for (auto& child: children) { + sp_object_ref(&child); + l.push_back(&child); + } + + for(auto child:l) { + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + sp_object_unref(child); + } +} + + +void SPFlowdiv::build(SPDocument *doc, Inkscape::XML::Node *repr) { + this->_requireSVGVersion(Inkscape::Version(1, 2)); + + SPItem::build(doc, repr); +} + +void SPFlowdiv::set(unsigned int key, const gchar* value) { + SPItem::set(key, value); +} + + +Inkscape::XML::Node* SPFlowdiv::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if ( flags & SP_OBJECT_WRITE_BUILD ) { + if ( repr == NULL ) { + repr = xml_doc->createElement("svg:flowDiv"); + } + + std::vector l; + + for (auto& child: children) { + Inkscape::XML::Node* c_repr = NULL; + + if ( SP_IS_FLOWTSPAN (&child) ) { + c_repr = child.updateRepr(xml_doc, NULL, flags); + } else if ( SP_IS_FLOWPARA(&child) ) { + c_repr = child.updateRepr(xml_doc, NULL, flags); + } else if ( SP_IS_STRING(&child) ) { + c_repr = xml_doc->createTextNode(SP_STRING(&child)->string.c_str()); + } + + if ( c_repr ) { + l.push_back(c_repr); + } + } + for (auto i=l.rbegin();i!=l.rend();++i) { + repr->addChild(*i, NULL); + Inkscape::GC::release(*i); + } + } else { + for (auto& child: children) { + if ( SP_IS_FLOWTSPAN (&child) ) { + child.updateRepr(flags); + } else if ( SP_IS_FLOWPARA(&child) ) { + child.updateRepr(flags); + } else if ( SP_IS_STRING(&child) ) { + child.getRepr()->setContent(SP_STRING(&child)->string.c_str()); + } + } + } + + SPItem::write(xml_doc, repr, flags); + + return repr; +} + + +/* + * + */ + +SPFlowtspan::SPFlowtspan() : SPItem() { +} + +SPFlowtspan::~SPFlowtspan() { +} + +void SPFlowtspan::release() { + SPItem::release(); +} + +void SPFlowtspan::update(SPCtx *ctx, unsigned int flags) { + SPItemCtx *ictx = reinterpret_cast(ctx); + SPItemCtx cctx = *ictx; + + unsigned childflags = flags; + if (flags & SP_OBJECT_MODIFIED_FLAG) { + childflags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + childflags &= SP_OBJECT_MODIFIED_CASCADE; + std::vector l; + for (auto& child: children) { + sp_object_ref(&child); + l.push_back(&child); + } + + for(auto child:l) { + if (childflags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + if (SP_IS_ITEM(child)) { + SPItem const &chi = *SP_ITEM(child); + cctx.i2doc = chi.transform * ictx->i2doc; + cctx.i2vp = chi.transform * ictx->i2vp; + child->updateDisplay((SPCtx *)&cctx, childflags); + } else { + child->updateDisplay(ctx, childflags); + } + } + + sp_object_unref(child); + } + + SPItem::update(ctx, flags); +} + +void SPFlowtspan::modified(unsigned int flags) { + SPItem::modified(flags); + + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + std::vector l; + for (auto& child: children) { + sp_object_ref(&child); + l.push_back(&child); + } + + for(auto child:l) { + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + sp_object_unref(child); + } +} + + +void SPFlowtspan::build(SPDocument *doc, Inkscape::XML::Node *repr) { + SPItem::build(doc, repr); +} + +void SPFlowtspan::set(unsigned int key, const gchar* value) { + SPItem::set(key, value); +} + +Inkscape::XML::Node *SPFlowtspan::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if ( flags&SP_OBJECT_WRITE_BUILD ) { + if ( repr == NULL ) { + repr = xml_doc->createElement("svg:flowSpan"); + } + + std::vector l; + + for (auto& child: children) { + Inkscape::XML::Node* c_repr = NULL; + + if ( SP_IS_FLOWTSPAN(&child) ) { + c_repr = child.updateRepr(xml_doc, NULL, flags); + } else if ( SP_IS_FLOWPARA(&child) ) { + c_repr = child.updateRepr(xml_doc, NULL, flags); + } else if ( SP_IS_STRING(&child) ) { + c_repr = xml_doc->createTextNode(SP_STRING(&child)->string.c_str()); + } + + if ( c_repr ) { + l.push_back(c_repr); + } + } + for (auto i=l.rbegin();i!=l.rend();++i) { + repr->addChild(*i, NULL); + Inkscape::GC::release(*i); + } + } else { + for (auto& child: children) { + if ( SP_IS_FLOWTSPAN(&child) ) { + child.updateRepr(flags); + } else if ( SP_IS_FLOWPARA(&child) ) { + child.updateRepr(flags); + } else if ( SP_IS_STRING(&child) ) { + child.getRepr()->setContent(SP_STRING(&child)->string.c_str()); + } + } + } + + SPItem::write(xml_doc, repr, flags); + + return repr; +} + + +/* + * + */ +SPFlowpara::SPFlowpara() : SPItem() { +} + +SPFlowpara::~SPFlowpara() { +} + +void SPFlowpara::release() { + SPItem::release(); +} + +void SPFlowpara::update(SPCtx *ctx, unsigned int flags) { + SPItemCtx *ictx = reinterpret_cast(ctx); + SPItemCtx cctx = *ictx; + + SPItem::update(ctx, flags); + + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + std::vector l; + for (auto& child: children) { + sp_object_ref(&child); + l.push_back(&child); + } + + for(auto child:l) { + if (flags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + if (SP_IS_ITEM(child)) { + SPItem const &chi = *SP_ITEM(child); + cctx.i2doc = chi.transform * ictx->i2doc; + cctx.i2vp = chi.transform * ictx->i2vp; + child->updateDisplay((SPCtx *)&cctx, flags); + } else { + child->updateDisplay(ctx, flags); + } + } + sp_object_unref(child); + } +} + +void SPFlowpara::modified(unsigned int flags) { + SPItem::modified(flags); + + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + std::vector l; + for (auto& child: children) { + sp_object_ref(&child); + l.push_back(&child); + } + + for(auto child:l) { + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + sp_object_unref(child); + } +} + + +void SPFlowpara::build(SPDocument *doc, Inkscape::XML::Node *repr) { + SPItem::build(doc, repr); +} + +void SPFlowpara::set(unsigned int key, const gchar* value) { + SPItem::set(key, value); +} + +Inkscape::XML::Node *SPFlowpara::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if ( flags&SP_OBJECT_WRITE_BUILD ) { + if ( repr == NULL ) { + repr = xml_doc->createElement("svg:flowPara"); + } + + std::vector l; + + for (auto& child: children) { + Inkscape::XML::Node* c_repr = NULL; + + if ( SP_IS_FLOWTSPAN(&child) ) { + c_repr = child.updateRepr(xml_doc, NULL, flags); + } else if ( SP_IS_FLOWPARA(&child) ) { + c_repr = child.updateRepr(xml_doc, NULL, flags); + } else if ( SP_IS_STRING(&child) ) { + c_repr = xml_doc->createTextNode(SP_STRING(&child)->string.c_str()); + } + + if ( c_repr ) { + l.push_back(c_repr); + } + } + + for (auto i=l.rbegin();i!=l.rend();++i) { + repr->addChild(*i, NULL); + Inkscape::GC::release(*i); + } + } else { + for (auto& child: children) { + if ( SP_IS_FLOWTSPAN(&child) ) { + child.updateRepr(flags); + } else if ( SP_IS_FLOWPARA(&child) ) { + child.updateRepr(flags); + } else if ( SP_IS_STRING(&child) ) { + child.getRepr()->setContent(SP_STRING(&child)->string.c_str()); + } + } + } + + SPItem::write(xml_doc, repr, flags); + + return repr; +} + + +/* + * + */ + +SPFlowline::SPFlowline() : SPObject() { +} + +SPFlowline::~SPFlowline() { +} + +void SPFlowline::release() { + SPObject::release(); +} + +void SPFlowline::modified(unsigned int flags) { + SPObject::modified(flags); + + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; +} + +Inkscape::XML::Node *SPFlowline::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if ( flags & SP_OBJECT_WRITE_BUILD ) { + if ( repr == NULL ) { + repr = xml_doc->createElement("svg:flowLine"); + } + } + + SPObject::write(xml_doc, repr, flags); + + return repr; +} + + +/* + * + */ + +SPFlowregionbreak::SPFlowregionbreak() : SPObject() { +} + +SPFlowregionbreak::~SPFlowregionbreak() { +} + +void SPFlowregionbreak::release() { + SPObject::release(); +} + +void SPFlowregionbreak::modified(unsigned int flags) { + SPObject::modified(flags); + + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; +} + +Inkscape::XML::Node *SPFlowregionbreak::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if ( flags & SP_OBJECT_WRITE_BUILD ) { + if ( repr == NULL ) { + repr = xml_doc->createElement("svg:flowLine"); + } + } + + SPObject::write(xml_doc, repr, flags); + + return repr; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-flowdiv.h b/src/object/sp-flowdiv.h new file mode 100644 index 000000000..4a3690726 --- /dev/null +++ b/src/object/sp-flowdiv.h @@ -0,0 +1,96 @@ +#ifndef SEEN_SP_ITEM_FLOWDIV_H +#define SEEN_SP_ITEM_FLOWDIV_H + +/* + */ + +#include "sp-object.h" +#include "sp-item.h" + +#define SP_FLOWDIV(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FLOWDIV(obj) (dynamic_cast((SPObject*)obj) != NULL) + +#define SP_FLOWTSPAN(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FLOWTSPAN(obj) (dynamic_cast((SPObject*)obj) != NULL) + +#define SP_FLOWPARA(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FLOWPARA(obj) (dynamic_cast((SPObject*)obj) != NULL) + +#define SP_FLOWLINE(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FLOWLINE(obj) (dynamic_cast((SPObject*)obj) != NULL) + +#define SP_FLOWREGIONBREAK(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FLOWREGIONBREAK(obj) (dynamic_cast((SPObject*)obj) != NULL) + +// these 3 are derivatives of SPItem to get the automatic style handling +class SPFlowdiv : public SPItem { +public: + SPFlowdiv(); + virtual ~SPFlowdiv(); + +protected: + virtual void build(SPDocument *document, Inkscape::XML::Node *repr); + virtual void release(); + virtual void update(SPCtx* ctx, unsigned int flags); + virtual void modified(unsigned int flags); + + virtual void set(unsigned int key, char const* value); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); +}; + +class SPFlowtspan : public SPItem { +public: + SPFlowtspan(); + virtual ~SPFlowtspan(); + +protected: + virtual void build(SPDocument *document, Inkscape::XML::Node *repr); + virtual void release(); + virtual void update(SPCtx* ctx, unsigned int flags); + virtual void modified(unsigned int flags); + + virtual void set(unsigned int key, char const* value); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); +}; + +class SPFlowpara : public SPItem { +public: + SPFlowpara(); + virtual ~SPFlowpara(); + +protected: + virtual void build(SPDocument *document, Inkscape::XML::Node *repr); + virtual void release(); + virtual void update(SPCtx* ctx, unsigned int flags); + virtual void modified(unsigned int flags); + + virtual void set(unsigned int key, char const* value); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); +}; + +// these do not need any style +class SPFlowline : public SPObject { +public: + SPFlowline(); + virtual ~SPFlowline(); + +protected: + virtual void release(); + virtual void modified(unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); +}; + +class SPFlowregionbreak : public SPObject { +public: + SPFlowregionbreak(); + virtual ~SPFlowregionbreak(); + +protected: + virtual void release(); + virtual void modified(unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); +}; + +#endif diff --git a/src/object/sp-flowregion.cpp b/src/object/sp-flowregion.cpp new file mode 100644 index 000000000..6640d93c2 --- /dev/null +++ b/src/object/sp-flowregion.cpp @@ -0,0 +1,392 @@ +/* + */ + +#ifdef HAVE_CONFIG_H +#include +#endif +#include + +#include +#include "display/curve.h" +#include "sp-shape.h" +#include "sp-text.h" +#include "sp-use.h" +#include "style.h" +#include "document.h" +#include "sp-title.h" +#include "sp-desc.h" + +#include "sp-flowregion.h" + +#include "livarot/Path.h" +#include "livarot/Shape.h" + + +static void GetDest(SPObject* child,Shape **computed); + + +SPFlowregion::SPFlowregion() : SPItem() { +} + +SPFlowregion::~SPFlowregion() { + for (std::vector::iterator it = this->computed.begin() ; it != this->computed.end() ; ++it) { + delete *it; + } +} + +void SPFlowregion::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) { + SPItem::child_added(child, ref); + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/* fixme: hide (Lauris) */ + +void SPFlowregion::remove_child(Inkscape::XML::Node * child) { + SPItem::remove_child(child); + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + + +void SPFlowregion::update(SPCtx *ctx, unsigned int flags) { + SPItemCtx *ictx = reinterpret_cast(ctx); + SPItemCtx cctx = *ictx; + + unsigned childflags = flags; + if (flags & SP_OBJECT_MODIFIED_FLAG) { + childflags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + childflags &= SP_OBJECT_MODIFIED_CASCADE; + + std::vectorl; + + for (auto& child: children) { + sp_object_ref(&child); + l.push_back(&child); + } + + for (auto child:l) { + g_assert(child != NULL); + SPItem *item = dynamic_cast(child); + + if (childflags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + if (item) { + SPItem const &chi = *item; + cctx.i2doc = chi.transform * ictx->i2doc; + cctx.i2vp = chi.transform * ictx->i2vp; + child->updateDisplay((SPCtx *)&cctx, childflags); + } else { + child->updateDisplay(ctx, childflags); + } + } + + sp_object_unref(child); + } + + SPItem::update(ctx, flags); + + this->UpdateComputed(); +} + +void SPFlowregion::UpdateComputed(void) +{ + for (std::vector::iterator it = computed.begin() ; it != computed.end() ; ++it) { + delete *it; + } + computed.clear(); + + for (auto& child: children) { + Shape *shape = 0; + GetDest(&child, &shape); + computed.push_back(shape); + } +} + +void SPFlowregion::modified(guint flags) { + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + std::vectorl; + + for (auto& child: children) { + sp_object_ref(&child); + l.push_back(&child); + } + + for (auto child:l) { + g_assert(child != NULL); + + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + + sp_object_unref(child); + } +} + +Inkscape::XML::Node *SPFlowregion::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if (flags & SP_OBJECT_WRITE_BUILD) { + if ( repr == NULL ) { + repr = xml_doc->createElement("svg:flowRegion"); + } + + std::vector l; + for (auto& child: children) { + if ( !dynamic_cast(&child) && !dynamic_cast(&child) ) { + Inkscape::XML::Node *crepr = child.updateRepr(xml_doc, NULL, flags); + + if (crepr) { + l.push_back(crepr); + } + } + } + + for (auto i = l.rbegin(); i != l.rend(); ++i) { + repr->addChild(*i, NULL); + Inkscape::GC::release(*i); + } + + for (auto& child: children) { + if ( !dynamic_cast(&child) && !dynamic_cast(&child) ) { + child.updateRepr(flags); + } + } + } + + SPItem::write(xml_doc, repr, flags); + + this->UpdateComputed(); // copied from update(), see LP Bug 1339305 + + return repr; +} + +const char* SPFlowregion::displayName() const { + // TRANSLATORS: "Flow region" is an area where text is allowed to flow + return _("Flow Region"); +} + +SPFlowregionExclude::SPFlowregionExclude() : SPItem() { + this->computed = NULL; +} + +SPFlowregionExclude::~SPFlowregionExclude() { + if (this->computed) { + delete this->computed; + this->computed = NULL; + } +} + +void SPFlowregionExclude::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) { + SPItem::child_added(child, ref); + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/* fixme: hide (Lauris) */ + +void SPFlowregionExclude::remove_child(Inkscape::XML::Node * child) { + SPItem::remove_child(child); + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + + +void SPFlowregionExclude::update(SPCtx *ctx, unsigned int flags) { + SPItemCtx *ictx = reinterpret_cast(ctx); + SPItemCtx cctx = *ictx; + + SPItem::update(ctx, flags); + + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + std::vector l; + + for (auto& child: children) { + sp_object_ref(&child); + l.push_back(&child); + } + + for(auto child:l) { + g_assert(child != NULL); + + if (flags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + SPItem *item = dynamic_cast(child); + if (item) { + SPItem const &chi = *item; + cctx.i2doc = chi.transform * ictx->i2doc; + cctx.i2vp = chi.transform * ictx->i2vp; + child->updateDisplay((SPCtx *)&cctx, flags); + } else { + child->updateDisplay(ctx, flags); + } + } + + sp_object_unref(child); + } + + this->UpdateComputed(); +} + + +void SPFlowregionExclude::UpdateComputed(void) +{ + if (computed) { + delete computed; + computed = NULL; + } + + for (auto& child: children) { + GetDest(&child, &computed); + } +} + +void SPFlowregionExclude::modified(guint flags) { + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + std::vector l; + + for (auto& child: children) { + sp_object_ref(&child); + l.push_back(&child); + } + + for (auto child:l) { + g_assert(child != NULL); + + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + + sp_object_unref(child); + } +} + +Inkscape::XML::Node *SPFlowregionExclude::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if (flags & SP_OBJECT_WRITE_BUILD) { + if ( repr == NULL ) { + repr = xml_doc->createElement("svg:flowRegionExclude"); + } + + std::vector l; + + for (auto& child: children) { + Inkscape::XML::Node *crepr = child.updateRepr(xml_doc, NULL, flags); + + if (crepr) { + l.push_back(crepr); + } + } + + for (auto i = l.rbegin(); i != l.rend(); ++i) { + repr->addChild(*i, NULL); + Inkscape::GC::release(*i); + } + + } else { + for (auto& child: children) { + child.updateRepr(flags); + } + } + + SPItem::write(xml_doc, repr, flags); + + return repr; +} + +const char* SPFlowregionExclude::displayName() const { + /* TRANSLATORS: A region "cut out of" a flow region; text is not allowed to flow inside the + * flow excluded region. flowRegionExclude in SVG 1.2: see + * http://www.w3.org/TR/2004/WD-SVG12-20041027/flow.html#flowRegion-elem and + * http://www.w3.org/TR/2004/WD-SVG12-20041027/flow.html#flowRegionExclude-elem. */ + return _("Flow Excluded Region"); +} + +static void UnionShape(Shape **base_shape, Shape const *add_shape) +{ + if (*base_shape == NULL) + *base_shape = new Shape; + if ( (*base_shape)->hasEdges() == false ) { + (*base_shape)->Copy(const_cast(add_shape)); + } else if ( add_shape->hasEdges() ) { + Shape* temp=new Shape; + temp->Booleen(const_cast(add_shape), *base_shape, bool_op_union); + delete *base_shape; + *base_shape = temp; + } +} + +static void GetDest(SPObject* child,Shape **computed) +{ + if ( child == NULL ) return; + + SPCurve *curve=NULL; + Geom::Affine tr_mat; + + SPObject* u_child = child; + SPItem *item = dynamic_cast(u_child); + g_assert(item != NULL); + SPUse *use = dynamic_cast(item); + if ( use ) { + u_child = use->child; + tr_mat = use->getRelativeTransform(child->parent); + } else { + tr_mat = item->transform; + } + SPShape *shape = dynamic_cast(u_child); + if ( shape ) { + if (!(shape->_curve)) { + shape->set_shape(); + } + curve = shape->getCurve(); + } else { + SPText *text = dynamic_cast(u_child); + if ( text ) { + curve = text->getNormalizedBpath(); + } + } + + if ( curve ) { + Path* temp=new Path; + temp->LoadPathVector(curve->get_pathvector(), tr_mat, true); + Shape* n_shp=new Shape; + temp->Convert(0.25); + temp->Fill(n_shp,0); + Shape* uncross=new Shape; + SPStyle* style = u_child->style; + if ( style && style->fill_rule.computed == SP_WIND_RULE_EVENODD ) { + uncross->ConvertToShape(n_shp,fill_oddEven); + } else { + uncross->ConvertToShape(n_shp,fill_nonZero); + } + UnionShape(computed, uncross); + delete uncross; + delete n_shp; + delete temp; + curve->unref(); + } else { +// printf("no curve\n"); + } +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-flowregion.h b/src/object/sp-flowregion.h new file mode 100644 index 000000000..024a298b8 --- /dev/null +++ b/src/object/sp-flowregion.h @@ -0,0 +1,54 @@ +#ifndef SEEN_SP_ITEM_FLOWREGION_H +#define SEEN_SP_ITEM_FLOWREGION_H + +/* + */ + +#include "sp-item.h" + +#define SP_FLOWREGION(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FLOWREGION(obj) (dynamic_cast((SPObject*)obj) != NULL) + +#define SP_FLOWREGIONEXCLUDE(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FLOWREGIONEXCLUDE(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class Path; +class Shape; +class flow_dest; +class FloatLigne; + +class SPFlowregion : public SPItem { +public: + SPFlowregion(); + virtual ~SPFlowregion(); + + std::vector computed; + + void UpdateComputed(void); + + virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref); + virtual void remove_child(Inkscape::XML::Node *child); + virtual void update(SPCtx *ctx, unsigned int flags); + virtual void modified(guint flags); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); + virtual const char* displayName() const; +}; + +class SPFlowregionExclude : public SPItem { +public: + SPFlowregionExclude(); + virtual ~SPFlowregionExclude(); + + Shape *computed; + + void UpdateComputed(void); + + virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref); + virtual void remove_child(Inkscape::XML::Node *child); + virtual void update(SPCtx *ctx, unsigned int flags); + virtual void modified(guint flags); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); + virtual const char* displayName() const; +}; + +#endif diff --git a/src/object/sp-flowtext.cpp b/src/object/sp-flowtext.cpp new file mode 100644 index 000000000..5cab62c83 --- /dev/null +++ b/src/object/sp-flowtext.cpp @@ -0,0 +1,749 @@ +/* + */ + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include + +#include "attributes.h" +#include "xml/repr.h" +#include "style.h" +#include "inkscape.h" +#include "document.h" + +#include "desktop.h" + +#include "text-tag-attributes.h" +#include "text-editing.h" + +#include "sp-flowdiv.h" +#include "sp-flowregion.h" +#include "sp-flowtext.h" +#include "sp-rect.h" +#include "sp-string.h" +#include "sp-text.h" +#include "sp-use.h" + +#include "libnrtype/font-instance.h" + +#include "livarot/Shape.h" + +#include "display/drawing-text.h" + +SPFlowtext::SPFlowtext() : SPItem(), + par_indent(0), + _optimizeScaledText(false) +{ +} + +SPFlowtext::~SPFlowtext() { +} + +void SPFlowtext::child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref) { + SPItem::child_added(child, ref); + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + + +/* fixme: hide (Lauris) */ + +void SPFlowtext::remove_child(Inkscape::XML::Node* child) { + SPItem::remove_child(child); + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +void SPFlowtext::update(SPCtx* ctx, unsigned int flags) { + SPItemCtx *ictx = (SPItemCtx *) ctx; + SPItemCtx cctx = *ictx; + + unsigned childflags = flags; + if (flags & SP_OBJECT_MODIFIED_FLAG) { + childflags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + childflags &= SP_OBJECT_MODIFIED_CASCADE; + + std::vector l; + for (auto& child: children) { + sp_object_ref(&child); + l.push_back(&child); + } + + for (auto child:l) { + g_assert(child != NULL); + + if (childflags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + SPItem *item = dynamic_cast(child); + if (item) { + SPItem const &chi = *item; + cctx.i2doc = chi.transform * ictx->i2doc; + cctx.i2vp = chi.transform * ictx->i2vp; + child->updateDisplay((SPCtx *)&cctx, childflags); + } else { + child->updateDisplay(ctx, childflags); + } + } + + sp_object_unref(child); + } + + SPItem::update(ctx, flags); + + this->rebuildLayout(); + + Geom::OptRect pbox = this->geometricBounds(); + + for (SPItemView *v = this->display; v != NULL; v = v->next) { + Inkscape::DrawingGroup *g = dynamic_cast(v->arenaitem); + this->_clearFlow(g); + g->setStyle(this->style); + // pass the bbox of the flowtext object as paintbox (used for paintserver fills) + this->layout.show(g, pbox); + } +} + +void SPFlowtext::modified(unsigned int flags) { + SPObject *region = NULL; + + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + // FIXME: the below stanza is copied over from sp_text_modified, consider factoring it out + if (flags & ( SP_OBJECT_STYLE_MODIFIED_FLAG )) { + Geom::OptRect pbox = geometricBounds(); + + for (SPItemView* v = display; v != NULL; v = v->next) { + Inkscape::DrawingGroup *g = dynamic_cast(v->arenaitem); + _clearFlow(g); + g->setStyle(style); + layout.show(g, pbox); + } + } + + for (auto& o: children) { + if (dynamic_cast(&o)) { + region = &o; + break; + } + } + + if (region) { + if (flags || (region->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + region->emitModified(flags); // pass down to the region only + } + } +} + +void SPFlowtext::build(SPDocument* doc, Inkscape::XML::Node* repr) { + this->_requireSVGVersion(Inkscape::Version(1, 2)); + + SPItem::build(doc, repr); + + this->readAttr( "inkscape:layoutOptions" ); // must happen after css has been read +} + +void SPFlowtext::set(unsigned int key, const gchar* value) { + switch (key) { + case SP_ATTR_LAYOUT_OPTIONS: { + // deprecated attribute, read for backward compatibility only + //XML Tree being directly used while it shouldn't be. + SPCSSAttr *opts = sp_repr_css_attr(this->getRepr(), "inkscape:layoutOptions"); + { + gchar const *val = sp_repr_css_property(opts, "justification", NULL); + + if (val != NULL && !this->style->text_align.set) { + if ( strcmp(val, "0") == 0 || strcmp(val, "false") == 0 ) { + this->style->text_align.value = SP_CSS_TEXT_ALIGN_LEFT; + } else { + this->style->text_align.value = SP_CSS_TEXT_ALIGN_JUSTIFY; + } + + this->style->text_align.set = TRUE; + this->style->text_align.inherit = FALSE; + this->style->text_align.computed = this->style->text_align.value; + } + } + /* no equivalent css attribute for these two (yet) + { + gchar const *val = sp_repr_css_property(opts, "layoutAlgo", NULL); + if ( val == NULL ) { + group->algo = 0; + } else { + if ( strcmp(val, "better") == 0 ) { // knuth-plass, never worked for general cases + group->algo = 2; + } else if ( strcmp(val, "simple") == 0 ) { // greedy, but allowed lines to be compressed by up to 20% if it would make them fit + group->algo = 1; + } else if ( strcmp(val, "default") == 0 ) { // the same one we use, a standard greedy + group->algo = 0; + } + } + } + */ + { // This would probably translate to padding-left, if SPStyle had it. + gchar const *val = sp_repr_css_property(opts, "par-indent", NULL); + + if ( val == NULL ) { + this->par_indent = 0.0; + } else { + this->par_indent = g_ascii_strtod(val, NULL); + } + } + + sp_repr_css_attr_unref(opts); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + + default: + SPItem::set(key, value); + break; + } +} + +Inkscape::XML::Node* SPFlowtext::write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags) { + if ( flags & SP_OBJECT_WRITE_BUILD ) { + if ( repr == NULL ) { + repr = doc->createElement("svg:flowRoot"); + } + + std::vector l; + + for (auto& child: children) { + Inkscape::XML::Node *c_repr = NULL; + + if ( dynamic_cast(&child) || dynamic_cast(&child) || dynamic_cast(&child) || dynamic_cast(&child)) { + c_repr = child.updateRepr(doc, NULL, flags); + } + + if ( c_repr ) { + l.push_back(c_repr); + } + } + + for (auto i=l.rbegin();i!=l.rend();++i) { + repr->addChild(*i, NULL); + Inkscape::GC::release(*i); + } + } else { + for (auto& child: children) { + if ( dynamic_cast(&child) || dynamic_cast(&child) || dynamic_cast(&child) || dynamic_cast(&child)) { + child.updateRepr(flags); + } + } + } + + this->rebuildLayout(); // copied from update(), see LP Bug 1339305 + + SPItem::write(doc, repr, flags); + + return repr; +} + +Geom::OptRect SPFlowtext::bbox(Geom::Affine const &transform, SPItem::BBoxType type) const { + Geom::OptRect bbox = this->layout.bounds(transform); + + // Add stroke width + // FIXME this code is incorrect + if (bbox && type == SPItem::VISUAL_BBOX && !this->style->stroke.isNone()) { + double scale = transform.descrim(); + bbox->expandBy(0.5 * this->style->stroke_width.computed * scale); + } + + return bbox; +} + +void SPFlowtext::print(SPPrintContext *ctx) { + Geom::OptRect pbox, bbox, dbox; + pbox = this->geometricBounds(); + bbox = this->desktopVisualBounds(); + dbox = Geom::Rect::from_xywh(Geom::Point(0,0), this->document->getDimensions()); + + Geom::Affine const ctm (this->i2dt_affine()); + + this->layout.print(ctx, pbox, dbox, bbox, ctm); +} + +const char* SPFlowtext::displayName() const { + if (has_internal_frame()) { + return _("Flowed Text"); + } else { + return _("Linked Flowed Text"); + } +} + +gchar* SPFlowtext::description() const { + int const nChars = layout.iteratorToCharIndex(layout.end()); + char const *trunc = (layout.inputTruncated()) ? _(" [truncated]") : ""; + + return g_strdup_printf(ngettext("(%d character%s)", "(%d characters%s)", nChars), nChars, trunc); +} + +void SPFlowtext::snappoints(std::vector &p, Inkscape::SnapPreferences const *snapprefs) const { + if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_TEXT_BASELINE)) { + // Choose a point on the baseline for snapping from or to, with the horizontal position + // of this point depending on the text alignment (left vs. right) + Inkscape::Text::Layout const *layout = te_get_layout((SPItem *) this); + + if (layout != NULL && layout->outputExists()) { + boost::optional pt = layout->baselineAnchorPoint(); + + if (pt) { + p.push_back(Inkscape::SnapCandidatePoint((*pt) * this->i2dt_affine(), Inkscape::SNAPSOURCE_TEXT_ANCHOR, Inkscape::SNAPTARGET_TEXT_ANCHOR)); + } + } + } +} + +Inkscape::DrawingItem* SPFlowtext::show(Inkscape::Drawing &drawing, unsigned int /*key*/, unsigned int /*flags*/) { + Inkscape::DrawingGroup *flowed = new Inkscape::DrawingGroup(drawing); + flowed->setPickChildren(false); + flowed->setStyle(this->style); + + // pass the bbox of the flowtext object as paintbox (used for paintserver fills) + Geom::OptRect bbox = this->geometricBounds(); + this->layout.show(flowed, bbox); + + return flowed; +} + +void SPFlowtext::hide(unsigned int key) { + for (SPItemView* v = this->display; v != NULL; v = v->next) { + if (v->key == key) { + Inkscape::DrawingGroup *g = dynamic_cast(v->arenaitem); + this->_clearFlow(g); + } + } +} + + +/* + * + */ +void SPFlowtext::_buildLayoutInput(SPObject *root, Shape const *exclusion_shape, std::list *shapes, SPObject **pending_line_break_object) +{ + Inkscape::Text::Layout::OptionalTextTagAttrs pi; + bool with_indent = false; + + if (dynamic_cast(root)) { + + layout.strut.reset(); + if (style) { + font_instance *font = font_factory::Default()->FaceFromStyle( style ); + if (font) { + font->FontMetrics(layout.strut.ascent, layout.strut.descent, layout.strut.xheight); + font->Unref(); + } + layout.strut *= style->font_size.computed; + if (style->line_height.normal ) { + layout.strut.computeEffective( Inkscape::Text::Layout::LINE_HEIGHT_NORMAL ); + } else if (style->line_height.unit == SP_CSS_UNIT_NONE) { + layout.strut.computeEffective( style->line_height.computed ); + } else { + if( style->font_size.computed > 0.0 ) { + layout.strut.computeEffective( style->line_height.computed/style->font_size.computed ); + } + } + } + + // emulate par-indent with the first char's kern + SPObject *t = root; + SPFlowtext *ft = NULL; + while (t && !ft) { + ft = dynamic_cast(t); + t = t->parent; + } + + if (ft) { + double indent = ft->par_indent; + if (indent != 0) { + with_indent = true; + SVGLength sl; + sl.value = sl.computed = indent; + sl._set = true; + pi.dx.push_back(sl); + } + } + } + + if (*pending_line_break_object) { + if (dynamic_cast(*pending_line_break_object)) { + layout.appendControlCode(Inkscape::Text::Layout::SHAPE_BREAK, *pending_line_break_object); + } else { + layout.appendControlCode(Inkscape::Text::Layout::PARAGRAPH_BREAK, *pending_line_break_object); + } + *pending_line_break_object = NULL; + } + + for (auto& child: root->children) { + SPString *str = dynamic_cast(&child); + if (str) { + if (*pending_line_break_object) { + if (dynamic_cast(*pending_line_break_object)) + layout.appendControlCode(Inkscape::Text::Layout::SHAPE_BREAK, *pending_line_break_object); + else { + layout.appendControlCode(Inkscape::Text::Layout::PARAGRAPH_BREAK, *pending_line_break_object); + } + *pending_line_break_object = NULL; + } + if (with_indent) { + layout.appendText(str->string, root->style, &child, &pi); + } else { + layout.appendText(str->string, root->style, &child); + } + } else { + SPFlowregion *region = dynamic_cast(&child); + if (region) { + std::vector const &computed = region->computed; + for (std::vector::const_iterator it = computed.begin() ; it != computed.end() ; ++it) { + shapes->push_back(Shape()); + if (exclusion_shape->hasEdges()) { + shapes->back().Booleen(*it, const_cast(exclusion_shape), bool_op_diff); + } else { + shapes->back().Copy(*it); + } + layout.appendWrapShape(&shapes->back()); + } + } + //Xml Tree is being directly used while it shouldn't be. + else if (!dynamic_cast(&child) && !sp_repr_is_meta_element(child.getRepr())) { + _buildLayoutInput(&child, exclusion_shape, shapes, pending_line_break_object); + } + } + } + + if (dynamic_cast(root) || dynamic_cast(root) || dynamic_cast(root) || dynamic_cast(root)) { + if (!root->hasChildren()) { + layout.appendText("", root->style, root); + } + *pending_line_break_object = root; + } +} + +Shape* SPFlowtext::_buildExclusionShape() const +{ + Shape *shape = new Shape(); + Shape *shape_temp = new Shape(); + + for (auto& child: children) { + // RH: is it right that this shouldn't be recursive? + SPFlowregionExclude *c_child = dynamic_cast(const_cast(&child)); + if ( c_child && c_child->computed && c_child->computed->hasEdges() ) { + if (shape->hasEdges()) { + shape_temp->Booleen(shape, c_child->computed, bool_op_union); + std::swap(shape, shape_temp); + } else { + shape->Copy(c_child->computed); + } + } + } + + delete shape_temp; + + return shape; +} + +void SPFlowtext::rebuildLayout() +{ + std::list shapes; + + layout.clear(); + Shape *exclusion_shape = _buildExclusionShape(); + SPObject *pending_line_break_object = NULL; + _buildLayoutInput(this, exclusion_shape, &shapes, &pending_line_break_object); + delete exclusion_shape; + layout.calculateFlow(); +#if DEBUG_TEXTLAYOUT_DUMPASTEXT + g_print("%s", layout.dumpAsText().c_str()); +#endif +} + +void SPFlowtext::_clearFlow(Inkscape::DrawingGroup *in_arena) +{ + in_arena->clearChildren(); +} + +Inkscape::XML::Node *SPFlowtext::getAsText() +{ + if (!this->layout.outputExists()) { + return NULL; + } + + Inkscape::XML::Document *xml_doc = this->document->getReprDoc(); + Inkscape::XML::Node *repr = xml_doc->createElement("svg:text"); + repr->setAttribute("xml:space", "preserve"); + repr->setAttribute("style", this->getRepr()->attribute("style")); + Geom::Point anchor_point = this->layout.characterAnchorPoint(this->layout.begin()); + sp_repr_set_svg_double(repr, "x", anchor_point[Geom::X]); + sp_repr_set_svg_double(repr, "y", anchor_point[Geom::Y]); + + for (Inkscape::Text::Layout::iterator it = this->layout.begin() ; it != this->layout.end() ; ) { + Inkscape::XML::Node *line_tspan = xml_doc->createElement("svg:tspan"); + line_tspan->setAttribute("sodipodi:role", "line"); + + Inkscape::Text::Layout::iterator it_line_end = it; + it_line_end.nextStartOfLine(); + + while (it != it_line_end) { + + Inkscape::XML::Node *span_tspan = xml_doc->createElement("svg:tspan"); + Geom::Point anchor_point = this->layout.characterAnchorPoint(it); + // use kerning to simulate justification and whatnot + Inkscape::Text::Layout::iterator it_span_end = it; + it_span_end.nextStartOfSpan(); + Inkscape::Text::Layout::OptionalTextTagAttrs attrs; + this->layout.simulateLayoutUsingKerning(it, it_span_end, &attrs); + // set x,y attributes only when we need to + bool set_x = false; + bool set_y = false; + if (!this->transform.isIdentity()) { + set_x = set_y = true; + } else { + Inkscape::Text::Layout::iterator it_chunk_start = it; + it_chunk_start.thisStartOfChunk(); + if (it == it_chunk_start) { + set_x = true; + // don't set y so linespacing adjustments and things will still work + } + Inkscape::Text::Layout::iterator it_shape_start = it; + it_shape_start.thisStartOfShape(); + if (it == it_shape_start) + set_y = true; + } + if (set_x && !attrs.dx.empty()) + attrs.dx[0] = 0.0; + TextTagAttributes(attrs).writeTo(span_tspan); + if (set_x) + sp_repr_set_svg_double(span_tspan, "x", anchor_point[Geom::X]); // FIXME: this will pick up the wrong end of counter-directional runs + if (set_y) + sp_repr_set_svg_double(span_tspan, "y", anchor_point[Geom::Y]); + if (line_tspan->childCount() == 0) { + sp_repr_set_svg_double(line_tspan, "x", anchor_point[Geom::X]); // FIXME: this will pick up the wrong end of counter-directional runs + sp_repr_set_svg_double(line_tspan, "y", anchor_point[Geom::Y]); + } + + void *rawptr = 0; + Glib::ustring::iterator span_text_start_iter; + this->layout.getSourceOfCharacter(it, &rawptr, &span_text_start_iter); + SPObject *source_obj = reinterpret_cast(rawptr); + + Glib::ustring style_text = (dynamic_cast(source_obj) ? source_obj->parent : source_obj)->style->write( SP_STYLE_FLAG_IFDIFF, SP_STYLE_SRC_UNSET, this->style); + if (!style_text.empty()) { + span_tspan->setAttribute("style", style_text.c_str()); + } + + SPString *str = dynamic_cast(source_obj); + if (str) { + Glib::ustring *string = &(str->string); // TODO fixme: dangerous, unsafe premature-optimization + void *rawptr = 0; + Glib::ustring::iterator span_text_end_iter; + this->layout.getSourceOfCharacter(it_span_end, &rawptr, &span_text_end_iter); + SPObject *span_end_obj = reinterpret_cast(rawptr); + if (span_end_obj != source_obj) { + if (it_span_end == this->layout.end()) { + span_text_end_iter = span_text_start_iter; + for (int i = this->layout.iteratorToCharIndex(it_span_end) - this->layout.iteratorToCharIndex(it) ; i ; --i) + ++span_text_end_iter; + } else + span_text_end_iter = string->end(); // spans will never straddle a source boundary + } + + if (span_text_start_iter != span_text_end_iter) { + Glib::ustring new_string; + while (span_text_start_iter != span_text_end_iter) + new_string += *span_text_start_iter++; // grr. no substr() with iterators + Inkscape::XML::Node *new_text = xml_doc->createTextNode(new_string.c_str()); + span_tspan->appendChild(new_text); + Inkscape::GC::release(new_text); + } + } + it = it_span_end; + + line_tspan->appendChild(span_tspan); + Inkscape::GC::release(span_tspan); + } + repr->appendChild(line_tspan); + Inkscape::GC::release(line_tspan); + } + + return repr; +} + +SPItem const *SPFlowtext::get_frame(SPItem const *after) const +{ + SPItem *item = const_cast(this)->get_frame(after); + return item; +} + +SPItem *SPFlowtext::get_frame(SPItem const *after) +{ + SPItem *frame = 0; + + SPObject *region = 0; + for (auto& o: children) { + if (dynamic_cast(&o)) { + region = &o; + break; + } + } + + if (region) { + bool past = false; + + for (auto& o: region->children) { + SPItem *item = dynamic_cast(&o); + if (item) { + if ( (after == NULL) || past ) { + frame = item; + } else { + if (item == after) { + past = true; + } + } + } + } + + SPUse *use = dynamic_cast(frame); + if ( use ) { + frame = use->get_original(); + } + } + return frame; +} + +bool SPFlowtext::has_internal_frame() const +{ + SPItem const *frame = get_frame(NULL); + + return (frame && isAncestorOf(frame) && dynamic_cast(frame)); +} + + +SPItem *create_flowtext_with_internal_frame (SPDesktop *desktop, Geom::Point p0, Geom::Point p1) +{ + SPDocument *doc = desktop->getDocument(); + + Inkscape::XML::Document *xml_doc = doc->getReprDoc(); + Inkscape::XML::Node *root_repr = xml_doc->createElement("svg:flowRoot"); + root_repr->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create + SPItem *ft_item = dynamic_cast(desktop->currentLayer()->appendChildRepr(root_repr)); + g_assert(ft_item != NULL); + SPObject *root_object = doc->getObjectByRepr(root_repr); + g_assert(dynamic_cast(root_object) != NULL); + + Inkscape::XML::Node *region_repr = xml_doc->createElement("svg:flowRegion"); + root_repr->appendChild(region_repr); + SPObject *region_object = doc->getObjectByRepr(region_repr); + g_assert(dynamic_cast(region_object) != NULL); + + Inkscape::XML::Node *rect_repr = xml_doc->createElement("svg:rect"); // FIXME: use path!!! after rects are converted to use path + region_repr->appendChild(rect_repr); + + SPRect *rect = dynamic_cast(doc->getObjectByRepr(rect_repr)); + g_assert(rect != NULL); + + p0 *= desktop->dt2doc(); + p1 *= desktop->dt2doc(); + using Geom::X; + using Geom::Y; + Geom::Coord const x0 = MIN(p0[X], p1[X]); + Geom::Coord const y0 = MIN(p0[Y], p1[Y]); + Geom::Coord const x1 = MAX(p0[X], p1[X]); + Geom::Coord const y1 = MAX(p0[Y], p1[Y]); + Geom::Coord const w = x1 - x0; + Geom::Coord const h = y1 - y0; + + rect->setPosition(x0, y0, w, h); + rect->updateRepr(); + + Inkscape::XML::Node *para_repr = xml_doc->createElement("svg:flowPara"); + root_repr->appendChild(para_repr); + SPObject *para_object = doc->getObjectByRepr(para_repr); + g_assert(dynamic_cast(para_object) != NULL); + + Inkscape::XML::Node *text = xml_doc->createTextNode(""); + para_repr->appendChild(text); + + Inkscape::GC::release(root_repr); + Inkscape::GC::release(region_repr); + Inkscape::GC::release(para_repr); + Inkscape::GC::release(rect_repr); + + + SPItem *item = dynamic_cast(desktop->currentLayer()); + g_assert(item != NULL); + ft_item->transform = item->i2doc_affine().inverse(); + + return ft_item; +} + +Geom::Affine SPFlowtext::set_transform (Geom::Affine const &xform) +{ + if ((this->_optimizeScaledText && !xform.withoutTranslation().isNonzeroUniformScale()) + || (!this->_optimizeScaledText && !xform.isNonzeroUniformScale())) { + this->_optimizeScaledText = false; + return xform; + } + this->_optimizeScaledText = false; + + SPText *text = reinterpret_cast(this); + + double const ex = xform.descrim(); + if (ex == 0) { + return xform; + } + + SPObject *region = NULL; + for (auto& o: children) { + if (dynamic_cast(&o)) { + region = &o; + break; + } + } + if (region) { + SPRect *rect = dynamic_cast(region->firstChild()); + if (rect) { + rect->set_i2d_affine(xform * rect->i2dt_affine()); + rect->doWriteTransform(rect->transform, NULL, true); + } + } + + Geom::Affine ret(xform); + ret[0] /= ex; + ret[1] /= ex; + ret[2] /= ex; + ret[3] /= ex; + + // Adjust font size + text->_adjustFontsizeRecursive (this, ex); + + // Adjust stroke width + this->adjust_stroke_width_recursive (ex); + + // Adjust pattern fill + this->adjust_pattern(xform * ret.inverse()); + + // Adjust gradient fill + this->adjust_gradient(xform * ret.inverse()); + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG); + + return Geom::Affine(); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-flowtext.h b/src/object/sp-flowtext.h new file mode 100644 index 000000000..d0b0a19a4 --- /dev/null +++ b/src/object/sp-flowtext.h @@ -0,0 +1,106 @@ +#ifndef SEEN_SP_ITEM_FLOWTEXT_H +#define SEEN_SP_ITEM_FLOWTEXT_H + +/* + */ + +#include <2geom/forward.h> + +#include "libnrtype/Layout-TNG.h" +#include "sp-item.h" +#include "desktop.h" + +#define SP_FLOWTEXT(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FLOWTEXT(obj) (dynamic_cast((SPObject*)obj) != NULL) + + +namespace Inkscape { + +class DrawingGroup; + +} // namespace Inkscape + +class SPFlowtext : public SPItem { +public: + SPFlowtext(); + virtual ~SPFlowtext(); + + /** Completely recalculates the layout. */ + void rebuildLayout(); + + /** Converts the flowroot in into a \ tree, keeping all the formatting and positioning, + but losing the automatic wrapping ability. */ + Inkscape::XML::Node *getAsText(); + + // TODO check if these should return SPRect instead of SPItem + + SPItem *get_frame(SPItem const *after); + + SPItem const *get_frame(SPItem const *after) const; + + bool has_internal_frame() const; + +//semiprivate: (need to be accessed by the C-style functions still) + Inkscape::Text::Layout layout; + + /** discards the drawing objects representing this text. */ + void _clearFlow(Inkscape::DrawingGroup* in_arena); + + double par_indent; + + bool _optimizeScaledText; + + /** Converts the text object to its component curves */ + SPCurve *getNormalizedBpath() const { + return layout.convertToCurves(); + } + + /** Optimize scaled flow text on next set_transform. */ + void optimizeScaledText() + {_optimizeScaledText = true;} + +private: + /** Recursively walks the xml tree adding tags and their contents. */ + void _buildLayoutInput(SPObject *root, Shape const *exclusion_shape, std::list *shapes, SPObject **pending_line_break_object); + + /** calculates the union of all the \ children + of this flowroot. */ + Shape* _buildExclusionShape() const; + +public: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + + virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref); + virtual void remove_child(Inkscape::XML::Node* child); + + virtual void set(unsigned int key, const char* value); + virtual Geom::Affine set_transform(Geom::Affine const& xform); + + virtual void update(SPCtx* ctx, unsigned int flags); + virtual void modified(unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); + + virtual Geom::OptRect bbox(Geom::Affine const &transform, SPItem::BBoxType type) const; + virtual void print(SPPrintContext *ctx); + virtual const char* displayName() const; + virtual char* description() const; + virtual Inkscape::DrawingItem* show(Inkscape::Drawing &drawing, unsigned int key, unsigned int flags); + virtual void hide(unsigned int key); + virtual void snappoints(std::vector &p, Inkscape::SnapPreferences const *snapprefs) const; +}; + +SPItem *create_flowtext_with_internal_frame (SPDesktop *desktop, Geom::Point p1, Geom::Point p2); + +#endif // SEEN_SP_ITEM_FLOWTEXT_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-font-face.cpp b/src/object/sp-font-face.cpp new file mode 100644 index 000000000..52fc09ddd --- /dev/null +++ b/src/object/sp-font-face.cpp @@ -0,0 +1,829 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +/* + * SVG element implementation + * + * Section 20.8.3 of the W3C SVG 1.1 spec + * available at: + * http://www.w3.org/TR/SVG/fonts.html#FontFaceElement + * + * Author: + * Felipe C. da S. Sanches + * Abhishek Sharma + * + * Copyright (C) 2008, Felipe C. da S. Sanches + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "xml/repr.h" +#include "attributes.h" +#include "sp-font-face.h" +#include "document.h" + +#include + +static std::vector sp_read_fontFaceStyleType(gchar const *value){ + std::vector v; + + if (!value){ + v.push_back(SP_FONTFACE_STYLE_ALL); + return v; + } + + if (strncmp(value, "all", 3) == 0){ + value += 3; + while(value[0]==',' || value[0]==' ') + value++; + v.push_back(SP_FONTFACE_STYLE_ALL); + return v; + } + + while(value[0]!='\0'){ + switch(value[0]){ + case 'n': + if (strncmp(value, "normal", 6) == 0){ + v.push_back(SP_FONTFACE_STYLE_NORMAL); + value += 6; + } + break; + case 'i': + if (strncmp(value, "italic", 6) == 0){ + v.push_back(SP_FONTFACE_STYLE_ITALIC); + value += 6; + } + break; + case 'o': + if (strncmp(value, "oblique", 7) == 0){ + v.push_back(SP_FONTFACE_STYLE_OBLIQUE); + value += 7; + } + break; + } + while(value[0]==',' || value[0]==' ') + value++; + } + return v; +} + +static std::vector sp_read_fontFaceVariantType(gchar const *value){ + std::vector v; + + if (!value){ + v.push_back(SP_FONTFACE_VARIANT_NORMAL); + return v; + } + + while(value[0]!='\0'){ + switch(value[0]){ + case 'n': + if (strncmp(value, "normal", 6) == 0){ + v.push_back(SP_FONTFACE_VARIANT_NORMAL); + value += 6; + } + break; + case 's': + if (strncmp(value, "small-caps", 10) == 0){ + v.push_back(SP_FONTFACE_VARIANT_SMALL_CAPS); + value += 10; + } + break; + } + while(value[0]==',' || value[0]==' ') + value++; + } + return v; +} + +static std::vector sp_read_fontFaceWeightType(gchar const *value){ + std::vector v; + + if (!value){ + v.push_back(SP_FONTFACE_WEIGHT_ALL); + return v; + } + + if (strncmp(value, "all", 3) == 0){ + value += 3; + while(value[0]==',' || value[0]==' ') + value++; + v.push_back(SP_FONTFACE_WEIGHT_ALL); + return v; + } + + while(value[0]!='\0'){ + switch(value[0]){ + case 'n': + if (strncmp(value, "normal", 6) == 0){ + v.push_back(SP_FONTFACE_WEIGHT_NORMAL); + value += 6; + } + break; + case 'b': + if (strncmp(value, "bold", 4) == 0){ + v.push_back(SP_FONTFACE_WEIGHT_BOLD); + value += 4; + } + break; + case '1': + if (strncmp(value, "100", 3) == 0){ + v.push_back(SP_FONTFACE_WEIGHT_100); + value += 3; + } + break; + case '2': + if (strncmp(value, "200", 3) == 0){ + v.push_back(SP_FONTFACE_WEIGHT_200); + value += 3; + } + break; + case '3': + if (strncmp(value, "300", 3) == 0){ + v.push_back(SP_FONTFACE_WEIGHT_300); + value += 3; + } + break; + case '4': + if (strncmp(value, "400", 3) == 0){ + v.push_back(SP_FONTFACE_WEIGHT_400); + value += 3; + } + break; + case '5': + if (strncmp(value, "500", 3) == 0){ + v.push_back(SP_FONTFACE_WEIGHT_500); + value += 3; + } + break; + case '6': + if (strncmp(value, "600", 3) == 0){ + v.push_back(SP_FONTFACE_WEIGHT_600); + value += 3; + } + break; + case '7': + if (strncmp(value, "700", 3) == 0){ + v.push_back(SP_FONTFACE_WEIGHT_700); + value += 3; + } + break; + case '8': + if (strncmp(value, "800", 3) == 0){ + v.push_back(SP_FONTFACE_WEIGHT_800); + value += 3; + } + break; + case '9': + if (strncmp(value, "900", 3) == 0){ + v.push_back(SP_FONTFACE_WEIGHT_900); + value += 3; + } + break; + } + while(value[0]==',' || value[0]==' ') + value++; + } + return v; +} + +static std::vector sp_read_fontFaceStretchType(gchar const *value){ + std::vector v; + + if (!value){ + v.push_back(SP_FONTFACE_STRETCH_NORMAL); + return v; + } + + if (strncmp(value, "all", 3) == 0){ + value += 3; + while(value[0]==',' || value[0]==' ') + value++; + v.push_back(SP_FONTFACE_STRETCH_ALL); + return v; + } + + while(value[0]!='\0'){ + switch(value[0]){ + case 'n': + if (strncmp(value, "normal", 6) == 0){ + v.push_back(SP_FONTFACE_STRETCH_NORMAL); + value += 6; + } + break; + case 'u': + if (strncmp(value, "ultra-condensed", 15) == 0){ + v.push_back(SP_FONTFACE_STRETCH_ULTRA_CONDENSED); + value += 15; + } + if (strncmp(value, "ultra-expanded", 14) == 0){ + v.push_back(SP_FONTFACE_STRETCH_ULTRA_EXPANDED); + value += 14; + } + break; + case 'e': + if (strncmp(value, "expanded", 8) == 0){ + v.push_back(SP_FONTFACE_STRETCH_EXPANDED); + value += 8; + } + if (strncmp(value, "extra-condensed", 15) == 0){ + v.push_back(SP_FONTFACE_STRETCH_EXTRA_CONDENSED); + value += 15; + } + if (strncmp(value, "extra-expanded", 14) == 0){ + v.push_back(SP_FONTFACE_STRETCH_EXTRA_EXPANDED); + value += 14; + } + break; + case 'c': + if (strncmp(value, "condensed", 9) == 0){ + v.push_back(SP_FONTFACE_STRETCH_CONDENSED); + value += 9; + } + break; + case 's': + if (strncmp(value, "semi-condensed", 14) == 0){ + v.push_back(SP_FONTFACE_STRETCH_SEMI_CONDENSED); + value += 14; + } + if (strncmp(value, "semi-expanded", 13) == 0){ + v.push_back(SP_FONTFACE_STRETCH_SEMI_EXPANDED); + value += 13; + } + break; + } + while(value[0]==',' || value[0]==' ') + value++; + } + return v; +} + +SPFontFace::SPFontFace() : SPObject() { + std::vector style; + style.push_back(SP_FONTFACE_STYLE_ALL); + this->font_style = style; + + std::vector variant; + variant.push_back(SP_FONTFACE_VARIANT_NORMAL); + this->font_variant = variant; + + std::vector weight; + weight.push_back(SP_FONTFACE_WEIGHT_ALL); + this->font_weight = weight; + + std::vector stretch; + stretch.push_back(SP_FONTFACE_STRETCH_NORMAL); + this->font_stretch = stretch; + this->font_family = NULL; + + //this->font_style = ; + //this->font_variant = ; + //this->font_weight = ; + //this->font_stretch = ; + this->font_size = NULL; + //this->unicode_range = ; + this->units_per_em = 1000; + //this->panose_1 = ; + this->stemv = 0; + this->stemh = 0; + this->slope = 0; + this->cap_height = 0; + this->x_height = 0; + this->accent_height = 0; + this->ascent = 0; + this->descent = 0; + this->widths = NULL; + this->bbox = NULL; + this->ideographic = 0; + this->alphabetic = 0; + this->mathematical = 0; + this->hanging = 0; + this->v_ideographic = 0; + this->v_alphabetic = 0; + this->v_mathematical = 0; + this->v_hanging = 0; + this->underline_position = 0; + this->underline_thickness = 0; + this->strikethrough_position = 0; + this->strikethrough_thickness = 0; + this->overline_position = 0; + this->overline_thickness = 0; +} + +SPFontFace::~SPFontFace() { +} + +void SPFontFace::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPObject::build(document, repr); + + this->readAttr( "font-family" ); + this->readAttr( "font-style" ); + this->readAttr( "font-variant" ); + this->readAttr( "font-weight" ); + this->readAttr( "font-stretch" ); + this->readAttr( "font-size" ); + this->readAttr( "unicode-range" ); + this->readAttr( "units-per-em" ); + this->readAttr( "panose-1" ); + this->readAttr( "stem-v" ); + this->readAttr( "stem-h" ); + this->readAttr( "slope" ); + this->readAttr( "cap-height" ); + this->readAttr( "x-height" ); + this->readAttr( "accent-height" ); + this->readAttr( "ascent" ); + this->readAttr( "descent" ); + this->readAttr( "widths" ); + this->readAttr( "bbox" ); + this->readAttr( "ideographic" ); + this->readAttr( "alphabetic" ); + this->readAttr( "mathematical" ); + this->readAttr( "ranging" ); + this->readAttr( "v-ideogaphic" ); + this->readAttr( "v-alphabetic" ); + this->readAttr( "v-mathematical" ); + this->readAttr( "v-hanging" ); + this->readAttr( "underline-position" ); + this->readAttr( "underline-thickness" ); + this->readAttr( "strikethrough-position" ); + this->readAttr( "strikethrough-thickness" ); + this->readAttr( "overline-position" ); + this->readAttr( "overline-thickness" ); +} + +/** + * Callback for child_added event. + */ +void SPFontFace::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) { + SPObject::child_added(child, ref); + + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + + +/** + * Callback for remove_child event. + */ +void SPFontFace::remove_child(Inkscape::XML::Node *child) { + SPObject::remove_child(child); + + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +void SPFontFace::release() { + SPObject::release(); +} + +void SPFontFace::set(unsigned int key, const gchar *value) { + std::vector style; + std::vector variant; + std::vector weight; + std::vector stretch; + + switch (key) { + case SP_PROP_FONT_FAMILY: + if (this->font_family) { + g_free(this->font_family); + } + + this->font_family = g_strdup(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_PROP_FONT_STYLE: + style = sp_read_fontFaceStyleType(value); + + if (this->font_style.size() != style.size()){ + this->font_style = style; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } else { + for (unsigned int i=0;ifont_style[i]){ + this->font_style = style; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + } + } + break; + case SP_PROP_FONT_VARIANT: + variant = sp_read_fontFaceVariantType(value); + + if (this->font_variant.size() != variant.size()){ + this->font_variant = variant; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } else { + for (unsigned int i=0;ifont_variant[i]){ + this->font_variant = variant; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + } + } + break; + case SP_PROP_FONT_WEIGHT: + weight = sp_read_fontFaceWeightType(value); + + if (this->font_weight.size() != weight.size()){ + this->font_weight = weight; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } else { + for (unsigned int i=0;ifont_weight[i]){ + this->font_weight = weight; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + } + } + break; + case SP_PROP_FONT_STRETCH: + stretch = sp_read_fontFaceStretchType(value); + + if (this->font_stretch.size() != stretch.size()){ + this->font_stretch = stretch; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } else { + for (unsigned int i=0;ifont_stretch[i]){ + this->font_stretch = stretch; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + } + } + break; + case SP_ATTR_UNITS_PER_EM: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->units_per_em){ + this->units_per_em = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_STEMV: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->stemv){ + this->stemv = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_STEMH: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->stemh){ + this->stemh = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_SLOPE: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->slope){ + this->slope = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_CAP_HEIGHT: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->cap_height){ + this->cap_height = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_X_HEIGHT: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->x_height){ + this->x_height = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_ACCENT_HEIGHT: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->accent_height){ + this->accent_height = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_ASCENT: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->ascent){ + this->ascent = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_DESCENT: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->descent){ + this->descent = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_IDEOGRAPHIC: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->ideographic){ + this->ideographic = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_ALPHABETIC: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->alphabetic){ + this->alphabetic = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_MATHEMATICAL: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->mathematical){ + this->mathematical = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_HANGING: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->hanging){ + this->hanging = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_V_IDEOGRAPHIC: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->v_ideographic){ + this->v_ideographic = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_V_ALPHABETIC: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->v_alphabetic){ + this->v_alphabetic = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_V_MATHEMATICAL: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->v_mathematical){ + this->v_mathematical = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_V_HANGING: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->v_hanging){ + this->v_hanging = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_UNDERLINE_POSITION: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->underline_position){ + this->underline_position = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_UNDERLINE_THICKNESS: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->underline_thickness){ + this->underline_thickness = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_STRIKETHROUGH_POSITION: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->strikethrough_position){ + this->strikethrough_position = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_STRIKETHROUGH_THICKNESS: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->strikethrough_thickness){ + this->strikethrough_thickness = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_OVERLINE_POSITION: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->overline_position){ + this->overline_position = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_OVERLINE_THICKNESS: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->overline_thickness){ + this->overline_thickness = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + default: + SPObject::set(key, value); + break; + } +} + +/** + * Receives update notifications. + */ +void SPFontFace::update(SPCtx *ctx, guint flags) { + if (flags & (SP_OBJECT_MODIFIED_FLAG)) { + this->readAttr( "font-family" ); + this->readAttr( "font-style" ); + this->readAttr( "font-variant" ); + this->readAttr( "font-weight" ); + this->readAttr( "font-stretch" ); + this->readAttr( "font-size" ); + this->readAttr( "unicode-range" ); + this->readAttr( "units-per-em" ); + this->readAttr( "panose-1" ); + this->readAttr( "stemv" ); + this->readAttr( "stemh" ); + this->readAttr( "slope" ); + this->readAttr( "cap-height" ); + this->readAttr( "x-height" ); + this->readAttr( "accent-height" ); + this->readAttr( "ascent" ); + this->readAttr( "descent" ); + this->readAttr( "widths" ); + this->readAttr( "bbox" ); + this->readAttr( "ideographic" ); + this->readAttr( "alphabetic" ); + this->readAttr( "mathematical" ); + this->readAttr( "hanging" ); + this->readAttr( "v-ideographic" ); + this->readAttr( "v-alphabetic" ); + this->readAttr( "v-mathematical" ); + this->readAttr( "v-hanging" ); + this->readAttr( "underline-position" ); + this->readAttr( "underline-thickness" ); + this->readAttr( "strikethrough-position" ); + this->readAttr( "strikethrough-thickness" ); + this->readAttr( "overline-position" ); + this->readAttr( "overline-thickness" ); + } + + SPObject::update(ctx, flags); +} + +#define COPY_ATTR(rd,rs,key) (rd)->setAttribute((key), rs->attribute(key)); + +Inkscape::XML::Node* SPFontFace::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:font-face"); + } + + //TODO: + //sp_repr_set_svg_double(repr, "font-family", face->font_family); + //sp_repr_set_svg_double(repr, "font-style", face->font_style); + //sp_repr_set_svg_double(repr, "font-variant", face->font_variant); + //sp_repr_set_svg_double(repr, "font-weight", face->font_weight); + //sp_repr_set_svg_double(repr, "font-stretch", face->font_stretch); + //sp_repr_set_svg_double(repr, "font-size", face->font_size); + //sp_repr_set_svg_double(repr, "unicode-range", face->unicode_range); + sp_repr_set_svg_double(repr, "units-per-em", this->units_per_em); + //sp_repr_set_svg_double(repr, "panose-1", face->panose_1); + sp_repr_set_svg_double(repr, "stemv", this->stemv); + sp_repr_set_svg_double(repr, "stemh", this->stemh); + sp_repr_set_svg_double(repr, "slope", this->slope); + sp_repr_set_svg_double(repr, "cap-height", this->cap_height); + sp_repr_set_svg_double(repr, "x-height", this->x_height); + sp_repr_set_svg_double(repr, "accent-height", this->accent_height); + sp_repr_set_svg_double(repr, "ascent", this->ascent); + sp_repr_set_svg_double(repr, "descent", this->descent); + //sp_repr_set_svg_double(repr, "widths", face->widths); + //sp_repr_set_svg_double(repr, "bbox", face->bbox); + sp_repr_set_svg_double(repr, "ideographic", this->ideographic); + sp_repr_set_svg_double(repr, "alphabetic", this->alphabetic); + sp_repr_set_svg_double(repr, "mathematical", this->mathematical); + sp_repr_set_svg_double(repr, "hanging", this->hanging); + sp_repr_set_svg_double(repr, "v-ideographic", this->v_ideographic); + sp_repr_set_svg_double(repr, "v-alphabetic", this->v_alphabetic); + sp_repr_set_svg_double(repr, "v-mathematical", this->v_mathematical); + sp_repr_set_svg_double(repr, "v-hanging", this->v_hanging); + sp_repr_set_svg_double(repr, "underline-position", this->underline_position); + sp_repr_set_svg_double(repr, "underline-thickness", this->underline_thickness); + sp_repr_set_svg_double(repr, "strikethrough-position", this->strikethrough_position); + sp_repr_set_svg_double(repr, "strikethrough-thickness", this->strikethrough_thickness); + sp_repr_set_svg_double(repr, "overline-position", this->overline_position); + sp_repr_set_svg_double(repr, "overline-thickness", this->overline_thickness); + + if (repr != this->getRepr()) { + // In all COPY_ATTR given below the XML tree is + // being used directly while it shouldn't be. + COPY_ATTR(repr, this->getRepr(), "font-family"); + COPY_ATTR(repr, this->getRepr(), "font-style"); + COPY_ATTR(repr, this->getRepr(), "font-variant"); + COPY_ATTR(repr, this->getRepr(), "font-weight"); + COPY_ATTR(repr, this->getRepr(), "font-stretch"); + COPY_ATTR(repr, this->getRepr(), "font-size"); + COPY_ATTR(repr, this->getRepr(), "unicode-range"); + COPY_ATTR(repr, this->getRepr(), "units-per-em"); + COPY_ATTR(repr, this->getRepr(), "panose-1"); + COPY_ATTR(repr, this->getRepr(), "stemv"); + COPY_ATTR(repr, this->getRepr(), "stemh"); + COPY_ATTR(repr, this->getRepr(), "slope"); + COPY_ATTR(repr, this->getRepr(), "cap-height"); + COPY_ATTR(repr, this->getRepr(), "x-height"); + COPY_ATTR(repr, this->getRepr(), "accent-height"); + COPY_ATTR(repr, this->getRepr(), "ascent"); + COPY_ATTR(repr, this->getRepr(), "descent"); + COPY_ATTR(repr, this->getRepr(), "widths"); + COPY_ATTR(repr, this->getRepr(), "bbox"); + COPY_ATTR(repr, this->getRepr(), "ideographic"); + COPY_ATTR(repr, this->getRepr(), "alphabetic"); + COPY_ATTR(repr, this->getRepr(), "mathematical"); + COPY_ATTR(repr, this->getRepr(), "hanging"); + COPY_ATTR(repr, this->getRepr(), "v-ideographic"); + COPY_ATTR(repr, this->getRepr(), "v-alphabetic"); + COPY_ATTR(repr, this->getRepr(), "v-mathematical"); + COPY_ATTR(repr, this->getRepr(), "v-hanging"); + COPY_ATTR(repr, this->getRepr(), "underline-position"); + COPY_ATTR(repr, this->getRepr(), "underline-thickness"); + COPY_ATTR(repr, this->getRepr(), "strikethrough-position"); + COPY_ATTR(repr, this->getRepr(), "strikethrough-thickness"); + COPY_ATTR(repr, this->getRepr(), "overline-position"); + COPY_ATTR(repr, this->getRepr(), "overline-thickness"); + } + + SPObject::write(xml_doc, repr, flags); + + return repr; +} +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-font-face.h b/src/object/sp-font-face.h new file mode 100644 index 000000000..669b93197 --- /dev/null +++ b/src/object/sp-font-face.h @@ -0,0 +1,123 @@ +#ifndef SEEN_SP_FONTFACE_H +#define SEEN_SP_FONTFACE_H + +#include + +/* + * SVG element implementation + * + * Section 20.8.3 of the W3C SVG 1.1 spec + * available at: + * http://www.w3.org/TR/SVG/fonts.html#FontFaceElement + * + * Authors: + * Felipe C. da S. Sanches + * + * Copyright (C) 2008 Felipe C. da S. Sanches + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-object.h" + +#define SP_FONTFACE(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FONTFACE(obj) (dynamic_cast((SPObject*)obj) != NULL) + +enum FontFaceStyleType{ + SP_FONTFACE_STYLE_ALL, + SP_FONTFACE_STYLE_NORMAL, + SP_FONTFACE_STYLE_ITALIC, + SP_FONTFACE_STYLE_OBLIQUE +}; + +enum FontFaceVariantType{ + SP_FONTFACE_VARIANT_NORMAL, + SP_FONTFACE_VARIANT_SMALL_CAPS +}; + +enum FontFaceWeightType{ + SP_FONTFACE_WEIGHT_ALL, + SP_FONTFACE_WEIGHT_NORMAL, + SP_FONTFACE_WEIGHT_BOLD, + SP_FONTFACE_WEIGHT_100, + SP_FONTFACE_WEIGHT_200, + SP_FONTFACE_WEIGHT_300, + SP_FONTFACE_WEIGHT_400, + SP_FONTFACE_WEIGHT_500, + SP_FONTFACE_WEIGHT_600, + SP_FONTFACE_WEIGHT_700, + SP_FONTFACE_WEIGHT_800, + SP_FONTFACE_WEIGHT_900 +}; + +enum FontFaceStretchType{ + SP_FONTFACE_STRETCH_ALL, + SP_FONTFACE_STRETCH_NORMAL, + SP_FONTFACE_STRETCH_ULTRA_CONDENSED, + SP_FONTFACE_STRETCH_EXTRA_CONDENSED, + SP_FONTFACE_STRETCH_CONDENSED, + SP_FONTFACE_STRETCH_SEMI_CONDENSED, + SP_FONTFACE_STRETCH_SEMI_EXPANDED, + SP_FONTFACE_STRETCH_EXPANDED, + SP_FONTFACE_STRETCH_EXTRA_EXPANDED, + SP_FONTFACE_STRETCH_ULTRA_EXPANDED +}; + +enum FontFaceUnicodeRangeType{ + FONTFACE_UNICODERANGE_FIXME_HERE, +}; + +class SPFontFace : public SPObject { +public: + SPFontFace(); + virtual ~SPFontFace(); + + char* font_family; + std::vector font_style; + std::vector font_variant; + std::vector font_weight; + std::vector font_stretch; + char* font_size; + std::vector unicode_range; + double units_per_em; + std::vector panose_1; + double stemv; + double stemh; + double slope; + double cap_height; + double x_height; + double accent_height; + double ascent; + double descent; + char* widths; + char* bbox; + double ideographic; + double alphabetic; + double mathematical; + double hanging; + double v_ideographic; + double v_alphabetic; + double v_mathematical; + double v_hanging; + double underline_position; + double underline_thickness; + double strikethrough_position; + double strikethrough_thickness; + double overline_position; + double overline_thickness; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref); + virtual void remove_child(Inkscape::XML::Node* child); + + virtual void set(unsigned int key, const char* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); +}; + +#endif //#ifndef __SP_FONTFACE_H__ diff --git a/src/object/sp-font.cpp b/src/object/sp-font.cpp new file mode 100644 index 000000000..a0193224c --- /dev/null +++ b/src/object/sp-font.cpp @@ -0,0 +1,205 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +/* + * SVG element implementation + * + * Author: + * Felipe C. da S. Sanches + * Abhishek Sharma + * + * Copyright (C) 2008, Felipe C. da S. Sanches + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "xml/repr.h" +#include "attributes.h" +#include "sp-font.h" +#include "document.h" + +#include "display/nr-svgfonts.h" + + +//I think we should have extra stuff here and in the set method in order to set default value as specified at http://www.w3.org/TR/SVG/fonts.html + +// TODO determine better values and/or make these dynamic: +double FNT_DEFAULT_ADV = 1024; // TODO determine proper default +double FNT_DEFAULT_ASCENT = 768; // TODO determine proper default +double FNT_UNITS_PER_EM = 1024; // TODO determine proper default + +SPFont::SPFont() : SPObject() { + this->horiz_origin_x = 0; + this->horiz_origin_y = 0; + this->horiz_adv_x = FNT_DEFAULT_ADV; + this->vert_origin_x = FNT_DEFAULT_ADV / 2.0; + this->vert_origin_y = FNT_DEFAULT_ASCENT; + this->vert_adv_y = FNT_UNITS_PER_EM; +} + +SPFont::~SPFont() { +} + +void SPFont::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPObject::build(document, repr); + + this->readAttr( "horiz-origin-x" ); + this->readAttr( "horiz-origin-y" ); + this->readAttr( "horiz-adv-x" ); + this->readAttr( "vert-origin-x" ); + this->readAttr( "vert-origin-y" ); + this->readAttr( "vert-adv-y" ); + + document->addResource("font", this); +} + +/** + * Callback for child_added event. + */ +void SPFont::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) { + SPObject::child_added(child, ref); + + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + + +/** + * Callback for remove_child event. + */ +void SPFont::remove_child(Inkscape::XML::Node* child) { + SPObject::remove_child(child); + + this->parent->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +void SPFont::release() { + this->document->removeResource("font", this); + + SPObject::release(); +} + +void SPFont::set(unsigned int key, const gchar *value) { + // TODO these are floating point, so some epsilon comparison would be good + switch (key) { + case SP_ATTR_HORIZ_ORIGIN_X: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->horiz_origin_x){ + this->horiz_origin_x = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_HORIZ_ORIGIN_Y: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->horiz_origin_y){ + this->horiz_origin_y = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_HORIZ_ADV_X: + { + double number = value ? g_ascii_strtod(value, 0) : FNT_DEFAULT_ADV; + + if (number != this->horiz_adv_x){ + this->horiz_adv_x = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_VERT_ORIGIN_X: + { + double number = value ? g_ascii_strtod(value, 0) : FNT_DEFAULT_ADV / 2.0; + + if (number != this->vert_origin_x){ + this->vert_origin_x = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_VERT_ORIGIN_Y: + { + double number = value ? g_ascii_strtod(value, 0) : FNT_DEFAULT_ASCENT; + + if (number != this->vert_origin_y){ + this->vert_origin_y = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_VERT_ADV_Y: + { + double number = value ? g_ascii_strtod(value, 0) : FNT_UNITS_PER_EM; + + if (number != this->vert_adv_y){ + this->vert_adv_y = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + default: + SPObject::set(key, value); + break; + } +} + +/** + * Receives update notifications. + */ +void SPFont::update(SPCtx *ctx, guint flags) { + if (flags & (SP_OBJECT_MODIFIED_FLAG)) { + this->readAttr( "horiz-origin-x" ); + this->readAttr( "horiz-origin-y" ); + this->readAttr( "horiz-adv-x" ); + this->readAttr( "vert-origin-x" ); + this->readAttr( "vert-origin-y" ); + this->readAttr( "vert-adv-y" ); + } + + SPObject::update(ctx, flags); +} + +#define COPY_ATTR(rd,rs,key) (rd)->setAttribute((key), rs->attribute(key)); + +Inkscape::XML::Node* SPFont::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:font"); + } + + sp_repr_set_svg_double(repr, "horiz-origin-x", this->horiz_origin_x); + sp_repr_set_svg_double(repr, "horiz-origin-y", this->horiz_origin_y); + sp_repr_set_svg_double(repr, "horiz-adv-x", this->horiz_adv_x); + sp_repr_set_svg_double(repr, "vert-origin-x", this->vert_origin_x); + sp_repr_set_svg_double(repr, "vert-origin-y", this->vert_origin_y); + sp_repr_set_svg_double(repr, "vert-adv-y", this->vert_adv_y); + + if (repr != this->getRepr()) { + // All the below COPY_ATTR functions are directly using + // the XML Tree while they shouldn't + COPY_ATTR(repr, this->getRepr(), "horiz-origin-x"); + COPY_ATTR(repr, this->getRepr(), "horiz-origin-y"); + COPY_ATTR(repr, this->getRepr(), "horiz-adv-x"); + COPY_ATTR(repr, this->getRepr(), "vert-origin-x"); + COPY_ATTR(repr, this->getRepr(), "vert-origin-y"); + COPY_ATTR(repr, this->getRepr(), "vert-adv-y"); + } + + SPObject::write(xml_doc, repr, flags); + + return repr; +} +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-font.h b/src/object/sp-font.h new file mode 100644 index 000000000..6e26a02b2 --- /dev/null +++ b/src/object/sp-font.h @@ -0,0 +1,46 @@ +#ifndef SP_FONT_H_SEEN +#define SP_FONT_H_SEEN + +/* + * SVG element implementation + * + * Authors: + * Felipe C. da S. Sanches + * + * Copyright (C) 2008 Felipe C. da S. Sanches + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-object.h" + +#define SP_FONT(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_FONT(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPFont : public SPObject { +public: + SPFont(); + virtual ~SPFont(); + + double horiz_origin_x; + double horiz_origin_y; + double horiz_adv_x; + double vert_origin_x; + double vert_origin_y; + double vert_adv_y; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref); + virtual void remove_child(Inkscape::XML::Node* child); + + virtual void set(unsigned int key, char const* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); +}; + +#endif //#ifndef SP_FONT_H_SEEN diff --git a/src/object/sp-glyph-kerning.cpp b/src/object/sp-glyph-kerning.cpp new file mode 100644 index 000000000..66de5aed9 --- /dev/null +++ b/src/object/sp-glyph-kerning.cpp @@ -0,0 +1,190 @@ +/** + * SVG and elements implementation + * W3C SVG 1.1 spec, page 476, section 20.7 + * + * Authors: + * Felipe C. da S. Sanches + * Abhishek Sharma + * + * Copyright (C) 2008 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "xml/repr.h" +#include "attributes.h" +#include "sp-glyph-kerning.h" + +#include "document.h" +#include + + +SPGlyphKerning::SPGlyphKerning() + : SPObject() +//TODO: correct these values: + , u1(NULL) + , g1(NULL) + , u2(NULL) + , g2(NULL) + , k(0) +{ +} + +void SPGlyphKerning::build(SPDocument *document, Inkscape::XML::Node *repr) +{ + SPObject::build(document, repr); + + this->readAttr( "u1" ); + this->readAttr( "g1" ); + this->readAttr( "u2" ); + this->readAttr( "g2" ); + this->readAttr( "k" ); +} + +void SPGlyphKerning::release() +{ + SPObject::release(); +} + +GlyphNames::GlyphNames(const gchar* value) +{ + if (value) { + names = g_strdup(value); + } +} + +GlyphNames::~GlyphNames() +{ + if (names) { + g_free(names); + } +} + +bool GlyphNames::contains(const char* name) +{ + if (!(this->names) || !name) { + return false; + } + + std::istringstream is(this->names); + std::string str; + std::string s(name); + + while (is >> str) { + if (str == s) { + return true; + } + } + + return false; +} + +void SPGlyphKerning::set(unsigned int key, const gchar *value) +{ + switch (key) { + case SP_ATTR_U1: + { + if (this->u1) { + delete this->u1; + } + + this->u1 = new UnicodeRange(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + case SP_ATTR_U2: + { + if (this->u2) { + delete this->u2; + } + + this->u2 = new UnicodeRange(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + case SP_ATTR_G1: + { + if (this->g1) { + delete this->g1; + } + + this->g1 = new GlyphNames(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + case SP_ATTR_G2: + { + if (this->g2) { + delete this->g2; + } + + this->g2 = new GlyphNames(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + case SP_ATTR_K: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->k){ + this->k = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + default: + { + SPObject::set(key, value); + break; + } + } +} + +/** + * Receives update notifications. + */ +void SPGlyphKerning::update(SPCtx *ctx, guint flags) +{ + if (flags & SP_OBJECT_MODIFIED_FLAG) { + /* do something to trigger redisplay, updates? */ + this->readAttr( "u1" ); + this->readAttr( "u2" ); + this->readAttr( "g2" ); + this->readAttr( "k" ); + } + + SPObject::update(ctx, flags); +} + +#define COPY_ATTR(rd,rs,key) (rd)->setAttribute((key), rs->attribute(key)); + +Inkscape::XML::Node* SPGlyphKerning::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) +{ + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:glyphkerning"); // fix this! + } + + if (repr != this->getRepr()) { + // All the COPY_ATTR functions below use + // XML Tree directly, while they shouldn't. + COPY_ATTR(repr, this->getRepr(), "u1"); + COPY_ATTR(repr, this->getRepr(), "g1"); + COPY_ATTR(repr, this->getRepr(), "u2"); + COPY_ATTR(repr, this->getRepr(), "g2"); + COPY_ATTR(repr, this->getRepr(), "k"); + } + SPObject::write(xml_doc, repr, flags); + + return repr; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-glyph-kerning.h b/src/object/sp-glyph-kerning.h new file mode 100644 index 000000000..c96c0b6e4 --- /dev/null +++ b/src/object/sp-glyph-kerning.h @@ -0,0 +1,74 @@ +/* + * SVG and elements implementation + * + * Authors: + * Felipe C. da S. Sanches + * + * Copyright (C) 2008 Felipe C. da S. Sanches + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_SP_GLYPH_KERNING_H +#define SEEN_SP_GLYPH_KERNING_H + +#include "sp-object.h" +#include "unicoderange.h" + +#define SP_HKERN(obj) (dynamic_cast(obj)) +#define SP_IS_HKERN(obj) (dynamic_cast(obj) != NULL) + +#define SP_VKERN(obj) (dynamic_cast(obj)) +#define SP_IS_VKERN(obj) (dynamic_cast(obj) != NULL) + +// CPPIFY: These casting macros are buggy, as Vkern and Hkern aren't "real" classes. + +class GlyphNames { +public: + GlyphNames(char const* value); + ~GlyphNames(); + bool contains(char const* name); +private: + char* names; +}; + +class SPGlyphKerning : public SPObject { +public: + SPGlyphKerning(); + virtual ~SPGlyphKerning() {} + + // FIXME encapsulation + UnicodeRange* u1; + GlyphNames* g1; + UnicodeRange* u2; + GlyphNames* g2; + double k; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + virtual void set(unsigned int key, char const* value); + virtual void update(SPCtx* ctx, unsigned int flags); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); +}; + +class SPHkern : public SPGlyphKerning { + virtual ~SPHkern() {} +}; + +class SPVkern : public SPGlyphKerning { + virtual ~SPVkern() {} +}; + +#endif // !SEEN_SP_GLYPH_KERNING_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 : diff --git a/src/object/sp-glyph.cpp b/src/object/sp-glyph.cpp new file mode 100644 index 000000000..6284cbfa1 --- /dev/null +++ b/src/object/sp-glyph.cpp @@ -0,0 +1,289 @@ +#ifdef HAVE_CONFIG_H +#endif + +/* + * SVG element implementation + * + * Author: + * Felipe C. da S. Sanches + * Abhishek Sharma + * + * Copyright (C) 2008, Felipe C. da S. Sanches + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "xml/repr.h" +#include "attributes.h" +#include "sp-glyph.h" +#include "document.h" + +SPGlyph::SPGlyph() + : SPObject() +//TODO: correct these values: + , d(NULL) + , orientation(GLYPH_ORIENTATION_BOTH) + , arabic_form(GLYPH_ARABIC_FORM_INITIAL) + , lang(NULL) + , horiz_adv_x(0) + , vert_origin_x(0) + , vert_origin_y(0) + , vert_adv_y(0) +{ +} + +void SPGlyph::build(SPDocument *document, Inkscape::XML::Node *repr) +{ + SPObject::build(document, repr); + + this->readAttr( "unicode" ); + this->readAttr( "glyph-name" ); + this->readAttr( "d" ); + this->readAttr( "orientation" ); + this->readAttr( "arabic-form" ); + this->readAttr( "lang" ); + this->readAttr( "horiz-adv-x" ); + this->readAttr( "vert-origin-x" ); + this->readAttr( "vert-origin-y" ); + this->readAttr( "vert-adv-y" ); +} + +void SPGlyph::release() { + SPObject::release(); +} + +static glyphArabicForm sp_glyph_read_arabic_form(gchar const *value){ + if (!value) { + return GLYPH_ARABIC_FORM_INITIAL; //TODO: verify which is the default default (for me, the spec is not clear) + } + + switch(value[0]){ + case 'i': + if (strncmp(value, "initial", 7) == 0) { + return GLYPH_ARABIC_FORM_INITIAL; + } + + if (strncmp(value, "isolated", 8) == 0) { + return GLYPH_ARABIC_FORM_ISOLATED; + } + break; + case 'm': + if (strncmp(value, "medial", 6) == 0) { + return GLYPH_ARABIC_FORM_MEDIAL; + } + break; + case 't': + if (strncmp(value, "terminal", 8) == 0) { + return GLYPH_ARABIC_FORM_TERMINAL; + } + break; + } + + return GLYPH_ARABIC_FORM_INITIAL; //TODO: VERIFY DEFAULT! +} + +static glyphOrientation sp_glyph_read_orientation(gchar const *value) +{ + if (!value) { + return GLYPH_ORIENTATION_BOTH; + } + + switch(value[0]){ + case 'h': + return GLYPH_ORIENTATION_HORIZONTAL; + break; + case 'v': + return GLYPH_ORIENTATION_VERTICAL; + break; + } + +//ERROR? TODO: VERIFY PROPER ERROR HANDLING + return GLYPH_ORIENTATION_BOTH; +} + +void SPGlyph::set(unsigned int key, const gchar *value) +{ + switch (key) { + case SP_ATTR_UNICODE: + { + this->unicode.clear(); + + if (value) { + this->unicode.append(value); + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + case SP_ATTR_GLYPH_NAME: + { + this->glyph_name.clear(); + + if (value) { + this->glyph_name.append(value); + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + case SP_ATTR_D: + { + if (this->d) { + g_free(this->d); + } + + this->d = g_strdup(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + case SP_ATTR_ORIENTATION: + { + glyphOrientation orient = sp_glyph_read_orientation(value); + + if (this->orientation != orient){ + this->orientation = orient; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_ARABIC_FORM: + { + glyphArabicForm form = sp_glyph_read_arabic_form(value); + + if (this->arabic_form != form){ + this->arabic_form = form; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_LANG: + { + if (this->lang) { + g_free(this->lang); + } + + this->lang = g_strdup(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + case SP_ATTR_HORIZ_ADV_X: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->horiz_adv_x){ + this->horiz_adv_x = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_VERT_ORIGIN_X: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->vert_origin_x){ + this->vert_origin_x = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_VERT_ORIGIN_Y: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->vert_origin_y){ + this->vert_origin_y = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_VERT_ADV_Y: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + + if (number != this->vert_adv_y){ + this->vert_adv_y = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + default: + { + SPObject::set(key, value); + break; + } + } +} + +/** + * Receives update notifications. + */ +void SPGlyph::update(SPCtx *ctx, guint flags) +{ + if (flags & SP_OBJECT_MODIFIED_FLAG) { + /* do something to trigger redisplay, updates? */ + this->readAttr( "unicode" ); + this->readAttr( "glyph-name" ); + this->readAttr( "d" ); + this->readAttr( "orientation" ); + this->readAttr( "arabic-form" ); + this->readAttr( "lang" ); + this->readAttr( "horiz-adv-x" ); + this->readAttr( "vert-origin-x" ); + this->readAttr( "vert-origin-y" ); + this->readAttr( "vert-adv-y" ); + } + + SPObject::update(ctx, flags); +} + +#define COPY_ATTR(rd,rs,key) (rd)->setAttribute((key), rs->attribute(key)); + +Inkscape::XML::Node* SPGlyph::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) +{ + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:glyph"); + } + + /* I am commenting out this part because I am not certain how does it work. I will have to study it later. Juca + repr->setAttribute("unicode", glyph->unicode); + repr->setAttribute("glyph-name", glyph->glyph_name); + repr->setAttribute("d", glyph->d); + sp_repr_set_svg_double(repr, "orientation", (double) glyph->orientation); + sp_repr_set_svg_double(repr, "arabic-form", (double) glyph->arabic_form); + repr->setAttribute("lang", glyph->lang); + sp_repr_set_svg_double(repr, "horiz-adv-x", glyph->horiz_adv_x); + sp_repr_set_svg_double(repr, "vert-origin-x", glyph->vert_origin_x); + sp_repr_set_svg_double(repr, "vert-origin-y", glyph->vert_origin_y); + sp_repr_set_svg_double(repr, "vert-adv-y", glyph->vert_adv_y); + */ + + if (repr != this->getRepr()) { + // All the COPY_ATTR functions below use + // XML Tree directly while they shouldn't. + COPY_ATTR(repr, this->getRepr(), "unicode"); + COPY_ATTR(repr, this->getRepr(), "glyph-name"); + COPY_ATTR(repr, this->getRepr(), "d"); + COPY_ATTR(repr, this->getRepr(), "orientation"); + COPY_ATTR(repr, this->getRepr(), "arabic-form"); + COPY_ATTR(repr, this->getRepr(), "lang"); + COPY_ATTR(repr, this->getRepr(), "horiz-adv-x"); + COPY_ATTR(repr, this->getRepr(), "vert-origin-x"); + COPY_ATTR(repr, this->getRepr(), "vert-origin-y"); + COPY_ATTR(repr, this->getRepr(), "vert-adv-y"); + } + + SPObject::write(xml_doc, repr, flags); + + return repr; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-glyph.h b/src/object/sp-glyph.h new file mode 100644 index 000000000..297ac930e --- /dev/null +++ b/src/object/sp-glyph.h @@ -0,0 +1,72 @@ +/** + * Authors: + * Felipe C. da S. Sanches + * + * Copyright (C) 2008 Felipe C. da S. Sanches + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_SP_GLYPH_H +#define SEEN_SP_GLYPH_H + +#include "sp-object.h" + +#define SP_GLYPH(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_GLYPH(obj) (dynamic_cast((SPObject*)obj) != NULL) + +enum glyphArabicForm { + GLYPH_ARABIC_FORM_INITIAL, + GLYPH_ARABIC_FORM_MEDIAL, + GLYPH_ARABIC_FORM_TERMINAL, + GLYPH_ARABIC_FORM_ISOLATED, +}; + +enum glyphOrientation { + GLYPH_ORIENTATION_HORIZONTAL, + GLYPH_ORIENTATION_VERTICAL, + GLYPH_ORIENTATION_BOTH +}; + +/* + * SVG element + */ + +class SPGlyph : public SPObject { +public: + SPGlyph(); + virtual ~SPGlyph() {} + + // FIXME encapsulation + Glib::ustring unicode; + Glib::ustring glyph_name; + char* d; + glyphOrientation orientation; + glyphArabicForm arabic_form; + char* lang; + double horiz_adv_x; + double vert_origin_x; + double vert_origin_y; + double vert_adv_y; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + virtual void set(unsigned int key, const char* value); + virtual void update(SPCtx* ctx, unsigned int flags); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); + +}; + +#endif // !SEEN_SP_GLYPH_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 : diff --git a/src/object/sp-gradient-reference.cpp b/src/object/sp-gradient-reference.cpp new file mode 100644 index 000000000..216ac73de --- /dev/null +++ b/src/object/sp-gradient-reference.cpp @@ -0,0 +1,22 @@ +#include "sp-gradient-reference.h" +#include "sp-gradient.h" + +bool +SPGradientReference::_acceptObject(SPObject *obj) const +{ + return SP_IS_GRADIENT(obj) && URIReference::_acceptObject(obj); + /* effic: Don't bother making this an inline function: _acceptObject is a virtual function, + typically called from a context where the runtime type is not known at compile time. */ +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-gradient-reference.h b/src/object/sp-gradient-reference.h new file mode 100644 index 000000000..96980fd6f --- /dev/null +++ b/src/object/sp-gradient-reference.h @@ -0,0 +1,33 @@ +#ifndef SEEN_SP_GRADIENT_REFERENCE_H +#define SEEN_SP_GRADIENT_REFERENCE_H + +#include "uri-references.h" + +class SPGradient; +class SPObject; + +class SPGradientReference : public Inkscape::URIReference { +public: + SPGradientReference(SPObject *obj) : URIReference(obj) {} + + SPGradient *getObject() const { + return reinterpret_cast(URIReference::getObject()); + } + +protected: + virtual bool _acceptObject(SPObject *obj) const; +}; + + +#endif /* !SEEN_SP_GRADIENT_REFERENCE_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-gradient-spread.h b/src/object/sp-gradient-spread.h new file mode 100644 index 000000000..60e33b7c0 --- /dev/null +++ b/src/object/sp-gradient-spread.h @@ -0,0 +1,23 @@ +#ifndef SEEN_SP_GRADIENT_SPREAD_H +#define SEEN_SP_GRADIENT_SPREAD_H + +enum SPGradientSpread { + SP_GRADIENT_SPREAD_PAD, + SP_GRADIENT_SPREAD_REFLECT, + SP_GRADIENT_SPREAD_REPEAT, + SP_GRADIENT_SPREAD_UNDEFINED = INT_MAX +}; + + +#endif /* !SEEN_SP_GRADIENT_SPREAD_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-gradient-units.h b/src/object/sp-gradient-units.h new file mode 100644 index 000000000..2f58897b0 --- /dev/null +++ b/src/object/sp-gradient-units.h @@ -0,0 +1,21 @@ +#ifndef SEEN_SP_GRADIENT_UNITS_H +#define SEEN_SP_GRADIENT_UNITS_H + +enum SPGradientUnits { + SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX, + SP_GRADIENT_UNITS_USERSPACEONUSE +}; + + +#endif /* !SEEN_SP_GRADIENT_UNITS_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-gradient-vector.h b/src/object/sp-gradient-vector.h new file mode 100644 index 000000000..e57820b56 --- /dev/null +++ b/src/object/sp-gradient-vector.h @@ -0,0 +1,41 @@ +#ifndef SEEN_SP_GRADIENT_VECTOR_H +#define SEEN_SP_GRADIENT_VECTOR_H + +#include +#include "color.h" + +/** + * Differs from SPStop in that SPStop mirrors the \ element in the document, whereas + * SPGradientStop shows more the effective stop color. + * + * For example, SPGradientStop has no currentColor option: currentColor refers to the color + * property value of the gradient where currentColor appears, so we interpret currentColor before + * copying from SPStop to SPGradientStop. + */ +struct SPGradientStop { + double offset; + SPColor color; + float opacity; +}; + +/** + * The effective gradient vector, after copying stops from the referenced gradient if necessary. + */ +struct SPGradientVector { + bool built; + std::vector stops; +}; + + +#endif /* !SEEN_SP_GRADIENT_VECTOR_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-gradient.cpp b/src/object/sp-gradient.cpp new file mode 100644 index 000000000..feaa04e0f --- /dev/null +++ b/src/object/sp-gradient.cpp @@ -0,0 +1,1204 @@ +/** \file + * SPGradient, SPStop, SPLinearGradient, SPRadialGradient, + * SPMeshGradient, SPMeshRow, SPMeshPatch + */ +/* + * Authors: + * Lauris Kaplinski + * bulia byak + * Jasper van de Gronde + * Jon A. Cruz + * Abhishek Sharma + * Tavmjong Bah + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2004 David Turner + * Copyright (C) 2009 Jasper van de Gronde + * Copyright (C) 2011 Tavmjong Bah + * + * Released under GNU GPL, read the file 'COPYING' for more information + * + */ + +#define noSP_GRADIENT_VERBOSE +//#define OBJECT_TRACE + +#include "sp-gradient.h" + +#include +#include + +#include <2geom/transforms.h> + +#include + +#include +#include + +#include "bad-uri-exception.h" +#include "display/cairo-utils.h" +#include "svg/svg.h" +#include "svg/css-ostringstream.h" +#include "attributes.h" +#include "document-private.h" +#include "gradient-chemistry.h" +#include "sp-gradient-reference.h" +#include "sp-linear-gradient.h" +#include "sp-radial-gradient.h" +#include "sp-mesh-gradient.h" +#include "sp-mesh-row.h" +#include "sp-mesh-patch.h" +#include "sp-stop.h" + +/// Has to be power of 2 Seems to be unused. +//#define NCOLORS NR_GRADIENT_VECTOR_LENGTH + +bool SPGradient::hasStops() const +{ + return has_stops; +} + +bool SPGradient::hasPatches() const +{ + return has_patches; +} + +bool SPGradient::isUnitsSet() const +{ + return units_set; +} + +SPGradientUnits SPGradient::getUnits() const +{ + return units; +} + +bool SPGradient::isSpreadSet() const +{ + return spread_set; +} + +SPGradientSpread SPGradient::getSpread() const +{ + return spread; +} + +void SPGradient::setSwatch( bool swatch ) +{ + if ( swatch != isSwatch() ) { + this->swatch = swatch; // to make isSolid() work, this happens first + gchar const* paintVal = swatch ? (isSolid() ? "solid" : "gradient") : 0; + setAttribute( "osb:paint", paintVal, 0 ); + + requestModified( SP_OBJECT_MODIFIED_FLAG ); + } +} + + +/** + * return true if this gradient is "equivalent" to that gradient. + * Equivalent meaning they have the same stop count, same stop colors and same stop opacity + * @param that - A gradient to compare this to + */ +bool SPGradient::isEquivalent(SPGradient *that) +{ + //TODO Make this work for mesh gradients + + bool status = false; + + while(1){ // not really a loop, used to avoid deep nesting or multiple exit points from function + if (this->getStopCount() != that->getStopCount()) { break; } + if (this->hasStops() != that->hasStops()) { break; } + if (!this->getVector() || !that->getVector()) { break; } + if (this->isSwatch() != that->isSwatch()) { break; } + if ( this->isSwatch() ){ + // drop down to check stops. + } + else if ( + (SP_IS_LINEARGRADIENT(this) && SP_IS_LINEARGRADIENT(that)) || + (SP_IS_RADIALGRADIENT(this) && SP_IS_RADIALGRADIENT(that)) || + (SP_IS_MESHGRADIENT(this) && SP_IS_MESHGRADIENT(that))) { + if(!this->isAligned(that))break; + } + else { break; } // this should never happen, some unhandled type of gradient + + SPStop *as = this->getVector()->getFirstStop(); + SPStop *bs = that->getVector()->getFirstStop(); + + bool effective = true; + while (effective && (as && bs)) { + if (!as->getEffectiveColor().isClose(bs->getEffectiveColor(), 0.001) || + as->offset != bs->offset || as->opacity != bs->opacity ) { + effective = false; + break; + } + else { + as = as->getNextStop(); + bs = bs->getNextStop(); + } + } + if (!effective) break; + + status = true; + break; + } + return status; +} + +/** + * return true if this gradient is "aligned" to that gradient. + * Aligned means that they have exactly the same coordinates and transform. + * @param that - A gradient to compare this to + */ +bool SPGradient::isAligned(SPGradient *that) +{ + bool status = false; + + /* Some gradients have coordinates/other values specified, some don't. + yes/yes check the coordinates/other values + no/no aligned (because both have all default values) + yes/no not aligned + no/yes not aligned + It is NOT safe to just compare the computed values because if that field has + not been set the computed value could be full of garbage. + + In theory the yes/no and no/yes cases could be aligned if the specified value + matches the default value. + */ + + while(1){ // not really a loop, used to avoid deep nesting or multiple exit points from function + if(this->gradientTransform_set != that->gradientTransform_set) { break; } + if(this->gradientTransform_set && + (this->gradientTransform != that->gradientTransform)) { break; } + if (SP_IS_LINEARGRADIENT(this) && SP_IS_LINEARGRADIENT(that)) { + SPLinearGradient *sg=SP_LINEARGRADIENT(this); + SPLinearGradient *tg=SP_LINEARGRADIENT(that); + + if( sg->x1._set != tg->x1._set) { break; } + if( sg->y1._set != tg->y1._set) { break; } + if( sg->x2._set != tg->x2._set) { break; } + if( sg->y2._set != tg->y2._set) { break; } + if( sg->x1._set && sg->y1._set && sg->x2._set && sg->y2._set) { + if( (sg->x1.computed != tg->x1.computed) || + (sg->y1.computed != tg->y1.computed) || + (sg->x2.computed != tg->x2.computed) || + (sg->y2.computed != tg->y2.computed) ) { break; } + } else if( sg->x1._set || sg->y1._set || sg->x2._set || sg->y2._set) { break; } // some mix of set and not set + // none set? assume aligned and fall through + } else if (SP_IS_RADIALGRADIENT(this) && SP_IS_LINEARGRADIENT(that)) { + SPRadialGradient *sg=SP_RADIALGRADIENT(this); + SPRadialGradient *tg=SP_RADIALGRADIENT(that); + + if( sg->cx._set != tg->cx._set) { break; } + if( sg->cy._set != tg->cy._set) { break; } + if( sg->r._set != tg->r._set) { break; } + if( sg->fx._set != tg->fx._set) { break; } + if( sg->fy._set != tg->fy._set) { break; } + if( sg->cx._set && sg->cy._set && sg->fx._set && sg->fy._set && sg->r._set) { + if( (sg->cx.computed != tg->cx.computed) || + (sg->cy.computed != tg->cy.computed) || + (sg->r.computed != tg->r.computed ) || + (sg->fx.computed != tg->fx.computed) || + (sg->fy.computed != tg->fy.computed) ) { break; } + } else if( sg->cx._set || sg->cy._set || sg->fx._set || sg->fy._set || sg->r._set ) { break; } // some mix of set and not set + // none set? assume aligned and fall through + } else if (SP_IS_MESHGRADIENT(this) && SP_IS_MESHGRADIENT(that)) { + SPMeshGradient *sg=SP_MESHGRADIENT(this); + SPMeshGradient *tg=SP_MESHGRADIENT(that); + + if( sg->x._set != !tg->x._set) { break; } + if( sg->y._set != !tg->y._set) { break; } + if( sg->x._set && sg->y._set) { + if( (sg->x.computed != tg->x.computed) || + (sg->y.computed != tg->y.computed) ) { break; } + } else if( sg->x._set || sg->y._set) { break; } // some mix of set and not set + // none set? assume aligned and fall through + } else { + break; + } + status = true; + break; + } + return status; +} + +/* + * Gradient + */ +SPGradient::SPGradient() : SPPaintServer(), units(), + spread(), + ref(NULL), + state(2), + vector() { + + this->ref = new SPGradientReference(this); + this->ref->changedSignal().connect(sigc::bind(sigc::ptr_fun(SPGradient::gradientRefChanged), this)); + + /** \todo + * Fixme: reprs being rearranged (e.g. via the XML editor) + * may require us to clear the state. + */ + this->state = SP_GRADIENT_STATE_UNKNOWN; + + this->units = SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX; + this->units_set = FALSE; + + this->gradientTransform = Geom::identity(); + this->gradientTransform_set = FALSE; + + this->spread = SP_GRADIENT_SPREAD_PAD; + this->spread_set = FALSE; + + this->has_stops = FALSE; + this->has_patches = FALSE; + + this->vector.built = false; + this->vector.stops.clear(); +} + +SPGradient::~SPGradient() { +} + +/** + * Virtual build: set gradient attributes from its associated repr. + */ +void SPGradient::build(SPDocument *document, Inkscape::XML::Node *repr) +{ + // Work-around in case a swatch had been marked for immediate collection: + if ( repr->attribute("osb:paint") && repr->attribute("inkscape:collect") ) { + repr->setAttribute("inkscape:collect", 0); + } + + SPPaintServer::build(document, repr); + + for (auto& ochild: children) { + if (SP_IS_STOP(&ochild)) { + this->has_stops = TRUE; + break; + } + if (SP_IS_MESHROW(&ochild)) { + for (auto& ochild2: ochild.children) { + if (SP_IS_MESHPATCH(&ochild2)) { + this->has_patches = TRUE; + break; + } + } + if (this->has_patches == TRUE) { + break; + } + } + } + + this->readAttr( "gradientUnits" ); + this->readAttr( "gradientTransform" ); + this->readAttr( "spreadMethod" ); + this->readAttr( "xlink:href" ); + this->readAttr( "osb:paint" ); + + // Register ourselves + document->addResource("gradient", this); +} + +/** + * Virtual release of SPGradient members before destruction. + */ +void SPGradient::release() +{ + +#ifdef SP_GRADIENT_VERBOSE + g_print("Releasing this %s\n", this->getId()); +#endif + + if (this->document) { + // Unregister ourselves + this->document->removeResource("gradient", this); + } + + if (this->ref) { + this->modified_connection.disconnect(); + this->ref->detach(); + delete this->ref; + this->ref = NULL; + } + + //this->modified_connection.~connection(); + + SPPaintServer::release(); +} + +/** + * Set gradient attribute to value. + */ +void SPGradient::set(unsigned key, gchar const *value) +{ +#ifdef OBJECT_TRACE + std::stringstream temp; + temp << "SPGradient::set: " << key << " " << (value?value:"null"); + objectTrace( temp.str() ); +#endif + + switch (key) { + case SP_ATTR_GRADIENTUNITS: + if (value) { + if (!strcmp(value, "userSpaceOnUse")) { + this->units = SP_GRADIENT_UNITS_USERSPACEONUSE; + } else { + this->units = SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX; + } + + this->units_set = TRUE; + } else { + this->units = SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX; + this->units_set = FALSE; + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_GRADIENTTRANSFORM: { + Geom::Affine t; + if (value && sp_svg_transform_read(value, &t)) { + this->gradientTransform = t; + this->gradientTransform_set = TRUE; + } else { + this->gradientTransform = Geom::identity(); + this->gradientTransform_set = FALSE; + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + case SP_ATTR_SPREADMETHOD: + if (value) { + if (!strcmp(value, "reflect")) { + this->spread = SP_GRADIENT_SPREAD_REFLECT; + } else if (!strcmp(value, "repeat")) { + this->spread = SP_GRADIENT_SPREAD_REPEAT; + } else { + this->spread = SP_GRADIENT_SPREAD_PAD; + } + + this->spread_set = TRUE; + } else { + this->spread_set = FALSE; + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_XLINK_HREF: + if (value) { + try { + this->ref->attach(Inkscape::URI(value)); + } catch (Inkscape::BadURIException &e) { + g_warning("%s", e.what()); + this->ref->detach(); + } + } else { + this->ref->detach(); + } + break; + + case SP_ATTR_OSB_SWATCH: + { + bool newVal = (value != 0); + bool modified = false; + + if (newVal != this->swatch) { + this->swatch = newVal; + modified = true; + } + + if (newVal) { + // Might need to flip solid/gradient + Glib::ustring paintVal = ( this->hasStops() && (this->getStopCount() == 0) ) ? "solid" : "gradient"; + + if ( paintVal != value ) { + this->setAttribute( "osb:paint", paintVal.c_str(), 0 ); + modified = true; + } + } + + if (modified) { + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + } + break; + default: + SPPaintServer::set(key, value); + break; + } + +#ifdef OBJECT_TRACE + objectTrace( "SPGradient::set", false ); +#endif +} + +/** + * Gets called when the gradient is (re)attached to another gradient. + */ +void SPGradient::gradientRefChanged(SPObject *old_ref, SPObject *ref, SPGradient *gr) +{ + if (old_ref) { + gr->modified_connection.disconnect(); + } + if ( SP_IS_GRADIENT(ref) + && ref != gr ) + { + gr->modified_connection = ref->connectModified(sigc::bind<2>(sigc::ptr_fun(&SPGradient::gradientRefModified), gr)); + } + + // Per SVG, all unset attributes must be inherited from linked gradient. + // So, as we're now (re)linked, we assign linkee's values to this gradient if they are not yet set - + // but without setting the _set flags. + // FIXME: do the same for gradientTransform too + if (!gr->units_set) { + gr->units = gr->fetchUnits(); + } + if (!gr->spread_set) { + gr->spread = gr->fetchSpread(); + } + + /// \todo Fixme: what should the flags (second) argument be? */ + gradientRefModified(ref, 0, gr); +} + +/** + * Callback for child_added event. + */ +void SPGradient::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) +{ + this->invalidateVector(); + + SPPaintServer::child_added(child, ref); + + SPObject *ochild = this->get_child_by_repr(child); + if ( ochild && SP_IS_STOP(ochild) ) { + this->has_stops = TRUE; + if ( this->getStopCount() > 0 ) { + gchar const * attr = this->getAttribute("osb:paint"); + if ( attr && strcmp(attr, "gradient") ) { + this->setAttribute( "osb:paint", "gradient", 0 ); + } + } + } + if ( ochild && SP_IS_MESHROW(ochild) ) { + this->has_patches = TRUE; + } + + /// \todo Fixme: should we schedule "modified" here? + this->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/** + * Callback for remove_child event. + */ +void SPGradient::remove_child(Inkscape::XML::Node *child) +{ + this->invalidateVector(); + + SPPaintServer::remove_child(child); + + this->has_stops = FALSE; + this->has_patches = FALSE; + for (auto& ochild: children) { + if (SP_IS_STOP(&ochild)) { + this->has_stops = TRUE; + break; + } + if (SP_IS_MESHROW(&ochild)) { + for (auto& ochild2: ochild.children) { + if (SP_IS_MESHPATCH(&ochild2)) { + this->has_patches = TRUE; + break; + } + } + if (this->has_patches == TRUE) { + break; + } + } + } + + if ( this->getStopCount() == 0 ) { + gchar const * attr = this->getAttribute("osb:paint"); + + if ( attr && strcmp(attr, "solid") ) { + this->setAttribute( "osb:paint", "solid", 0 ); + } + } + + /* Fixme: should we schedule "modified" here? */ + this->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/** + * Callback for modified event. + */ +void SPGradient::modified(guint flags) +{ +#ifdef OBJECT_TRACE + objectTrace( "SPGradient::modified" ); +#endif + + if (flags & SP_OBJECT_CHILD_MODIFIED_FLAG) { + if (SP_IS_MESHGRADIENT(this)) { + this->invalidateArray(); + } else { + this->invalidateVector(); + } + } + + if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) { + if (SP_IS_MESHGRADIENT(this)) { + this->ensureArray(); + } else { + this->ensureVector(); + } + } + + if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + flags &= SP_OBJECT_MODIFIED_CASCADE; + + // FIXME: climb up the ladder of hrefs + std::vector l; + for (auto& child: children) { + sp_object_ref(&child); + l.push_back(&child); + } + + for (auto child:l) { + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + sp_object_unref(child); + } + +#ifdef OBJECT_TRACE + objectTrace( "SPGradient::modified", false ); +#endif +} + +SPStop* SPGradient::getFirstStop() +{ + SPStop* first = nullptr; + for (auto& ochild: children) { + if (SP_IS_STOP(&ochild)) { + first = SP_STOP(&ochild); + break; + } + } + return first; +} + +int SPGradient::getStopCount() const +{ + int count = 0; + + for (SPStop *stop = const_cast(this)->getFirstStop(); stop && stop->getNextStop(); stop = stop->getNextStop()) { + count++; + } + + return count; +} + +/** + * Write gradient attributes to repr. + */ +Inkscape::XML::Node *SPGradient::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) +{ +#ifdef OBJECT_TRACE + objectTrace( "SPGradient::write" ); +#endif + + SPPaintServer::write(xml_doc, repr, flags); + + if (flags & SP_OBJECT_WRITE_BUILD) { + std::vector l; + + for (auto& child: children) { + Inkscape::XML::Node *crepr = child.updateRepr(xml_doc, NULL, flags); + + if (crepr) { + l.push_back(crepr); + } + } + + for (auto i=l.rbegin();i!=l.rend();++i) { + repr->addChild(*i, NULL); + Inkscape::GC::release(*i); + } + } + + if (this->ref->getURI()) { + gchar *uri_string = this->ref->getURI()->toString(); + repr->setAttribute("xlink:href", uri_string); + g_free(uri_string); + } + + if ((flags & SP_OBJECT_WRITE_ALL) || this->units_set) { + switch (this->units) { + case SP_GRADIENT_UNITS_USERSPACEONUSE: + repr->setAttribute("gradientUnits", "userSpaceOnUse"); + break; + default: + repr->setAttribute("gradientUnits", "objectBoundingBox"); + break; + } + } + + if ((flags & SP_OBJECT_WRITE_ALL) || this->gradientTransform_set) { + gchar *c=sp_svg_transform_write(this->gradientTransform); + repr->setAttribute("gradientTransform", c); + g_free(c); + } + + if ((flags & SP_OBJECT_WRITE_ALL) || this->spread_set) { + /* FIXME: Ensure that this->spread is the inherited value + * if !this->spread_set. Not currently happening: see SPGradient::modified. + */ + switch (this->spread) { + case SP_GRADIENT_SPREAD_REFLECT: + repr->setAttribute("spreadMethod", "reflect"); + break; + case SP_GRADIENT_SPREAD_REPEAT: + repr->setAttribute("spreadMethod", "repeat"); + break; + default: + repr->setAttribute("spreadMethod", "pad"); + break; + } + } + + if ( (flags & SP_OBJECT_WRITE_EXT) && this->isSwatch() ) { + if ( this->isSolid() ) { + repr->setAttribute( "osb:paint", "solid" ); + } else { + repr->setAttribute( "osb:paint", "gradient" ); + } + } else { + repr->setAttribute( "osb:paint", 0 ); + } + +#ifdef OBJECT_TRACE + objectTrace( "SPGradient::write", false ); +#endif + return repr; +} + +/** + * Forces the vector to be built, if not present (i.e., changed). + * + * \pre SP_IS_GRADIENT(gradient). + */ +void SPGradient::ensureVector() +{ + if ( !vector.built ) { + rebuildVector(); + } +} + +/** + * Forces the array to be built, if not present (i.e., changed). + * + * \pre SP_IS_GRADIENT(gradient). + */ +void SPGradient::ensureArray() +{ + //std::cout << "SPGradient::ensureArray()" << std::endl; + if ( !array.built ) { + rebuildArray(); + } +} + +/** + * Set units property of gradient and emit modified. + */ +void SPGradient::setUnits(SPGradientUnits units) +{ + if (units != this->units) { + this->units = units; + units_set = TRUE; + requestModified(SP_OBJECT_MODIFIED_FLAG); + } +} + +/** + * Set spread property of gradient and emit modified. + */ +void SPGradient::setSpread(SPGradientSpread spread) +{ + if (spread != this->spread) { + this->spread = spread; + spread_set = TRUE; + requestModified(SP_OBJECT_MODIFIED_FLAG); + } +} + +/** + * Returns the first of {src, src-\>ref-\>getObject(), + * src-\>ref-\>getObject()-\>ref-\>getObject(),...} + * for which \a match is true, or NULL if none found. + * + * The raison d'être of this routine is that it correctly handles cycles in the href chain (e.g., if + * a gradient gives itself as its href, or if each of two gradients gives the other as its href). + * + * \pre SP_IS_GRADIENT(src). + */ +static SPGradient * +chase_hrefs(SPGradient *const src, bool (*match)(SPGradient const *)) +{ + g_return_val_if_fail(SP_IS_GRADIENT(src), NULL); + + /* Use a pair of pointers for detecting loops: p1 advances half as fast as p2. If there is a + loop, then once p1 has entered the loop, we'll detect it the next time the distance between + p1 and p2 is a multiple of the loop size. */ + SPGradient *p1 = src, *p2 = src; + bool do1 = false; + for (;;) { + if (match(p2)) { + return p2; + } + + p2 = p2->ref->getObject(); + if (!p2) { + return p2; + } + if (do1) { + p1 = p1->ref->getObject(); + } + do1 = !do1; + + if ( p2 == p1 ) { + /* We've been here before, so return NULL to indicate that no matching gradient found + * in the chain. */ + return NULL; + } + } +} + +/** + * True if gradient has stops. + */ +static bool has_stopsFN(SPGradient const *gr) +{ + return gr->hasStops(); +} + +/** + * True if gradient has patches (i.e. a mesh). + */ +static bool has_patchesFN(SPGradient const *gr) +{ + return gr->hasPatches(); +} + +/** + * True if gradient has spread set. + */ +static bool has_spread_set(SPGradient const *gr) +{ + return gr->isSpreadSet(); +} + +/** + * True if gradient has units set. + */ +static bool +has_units_set(SPGradient const *gr) +{ + return gr->isUnitsSet(); +} + + +SPGradient *SPGradient::getVector(bool force_vector) +{ + SPGradient * src = chase_hrefs(this, has_stopsFN); + if (src == NULL) { + src = this; + } + + if (force_vector) { + src = sp_gradient_ensure_vector_normalized(src); + } + return src; +} + +SPGradient *SPGradient::getArray(bool force_vector) +{ + SPGradient * src = chase_hrefs(this, has_patchesFN); + if (src == NULL) { + src = this; + } + return src; +} + +/** + * Returns the effective spread of given gradient (climbing up the refs chain if needed). + * + * \pre SP_IS_GRADIENT(gradient). + */ +SPGradientSpread SPGradient::fetchSpread() +{ + SPGradient const *src = chase_hrefs(this, has_spread_set); + return ( src + ? src->spread + : SP_GRADIENT_SPREAD_PAD ); // pad is the default +} + +/** + * Returns the effective units of given gradient (climbing up the refs chain if needed). + * + * \pre SP_IS_GRADIENT(gradient). + */ +SPGradientUnits SPGradient::fetchUnits() +{ + SPGradient const *src = chase_hrefs(this, has_units_set); + return ( src + ? src->units + : SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX ); // bbox is the default +} + + +/** + * Clears the gradient's svg:stop children from its repr. + */ +void +sp_gradient_repr_clear_vector(SPGradient *gr) +{ + Inkscape::XML::Node *repr = gr->getRepr(); + + /* Collect stops from original repr */ + std::vector l; + for (Inkscape::XML::Node *child = repr->firstChild() ; child != NULL; child = child->next() ) { + if (!strcmp(child->name(), "svg:stop")) { + l.push_back(child); + } + } + /* Remove all stops */ + for (auto i=l.rbegin();i!=l.rend();++i) { + /** \todo + * fixme: This should work, unless we make gradient + * into generic group. + */ + sp_repr_unparent(*i); + } +} + +/** + * Writes the gradient's internal vector (whether from its own stops, or + * inherited from refs) into the gradient repr as svg:stop elements. + */ +void +sp_gradient_repr_write_vector(SPGradient *gr) +{ + g_return_if_fail(gr != NULL); + g_return_if_fail(SP_IS_GRADIENT(gr)); + + Inkscape::XML::Document *xml_doc = gr->document->getReprDoc(); + Inkscape::XML::Node *repr = gr->getRepr(); + + /* We have to be careful, as vector may be our own, so construct repr list at first */ + std::vector l; + + for (guint i = 0; i < gr->vector.stops.size(); i++) { + Inkscape::CSSOStringStream os; + Inkscape::XML::Node *child = xml_doc->createElement("svg:stop"); + sp_repr_set_css_double(child, "offset", gr->vector.stops[i].offset); + /* strictly speaking, offset an SVG rather than a CSS one, but exponents make no + * sense for offset proportions. */ + os << "stop-color:" << gr->vector.stops[i].color.toString() << ";stop-opacity:" << gr->vector.stops[i].opacity; + child->setAttribute("style", os.str().c_str()); + /* Order will be reversed here */ + l.push_back(child); + } + + sp_gradient_repr_clear_vector(gr); + + /* And insert new children from list */ + for (auto i=l.rbegin();i!=l.rend();++i) { + Inkscape::XML::Node *child = *i; + repr->addChild(child, NULL); + Inkscape::GC::release(child); + } +} + + +void SPGradient::gradientRefModified(SPObject */*href*/, guint /*flags*/, SPGradient *gradient) +{ + if ( gradient->invalidateVector() ) { + gradient->requestModified(SP_OBJECT_MODIFIED_FLAG); + // Conditional to avoid causing infinite loop if there's a cycle in the href chain. + } +} + +/** Return true if change made. */ +bool SPGradient::invalidateVector() +{ + bool ret = false; + + if (vector.built) { + vector.built = false; + vector.stops.clear(); + ret = true; + } + + return ret; +} + +/** Return true if change made. */ +bool SPGradient::invalidateArray() +{ + bool ret = false; + + if (array.built) { + array.built = false; + // array.clear(); + ret = true; + } + + return ret; +} + +/** Creates normalized color vector */ +void SPGradient::rebuildVector() +{ + gint len = 0; + for (auto& child: children) { + if (SP_IS_STOP(&child)) { + len ++; + } + } + + has_stops = (len != 0); + + vector.stops.clear(); + + SPGradient *reffed = ref ? ref->getObject() : NULL; + if ( !hasStops() && reffed ) { + /* Copy vector from referenced gradient */ + vector.built = true; // Prevent infinite recursion. + reffed->ensureVector(); + if (!reffed->vector.stops.empty()) { + vector.built = reffed->vector.built; + vector.stops.assign(reffed->vector.stops.begin(), reffed->vector.stops.end()); + return; + } + } + + for (auto& child: children) { + if (SP_IS_STOP(&child)) { + SPStop *stop = SP_STOP(&child); + + SPGradientStop gstop; + if (!vector.stops.empty()) { + // "Each gradient offset value is required to be equal to or greater than the + // previous gradient stop's offset value. If a given gradient stop's offset + // value is not equal to or greater than all previous offset values, then the + // offset value is adjusted to be equal to the largest of all previous offset + // values." + gstop.offset = MAX(stop->offset, vector.stops.back().offset); + } else { + gstop.offset = stop->offset; + } + + // "Gradient offset values less than 0 (or less than 0%) are rounded up to + // 0%. Gradient offset values greater than 1 (or greater than 100%) are rounded + // down to 100%." + gstop.offset = CLAMP(gstop.offset, 0, 1); + + gstop.color = stop->getEffectiveColor(); + gstop.opacity = stop->opacity; + + vector.stops.push_back(gstop); + } + } + + // Normalize per section 13.2.4 of SVG 1.1. + if (vector.stops.empty()) { + /* "If no stops are defined, then painting shall occur as if 'none' were specified as the + * paint style." + */ + { + SPGradientStop gstop; + gstop.offset = 0.0; + gstop.color.set( 0x00000000 ); + gstop.opacity = 0.0; + vector.stops.push_back(gstop); + } + { + SPGradientStop gstop; + gstop.offset = 1.0; + gstop.color.set( 0x00000000 ); + gstop.opacity = 0.0; + vector.stops.push_back(gstop); + } + } else { + /* "If one stop is defined, then paint with the solid color fill using the color defined + * for that gradient stop." + */ + if (vector.stops.front().offset > 0.0) { + // If the first one is not at 0, then insert a copy of the first at 0. + SPGradientStop gstop; + gstop.offset = 0.0; + gstop.color = vector.stops.front().color; + gstop.opacity = vector.stops.front().opacity; + vector.stops.insert(vector.stops.begin(), gstop); + } + if (vector.stops.back().offset < 1.0) { + // If the last one is not at 1, then insert a copy of the last at 1. + SPGradientStop gstop; + gstop.offset = 1.0; + gstop.color = vector.stops.back().color; + gstop.opacity = vector.stops.back().opacity; + vector.stops.push_back(gstop); + } + } + + vector.built = true; +} + +/** Creates normalized color mesh patch array */ +void SPGradient::rebuildArray() +{ + // std::cout << "SPGradient::rebuildArray()" << std::endl; + + if( !SP_IS_MESHGRADIENT(this) ) { + g_warning( "SPGradient::rebuildArray() called for non-mesh gradient" ); + return; + } + + array.read( SP_MESHGRADIENT( this ) ); + has_patches = array.patch_columns() > 0; +} + +Geom::Affine +sp_gradient_get_g2d_matrix(SPGradient const *gr, Geom::Affine const &ctm, Geom::Rect const &bbox) +{ + if (gr->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) { + return ( Geom::Scale(bbox.dimensions()) + * Geom::Translate(bbox.min()) + * Geom::Affine(ctm) ); + } else { + return ctm; + } +} + +Geom::Affine +sp_gradient_get_gs2d_matrix(SPGradient const *gr, Geom::Affine const &ctm, Geom::Rect const &bbox) +{ + if (gr->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) { + return ( gr->gradientTransform + * Geom::Scale(bbox.dimensions()) + * Geom::Translate(bbox.min()) + * Geom::Affine(ctm) ); + } else { + return gr->gradientTransform * ctm; + } +} + +void +sp_gradient_set_gs2d_matrix(SPGradient *gr, Geom::Affine const &ctm, + Geom::Rect const &bbox, Geom::Affine const &gs2d) +{ + gr->gradientTransform = gs2d * ctm.inverse(); + if (gr->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX ) { + gr->gradientTransform = ( gr->gradientTransform + * Geom::Translate(-bbox.min()) + * Geom::Scale(bbox.dimensions()).inverse() ); + } + gr->gradientTransform_set = TRUE; + + gr->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + + + + +/* CAIRO RENDERING STUFF */ + +void +sp_gradient_pattern_common_setup(cairo_pattern_t *cp, + SPGradient *gr, + Geom::OptRect const &bbox, + double opacity) +{ + // set spread type + switch (gr->getSpread()) { + case SP_GRADIENT_SPREAD_REFLECT: + cairo_pattern_set_extend(cp, CAIRO_EXTEND_REFLECT); + break; + case SP_GRADIENT_SPREAD_REPEAT: + cairo_pattern_set_extend(cp, CAIRO_EXTEND_REPEAT); + break; + case SP_GRADIENT_SPREAD_PAD: + default: + cairo_pattern_set_extend(cp, CAIRO_EXTEND_PAD); + break; + } + + // add stops + if (!SP_IS_MESHGRADIENT(gr)) { + for (std::vector::iterator i = gr->vector.stops.begin(); + i != gr->vector.stops.end(); ++i) + { + // multiply stop opacity by paint opacity + cairo_pattern_add_color_stop_rgba(cp, i->offset, + i->color.v.c[0], i->color.v.c[1], i->color.v.c[2], i->opacity * opacity); + } + } + + // set pattern transform matrix + Geom::Affine gs2user = gr->gradientTransform; + if (gr->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX && bbox) { + Geom::Affine bbox2user(bbox->width(), 0, 0, bbox->height(), bbox->left(), bbox->top()); + gs2user *= bbox2user; + } + ink_cairo_pattern_set_matrix(cp, gs2user.inverse()); +} + +cairo_pattern_t * +sp_gradient_create_preview_pattern(SPGradient *gr, double width) +{ + cairo_pattern_t *pat = NULL; + + if (!SP_IS_MESHGRADIENT(gr)) { + gr->ensureVector(); + + pat = cairo_pattern_create_linear(0, 0, width, 0); + + for (std::vector::iterator i = gr->vector.stops.begin(); + i != gr->vector.stops.end(); ++i) + { + cairo_pattern_add_color_stop_rgba(pat, i->offset, + i->color.v.c[0], i->color.v.c[1], i->color.v.c[2], i->opacity); + } + } else { + + // For the moment, use the top row of nodes for preview. + unsigned columns = gr->array.patch_columns(); + + double offset = 1.0/double(columns); + + pat = cairo_pattern_create_linear(0, 0, width, 0); + + for (unsigned i = 0; i < columns+1; ++i) { + SPMeshNode* node = gr->array.node( 0, i*3 ); + cairo_pattern_add_color_stop_rgba(pat, i*offset, + node->color.v.c[0], node->color.v.c[1], node->color.v.c[2], node->opacity); + } + } + + return pat; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-gradient.h b/src/object/sp-gradient.h new file mode 100644 index 000000000..9a0d5070f --- /dev/null +++ b/src/object/sp-gradient.h @@ -0,0 +1,238 @@ +#ifndef SEEN_SP_GRADIENT_H +#define SEEN_SP_GRADIENT_H +/* + * Authors: + * Lauris Kaplinski + * Johan Engelen + * Jon A. Cruz + * + * Copyrigt (C) 2010 Jon A. Cruz + * Copyright (C) 2007 Johan Engelen + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <2geom/affine.h> +#include +#include +#include +#include + +#include "sp-paint-server.h" +#include "sp-gradient-spread.h" +#include "sp-gradient-units.h" +#include "sp-gradient-vector.h" +#include "sp-mesh-array.h" + +class SPGradientReference; +class SPStop; + +#define SP_GRADIENT(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_GRADIENT(obj) (dynamic_cast((SPObject*)obj) != NULL) + +enum SPGradientType { + SP_GRADIENT_TYPE_UNKNOWN, + SP_GRADIENT_TYPE_LINEAR, + SP_GRADIENT_TYPE_RADIAL, + SP_GRADIENT_TYPE_MESH +}; + +enum SPGradientState { + SP_GRADIENT_STATE_UNKNOWN, + SP_GRADIENT_STATE_VECTOR, + SP_GRADIENT_STATE_PRIVATE +}; + +enum GrPointType { + POINT_LG_BEGIN = 0, //start enum at 0 (for indexing into gr_knot_shapes array for example) + POINT_LG_END, + POINT_LG_MID, + POINT_RG_CENTER, + POINT_RG_R1, + POINT_RG_R2, + POINT_RG_FOCUS, + POINT_RG_MID1, + POINT_RG_MID2, + POINT_MG_CORNER, + POINT_MG_HANDLE, + POINT_MG_TENSOR, + // insert new point types here. + + POINT_G_INVALID +}; + +namespace Inkscape { + +enum PaintTarget { + FOR_FILL, + FOR_STROKE +}; + +/** + * Convenience function to access a common vector of all enum values. + */ +std::vector const &allPaintTargets(); + +} // namespace Inkscape + +/** + * Gradient + * + * Implement spread, stops list + * \todo fixme: Implement more here (Lauris) + */ +class SPGradient : public SPPaintServer { +public: + SPGradient(); + virtual ~SPGradient(); + +private: + /** gradientUnits attribute */ + SPGradientUnits units; + unsigned int units_set : 1; +public: + + /** gradientTransform attribute */ + Geom::Affine gradientTransform; + unsigned int gradientTransform_set : 1; + +private: + /** spreadMethod attribute */ + SPGradientSpread spread; + unsigned int spread_set : 1; + + /** Gradient stops */ + unsigned int has_stops : 1; + + /** Gradient patches */ + unsigned int has_patches : 1; + +public: + /** Reference (href) */ + SPGradientReference *ref; + + /** State in Inkscape gradient system */ + unsigned int state; + + /** Linear and Radial Gradients */ + + /** Composed vector */ + SPGradientVector vector; + + sigc::connection modified_connection; + + bool hasStops() const; + + SPStop* getFirstStop(); + int getStopCount() const; + + bool isEquivalent(SPGradient *b); + bool isAligned(SPGradient *b); + + /** Mesh Gradients **************/ + + /** Composed array (for mesh gradients) */ + SPMeshNodeArray array; + SPMeshNodeArray array_smoothed; // Smoothed version of array + + bool hasPatches() const; + + + /** All Gradients **************/ + bool isUnitsSet() const; + SPGradientUnits getUnits() const; + void setUnits(SPGradientUnits units); + + + bool isSpreadSet() const; + SPGradientSpread getSpread() const; + +/** + * Returns private vector of given gradient (the gradient at the end of the href chain which has + * stops), optionally normalizing it. + * + * \pre SP_IS_GRADIENT(gradient). + * \pre There exists a gradient in the chain that has stops. + */ + SPGradient *getVector(bool force_private = false); + + /** + * Returns private mesh of given gradient (the gradient at the end of the href chain which has + * patches), optionally normalizing it. + */ + SPGradient *getArray(bool force_private = false); + + //static GType getType(); + + /** Forces vector to be built, if not present (i.e. changed) */ + void ensureVector(); + + /** Forces array (mesh) to be built, if not present (i.e. changed) */ + void ensureArray(); + + /** + * Set spread property of gradient and emit modified. + */ + void setSpread(SPGradientSpread spread); + + SPGradientSpread fetchSpread(); + SPGradientUnits fetchUnits(); + + void setSwatch(bool swatch = true); + + static void gradientRefModified(SPObject *href, unsigned int flags, SPGradient *gradient); + static void gradientRefChanged(SPObject *old_ref, SPObject *ref, SPGradient *gr); + +private: + bool invalidateVector(); + bool invalidateArray(); + void rebuildVector(); + void rebuildArray(); + +protected: + virtual void build(SPDocument *document, Inkscape::XML::Node *repr); + virtual void release(); + virtual void modified(unsigned int flags); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); + + virtual void child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref); + virtual void remove_child(Inkscape::XML::Node *child); + + virtual void set(unsigned key, char const *value); +}; + +void +sp_gradient_pattern_common_setup(cairo_pattern_t *cp, + SPGradient *gr, + Geom::OptRect const &bbox, + double opacity); + +/* Gradient repr methods */ +void sp_gradient_repr_write_vector(SPGradient *gr); +void sp_gradient_repr_clear_vector(SPGradient *gr); + +cairo_pattern_t *sp_gradient_create_preview_pattern(SPGradient *gradient, double width); + +/** Transforms to/from gradient position space in given environment */ +Geom::Affine sp_gradient_get_g2d_matrix(SPGradient const *gr, Geom::Affine const &ctm, + Geom::Rect const &bbox); +Geom::Affine sp_gradient_get_gs2d_matrix(SPGradient const *gr, Geom::Affine const &ctm, + Geom::Rect const &bbox); +void sp_gradient_set_gs2d_matrix(SPGradient *gr, Geom::Affine const &ctm, Geom::Rect const &bbox, + Geom::Affine const &gs2d); + + +#endif // SEEN_SP_GRADIENT_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-guide.cpp b/src/object/sp-guide.cpp new file mode 100644 index 000000000..25bcee92b --- /dev/null +++ b/src/object/sp-guide.cpp @@ -0,0 +1,545 @@ +/* + * Inkscape guideline implementation + * + * Authors: + * Lauris Kaplinski + * Peter Moulder + * Johan Engelen + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 2000-2002 authors + * Copyright (C) 2004 Monash University + * Copyright (C) 2007 Johan Engelen + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include "display/sp-canvas.h" +#include "display/guideline.h" +#include "svg/svg.h" +#include "svg/svg-color.h" +#include "svg/stringstream.h" +#include "attributes.h" +#include "sp-guide.h" +#include "sp-item-notify-moveto.h" +#include +#include +#include +#include "inkscape.h" +#include "desktop.h" +#include "sp-root.h" +#include "sp-namedview.h" +#include "document-undo.h" +#include "helper-fns.h" +#include "verbs.h" + +using Inkscape::DocumentUndo; +using std::vector; + +SPGuide::SPGuide() + : SPObject() + , label(NULL) + , locked(0) + , normal_to_line(Geom::Point(0.,1.)) + , point_on_line(Geom::Point(0.,0.)) + , color(0x0000ff7f) + , hicolor(0xff00007f) +{} + +void SPGuide::setColor(guint32 c) +{ + color = c; + + for(std::vector::const_iterator it = this->views.begin(); it != this->views.end(); ++it) { + sp_guideline_set_color(*it, this->color); + } +} + +void SPGuide::build(SPDocument *document, Inkscape::XML::Node *repr) +{ + SPObject::build(document, repr); + + this->readAttr( "inkscape:color" ); + this->readAttr( "inkscape:label" ); + this->readAttr( "inkscape:locked" ); + this->readAttr( "orientation" ); + this->readAttr( "position" ); + + /* Register */ + document->addResource("guide", this); +} + +void SPGuide::release() +{ + for(std::vector::const_iterator it = this->views.begin(); it != this->views.end(); ++it) { + sp_guideline_delete(*it); + } + this->views.clear(); + + if (this->document) { + // Unregister ourselves + this->document->removeResource("guide", this); + } + + SPObject::release(); +} + +void SPGuide::set(unsigned int key, const gchar *value) { + switch (key) { + case SP_ATTR_INKSCAPE_COLOR: + if (value) { + this->setColor(sp_svg_read_color(value, 0x0000ff00) | 0x7f); + } + break; + case SP_ATTR_INKSCAPE_LABEL: + // this->label already freed in sp_guideline_set_label (src/display/guideline.cpp) + // see bug #1498444, bug #1469514 + if (value) { + this->label = g_strdup(value); + } else { + this->label = NULL; + } + + this->set_label(this->label, false); + break; + case SP_ATTR_INKSCAPE_LOCKED: + if (value) { + this->set_locked(helperfns_read_bool(value, false), false); + } + break; + case SP_ATTR_ORIENTATION: + { + if (value && !strcmp(value, "horizontal")) { + /* Visual representation of a horizontal line, constrain vertically (y coordinate). */ + this->normal_to_line = Geom::Point(0., 1.); + } else if (value && !strcmp(value, "vertical")) { + this->normal_to_line = Geom::Point(1., 0.); + } else if (value) { + gchar ** strarray = g_strsplit(value, ",", 2); + double newx, newy; + unsigned int success = sp_svg_number_read_d(strarray[0], &newx); + success += sp_svg_number_read_d(strarray[1], &newy); + g_strfreev (strarray); + if (success == 2 && (fabs(newx) > 1e-6 || fabs(newy) > 1e-6)) { + Geom::Point direction(newx, newy); + direction.normalize(); + this->normal_to_line = direction; + } else { + // default to vertical line for bad arguments + this->normal_to_line = Geom::Point(1., 0.); + } + } else { + // default to vertical line for bad arguments + this->normal_to_line = Geom::Point(1., 0.); + } + this->set_normal(this->normal_to_line, false); + } + break; + case SP_ATTR_POSITION: + { + if (value) { + gchar ** strarray = g_strsplit(value, ",", 2); + double newx, newy; + unsigned int success = sp_svg_number_read_d(strarray[0], &newx); + success += sp_svg_number_read_d(strarray[1], &newy); + g_strfreev (strarray); + if (success == 2) { + // If root viewBox set, interpret guides in terms of viewBox (90/96) + SPRoot *root = document->getRoot(); + if( root->viewBox_set ) { + if(Geom::are_near((root->width.computed * root->viewBox.height()) / (root->viewBox.width() * root->height.computed), 1.0, Geom::EPSILON)) { + // for uniform scaling, try to reduce numerical error + double vbunit2px = (root->width.computed / root->viewBox.width() + root->height.computed / root->viewBox.height())/2.0; + newx = newx * vbunit2px; + newy = newy * vbunit2px; + } else { + newx = newx * root->width.computed / root->viewBox.width(); + newy = newy * root->height.computed / root->viewBox.height(); + } + } + this->point_on_line = Geom::Point(newx, newy); + } else if (success == 1) { + // before 0.46 style guideline definition. + const gchar *attr = this->getRepr()->attribute("orientation"); + if (attr && !strcmp(attr, "horizontal")) { + this->point_on_line = Geom::Point(0, newx); + } else { + this->point_on_line = Geom::Point(newx, 0); + } + } + } else { + // default to (0,0) for bad arguments + this->point_on_line = Geom::Point(0,0); + } + // update position in non-committing way + // fixme: perhaps we need to add an update method instead, and request_update here + this->moveto(this->point_on_line, false); + } + break; + default: + SPObject::set(key, value); + break; + } +} + +/* Only used internally and in sp-line.cpp */ +SPGuide *SPGuide::createSPGuide(SPDocument *doc, Geom::Point const &pt1, Geom::Point const &pt2) +{ + Inkscape::XML::Document *xml_doc = doc->getReprDoc(); + + Inkscape::XML::Node *repr = xml_doc->createElement("sodipodi:guide"); + + Geom::Point n = Geom::rot90(pt2 - pt1); + + // If root viewBox set, interpret guides in terms of viewBox (90/96) + double newx = pt1.x(); + double newy = pt1.y(); + + SPRoot *root = doc->getRoot(); + if( root->viewBox_set ) { + // check to see if scaling is uniform + if(Geom::are_near((root->viewBox.width() * root->height.computed) / (root->width.computed * root->viewBox.height()), 1.0, Geom::EPSILON)) { + double px2vbunit = (root->viewBox.width()/root->width.computed + root->viewBox.height()/root->height.computed)/2.0; + newx = newx * px2vbunit; + newy = newy * px2vbunit; + } else { + newx = newx * root->viewBox.width() / root->width.computed; + newy = newy * root->viewBox.height() / root->height.computed; + } + } + + sp_repr_set_point(repr, "position", Geom::Point( newx, newy )); + sp_repr_set_point(repr, "orientation", n); + + SPNamedView *namedview = sp_document_namedview(doc, NULL); + if (namedview) { + namedview->appendChild(repr); + } + Inkscape::GC::release(repr); + + SPGuide *guide= SP_GUIDE(doc->getObjectByRepr(repr)); + return guide; +} + +void sp_guide_pt_pairs_to_guides(SPDocument *doc, std::list > &pts) +{ + for (std::list >::iterator i = pts.begin(); i != pts.end(); ++i) { + SPGuide::createSPGuide(doc, (*i).first, (*i).second); + } +} + +void sp_guide_create_guides_around_page(SPDesktop *dt) +{ + SPDocument *doc=dt->getDocument(); + std::list > pts; + + Geom::Point A(0, 0); + Geom::Point C(doc->getWidth().value("px"), doc->getHeight().value("px")); + Geom::Point B(C[Geom::X], 0); + Geom::Point D(0, C[Geom::Y]); + + pts.push_back(std::pair(A, B)); + pts.push_back(std::pair(B, C)); + pts.push_back(std::pair(C, D)); + pts.push_back(std::pair(D, A)); + + sp_guide_pt_pairs_to_guides(doc, pts); + + DocumentUndo::done(doc, SP_VERB_NONE, _("Create Guides Around the Page")); +} + +void sp_guide_delete_all_guides(SPDesktop *dt) +{ + SPDocument *doc=dt->getDocument(); + std::vector current = doc->getResourceList("guide"); + while (!current.empty()){ + SPGuide* guide = SP_GUIDE(*(current.begin())); + sp_guide_remove(guide); + current = doc->getResourceList("guide"); + } + + DocumentUndo::done(doc, SP_VERB_NONE, _("Delete All Guides")); +} + +void SPGuide::showSPGuide(SPCanvasGroup *group, GCallback handler) +{ + SPCanvasItem *item = sp_guideline_new(group, label, point_on_line, normal_to_line); + sp_guideline_set_color(SP_GUIDELINE(item), color); + sp_guideline_set_locked(SP_GUIDELINE(item), locked); + + g_signal_connect(G_OBJECT(item), "event", G_CALLBACK(handler), this); + + views.push_back(SP_GUIDELINE(item)); +} + +void SPGuide::showSPGuide() +{ + for(std::vector::const_iterator it = this->views.begin(); it != this->views.end(); ++it) { + sp_canvas_item_show(SP_CANVAS_ITEM(*it)); + if((*it)->origin) { + sp_canvas_item_show(SP_CANVAS_ITEM((*it)->origin)); + } else { + //reposition to same place to show knots + sp_guideline_set_position(*it, point_on_line); + } + } +} + +void SPGuide::hideSPGuide(SPCanvas *canvas) +{ + g_assert(canvas != NULL); + g_assert(SP_IS_CANVAS(canvas)); + for(std::vector::iterator it = this->views.begin(); it != this->views.end(); ++it) { + if (canvas == SP_CANVAS_ITEM(*it)->canvas) { + sp_guideline_delete(*it); + views.erase(it); + return; + } + } + + assert(false); +} + +void SPGuide::hideSPGuide() +{ + for(std::vector::const_iterator it = this->views.begin(); it != this->views.end(); ++it) { + sp_canvas_item_hide(SP_CANVAS_ITEM(*it)); + if ((*it)->origin) { + sp_canvas_item_hide(SP_CANVAS_ITEM((*it)->origin)); + } + } +} + +void SPGuide::sensitize(SPCanvas *canvas, bool sensitive) +{ + g_assert(canvas != NULL); + g_assert(SP_IS_CANVAS(canvas)); + + for(std::vector::const_iterator it = this->views.begin(); it != this->views.end(); ++it) { + if (canvas == SP_CANVAS_ITEM(*it)->canvas) { + sp_guideline_set_sensitive(*it, sensitive); + return; + } + } + + assert(false); +} + +Geom::Point SPGuide::getPositionFrom(Geom::Point const &pt) const +{ + return -(pt - point_on_line); +} + +double SPGuide::getDistanceFrom(Geom::Point const &pt) const +{ + return Geom::dot(pt - point_on_line, normal_to_line); +} + +/** + * \arg commit False indicates temporary moveto in response to motion event while dragging, + * true indicates a "committing" version: in response to button release event after + * dragging a guideline, or clicking OK in guide editing dialog. + */ +void SPGuide::moveto(Geom::Point const point_on_line, bool const commit) +{ + if(this->locked) { + return; + } + for(std::vector::const_iterator it = this->views.begin(); it != this->views.end(); ++it) { + sp_guideline_set_position(*it, point_on_line); + } + + /* Calling sp_repr_set_point must precede calling sp_item_notify_moveto in the commit + case, so that the guide's new position is available for sp_item_rm_unsatisfied_cns. */ + if (commit) { + // If root viewBox set, interpret guides in terms of viewBox (90/96) + double newx = point_on_line.x(); + double newy = point_on_line.y(); + + SPRoot *root = document->getRoot(); + if( root->viewBox_set ) { + // check to see if scaling is uniform + if(Geom::are_near((root->viewBox.width() * root->height.computed) / (root->width.computed * root->viewBox.height()), 1.0, Geom::EPSILON)) { + double px2vbunit = (root->viewBox.width()/root->width.computed + root->viewBox.height()/root->height.computed)/2.0; + newx = newx * px2vbunit; + newy = newy * px2vbunit; + } else { + newx = newx * root->viewBox.width() / root->width.computed; + newy = newy * root->viewBox.height() / root->height.computed; + } + } + + //XML Tree being used here directly while it shouldn't be. + sp_repr_set_point(getRepr(), "position", Geom::Point(newx, newy) ); + } + +/* DISABLED CODE BECAUSE SPGuideAttachment IS NOT USE AT THE MOMENT (johan) + for (vector::const_iterator i(attached_items.begin()), + iEnd(attached_items.end()); + i != iEnd; ++i) + { + SPGuideAttachment const &att = *i; + sp_item_notify_moveto(*att.item, this, att.snappoint_ix, position, commit); + } +*/ +} + +/** + * \arg commit False indicates temporary moveto in response to motion event while dragging, + * true indicates a "committing" version: in response to button release event after + * dragging a guideline, or clicking OK in guide editing dialog. + */ +void SPGuide::set_normal(Geom::Point const normal_to_line, bool const commit) +{ + if(this->locked) { + return; + } + for(std::vector::const_iterator it = this->views.begin(); it != this->views.end(); ++it) { + sp_guideline_set_normal(*it, normal_to_line); + } + + /* Calling sp_repr_set_svg_point must precede calling sp_item_notify_moveto in the commit + case, so that the guide's new position is available for sp_item_rm_unsatisfied_cns. */ + if (commit) { + //XML Tree being used directly while it shouldn't be + sp_repr_set_point(getRepr(), "orientation", normal_to_line); + } + +/* DISABLED CODE BECAUSE SPGuideAttachment IS NOT USE AT THE MOMENT (johan) + for (vector::const_iterator i(attached_items.begin()), + iEnd(attached_items.end()); + i != iEnd; ++i) + { + SPGuideAttachment const &att = *i; + sp_item_notify_moveto(*att.item, this, att.snappoint_ix, position, commit); + } +*/ +} + +void SPGuide::set_color(const unsigned r, const unsigned g, const unsigned b, bool const commit) +{ + this->color = (r << 24) | (g << 16) | (b << 8) | 0x7f; + + if (! views.empty()) { + sp_guideline_set_color(views[0], this->color); + } + + if (commit) { + std::ostringstream os; + os << "rgb(" << r << "," << g << "," << b << ")"; + //XML Tree being used directly while it shouldn't be + getRepr()->setAttribute("inkscape:color", os.str().c_str()); + } +} + +void SPGuide::set_locked(const bool locked, bool const commit) +{ + this->locked = locked; + if ( !views.empty() ) { + sp_guideline_set_locked(views[0], locked); + } + + if (commit) { + getRepr()->setAttribute("inkscape:locked", g_strdup(locked ? "true" : "false")); + } +} + +void SPGuide::set_label(const char* label, bool const commit) +{ + if (!views.empty()) { + sp_guideline_set_label(views[0], label); + } + + if (commit) { + //XML Tree being used directly while it shouldn't be + getRepr()->setAttribute("inkscape:label", label); + } +} + +/** + * Returns a human-readable description of the guideline for use in dialog boxes and status bar. + * If verbose is false, only positioning information is included (useful for dialogs). + * + * The caller is responsible for freeing the string. + */ +char* SPGuide::description(bool const verbose) const +{ + using Geom::X; + using Geom::Y; + + char *descr = NULL; + if ( !this->document ) { + // Guide has probably been deleted and no longer has an attached namedview. + descr = g_strdup(_("Deleted")); + } else { + SPNamedView *namedview = sp_document_namedview(this->document, NULL); + + Inkscape::Util::Quantity x_q = Inkscape::Util::Quantity(this->point_on_line[X], "px"); + Inkscape::Util::Quantity y_q = Inkscape::Util::Quantity(this->point_on_line[Y], "px"); + Glib::ustring position_string_x = x_q.string(namedview->display_units); + Glib::ustring position_string_y = y_q.string(namedview->display_units); + + gchar *shortcuts = g_strdup_printf("; %s", _("Shift+drag to rotate, Ctrl+drag to move origin, Del to delete")); + + if ( are_near(this->normal_to_line, Geom::Point(1., 0.)) || + are_near(this->normal_to_line, -Geom::Point(1., 0.)) ) { + descr = g_strdup_printf(_("vertical, at %s"), position_string_x.c_str()); + } else if ( are_near(this->normal_to_line, Geom::Point(0., 1.)) || + are_near(this->normal_to_line, -Geom::Point(0., 1.)) ) { + descr = g_strdup_printf(_("horizontal, at %s"), position_string_y.c_str()); + } else { + double const radians = this->angle(); + double const degrees = Geom::deg_from_rad(radians); + int const degrees_int = (int) round(degrees); + descr = g_strdup_printf(_("at %d degrees, through (%s,%s)"), + degrees_int, position_string_x.c_str(), position_string_y.c_str()); + } + + if (verbose) { + gchar *oldDescr = descr; + descr = g_strconcat(oldDescr, shortcuts, NULL); + g_free(oldDescr); + } + + g_free(shortcuts); + } + + return descr; +} + +void sp_guide_remove(SPGuide *guide) +{ + g_assert(SP_IS_GUIDE(guide)); + + for (vector::const_iterator i(guide->attached_items.begin()), + iEnd(guide->attached_items.end()); + i != iEnd; ++i) + { + SPGuideAttachment const &att = *i; + remove_last(att.item->constraints, SPGuideConstraint(guide, att.snappoint_ix)); + } + guide->attached_items.clear(); + + //XML Tree being used directly while it shouldn't be. + sp_repr_unparent(guide->getRepr()); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-guide.h b/src/object/sp-guide.h new file mode 100644 index 000000000..25a0e5af8 --- /dev/null +++ b/src/object/sp-guide.h @@ -0,0 +1,111 @@ +#ifndef SEEN_SP_GUIDE_H +#define SEEN_SP_GUIDE_H + +/* + * SPGuide + * + * A guideline + * + * Copyright (C) Lauris Kaplinski 2000 + * Copyright (C) Johan Engelen 2007 + * Abhishek Sharma + * Jon A. Cruz + * + */ + +#include <2geom/point.h> +#include + +#include "sp-object.h" +#include "sp-guide-attachment.h" + +typedef unsigned int guint32; +extern "C" { + typedef void (*GCallback) (void); +} + +class SPDesktop; +struct SPCanvas; +struct SPCanvasGroup; +struct SPGuideLine; +#define SP_GUIDE(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_GUIDE(obj) (dynamic_cast((SPObject*)obj) != NULL) + +/* Represents the constraint on p that dot(g.direction, p) == g.position. */ +class SPGuide : public SPObject { +public: + SPGuide(); + virtual ~SPGuide() {} + + void set_color(const unsigned r, const unsigned g, const unsigned b, bool const commit); + void setColor(guint32 c); + void setHiColor(guint32 h) { hicolor = h; } + + guint32 getColor() const { return color; } + guint32 getHiColor() const { return hicolor; } + Geom::Point getPoint() const { return point_on_line; } + Geom::Point getNormal() const { return normal_to_line; } + + void moveto(Geom::Point const point_on_line, bool const commit); + void set_normal(Geom::Point const normal_to_line, bool const commit); + + void set_label(const char* label, bool const commit); + char const* getLabel() const { return label; } + + void set_locked(const bool locked, bool const commit); + bool getLocked() const { return locked; } + + static SPGuide *createSPGuide(SPDocument *doc, Geom::Point const &pt1, Geom::Point const &pt2); + + void showSPGuide(SPCanvasGroup *group, GCallback handler); + void hideSPGuide(SPCanvas *canvas); + void showSPGuide(); // argument-free versions + void hideSPGuide(); + + void sensitize(SPCanvas *canvas, bool sensitive); + + bool isHorizontal() const { return (normal_to_line[Geom::X] == 0.); }; + bool isVertical() const { return (normal_to_line[Geom::Y] == 0.); }; + + char* description(bool const verbose = true) const; + + double angle() const { return std::atan2( - normal_to_line[Geom::X], normal_to_line[Geom::Y] ); } + double getDistanceFrom(Geom::Point const &pt) const; + Geom::Point getPositionFrom(Geom::Point const &pt) const; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + virtual void set(unsigned int key, const char* value); + + char* label; + std::vector views; // contains an object of type SPGuideline (see display/guideline.cpp for definition) + bool locked; + Geom::Point normal_to_line; + Geom::Point point_on_line; + + guint32 color; + guint32 hicolor; +public: + std::vector attached_items; // unused +}; + +// These functions rightfully belong to SPDesktop. What gives?! +void sp_guide_pt_pairs_to_guides(SPDocument *doc, std::list > &pts); +void sp_guide_create_guides_around_page(SPDesktop *dt); +void sp_guide_delete_all_guides(SPDesktop *dt); + +void sp_guide_remove(SPGuide *guide); + +#endif // SEEN_SP_GUIDE_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-hatch-path.cpp b/src/object/sp-hatch-path.cpp new file mode 100644 index 000000000..4497b6911 --- /dev/null +++ b/src/object/sp-hatch-path.cpp @@ -0,0 +1,340 @@ +/** + * @file + * SVG implementation + */ +/* + * Author: + * Tomasz Boczkowski + * Jon A. Cruz + * + * Copyright (C) 2014 Tomasz Boczkowski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include <2geom/path.h> + +#include "svg/svg.h" +#include "display/cairo-utils.h" +#include "display/curve.h" +#include "display/drawing-context.h" +#include "display/drawing-surface.h" +#include "display/drawing.h" +#include "display/drawing-shape.h" +#include "helper/geom.h" +#include "attributes.h" +#include "document-private.h" +#include "sp-item.h" +#include "sp-hatch-path.h" +#include "svg/css-ostringstream.h" + +SPHatchPath::SPHatchPath() + : offset(), + _display(), + _curve(NULL), + _continuous(false) +{ + offset.unset(); +} + +SPHatchPath::~SPHatchPath() +{ +} + +void SPHatchPath::setCurve(SPCurve *new_curve, bool owner) +{ + if (_curve) { + _curve = _curve->unref(); + } + + if (new_curve) { + if (owner) { + _curve = new_curve->ref(); + } else { + _curve = new_curve->copy(); + } + } + + requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +void SPHatchPath::build(SPDocument* doc, Inkscape::XML::Node* repr) +{ + SPObject::build(doc, repr); + + readAttr("d"); + readAttr("offset"); + readAttr( "style" ); + + style->fill.setNone(); +} + +void SPHatchPath::release() +{ + for (ViewIterator iter = _display.begin(); iter != _display.end(); ++iter) { + delete iter->arenaitem; + iter->arenaitem = NULL; + } + + SPObject::release(); +} + +void SPHatchPath::set(unsigned int key, const gchar* value) +{ + switch (key) { + case SP_ATTR_D: + if (value) { + Geom::PathVector pv; + _readHatchPathVector(value, pv, _continuous); + SPCurve *curve = new SPCurve(pv); + + if (curve) { + setCurve(curve, true); + curve->unref(); + } + } else { + setCurve(NULL, true); + } + + requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_OFFSET: + offset.readOrUnset(value); + requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + default: + if (SP_ATTRIBUTE_IS_CSS(key)) { + style->readFromObject( this ); + requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + } else { + SPObject::set(key, value); + } + break; + } +} + + +void SPHatchPath::update(SPCtx* ctx, unsigned int flags) +{ + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; + } + + if (flags & (SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + if (style->stroke_width.unit == SP_CSS_UNIT_PERCENT) { + //TODO: Check specification + + SPItemCtx *ictx = static_cast(ctx); + double const aw = (ictx) ? 1.0 / ictx->i2vp.descrim() : 1.0; + style->stroke_width.computed = style->stroke_width.value * aw; + + for (ViewIterator iter = _display.begin(); iter != _display.end(); ++iter) { + iter->arenaitem->setStyle(style); + } + } + } + + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG)) { + for (ViewIterator iter = _display.begin(); iter != _display.end(); ++iter) { + _updateView(*iter); + } + } +} + +bool SPHatchPath::isValid() const +{ + if (_curve && (_repeatLength() <= 0)) { + return false; + } else { + return true; + } +} + +Inkscape::DrawingItem *SPHatchPath::show(Inkscape::Drawing &drawing, unsigned int key, Geom::OptInterval extents) +{ + Inkscape::DrawingShape *s = new Inkscape::DrawingShape(drawing); + _display.push_front(View(s, key)); + _display.front().extents = extents; + + _updateView(_display.front()); + + return s; +} + +void SPHatchPath::hide(unsigned int key) +{ + for (ViewIterator iter = _display.begin(); iter != _display.end(); ++iter) { + if (iter->key == key) { + delete iter->arenaitem; + _display.erase(iter); + return; + } + } + + g_assert_not_reached(); +} + +void SPHatchPath::setStripExtents(unsigned int key, Geom::OptInterval const &extents) +{ + for (ViewIterator iter = _display.begin(); iter != _display.end(); ++iter) { + if (iter->key == key) { + iter->extents = extents; + break; + } + } +} + +Geom::Interval SPHatchPath::bounds() const +{ + Geom::OptRect bbox; + Geom::Interval result; + + Geom::Affine transform = Geom::Translate(offset.computed, 0); + if (!_curve) { + SPCurve test_curve; + test_curve.moveto(Geom::Point(0, 0)); + test_curve.moveto(Geom::Point(0, 1)); + bbox = bounds_exact_transformed(test_curve.get_pathvector(), transform); + } else { + bbox = bounds_exact_transformed(_curve->get_pathvector(), transform); + } + + gdouble stroke_width = style->stroke_width.computed; + result.setMin(bbox->left() - stroke_width / 2); + result.setMax(bbox->right() + stroke_width / 2); + return result; +} + +SPCurve *SPHatchPath::calculateRenderCurve(unsigned key) const +{ + for (ConstViewIterator iter = _display.begin(); iter != _display.end(); ++iter) { + if (iter->key == key) { + return _calculateRenderCurve(*iter); + } + } + g_assert_not_reached(); + return NULL; +} + +gdouble SPHatchPath::_repeatLength() const +{ + gdouble val = 0; + + if (_curve && _curve->last_point()) { + val = _curve->last_point()->y(); + } + + return val; +} + +void SPHatchPath::_updateView(View &view) +{ + SPCurve *calculated_curve = _calculateRenderCurve(view); + + Geom::Affine offset_transform = Geom::Translate(offset.computed, 0); + view.arenaitem->setTransform(offset_transform); + style->fill.setNone(); + view.arenaitem->setStyle(style); + view.arenaitem->setPath(calculated_curve); + + calculated_curve->unref(); +} + +SPCurve *SPHatchPath::_calculateRenderCurve(View const &view) const +{ + SPCurve *calculated_curve = new SPCurve; + + if (!view.extents) { + return calculated_curve; + } + + if (!_curve) { + calculated_curve->moveto(0, view.extents->min()); + calculated_curve->lineto(0, view.extents->max()); + //TODO: if hatch has a dasharray defined, adjust line ends + } else { + gdouble repeatLength = _repeatLength(); + if (repeatLength > 0) { + gdouble initial_y = floor(view.extents->min() / repeatLength) * repeatLength; + int segment_cnt = ceil((view.extents->extent()) / repeatLength) + 1; + + SPCurve *segment =_curve->copy(); + segment->transform(Geom::Translate(0, initial_y)); + + Geom::Affine step_transform = Geom::Translate(0, repeatLength); + for (int i = 0; i < segment_cnt; ++i) { + if (_continuous) { + calculated_curve->append_continuous(segment, 0.0625); + } else { + calculated_curve->append(segment, false); + } + segment->transform(step_transform); + } + + segment->unref(); + } + } + return calculated_curve; +} + + +void SPHatchPath::_readHatchPathVector(char const *str, Geom::PathVector &pathv, bool &continous_join) +{ + if (!str) { + return; + } + + pathv = sp_svg_read_pathv(str); + + if (!pathv.empty()) { + continous_join = false; + } else { + Glib::ustring str2 = Glib::ustring::compose("M0,0 %1", str); + pathv = sp_svg_read_pathv(str2.c_str()); + if (pathv.empty()) { + return; + } + + gdouble last_point_x = pathv.back().finalPoint().x(); + Inkscape::CSSOStringStream stream; + stream << last_point_x; + Glib::ustring str3 = Glib::ustring::compose("M%1,0 %2", stream.str(), str); + Geom::PathVector pathv3 = sp_svg_read_pathv(str3.c_str()); + + //Path can be composed of relative commands only. In this case final point + //coordinates would depend on first point position. If this happens, fall + //back to using 0,0 as first path point + if (pathv3.back().finalPoint().y() == pathv.back().finalPoint().y()) { + pathv = pathv3; + } + continous_join = true; + } +} + +SPHatchPath::View::View(Inkscape::DrawingShape *arenaitem, int key) + : arenaitem(arenaitem), + extents(), + key(key) +{ +} + +SPHatchPath::View::~View() +{ + // remember, do not delete arenaitem here + arenaitem = NULL; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: + */ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-hatch-path.h b/src/object/sp-hatch-path.h new file mode 100644 index 000000000..11fc274ec --- /dev/null +++ b/src/object/sp-hatch-path.h @@ -0,0 +1,95 @@ +/** + * @file + * SVG implementation + */ +/* + * Author: + * Tomasz Boczkowski + * Jon A. Cruz + * + * Copyright (C) 2014 Tomasz Boczkowski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_SP_HATCH_PATH_H +#define SEEN_SP_HATCH_PATH_H + +#include +#include +#include +#include + +#include "svg/svg-length.h" + +namespace Inkscape { + +class Drawing; +class DrawingShape; + +} + +class SPHatchPath : public SPObject { +public: + SPHatchPath(); + virtual ~SPHatchPath(); + + SVGLength offset; + + void setCurve(SPCurve *curve, bool owner); + + bool isValid() const; + + Inkscape::DrawingItem *show(Inkscape::Drawing &drawing, unsigned int key, Geom::OptInterval extents); + void hide(unsigned int key); + + void setStripExtents(unsigned int key, Geom::OptInterval const &extents); + Geom::Interval bounds() const; + + SPCurve *calculateRenderCurve(unsigned key) const; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + virtual void set(unsigned int key, const gchar* value); + virtual void update(SPCtx* ctx, unsigned int flags); + +private: + class View { + public: + View(Inkscape::DrawingShape *arenaitem, int key); + //Do not delete arenaitem in destructor. + + ~View(); + + Inkscape::DrawingShape *arenaitem; + Geom::OptInterval extents; + unsigned int key; + }; + + typedef std::list::iterator ViewIterator; + typedef std::list::const_iterator ConstViewIterator; + std::list _display; + + gdouble _repeatLength() const; + void _updateView(View &view); + SPCurve *_calculateRenderCurve(View const &view) const; + + void _readHatchPathVector(char const *str, Geom::PathVector &pathv, bool &continous_join); + + SPCurve *_curve; + bool _continuous; +}; + +#endif // SEEN_SP_HATCH_PATH_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-hatch.cpp b/src/object/sp-hatch.cpp new file mode 100644 index 000000000..f1958a53b --- /dev/null +++ b/src/object/sp-hatch.cpp @@ -0,0 +1,739 @@ +/** + * @file + * SVG implementation + */ +/* + * Authors: + * Tomasz Boczkowski + * Jon A. Cruz + * + * Copyright (C) 2014 Tomasz Boczkowski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-hatch.h" + +#include +#include + +#include <2geom/transforms.h> +#include + +#include "bad-uri-exception.h" +#include "svg/svg.h" +#include "display/cairo-utils.h" +#include "display/drawing-context.h" +#include "display/drawing-surface.h" +#include "display/drawing.h" +#include "display/drawing-pattern.h" +#include "attributes.h" +#include "document-private.h" +#include "sp-hatch-path.h" + +SPHatch::SPHatch() + : SPPaintServer(), + href(), + ref(NULL), // avoiding 'this' in initializer list + _hatchUnits(UNITS_OBJECTBOUNDINGBOX), + _hatchUnits_set(false), + _hatchContentUnits(UNITS_USERSPACEONUSE), + _hatchContentUnits_set(false), + _hatchTransform(Geom::identity()), + _hatchTransform_set(false), + _x(), + _y(), + _pitch(), + _rotate(), + _modified_connection(), + _display() +{ + ref = new SPHatchReference(this); + ref->changedSignal().connect(sigc::mem_fun(this, &SPHatch::_onRefChanged)); + + // TODO check that these should start already as unset: + _x.unset(); + _y.unset(); + _pitch.unset(); + _rotate.unset(); +} + +SPHatch::~SPHatch() { +} + +void SPHatch::build(SPDocument* doc, Inkscape::XML::Node* repr) +{ + SPPaintServer::build(doc, repr); + + readAttr("hatchUnits"); + readAttr("hatchContentUnits"); + readAttr("hatchTransform"); + readAttr("x"); + readAttr("y"); + readAttr("pitch"); + readAttr("rotate"); + readAttr("xlink:href"); + readAttr( "style" ); + + // Register ourselves + doc->addResource("hatch", this); +} + +void SPHatch::release() +{ + if (document) { + // Unregister ourselves + document->removeResource("hatch", this); + } + + std::vector children(hatchPaths()); + for (ViewIterator view_iter = _display.begin(); view_iter != _display.end(); ++view_iter) { + for (ChildIterator child_iter = children.begin(); child_iter != children.end(); ++child_iter) { + SPHatchPath *child = *child_iter; + child->hide(view_iter->key); + } + delete view_iter->arenaitem; + view_iter->arenaitem = NULL; + } + + if (ref) { + _modified_connection.disconnect(); + ref->detach(); + delete ref; + ref = NULL; + } + + SPPaintServer::release(); +} + +void SPHatch::child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref) +{ + SPObject::child_added(child, ref); + + SPHatchPath *path_child = dynamic_cast(document->getObjectByRepr(child)); + + if (path_child) { + for (ViewIterator iter = _display.begin(); iter != _display.end(); ++iter) { + Geom::OptInterval extents = _calculateStripExtents(iter->bbox); + Inkscape::DrawingItem *ac = path_child->show(iter->arenaitem->drawing(), iter->key, extents); + + path_child->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + if (ac) { + iter->arenaitem->prependChild(ac); + } + } + } + //FIXME: notify all hatches that refer to this child set +} + +void SPHatch::set(unsigned int key, const gchar* value) +{ + switch (key) { + case SP_ATTR_HATCHUNITS: + if (value) { + if (!strcmp(value, "userSpaceOnUse")) { + _hatchUnits = UNITS_USERSPACEONUSE; + } else { + _hatchUnits = UNITS_OBJECTBOUNDINGBOX; + } + + _hatchUnits_set = true; + } else { + _hatchUnits_set = false; + } + + requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_HATCHCONTENTUNITS: + if (value) { + if (!strcmp(value, "userSpaceOnUse")) { + _hatchContentUnits = UNITS_USERSPACEONUSE; + } else { + _hatchContentUnits = UNITS_OBJECTBOUNDINGBOX; + } + + _hatchContentUnits_set = true; + } else { + _hatchContentUnits_set = false; + } + + requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_HATCHTRANSFORM: { + Geom::Affine t; + + if (value && sp_svg_transform_read(value, &t)) { + _hatchTransform = t; + _hatchTransform_set = true; + } else { + _hatchTransform = Geom::identity(); + _hatchTransform_set = false; + } + + requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + } + case SP_ATTR_X: + _x.readOrUnset(value); + requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_Y: + _y.readOrUnset(value); + requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_PITCH: + _pitch.readOrUnset(value); + requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_ROTATE: + _rotate.readOrUnset(value); + requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_XLINK_HREF: + if (value && href == value) { + // Href unchanged, do nothing. + } else { + href.clear(); + + if (value) { + // First, set the href field; it's only used in the "unchanged" check above. + href = value; + // Now do the attaching, which emits the changed signal. + if (value) { + try { + ref->attach(Inkscape::URI(value)); + } catch (Inkscape::BadURIException &e) { + g_warning("%s", e.what()); + ref->detach(); + } + } else { + ref->detach(); + } + } + } + requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + default: + if (SP_ATTRIBUTE_IS_CSS(key)) { + style->readFromObject( this ); + requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + } else { + SPPaintServer::set(key, value); + } + break; + } +} + +bool SPHatch::_hasHatchPatchChildren(SPHatch const *hatch) +{ + for (auto& child: hatch->children) { + SPHatchPath const *hatchPath = dynamic_cast(&child); + if (hatchPath) { + return true; + } + } + return false; +} + +std::vector SPHatch::hatchPaths() +{ + std::vector list; + SPHatch *src = chase_hrefs(this, sigc::ptr_fun(&_hasHatchPatchChildren)); + + if (src) { + for (auto& child: src->children) { + SPHatchPath *hatchPath = dynamic_cast(&child); + if (hatchPath) { + list.push_back(hatchPath); + } + } + } + return list; +} + +std::vector SPHatch::hatchPaths() const +{ + std::vector list; + SPHatch const *src = chase_hrefs(this, sigc::ptr_fun(&_hasHatchPatchChildren)); + + if (src) { + for (auto& child: src->children) { + SPHatchPath const *hatchPath = dynamic_cast(&child); + if (hatchPath) { + list.push_back(hatchPath); + } + } + } + return list; +} + +// TODO: ::remove_child and ::order_changed handles - see SPPattern + + +void SPHatch::update(SPCtx* ctx, unsigned int flags) +{ + typedef std::list::iterator ViewIterator; + + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + std::vector children(hatchPaths()); + + for (ChildIterator iter = children.begin(); iter != children.end(); ++iter) { + SPHatchPath* child = *iter; + + sp_object_ref(child, NULL); + + for (ViewIterator view_iter = _display.begin(); view_iter != _display.end(); ++view_iter) { + Geom::OptInterval strip_extents = _calculateStripExtents(view_iter->bbox); + child->setStripExtents(view_iter->key, strip_extents); + } + + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + + child->updateDisplay(ctx, flags); + } + + sp_object_unref(child, NULL); + } + + for (ViewIterator iter = _display.begin(); iter != _display.end(); ++iter) { + _updateView(*iter); + } +} + +void SPHatch::modified(unsigned int flags) +{ + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + std::vector children(hatchPaths()); + + for (ChildIterator iter = children.begin(); iter != children.end(); ++iter) { + SPObject *child = *iter; + + sp_object_ref(child, NULL); + + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + + sp_object_unref(child, NULL); + } +} + +void SPHatch::_onRefChanged(SPObject *old_ref, SPObject *ref) +{ + typedef std::list::iterator ViewIterator; + + if (old_ref) { + _modified_connection.disconnect(); + } + + SPHatch *hatch = dynamic_cast(ref); + if (hatch) { + _modified_connection = ref->connectModified(sigc::mem_fun(this, &SPHatch::_onRefModified)); + } + + if (!_hasHatchPatchChildren(this)) { + SPHatch *old_shown = NULL; + SPHatch *new_shown = NULL; + std::vector oldhatchPaths; + std::vector newhatchPaths; + + SPHatch *old_hatch = dynamic_cast(old_ref); + if (old_hatch) { + old_shown = old_hatch->rootHatch(); + oldhatchPaths = old_shown->hatchPaths(); + } + if (hatch) { + new_shown = hatch->rootHatch(); + newhatchPaths = new_shown->hatchPaths(); + } + if (old_shown != new_shown) { + + for (ViewIterator iter = _display.begin(); iter != _display.end(); ++iter) { + Geom::OptInterval extents = _calculateStripExtents(iter->bbox); + + for (ChildIterator child_iter = oldhatchPaths.begin(); child_iter != oldhatchPaths.end(); ++child_iter) { + SPHatchPath *child = *child_iter; + child->hide(iter->key); + } + for (ChildIterator child_iter = newhatchPaths.begin(); child_iter != newhatchPaths.end(); ++child_iter) { + SPHatchPath *child = *child_iter; + Inkscape::DrawingItem *cai = child->show(iter->arenaitem->drawing(), iter->key, extents); + child->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + if (cai) { + iter->arenaitem->appendChild(cai); + } + + } + } + } + } + + _onRefModified(ref, 0); +} + +void SPHatch::_onRefModified(SPObject */*ref*/, guint /*flags*/) +{ + requestModified(SP_OBJECT_MODIFIED_FLAG); + // Conditional to avoid causing infinite loop if there's a cycle in the href chain. +} + + +SPHatch *SPHatch::rootHatch() +{ + SPHatch *src = chase_hrefs(this, sigc::ptr_fun(&_hasHatchPatchChildren)); + return src ? src : this; // document is broken, we can't get to root; but at least we can return pat which is supposedly a valid hatch +} + +// Access functions that look up fields up the chain of referenced hatchs and return the first one which is set +// FIXME: all of them must use chase_hrefs as children() and rootHatch() + +SPHatch::HatchUnits SPHatch::hatchUnits() const +{ + HatchUnits units = _hatchUnits; + for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : NULL) { + if (pat_i->_hatchUnits_set) { + units = pat_i->_hatchUnits; + break; + } + } + return units; +} + +SPHatch::HatchUnits SPHatch::hatchContentUnits() const +{ + HatchUnits units = _hatchContentUnits; + for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : NULL) { + if (pat_i->_hatchContentUnits_set) { + units = pat_i->_hatchContentUnits; + break; + } + } + return units; +} + +Geom::Affine const &SPHatch::hatchTransform() const +{ + for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : NULL) { + if (pat_i->_hatchTransform_set) { + return pat_i->_hatchTransform; + } + } + return _hatchTransform; +} + +gdouble SPHatch::x() const +{ + gdouble val = 0; + for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : NULL) { + if (pat_i->_x._set) { + val = pat_i->_x.computed; + break; + } + } + return val; +} + +gdouble SPHatch::y() const +{ + gdouble val = 0; + for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : NULL) { + if (pat_i->_y._set) { + val = pat_i->_y.computed; + break; + } + } + return val; +} + +gdouble SPHatch::pitch() const +{ + gdouble val = 0; + for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : NULL) { + if (pat_i->_pitch._set) { + val = pat_i->_pitch.computed; + break; + } + } + return val; +} + +gdouble SPHatch::rotate() const +{ + gdouble val = 0; + for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : NULL) { + if (pat_i->_rotate._set) { + val = pat_i->_rotate.computed; + break; + } + } + return val; +} + +bool SPHatch::isValid() const +{ + bool valid = false; + + if (pitch() > 0) { + std::vector children(hatchPaths()); + if (!children.empty()) { + valid = true; + for (ConstChildIterator iter = children.begin(); (iter != children.end()) && valid; ++iter) { + SPHatchPath const *child = *iter; + valid = child->isValid(); + } + } + } + + return valid; +} + +Inkscape::DrawingPattern *SPHatch::show(Inkscape::Drawing &drawing, unsigned int key, Geom::OptRect bbox) +{ + Inkscape::DrawingPattern *ai = new Inkscape::DrawingPattern(drawing); + //TODO: set some debug flag to see DrawingPattern + _display.push_front(View(ai, key)); + _display.front().bbox = bbox; + + std::vector children(hatchPaths()); + + Geom::OptInterval extents = _calculateStripExtents(bbox); + for (ChildIterator iter = children.begin(); iter != children.end(); ++iter) { + SPHatchPath *child = *iter; + Inkscape::DrawingItem *cai = child->show(drawing, key, extents); + if (cai) { + ai->appendChild(cai); + } + } + + View& view = _display.front(); + _updateView(view); + + return ai; +} + +void SPHatch::hide(unsigned int key) +{ + std::vector children(hatchPaths()); + + for (ChildIterator iter = children.begin(); iter != children.end(); ++iter) { + SPHatchPath *child = *iter; + child->hide(key); + } + + for (ViewIterator iter = _display.begin(); iter != _display.end(); ++iter) { + if (iter->key == key) { + delete iter->arenaitem; + _display.erase(iter); + return; + } + } + + g_assert_not_reached(); +} + + +Geom::Interval SPHatch::bounds() const +{ + Geom::Interval result; + std::vector children(hatchPaths()); + + for (ConstChildIterator iter = children.begin(); iter != children.end(); ++iter) { + SPHatchPath const *child = *iter; + if (result.extent() == 0) { + result = child->bounds(); + } else { + result |= child->bounds(); + } + } + return result; +} + +SPHatch::RenderInfo SPHatch::calculateRenderInfo(unsigned key) const +{ + RenderInfo info; + for (ConstViewIterator iter = _display.begin(); iter != _display.end(); ++iter) { + if (iter->key == key) { + return _calculateRenderInfo(*iter); + } + } + g_assert_not_reached(); + return info; +} + +void SPHatch::_updateView(View &view) +{ + RenderInfo info = _calculateRenderInfo(view); + //The rendering of hatch overflow is implemented by repeated drawing + //of hatch paths over one strip. Within each iteration paths are moved by pitch value. + //The movement progresses from right to left. This gives the same result + //as drawing whole strips in left-to-right order. + + + view.arenaitem->setChildTransform(info.child_transform); + view.arenaitem->setPatternToUserTransform(info.pattern_to_user_transform); + view.arenaitem->setTileRect(info.tile_rect); + view.arenaitem->setStyle(style); + view.arenaitem->setOverflow(info.overflow_initial_transform, info.overflow_steps, + info.overflow_step_transform); +} + +SPHatch::RenderInfo SPHatch::_calculateRenderInfo(View const &view) const +{ + RenderInfo info; + + Geom::OptInterval extents = _calculateStripExtents(view.bbox); + if (extents) { + double tile_x = x(); + double tile_y = y(); + double tile_width = pitch(); + double tile_height = extents->max() - extents->min(); + double tile_rotate = rotate(); + double tile_render_y = extents->min(); + + if (view.bbox && (hatchUnits() == UNITS_OBJECTBOUNDINGBOX)) { + tile_x *= view.bbox->width(); + tile_y *= view.bbox->height(); + tile_width *= view.bbox->width(); + tile_height *= view.bbox->height(); + tile_render_y *= view.bbox->height(); + } + + // Pattern size in hatch space + Geom::Rect hatch_tile = Geom::Rect::from_xywh(0, tile_render_y, tile_width, tile_height); + + // Content to bbox + Geom::Affine content2ps; + if (view.bbox && (hatchContentUnits() == UNITS_OBJECTBOUNDINGBOX)) { + content2ps = Geom::Affine(view.bbox->width(), 0.0, 0.0, view.bbox->height(), 0, 0); + } + + // Tile (hatch space) to user. + Geom::Affine ps2user = Geom::Translate(tile_x, tile_y) * Geom::Rotate::from_degrees(tile_rotate) * hatchTransform(); + + info.child_transform = content2ps; + info.pattern_to_user_transform = ps2user; + info.tile_rect = hatch_tile; + + if (style->overflow.computed == SP_CSS_OVERFLOW_VISIBLE) { + Geom::Interval bounds = this->bounds(); + gdouble pitch = this->pitch(); + gdouble overflow_right_strip = floor(bounds.max() / pitch) * pitch; + info.overflow_steps = ceil((overflow_right_strip - bounds.min()) / pitch) + 1; + info.overflow_step_transform = Geom::Translate(pitch, 0.0); + info.overflow_initial_transform = Geom::Translate(-overflow_right_strip, 0.0); + } else { + info.overflow_steps = 1; + } + } + + return info; +} + +//calculates strip extents in content space +Geom::OptInterval SPHatch::_calculateStripExtents(Geom::OptRect const &bbox) const +{ + if (!bbox || (bbox->area() == 0)) { + return Geom::OptInterval(); + } else { + double tile_x = x(); + double tile_y = y(); + double tile_rotate = rotate(); + + Geom::Affine ps2user = Geom::Translate(tile_x, tile_y) * Geom::Rotate::from_degrees(tile_rotate) * hatchTransform(); + Geom::Affine user2ps = ps2user.inverse(); + + Geom::Interval extents; + for (int i = 0; i < 4; ++i) { + Geom::Point corner = bbox->corner(i); + Geom::Point corner_ps = corner * user2ps; + if (i == 0 || corner_ps.y() < extents.min()) { + extents.setMin(corner_ps.y()); + } + if (i == 0 || corner_ps.y() > extents.max()) { + extents.setMax(corner_ps.y()); + } + } + + if (hatchUnits() == UNITS_OBJECTBOUNDINGBOX) { + extents /= bbox->height(); + } + + return extents; + } +} + +cairo_pattern_t* SPHatch::pattern_new(cairo_t * /*base_ct*/, Geom::OptRect const &/*bbox*/, double /*opacity*/) +{ + //this code should not be used + //it is however required by the fact that SPPaintServer::hatch_new is pure virtual + return cairo_pattern_create_rgb(0.5, 0.5, 1.0); +} + +void SPHatch::setBBox(unsigned int key, Geom::OptRect const &bbox) +{ + for (ViewIterator iter = _display.begin(); iter != _display.end(); ++iter) { + if (iter->key == key) { + iter->bbox = bbox; + break; + } + } +} + +// + +SPHatch::RenderInfo::RenderInfo() + : child_transform(), + pattern_to_user_transform(), + tile_rect(), + overflow_steps(0), + overflow_step_transform(), + overflow_initial_transform() +{ +} + +SPHatch::RenderInfo::~RenderInfo() +{ +} + +// + +SPHatch::View::View(Inkscape::DrawingPattern *arenaitem, int key) + : arenaitem(arenaitem), + bbox(), + key(key) +{ +} + +SPHatch::View::~View() +{ + // remember, do not delete arenaitem here + arenaitem = NULL; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: + */ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-hatch.h b/src/object/sp-hatch.h new file mode 100644 index 000000000..546f06a1e --- /dev/null +++ b/src/object/sp-hatch.h @@ -0,0 +1,186 @@ +/** + * @file + * SVG implementation + */ +/* + * Authors: + * Tomasz Boczkowski + * Jon A. Cruz + * + * Copyright (C) 2014 Tomasz Boczkowski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_SP_HATCH_H +#define SEEN_SP_HATCH_H + +#include +#include +#include +#include + +#include "svg/svg-length.h" +#include "svg/svg-angle.h" +#include "sp-paint-server.h" +#include "uri-references.h" + +class SPHatchReference; +class SPHatchPath; +class SPItem; + +namespace Inkscape { + +class Drawing; +class DrawingPattern; + +namespace XML { + +class Node; + +} +} + +class SPHatch : public SPPaintServer { +public: + enum HatchUnits { + UNITS_USERSPACEONUSE, + UNITS_OBJECTBOUNDINGBOX + }; + + class RenderInfo { + public: + RenderInfo(); + ~RenderInfo(); + + Geom::Affine child_transform; + Geom::Affine pattern_to_user_transform; + Geom::Rect tile_rect; + + int overflow_steps; + Geom::Affine overflow_step_transform; + Geom::Affine overflow_initial_transform; + }; + + SPHatch(); + virtual ~SPHatch(); + + // Reference (href) + Glib::ustring href; + SPHatchReference *ref; + + gdouble x() const; + gdouble y() const; + gdouble pitch() const; + gdouble rotate() const; + HatchUnits hatchUnits() const; + HatchUnits hatchContentUnits() const; + Geom::Affine const &hatchTransform() const; + SPHatch *rootHatch(); //TODO: const + + std::vector hatchPaths(); + std::vector hatchPaths() const; + + bool isValid() const; + + Inkscape::DrawingPattern *show(Inkscape::Drawing &drawing, unsigned int key, Geom::OptRect bbox); + void hide(unsigned int key); + virtual cairo_pattern_t* pattern_new(cairo_t *ct, Geom::OptRect const &bbox, double opacity); + + RenderInfo calculateRenderInfo(unsigned key) const; + Geom::Interval bounds() const; + void setBBox(unsigned int key, Geom::OptRect const &bbox); + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref); + virtual void set(unsigned int key, const gchar* value); + virtual void update(SPCtx* ctx, unsigned int flags); + virtual void modified(unsigned int flags); + +private: + class View { + public: + View(Inkscape::DrawingPattern *arenaitem, int key); + //Do not delete arenaitem in destructor. + + ~View(); + + Inkscape::DrawingPattern *arenaitem; + Geom::OptRect bbox; + unsigned int key; + }; + + typedef std::vector::iterator ChildIterator; + typedef std::vector::const_iterator ConstChildIterator; + typedef std::list::iterator ViewIterator; + typedef std::list::const_iterator ConstViewIterator; + + static bool _hasHatchPatchChildren(SPHatch const* hatch); + + void _updateView(View &view); + RenderInfo _calculateRenderInfo(View const &view) const; + Geom::OptInterval _calculateStripExtents(Geom::OptRect const &bbox) const; + + + /** + * Gets called when the hatch is reattached to another + */ + void _onRefChanged(SPObject *old_ref, SPObject *ref); + + /** + * Gets called when the referenced is changed + */ + void _onRefModified(SPObject *ref, guint flags); + + // patternUnits and patternContentUnits attribute + HatchUnits _hatchUnits : 1; + bool _hatchUnits_set : 1; + HatchUnits _hatchContentUnits : 1; + bool _hatchContentUnits_set : 1; + + // hatchTransform attribute + Geom::Affine _hatchTransform; + bool _hatchTransform_set : 1; + + // Strip + SVGLength _x; + SVGLength _y; + SVGLength _pitch; + SVGAngle _rotate; + + sigc::connection _modified_connection; + + std::list _display; +}; + + +class SPHatchReference : public Inkscape::URIReference { +public: + SPHatchReference (SPObject *obj) + : URIReference(obj) + {} + + SPHatch *getObject() const { + return reinterpret_cast(URIReference::getObject()); + } + +protected: + virtual bool _acceptObject(SPObject *obj) const { + return dynamic_cast(obj) != NULL && URIReference::_acceptObject(obj); + } +}; + +#endif // SEEN_SP_HATCH_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-image.cpp b/src/object/sp-image.cpp new file mode 100644 index 000000000..6736efdec --- /dev/null +++ b/src/object/sp-image.cpp @@ -0,0 +1,805 @@ +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * Edward Flick (EAF) + * Abhishek Sharma + * Jon A. Cruz + * + * Copyright (C) 1999-2005 Authors + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include <2geom/rect.h> +#include <2geom/transforms.h> +#include + +#include "display/drawing-image.h" +#include "display/cairo-utils.h" +#include "display/curve.h" +//Added for preserveAspectRatio support -- EAF +#include "attributes.h" +#include "print.h" +#include "brokenimage.xpm" +#include "document.h" +#include "sp-image.h" +#include "sp-clippath.h" +#include "xml/quote.h" +#include "preferences.h" +#include "io/sys.h" + +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) +#include "cms-system.h" +#include "color-profile.h" + +#if HAVE_LIBLCMS2 +# include +#elif HAVE_LIBLCMS1 +# include +#endif // HAVE_LIBLCMS2 + +//#define DEBUG_LCMS +#ifdef DEBUG_LCMS +#define DEBUG_MESSAGE(key, ...)\ +{\ + g_message( __VA_ARGS__ );\ +} +#include +#else +#define DEBUG_MESSAGE(key, ...) +#endif // DEBUG_LCMS +#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) +/* + * SPImage + */ + +// TODO: give these constants better names: +#define MAGIC_EPSILON 1e-9 +#define MAGIC_EPSILON_TOO 1e-18 +// TODO: also check if it is correct to be using two different epsilon values + +static void sp_image_set_curve(SPImage *image); + +static Inkscape::Pixbuf *sp_image_repr_read_image(gchar const *href, gchar const *absref, gchar const *base ); +static void sp_image_update_arenaitem (SPImage *img, Inkscape::DrawingImage *ai); +static void sp_image_update_canvas_image (SPImage *image); + +#ifdef DEBUG_LCMS +extern guint update_in_progress; +#define DEBUG_MESSAGE_SCISLAC(key, ...) \ +{\ + Inkscape::Preferences *prefs = Inkscape::Preferences::get();\ + bool dump = prefs->getBool("/options/scislac/" #key);\ + bool dumpD = prefs->getBool("/options/scislac/" #key "D");\ + bool dumpD2 = prefs->getBool("/options/scislac/" #key "D2");\ + dumpD &&= ( (update_in_progress == 0) || dumpD2 );\ + if ( dump )\ + {\ + g_message( __VA_ARGS__ );\ +\ + }\ + if ( dumpD )\ + {\ + GtkWidget *dialog = gtk_message_dialog_new(NULL,\ + GTK_DIALOG_DESTROY_WITH_PARENT, \ + GTK_MESSAGE_INFO, \ + GTK_BUTTONS_OK, \ + __VA_ARGS__ \ + );\ + g_signal_connect_swapped(dialog, "response",\ + G_CALLBACK(gtk_widget_destroy), \ + dialog); \ + gtk_widget_show_all( dialog );\ + }\ +} +#else // DEBUG_LCMS +#define DEBUG_MESSAGE_SCISLAC(key, ...) +#endif // DEBUG_LCMS + +SPImage::SPImage() : SPItem(), SPViewBox() { + + this->x.unset(); + this->y.unset(); + this->width.unset(); + this->height.unset(); + this->clipbox = Geom::Rect(); + this->sx = this->sy = 1.0; + this->ox = this->oy = 0.0; + + this->curve = NULL; + + this->href = 0; +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + this->color_profile = 0; +#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + this->pixbuf = 0; +} + +SPImage::~SPImage() { +} + +void SPImage::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPItem::build(document, repr); + + this->readAttr( "xlink:href" ); + this->readAttr( "x" ); + this->readAttr( "y" ); + this->readAttr( "width" ); + this->readAttr( "height" ); + this->readAttr( "preserveAspectRatio" ); + this->readAttr( "color-profile" ); + + /* Register */ + document->addResource("image", this); +} + +void SPImage::release() { + if (this->document) { + // Unregister ourselves + this->document->removeResource("image", this); + } + + if (this->href) { + g_free (this->href); + this->href = NULL; + } + + delete this->pixbuf; + this->pixbuf = NULL; + +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + if (this->color_profile) { + g_free (this->color_profile); + this->color_profile = NULL; + } +#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + + if (this->curve) { + this->curve = this->curve->unref(); + } + + SPItem::release(); +} + +void SPImage::set(unsigned int key, const gchar* value) { + switch (key) { + case SP_ATTR_XLINK_HREF: + g_free (this->href); + this->href = (value) ? g_strdup (value) : NULL; + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_IMAGE_HREF_MODIFIED_FLAG); + break; + + case SP_ATTR_X: + /* ex, em not handled correctly. */ + if (!this->x.read(value)) { + this->x.unset(); + } + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_Y: + /* ex, em not handled correctly. */ + if (!this->y.read(value)) { + this->y.unset(); + } + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_WIDTH: + /* ex, em not handled correctly. */ + if (!this->width.read(value)) { + this->width.unset(); + } + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_HEIGHT: + /* ex, em not handled correctly. */ + if (!this->height.read(value)) { + this->height.unset(); + } + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_PRESERVEASPECTRATIO: + set_preserveAspectRatio( value ); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + break; + +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + case SP_PROP_COLOR_PROFILE: + if ( this->color_profile ) { + g_free (this->color_profile); + } + + this->color_profile = (value) ? g_strdup (value) : NULL; + + if ( value ) { + DEBUG_MESSAGE( lcmsFour, " color-profile set to '%s'", value ); + } else { + DEBUG_MESSAGE( lcmsFour, " color-profile cleared" ); + } + + // TODO check on this HREF_MODIFIED flag + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_IMAGE_HREF_MODIFIED_FLAG); + break; + +#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + + default: + SPItem::set(key, value); + break; + } + + sp_image_set_curve(this); //creates a curve at the image's boundary for snapping +} + +// BLIP +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) +void SPImage::apply_profile(Inkscape::Pixbuf *pixbuf) { + + // TODO: this will prevent using MIME data when exporting. + // Integrate color correction into loading. + pixbuf->ensurePixelFormat(Inkscape::Pixbuf::PF_GDK); + int imagewidth = pixbuf->width(); + int imageheight = pixbuf->height(); + int rowstride = pixbuf->rowstride();; + guchar* px = pixbuf->pixels(); + + if ( px ) { + DEBUG_MESSAGE( lcmsFive, "in 's sp_image_update. About to call colorprofile_get_handle()" ); + + guint profIntent = Inkscape::RENDERING_INTENT_UNKNOWN; + cmsHPROFILE prof = Inkscape::CMSSystem::getHandle( this->document, + &profIntent, + this->color_profile ); + if ( prof ) { + cmsProfileClassSignature profileClass = cmsGetDeviceClass( prof ); + if ( profileClass != cmsSigNamedColorClass ) { + int intent = INTENT_PERCEPTUAL; + + switch ( profIntent ) { + case Inkscape::RENDERING_INTENT_RELATIVE_COLORIMETRIC: + intent = INTENT_RELATIVE_COLORIMETRIC; + break; + case Inkscape::RENDERING_INTENT_SATURATION: + intent = INTENT_SATURATION; + break; + case Inkscape::RENDERING_INTENT_ABSOLUTE_COLORIMETRIC: + intent = INTENT_ABSOLUTE_COLORIMETRIC; + break; + case Inkscape::RENDERING_INTENT_PERCEPTUAL: + case Inkscape::RENDERING_INTENT_UNKNOWN: + case Inkscape::RENDERING_INTENT_AUTO: + default: + intent = INTENT_PERCEPTUAL; + } + + cmsHPROFILE destProf = cmsCreate_sRGBProfile(); + cmsHTRANSFORM transf = cmsCreateTransform( prof, + TYPE_RGBA_8, + destProf, + TYPE_RGBA_8, + intent, 0 ); + if ( transf ) { + guchar* currLine = px; + for ( int y = 0; y < imageheight; y++ ) { + // Since the types are the same size, we can do the transformation in-place + cmsDoTransform( transf, currLine, currLine, imagewidth ); + currLine += rowstride; + } + + cmsDeleteTransform( transf ); + } else { + DEBUG_MESSAGE( lcmsSix, "in 's sp_image_update. Unable to create LCMS transform." ); + } + + cmsCloseProfile( destProf ); + } else { + DEBUG_MESSAGE( lcmsSeven, "in 's sp_image_update. Profile type is named color. Can't transform." ); + } + } else { + DEBUG_MESSAGE( lcmsEight, "in 's sp_image_update. No profile found." ); + } + } +} +#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + +void SPImage::update(SPCtx *ctx, unsigned int flags) { + + SPDocument *doc = this->document; + + SPItem::update(ctx, flags); + + if (flags & SP_IMAGE_HREF_MODIFIED_FLAG) { + delete this->pixbuf; + this->pixbuf = NULL; + + if (this->href) { + Inkscape::Pixbuf *pixbuf = NULL; + pixbuf = sp_image_repr_read_image ( + this->getRepr()->attribute("xlink:href"), + this->getRepr()->attribute("sodipodi:absref"), + doc->getBase()); + + if (pixbuf) { +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + if ( this->color_profile ) apply_profile( pixbuf ); +#endif + this->pixbuf = pixbuf; + } + } + } + + SPItemCtx *ictx = (SPItemCtx *) ctx; + + // Why continue without a pixbuf? So we can display "Missing Image" png. + // Eventually, we should properly support SVG image type (i.e. render it ourselves). + + if (this->pixbuf) { + if (!this->x._set) { + this->x.unit = SVGLength::PX; + this->x.computed = 0; + } + + if (!this->y._set) { + this->y.unit = SVGLength::PX; + this->y.computed = 0; + } + + if (!this->width._set) { + this->width.unit = SVGLength::PX; + this->width.computed = this->pixbuf->width(); + } + + if (!this->height._set) { + this->height.unit = SVGLength::PX; + this->height.computed = this->pixbuf->height(); + } + } + + // Calculate x, y, width, height from parent/initial viewport, see sp-root.cpp + this->calcDimsFromParentViewport(ictx); + + // Image creates a new viewport + ictx->viewport= Geom::Rect::from_xywh( this->x.computed, this->y.computed, + this->width.computed, this->height.computed); + + this->clipbox = ictx->viewport; + + this->ox = this->x.computed; + this->oy = this->y.computed; + + if (this->pixbuf) { + + // Viewbox is either from SVG (not supported) or dimensions of pixbuf (PNG, JPG) + this->viewBox = Geom::Rect::from_xywh(0, 0, this->pixbuf->width(), this->pixbuf->height()); + this->viewBox_set = true; + + // SPItemCtx rctx = + get_rctx( ictx ); + + this->ox = c2p[4]; + this->oy = c2p[5]; + this->sx = c2p[0]; + this->sy = c2p[3]; + } + + // TODO: eliminate ox, oy, sx, sy + sp_image_update_canvas_image ((SPImage *) this); +} + +void SPImage::modified(unsigned int flags) { +// SPItem::onModified(flags); + + if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) { + for (SPItemView *v = this->display; v != NULL; v = v->next) { + Inkscape::DrawingImage *img = dynamic_cast(v->arenaitem); + img->setStyle(this->style); + } + } +} + + +Inkscape::XML::Node *SPImage::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags ) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:image"); + } + + repr->setAttribute("xlink:href", this->href); + + /* fixme: Reset attribute if needed (Lauris) */ + if (this->x._set) { + sp_repr_set_svg_double(repr, "x", this->x.computed); + } + + if (this->y._set) { + sp_repr_set_svg_double(repr, "y", this->y.computed); + } + + if (this->width._set) { + sp_repr_set_svg_double(repr, "width", this->width.computed); + } + + if (this->height._set) { + sp_repr_set_svg_double(repr, "height", this->height.computed); + } + + //XML Tree being used directly here while it shouldn't be... + repr->setAttribute("preserveAspectRatio", this->getRepr()->attribute("preserveAspectRatio")); +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + if (this->color_profile) { + repr->setAttribute("color-profile", this->color_profile); + } +#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + + SPItem::write(xml_doc, repr, flags); + + return repr; +} + +Geom::OptRect SPImage::bbox(Geom::Affine const &transform, SPItem::BBoxType /*type*/) const { + Geom::OptRect bbox; + + if ((this->width.computed > 0.0) && (this->height.computed > 0.0)) { + bbox = Geom::Rect::from_xywh(this->x.computed, this->y.computed, this->width.computed, this->height.computed); + *bbox *= transform; + } + + return bbox; +} + +void SPImage::print(SPPrintContext *ctx) { + if (this->pixbuf && (this->width.computed > 0.0) && (this->height.computed > 0.0) ) { + Inkscape::Pixbuf *pb = new Inkscape::Pixbuf(*this->pixbuf); + pb->ensurePixelFormat(Inkscape::Pixbuf::PF_GDK); + + guchar *px = pb->pixels(); + int w = pb->width(); + int h = pb->height(); + int rs = pb->rowstride(); + + double vx = this->ox; + double vy = this->oy; + + Geom::Affine t; + Geom::Translate tp(vx, vy); + Geom::Scale s(this->sx, this->sy); + t = s * tp; + sp_print_image_R8G8B8A8_N(ctx, px, w, h, rs, t, this->style); + delete pb; + } +} + +const char* SPImage::displayName() const { + return _("Image"); +} + +gchar* SPImage::description() const { + char *href_desc; + + if (this->href) { + href_desc = (strncmp(this->href, "data:", 5) == 0) + ? g_strdup(_("embedded")) + : xml_quote_strdup(this->href); + } else { + g_warning("Attempting to call strncmp() with a null pointer."); + href_desc = g_strdup("(null_pointer)"); // we call g_free() on href_desc + } + + char *ret = ( this->pixbuf == NULL + ? g_strdup_printf(_("[bad reference]: %s"), href_desc) + : g_strdup_printf(_("%d × %d: %s"), + this->pixbuf->width(), + this->pixbuf->height(), + href_desc) ); + g_free(href_desc); + return ret; +} + +Inkscape::DrawingItem* SPImage::show(Inkscape::Drawing &drawing, unsigned int /*key*/, unsigned int /*flags*/) { + Inkscape::DrawingImage *ai = new Inkscape::DrawingImage(drawing); + + sp_image_update_arenaitem(this, ai); + + return ai; +} + +Inkscape::Pixbuf *sp_image_repr_read_image(gchar const *href, gchar const *absref, gchar const *base) +{ + Inkscape::Pixbuf *inkpb = 0; + + gchar const *filename = href; + + if (filename != NULL) { + if (strncmp (filename,"file:",5) == 0) { + gchar *fullname = g_filename_from_uri(filename, NULL, NULL); + if (fullname) { + inkpb = Inkscape::Pixbuf::create_from_file(fullname); + g_free(fullname); + if (inkpb != NULL) { + return inkpb; + } + } + } else if (strncmp (filename,"data:",5) == 0) { + /* data URI - embedded image */ + filename += 5; + inkpb = Inkscape::Pixbuf::create_from_data_uri(filename); + if (inkpb != NULL) { + return inkpb; + } + } else { + + if (!g_path_is_absolute (filename)) { + /* try to load from relative pos combined with document base*/ + const gchar *docbase = base; + if (!docbase) { + docbase = "."; + } + gchar *fullname = g_build_filename(docbase, filename, NULL); + + // document base can be wrong (on the temporary doc when importing bitmap from a + // different dir) or unset (when doc is not saved yet), so we check for base+href existence first, + // and if it fails, we also try to use bare href regardless of its g_path_is_absolute + if (g_file_test (fullname, G_FILE_TEST_EXISTS) && !g_file_test (fullname, G_FILE_TEST_IS_DIR)) { + inkpb = Inkscape::Pixbuf::create_from_file(fullname); + if (inkpb != NULL) { + g_free (fullname); + return inkpb; + } + } + g_free (fullname); + } + + /* try filename as absolute */ + if (g_file_test (filename, G_FILE_TEST_EXISTS) && !g_file_test (filename, G_FILE_TEST_IS_DIR)) { + inkpb = Inkscape::Pixbuf::create_from_file(filename); + if (inkpb != NULL) { + return inkpb; + } + } + } + } + + /* at last try to load from sp absolute path name */ + filename = absref; + if (filename != NULL) { + // using absref is outside of SVG rules, so we must at least warn the user + if ( base != NULL && href != NULL ) { + g_warning (" did not resolve to a valid image file (base dir is %s), now trying sodipodi:absref=\"%s\"", href, base, absref); + } else { + g_warning ("xlink:href did not resolve to a valid image file, now trying sodipodi:absref=\"%s\"", absref); + } + + inkpb = Inkscape::Pixbuf::create_from_file(filename); + if (inkpb != NULL) { + return inkpb; + } + } + /* Nope: We do not find any valid pixmap file :-( */ + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_xpm_data((const gchar **) brokenimage_xpm); + inkpb = new Inkscape::Pixbuf(pixbuf); + + /* It should be included xpm, so if it still does not does load, */ + /* our libraries are broken */ + g_assert (inkpb != NULL); + + return inkpb; +} + +/* We assert that realpixbuf is either NULL or identical size to pixbuf */ +static void +sp_image_update_arenaitem (SPImage *image, Inkscape::DrawingImage *ai) +{ + ai->setStyle(SP_OBJECT(image)->style); + ai->setPixbuf(image->pixbuf); + ai->setOrigin(Geom::Point(image->ox, image->oy)); + ai->setScale(image->sx, image->sy); + ai->setClipbox(image->clipbox); +} + +static void sp_image_update_canvas_image(SPImage *image) +{ + SPItem *item = SP_ITEM(image); + + for (SPItemView *v = item->display; v != NULL; v = v->next) { + sp_image_update_arenaitem(image, dynamic_cast(v->arenaitem)); + } +} + +void SPImage::snappoints(std::vector &p, Inkscape::SnapPreferences const *snapprefs) const { + /* An image doesn't have any nodes to snap, but still we want to be able snap one image + to another. Therefore we will create some snappoints at the corner, similar to a rect. If + the image is rotated, then the snappoints will rotate with it. Again, just like a rect. + */ + + if (this->clip_ref->getObject()) { + //We are looking at a clipped image: do not return any snappoints, as these might be + //far far away from the visible part from the clipped image + //TODO Do return snappoints, but only when within visual bounding box + } else { + if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_IMG_CORNER)) { + // The image has not been clipped: return its corners, which might be rotated for example + double const x0 = this->x.computed; + double const y0 = this->y.computed; + double const x1 = x0 + this->width.computed; + double const y1 = y0 + this->height.computed; + + Geom::Affine const i2d (this->i2dt_affine ()); + + p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x0, y0) * i2d, Inkscape::SNAPSOURCE_IMG_CORNER, Inkscape::SNAPTARGET_IMG_CORNER)); + p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x0, y1) * i2d, Inkscape::SNAPSOURCE_IMG_CORNER, Inkscape::SNAPTARGET_IMG_CORNER)); + p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x1, y1) * i2d, Inkscape::SNAPSOURCE_IMG_CORNER, Inkscape::SNAPTARGET_IMG_CORNER)); + p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x1, y0) * i2d, Inkscape::SNAPSOURCE_IMG_CORNER, Inkscape::SNAPTARGET_IMG_CORNER)); + } + } +} + +/* + * Initially we'll do: + * Transform x, y, set x, y, clear translation + */ + +Geom::Affine SPImage::set_transform(Geom::Affine const &xform) { + /* Calculate position in parent coords. */ + Geom::Point pos( Geom::Point(this->x.computed, this->y.computed) * xform ); + + /* This function takes care of translation and scaling, we return whatever parts we can't + handle. */ + Geom::Affine ret(Geom::Affine(xform).withoutTranslation()); + Geom::Point const scale(hypot(ret[0], ret[1]), + hypot(ret[2], ret[3])); + + if ( scale[Geom::X] > MAGIC_EPSILON ) { + ret[0] /= scale[Geom::X]; + ret[1] /= scale[Geom::X]; + } else { + ret[0] = 1.0; + ret[1] = 0.0; + } + + if ( scale[Geom::Y] > MAGIC_EPSILON ) { + ret[2] /= scale[Geom::Y]; + ret[3] /= scale[Geom::Y]; + } else { + ret[2] = 0.0; + ret[3] = 1.0; + } + + this->width = this->width.computed * scale[Geom::X]; + this->height = this->height.computed * scale[Geom::Y]; + + /* Find position in item coords */ + pos = pos * ret.inverse(); + this->x = pos[Geom::X]; + this->y = pos[Geom::Y]; + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + + return ret; +} + +static void sp_image_set_curve( SPImage *image ) +{ + //create a curve at the image's boundary for snapping + if ((image->height.computed < MAGIC_EPSILON_TOO) || (image->width.computed < MAGIC_EPSILON_TOO) || (image->clip_ref->getObject())) { + if (image->curve) { + image->curve = image->curve->unref(); + } + } else { + Geom::OptRect rect = image->bbox(Geom::identity(), SPItem::VISUAL_BBOX); + SPCurve *c = SPCurve::new_from_rect(*rect, true); + + if (image->curve) { + image->curve = image->curve->unref(); + } + + if (c) { + image->curve = c->ref(); + + c->unref(); + } + } +} + +/** + * Return duplicate of curve (if any exists) or NULL if there is no curve + */ +SPCurve *sp_image_get_curve( SPImage *image ) +{ + SPCurve *result = 0; + if (image->curve) { + result = image->curve->copy(); + } + return result; +} + +void sp_embed_image(Inkscape::XML::Node *image_node, Inkscape::Pixbuf *pb) +{ + bool free_data = false; + + // check whether the pixbuf has MIME data + guchar *data = NULL; + gsize len = 0; + std::string data_mimetype; + + data = const_cast(pb->getMimeData(len, data_mimetype)); + + if (data == NULL) { + // if there is no supported MIME data, embed as PNG + data_mimetype = "image/png"; + gdk_pixbuf_save_to_buffer(pb->getPixbufRaw(), reinterpret_cast(&data), &len, "png", NULL, NULL); + free_data = true; + } + + // Save base64 encoded data in image node + // this formula taken from Glib docs + gsize needed_size = len * 4 / 3 + len * 4 / (3 * 72) + 7; + needed_size += 5 + 8 + data_mimetype.size(); // 5 bytes for data: + 8 for ;base64, + + gchar *buffer = (gchar *) g_malloc(needed_size); + gchar *buf_work = buffer; + buf_work += g_sprintf(buffer, "data:%s;base64,", data_mimetype.c_str()); + + gint state = 0; + gint save = 0; + gsize written = 0; + written += g_base64_encode_step(data, len, TRUE, buf_work, &state, &save); + written += g_base64_encode_close(TRUE, buf_work + written, &state, &save); + buf_work[written] = 0; // null terminate + + // TODO: this is very wasteful memory-wise. + // It would be better to only keep the binary data around, + // and base64 encode on the fly when saving the XML. + image_node->setAttribute("xlink:href", buffer); + + g_free(buffer); + if (free_data) g_free(data); +} + +void sp_image_refresh_if_outdated( SPImage* image ) +{ + if ( image->href && image->pixbuf && image->pixbuf->modificationTime()) { + // It *might* change + + GStatBuf st; + memset(&st, 0, sizeof(st)); + int val = 0; + if (g_file_test (image->pixbuf->originalPath().c_str(), G_FILE_TEST_EXISTS)){ + val = g_stat(image->pixbuf->originalPath().c_str(), &st); + } + if ( !val ) { + // stat call worked. Check time now + if ( st.st_mtime != image->pixbuf->modificationTime() ) { + image->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_IMAGE_HREF_MODIFIED_FLAG); + } + } + } +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-image.h b/src/object/sp-image.h new file mode 100644 index 000000000..9cd5faa8b --- /dev/null +++ b/src/object/sp-image.h @@ -0,0 +1,73 @@ +/** @file + * SVG implementation + *//* + * Authors: + * Lauris Kaplinski + * Edward Flick (EAF) + * + * Copyright (C) 1999-2005 Authors + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_INKSCAPE_SP_IMAGE_H +#define SEEN_INKSCAPE_SP_IMAGE_H + +#include +#include "svg/svg-length.h" +#include "display/curve.h" +#include "sp-item.h" +#include "viewbox.h" +#include "sp-dimensions.h" + +#define SP_IMAGE(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_IMAGE(obj) (dynamic_cast((SPObject*)obj) != NULL) + +#define SP_IMAGE_HREF_MODIFIED_FLAG SP_OBJECT_USER_MODIFIED_FLAG_A + +namespace Inkscape { class Pixbuf; } +class SPImage : public SPItem, public SPViewBox, public SPDimensions { +public: + SPImage(); + virtual ~SPImage(); + + Geom::Rect clipbox; + double sx, sy; + double ox, oy; + + SPCurve *curve; // This curve is at the image's boundary for snapping + + char *href; +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + char *color_profile; +#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + + Inkscape::Pixbuf *pixbuf; + + virtual void build(SPDocument *document, Inkscape::XML::Node *repr); + virtual void release(); + virtual void set(unsigned int key, char const* value); + virtual void update(SPCtx *ctx, unsigned int flags); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); + virtual void modified(unsigned int flags); + + virtual Geom::OptRect bbox(Geom::Affine const &transform, SPItem::BBoxType type) const; + virtual void print(SPPrintContext *ctx); + virtual const char* displayName() const; + virtual char* description() const; + virtual Inkscape::DrawingItem* show(Inkscape::Drawing &drawing, unsigned int key, unsigned int flags); + virtual void snappoints(std::vector &p, Inkscape::SnapPreferences const *snapprefs) const; + virtual Geom::Affine set_transform(Geom::Affine const &transform); + +#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) + void apply_profile(Inkscape::Pixbuf *pixbuf); +#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) +}; + +/* Return duplicate of curve or NULL */ +SPCurve *sp_image_get_curve (SPImage *image); +void sp_embed_image(Inkscape::XML::Node *imgnode, Inkscape::Pixbuf *pb); +void sp_image_refresh_if_outdated( SPImage* image ); + +#endif diff --git a/src/object/sp-item-group.cpp b/src/object/sp-item-group.cpp new file mode 100644 index 000000000..73c1dcb6c --- /dev/null +++ b/src/object/sp-item-group.cpp @@ -0,0 +1,1022 @@ +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * bulia byak + * Johan Engelen + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 1999-2006 authors + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include "attributes.h" +#include "document.h" +#include "document-undo.h" +#include "selection-chemistry.h" +#include "verbs.h" + +#include "display/drawing-group.h" +#include "display/curve.h" +#include "live_effects/effect.h" +#include "live_effects/lpeobject.h" +#include "live_effects/lpeobject-reference.h" +#include "svg/svg.h" +#include "svg/css-ostringstream.h" +#include "xml/repr.h" +#include "xml/sp-css-attr.h" + +#include "box3d.h" +#include "persp3d.h" +#include "sp-defs.h" +#include "sp-item-transform.h" +#include "sp-root.h" +#include "sp-rect.h" +#include "sp-offset.h" +#include "sp-clippath.h" +#include "sp-mask.h" +#include "sp-path.h" +#include "sp-use.h" +#include "sp-title.h" +#include "sp-desc.h" +#include "sp-switch.h" +#include "sp-textpath.h" +#include "sp-flowtext.h" +#include "style.h" + +using Inkscape::DocumentUndo; + +static void sp_group_perform_patheffect(SPGroup *group, SPGroup *top_group, bool write); + +SPGroup::SPGroup() : SPLPEItem(), + _expanded(false), + _insert_bottom(false), + _layer_mode(SPGroup::GROUP) +{ +} + +SPGroup::~SPGroup() { +} + +void SPGroup::build(SPDocument *document, Inkscape::XML::Node *repr) { + this->readAttr( "inkscape:groupmode" ); + + SPLPEItem::build(document, repr); +} + +void SPGroup::release() { + if (this->_layer_mode == SPGroup::LAYER) { + this->document->removeResource("layer", this); + } + + SPLPEItem::release(); +} + +void SPGroup::child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref) { + SPLPEItem::child_added(child, ref); + + SPObject *last_child = this->lastChild(); + + if (last_child && last_child->getRepr() == child) { + // optimization for the common special case where the child is being added at the end + SPItem *item = dynamic_cast(last_child); + if ( item ) { + /* TODO: this should be moved into SPItem somehow */ + SPItemView *v; + + for (v = this->display; v != NULL; v = v->next) { + Inkscape::DrawingItem *ac = item->invoke_show (v->arenaitem->drawing(), v->key, v->flags); + + if (ac) { + v->arenaitem->appendChild(ac); + } + } + } + } else { // general case + SPItem *item = dynamic_cast(get_child_by_repr(child)); + if ( item ) { + /* TODO: this should be moved into SPItem somehow */ + SPItemView *v; + unsigned position = item->pos_in_parent(); + + for (v = this->display; v != NULL; v = v->next) { + Inkscape::DrawingItem *ac = item->invoke_show (v->arenaitem->drawing(), v->key, v->flags); + + if (ac) { + v->arenaitem->prependChild(ac); + ac->setZOrder(position); + } + } + } + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/* fixme: hide (Lauris) */ + +void SPGroup::remove_child(Inkscape::XML::Node *child) { + SPLPEItem::remove_child(child); + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +void SPGroup::order_changed (Inkscape::XML::Node *child, Inkscape::XML::Node *old_ref, Inkscape::XML::Node *new_ref) +{ + SPLPEItem::order_changed(child, old_ref, new_ref); + + SPItem *item = dynamic_cast(get_child_by_repr(child)); + if ( item ) { + /* TODO: this should be moved into SPItem somehow */ + SPItemView *v; + unsigned position = item->pos_in_parent(); + for ( v = item->display ; v != NULL ; v = v->next ) { + v->arenaitem->setZOrder(position); + } + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +void SPGroup::update(SPCtx *ctx, unsigned int flags) { + // std::cout << "SPGroup::update(): " << (getId()?getId():"null") << std::endl; + SPItemCtx *ictx, cctx; + + ictx = (SPItemCtx *) ctx; + cctx = *ictx; + + unsigned childflags = flags; + + if (flags & SP_OBJECT_MODIFIED_FLAG) { + childflags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + childflags &= SP_OBJECT_MODIFIED_CASCADE; + std::vector l=this->childList(true, SPObject::ActionUpdate); + for(std::vector ::const_iterator i=l.begin();i!=l.end();++i){ + SPObject *child = *i; + + if (childflags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + SPItem *item = dynamic_cast(child); + if (item) { + cctx.i2doc = item->transform * ictx->i2doc; + cctx.i2vp = item->transform * ictx->i2vp; + child->updateDisplay((SPCtx *)&cctx, childflags); + } else { + child->updateDisplay(ctx, childflags); + } + } + + sp_object_unref(child); + } + + // For a group, we need to update ourselves *after* updating children. + // this is because the group might contain shapes such as rect or ellipse, + // which recompute their equivalent path (a.k.a curve) in the update callback, + // and this is in turn used when computing bbox. + SPLPEItem::update(ctx, flags); + + if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) { + for (SPItemView *v = this->display; v != NULL; v = v->next) { + Inkscape::DrawingGroup *group = dynamic_cast(v->arenaitem); + if( this->parent ) { + this->context_style = this->parent->context_style; + } + group->setStyle(this->style, this->context_style); + } + } +} + +void SPGroup::modified(guint flags) { + // std::cout << "SPGroup::modified(): " << (getId()?getId():"null") << std::endl; + SPLPEItem::modified(flags); + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) { + for (SPItemView *v = this->display; v != NULL; v = v->next) { + Inkscape::DrawingGroup *group = dynamic_cast(v->arenaitem); + group->setStyle(this->style); + } + } + + std::vector l=this->childList(true); + for(std::vector::const_iterator i=l.begin();i!=l.end();++i){ + SPObject *child = *i; + + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + + sp_object_unref(child); + } +} + +Inkscape::XML::Node* SPGroup::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if (flags & SP_OBJECT_WRITE_BUILD) { + std::vector l; + + if (!repr) { + if (dynamic_cast(this)) { + repr = xml_doc->createElement("svg:switch"); + } else { + repr = xml_doc->createElement("svg:g"); + } + } + + for (auto& child: children) { + if ( !dynamic_cast(&child) && !dynamic_cast(&child) ) { + Inkscape::XML::Node *crepr = child.updateRepr(xml_doc, NULL, flags); + + if (crepr) { + l.push_back(crepr); + } + } + } + for (auto i=l.rbegin();i!=l.rend();++i) { + repr->addChild(*i, NULL); + Inkscape::GC::release(*i); + } + } else { + for (auto& child: children) { + if ( !dynamic_cast(&child) && !dynamic_cast(&child) ) { + child.updateRepr(flags); + } + } + } + + if ( flags & SP_OBJECT_WRITE_EXT ) { + const char *value; + if ( _layer_mode == SPGroup::LAYER ) { + value = "layer"; + } else if ( _layer_mode == SPGroup::MASK_HELPER ) { + value = "maskhelper"; + } else if ( flags & SP_OBJECT_WRITE_ALL ) { + value = "group"; + } else { + value = NULL; + } + + repr->setAttribute("inkscape:groupmode", value); + } + + SPLPEItem::write(xml_doc, repr, flags); + + return repr; +} + +Geom::OptRect SPGroup::bbox(Geom::Affine const &transform, SPItem::BBoxType bboxtype) const +{ + Geom::OptRect bbox; + + // TODO CPPIFY: replace this const_cast later + std::vector l = const_cast(this)->childList(false, SPObject::ActionBBox); + for(std::vector::const_iterator i=l.begin();i!=l.end();++i){ + SPObject *o = *i; + SPItem *item = dynamic_cast(o); + if (item && !item->isHidden()) { + Geom::Affine const ct(item->transform * transform); + bbox |= item->bounds(bboxtype, ct); + } + } + + return bbox; +} + +void SPGroup::print(SPPrintContext *ctx) { + for(auto& child: children){ + SPObject *o = &child; + SPItem *item = dynamic_cast(o); + if (item) { + item->invoke_print(ctx); + } + } +} + +const char *SPGroup::displayName() const { + return _("Group"); +} + +gchar *SPGroup::description() const { + gint len = this->getItemCount(); + return g_strdup_printf( + ngettext(_("of %d object"), _("of %d objects"), len), len); +} + +void SPGroup::set(unsigned int key, gchar const* value) { + switch (key) { + case SP_ATTR_INKSCAPE_GROUPMODE: + if ( value && !strcmp(value, "layer") ) { + this->setLayerMode(SPGroup::LAYER); + } else if ( value && !strcmp(value, "maskhelper") ) { + this->setLayerMode(SPGroup::MASK_HELPER); + } else { + this->setLayerMode(SPGroup::GROUP); + } + break; + + default: + SPLPEItem::set(key, value); + break; + } +} + +Inkscape::DrawingItem *SPGroup::show (Inkscape::Drawing &drawing, unsigned int key, unsigned int flags) { + // std::cout << "SPGroup::show(): " << (getId()?getId():"null") << std::endl; + Inkscape::DrawingGroup *ai; + + ai = new Inkscape::DrawingGroup(drawing); + ai->setPickChildren(this->effectiveLayerMode(key) == SPGroup::LAYER); + if( this->parent ) { + this->context_style = this->parent->context_style; + } + ai->setStyle(this->style, this->context_style); + + this->_showChildren(drawing, ai, key, flags); + return ai; +} + +void SPGroup::hide (unsigned int key) { + std::vector l=this->childList(false, SPObject::ActionShow); + for(std::vector::const_iterator i=l.begin();i!=l.end();++i){ + SPObject *o = *i; + + SPItem *item = dynamic_cast(o); + if (item) { + item->invoke_hide(key); + } + } + +// SPLPEItem::onHide(key); +} + + +void SPGroup::snappoints(std::vector &p, Inkscape::SnapPreferences const *snapprefs) const { + for (auto& o: children) + { + SPItem const *item = dynamic_cast(&o); + if (item) { + item->getSnappoints(p, snapprefs); + } + } +} + +void sp_item_group_ungroup_handle_clones(SPItem *parent, Geom::Affine const g) +{ + for(std::list::const_iterator refd=parent->hrefList.begin();refd!=parent->hrefList.end();++refd){ + SPItem *citem = dynamic_cast(*refd); + if (citem && !citem->cloned) { + SPUse *useitem = dynamic_cast(citem); + if (useitem && useitem->get_original() == parent) { + Geom::Affine ctrans; + ctrans = g.inverse() * citem->transform; + gchar *affinestr = sp_svg_transform_write(ctrans); + citem->setAttribute("transform", affinestr); + g_free(affinestr); + } + } + } +} + +void +sp_recursive_scale_text_size(Inkscape::XML::Node *repr, double scale){ + for (Inkscape::XML::Node *child = repr->firstChild() ; child; child = child->next() ){ + if ( child) { + sp_recursive_scale_text_size(child, scale); + } + } + SPCSSAttr * css = sp_repr_css_attr(repr,"style"); + Glib::ustring element = g_quark_to_string(repr->code()); + if ((css && element == "svg:text") || element == "svg:tspan") { + gchar const *w = sp_repr_css_property(css, "font-size", NULL); + if (w) { + gchar *units = NULL; + double wd = g_ascii_strtod(w, &units); + wd *= scale; + if (w != units) { + Inkscape::CSSOStringStream os; + os << wd << units; // reattach units + sp_repr_css_set_property(css, "font-size", os.str().c_str()); + Glib::ustring css_str; + sp_repr_css_write_string(css,css_str); + repr->setAttribute("style", css_str.c_str()); + } + } + w = NULL; + w = sp_repr_css_property(css, "letter-spacing", NULL); + if (w) { + gchar *units = NULL; + double wd = g_ascii_strtod(w, &units); + wd *= scale; + if (w != units) { + Inkscape::CSSOStringStream os; + os << wd << units; // reattach units + sp_repr_css_set_property(css, "letter-spacing", os.str().c_str()); + Glib::ustring css_str; + sp_repr_css_write_string(css,css_str); + repr->setAttribute("style", css_str.c_str()); + } + } + w = NULL; + w = sp_repr_css_property(css, "word-spacing", NULL); + if (w) { + gchar *units = NULL; + double wd = g_ascii_strtod(w, &units); + wd *= scale; + if (w != units) { + Inkscape::CSSOStringStream os; + os << wd << units; // reattach units + sp_repr_css_set_property(css, "word-spacing", os.str().c_str()); + Glib::ustring css_str; + sp_repr_css_write_string(css,css_str); + repr->setAttribute("style", css_str.c_str()); + } + } + gchar const *dx = repr->attribute("dx"); + if (dx) { + gchar ** dxarray = g_strsplit(dx, " ", 0); + Inkscape::SVGOStringStream dx_data; + while (*dxarray != NULL) { + double pos; + sp_svg_number_read_d(*dxarray, &pos); + pos *= scale; + dx_data << pos << " "; + dxarray++; + } + repr->setAttribute("dx", dx_data.str().c_str()); + } + gchar const *dy = repr->attribute("dy"); + if (dy) { + gchar ** dyarray = g_strsplit(dy, " ", 0); + Inkscape::SVGOStringStream dy_data; + while (*dyarray != NULL) { + double pos; + sp_svg_number_read_d(*dyarray, &pos); + pos *= scale; + dy_data << pos << " "; + dyarray++; + } + repr->setAttribute("dy", dy_data.str().c_str()); + } + } +} + +void +sp_item_group_ungroup (SPGroup *group, std::vector &children, bool do_done) +{ + g_return_if_fail (group != NULL); + + SPDocument *doc = group->document; + SPRoot *root = doc->getRoot(); + SPObject *defs = root->defs; + + Inkscape::XML::Node *grepr = group->getRepr(); + + g_return_if_fail (!strcmp (grepr->name(), "svg:g") + || !strcmp (grepr->name(), "svg:a") + || !strcmp (grepr->name(), "svg:switch") + || !strcmp (grepr->name(), "svg:svg")); + + // this converts the gradient/pattern fill/stroke on the group, if any, to userSpaceOnUse + group->adjust_paint_recursive (Geom::identity(), Geom::identity(), false); + + SPItem *pitem = dynamic_cast(group->parent); + g_assert(pitem); + Inkscape::XML::Node *prepr = pitem->getRepr(); + + { + SPBox3D *box = dynamic_cast(group); + if (box) { + group = box3d_convert_to_group(box); + } + } + + group->removeAllPathEffects(false); + + /* Step 1 - generate lists of children objects */ + std::vector items; + std::vector objects; + Geom::Affine const g(group->transform); + + for (auto& child: group->children) { + if (SPItem *citem = dynamic_cast(&child)) { + sp_item_group_ungroup_handle_clones(citem, g); + } + } + + for (auto& child: group->children) { + SPItem *citem = dynamic_cast(&child); + if (citem) { + /* Merging of style */ + // this converts the gradient/pattern fill/stroke, if any, to userSpaceOnUse; we need to do + // it here _before_ the new transform is set, so as to use the pre-transform bbox + citem->adjust_paint_recursive (Geom::identity(), Geom::identity(), false); + + child.style->merge( group->style ); + /* + * fixme: We currently make no allowance for the case where child is cloned + * and the group has any style settings. + * + * (This should never occur with documents created solely with the current + * version of inkscape without using the XML editor: we usually apply group + * style changes to children rather than to the group itself.) + * + * If the group has no style settings, then style->merge() should be a no-op. Otherwise + * (i.e. if we change the child's style to compensate for its parent going away) + * then those changes will typically be reflected in any clones of child, + * whereas we'd prefer for Ungroup not to affect the visual appearance. + * + * The only way of preserving styling appearance in general is for child to + * be put into a new group -- a somewhat surprising response to an Ungroup + * command. We could add a new groupmode:transparent that would mostly + * hide the existence of such groups from the user (i.e. editing behaves as + * if the transparent group's children weren't in a group), though that's + * extra complication & maintenance burden and this case is rare. + */ + + child.updateRepr(); + + Inkscape::XML::Node *nrepr = child.getRepr()->duplicate(prepr->document()); + + // Merging transform + Geom::Affine ctrans = citem->transform * g; + // We should not apply the group's transformation to both a linked offset AND to its source + if (dynamic_cast(citem)) { // Do we have an offset at hand (whether it's dynamic or linked)? + SPItem *source = sp_offset_get_source(dynamic_cast(citem)); + // When dealing with a chain of linked offsets, the transformation of an offset will be + // tied to the transformation of the top-most source, not to any of the intermediate + // offsets. So let's find the top-most source + while (source != NULL && dynamic_cast(source)) { + source = sp_offset_get_source(dynamic_cast(source)); + } + if (source != NULL && // If true then we must be dealing with a linked offset ... + group->isAncestorOf(source) ) { // ... of which the source is in the same group + ctrans = citem->transform; // then we should apply the transformation of the group to the offset + } + } + + // FIXME: constructing a transform that would fully preserve the appearance of a + // textpath if it is ungrouped with its path seems to be impossible in general + // case. E.g. if the group was squeezed, to keep the ungrouped textpath squeezed + // as well, we'll need to relink it to some "virtual" path which is inversely + // stretched relative to the actual path, and then squeeze the textpath back so it + // would both fit the actual path _and_ be squeezed as before. It's a bummer. + + // This is just a way to temporarily remember the transform in repr. When repr is + // reattached outside of the group, the transform will be written more properly + // (i.e. optimized into the object if the corresponding preference is set) + gchar *affinestr=sp_svg_transform_write(ctrans); + SPText * text = dynamic_cast(citem); + if (text) { + //this causes a change in text-on-path appearance when there is a non-conformal transform, see bug #1594565 + SPTextPath * text_path = dynamic_cast(text->firstChild()); + if (!text_path) { + nrepr->setAttribute("transform", affinestr); + } else { + // The following breaks roundtripping group -> ungroup + // double scale = (ctrans.expansionX() + ctrans.expansionY()) / 2.0; + // sp_recursive_scale_text_size(nrepr, scale); + Geom::Affine ttrans = ctrans.inverse() * SP_ITEM(text)->transform * ctrans; + gchar *affinestr = sp_svg_transform_write(ttrans); + nrepr->setAttribute("transform", affinestr); + g_free(affinestr); + } + } else { + nrepr->setAttribute("transform", affinestr); + } + g_free(affinestr); + + items.push_back(nrepr); + + } else { + Inkscape::XML::Node *nrepr = child.getRepr()->duplicate(prepr->document()); + objects.push_back(nrepr); + } + } + + /* Step 2 - clear group */ + // remember the position of the group + gint pos = group->getRepr()->position(); + + // the group is leaving forever, no heir, clones should take note; its children however are going to reemerge + group->deleteObject(true, false); + + /* Step 3 - add nonitems */ + if (!objects.empty()) { + Inkscape::XML::Node *last_def = defs->getRepr()->lastChild(); + for (auto i=objects.rbegin();i!=objects.rend();++i) { + Inkscape::XML::Node *repr = *i; + if (!sp_repr_is_meta_element(repr)) { + defs->getRepr()->addChild(repr, last_def); + } + Inkscape::GC::release(repr); + } + } + + /* Step 4 - add items */ + for (auto i=items.rbegin();i!=items.rend();++i) { + Inkscape::XML::Node *repr = *i; + // add item + prepr->appendChild(repr); + // restore position; since the items list was prepended (i.e. reverse), we now add + // all children at the same pos, which inverts the order once again + repr->setPosition(pos > 0 ? pos : 0); + + // fill in the children list if non-null + SPItem *item = static_cast(doc->getObjectByRepr(repr)); + + if (item) { + item->doWriteTransform(item->transform, NULL, false); + children.insert(children.begin(),item); + } else { + g_assert_not_reached(); + } + + Inkscape::GC::release(repr); + } + + if (do_done) { + DocumentUndo::done(doc, SP_VERB_NONE, _("Ungroup")); + } +} + +/* + * some API for list aspect of SPGroup + */ + +std::vector sp_item_group_item_list(SPGroup * group) +{ + std::vector s; + g_return_val_if_fail(group != NULL, s); + + for (auto& o: group->children) { + if ( dynamic_cast(&o) ) { + s.push_back((SPItem*)&o); + } + } + return s; +} + +SPObject *sp_item_group_get_child_by_name(SPGroup *group, SPObject *ref, const gchar *name) +{ + SPObject *child = (ref) ? ref->getNext() : group->firstChild(); + while ( child && strcmp(child->getRepr()->name(), name) ) { + child = child->getNext(); + } + return child; +} + +void SPGroup::setLayerMode(LayerMode mode) { + if ( _layer_mode != mode ) { + if ( mode == LAYER ) { + this->document->addResource("layer", this); + } else if ( _layer_mode == LAYER ) { + this->document->removeResource("layer", this); + } + _layer_mode = mode; + _updateLayerMode(); + } +} + +SPGroup::LayerMode SPGroup::layerDisplayMode(unsigned int dkey) const { + std::map::const_iterator iter; + iter = _display_modes.find(dkey); + if ( iter != _display_modes.end() ) { + return (*iter).second; + } else { + return GROUP; + } +} + +void SPGroup::setExpanded(bool isexpanded) { + if ( _expanded != isexpanded ){ + _expanded = isexpanded; + } +} + +void SPGroup::setInsertBottom(bool insertbottom) { + if ( _insert_bottom != insertbottom) { + _insert_bottom = insertbottom; + } +} + +void SPGroup::setLayerDisplayMode(unsigned int dkey, SPGroup::LayerMode mode) { + if ( layerDisplayMode(dkey) != mode ) { + _display_modes[dkey] = mode; + _updateLayerMode(dkey); + } +} + +void SPGroup::_updateLayerMode(unsigned int display_key) { + SPItemView *view; + for ( view = this->display ; view ; view = view->next ) { + if ( !display_key || view->key == display_key ) { + Inkscape::DrawingGroup *g = dynamic_cast(view->arenaitem); + if (g) { + g->setPickChildren(effectiveLayerMode(view->key) == SPGroup::LAYER); + } + } + } +} + +void SPGroup::translateChildItems(Geom::Translate const &tr) +{ + if ( hasChildren() ) { + for (auto& o: children) { + SPItem *item = dynamic_cast(&o); + if ( item ) { + sp_item_move_rel(item, tr); + } + } + } +} + +// Recursively (or not) scale child items around a point +void SPGroup::scaleChildItemsRec(Geom::Scale const &sc, Geom::Point const &p, bool noRecurse) +{ + if ( hasChildren() ) { + for (auto& o: children) { + if ( SPDefs *defs = dynamic_cast(&o) ) { // select symbols from defs, ignore clips, masks, patterns + for (auto& defschild: defs->children) { + SPGroup *defsgroup = dynamic_cast(&defschild); + if (defsgroup) + defsgroup->scaleChildItemsRec(sc, p, false); + } + } else if ( SPItem *item = dynamic_cast(&o) ) { + SPGroup *group = dynamic_cast(item); + if (group && !dynamic_cast(item)) { + /* Using recursion breaks clipping because transforms are applied + in coordinates for draws but nothing in defs is changed + instead change the transform on the entire group, and the transform + is applied after any references to clipping paths. However NOT using + recursion apparently breaks as of r13544 other parts of Inkscape + involved with showing/modifying units. So offer both for use + in different contexts. + */ + if(noRecurse) { + // used for EMF import + Geom::Translate const s(p); + Geom::Affine final = s.inverse() * sc * s; + Geom::Affine tAff = item->i2dt_affine() * final; + item->set_i2d_affine(tAff); + tAff = item->transform; + // Eliminate common rounding error affecting EMF/WMF input. + // When the rounding error persists it converts the simple + // transform=scale() to transform=matrix(). + if(std::abs(tAff[4]) < 1.0e-5 && std::abs(tAff[5]) < 1.0e-5){ + tAff[4] = 0.0; + tAff[5] = 0.0; + } + item->doWriteTransform(tAff, NULL, true); + } else { + // used for other import + SPItem *sub_item = NULL; + if (item->clip_ref->getObject()) { + sub_item = dynamic_cast(item->clip_ref->getObject()->firstChild()); + } + if (sub_item != NULL) { + sub_item->doWriteTransform(sub_item->transform*sc, NULL, true); + } + sub_item = NULL; + if (item->mask_ref->getObject()) { + sub_item = dynamic_cast(item->mask_ref->getObject()->firstChild()); + } + if (sub_item != NULL) { + sub_item->doWriteTransform(sub_item->transform*sc, NULL, true); + } + item->doWriteTransform(sc.inverse()*item->transform*sc, NULL, true); + group->scaleChildItemsRec(sc, p, false); + } + } else { +// Geom::OptRect bbox = item->desktopVisualBounds(); +// if (bbox) { // test not needed, this was causing a failure to scale and in the clipboard, see LP Bug 1365451 + // Scale item + Geom::Translate const s(p); + Geom::Affine final = s.inverse() * sc * s; + + gchar const *conn_type = NULL; + SPText *text_item = dynamic_cast(item); + bool is_text_path = text_item && text_item->firstChild() && dynamic_cast(text_item->firstChild()); + if (is_text_path) { + text_item->optimizeTextpathText(); + } else { + SPFlowtext *flowText = dynamic_cast(item); + if (flowText) { + flowText->optimizeScaledText(); + } else { + SPBox3D *box = dynamic_cast(item); + if (box) { + // Force recalculation from perspective + box3d_position_set(box); + } else if (item->getAttribute("inkscape:connector-type") != NULL + && (item->getAttribute("inkscape:connection-start") == NULL + || item->getAttribute("inkscape:connection-end") == NULL)) { + // Remove and store connector type for transform if disconnected + conn_type = item->getAttribute("inkscape:connector-type"); + item->removeAttribute("inkscape:connector-type"); + } + } + } + + Persp3D *persp = dynamic_cast(item); + if (persp) { + persp3d_apply_affine_transformation(persp, final); + } else if (is_text_path && !item->transform.isIdentity()) { + // Save and reset current transform + Geom::Affine tmp(item->transform); + item->transform = Geom::Affine(); + // Apply scale + item->set_i2d_affine(item->i2dt_affine() * sc); + item->doWriteTransform(item->transform, NULL, true); + // Scale translation and restore original transform + tmp[4] *= sc[0]; + tmp[5] *= sc[1]; + item->doWriteTransform(tmp, NULL, true); + } else if (dynamic_cast(item)) { + // calculate the matrix we need to apply to the clone + // to cancel its induced transform from its original + Geom::Affine move = final.inverse() * item->transform * final; + item->doWriteTransform(move, &move, true); + } else { + item->doWriteTransform(item->transform*sc, NULL, true); + } + + if (conn_type != NULL) { + item->setAttribute("inkscape:connector-type", conn_type); + } + + if (item->isCenterSet() && !(final.isTranslation() || final.isIdentity())) { + item->scaleCenter(sc); // All coordinates have been scaled, so also the center must be scaled + item->updateRepr(); + } +// } + } + } + } + } +} + +gint SPGroup::getItemCount() const { + gint len = 0; + for (auto& child: children) { + if (dynamic_cast(&child)) { + len++; + } + } + + return len; +} + +void SPGroup::_showChildren (Inkscape::Drawing &drawing, Inkscape::DrawingItem *ai, unsigned int key, unsigned int flags) { + Inkscape::DrawingItem *ac = NULL; + std::vector l=this->childList(false, SPObject::ActionShow); + for(std::vector::const_iterator i=l.begin();i!=l.end();++i){ + SPObject *o = *i; + SPItem * child = dynamic_cast(o); + if (child) { + ac = child->invoke_show (drawing, key, flags); + if (ac) { + ai->appendChild(ac); + } + } + } +} + +void SPGroup::update_patheffect(bool write) { +#ifdef GROUP_VERBOSE + g_message("sp_group_update_patheffect: %p\n", lpeitem); +#endif + + std::vector const item_list = sp_item_group_item_list(this); + + for ( std::vector::const_iterator iter=item_list.begin();iter!=item_list.end();++iter) { + SPObject *sub_item = *iter; + + SPLPEItem *lpe_item = dynamic_cast(sub_item); + if (lpe_item) { + lpe_item->update_patheffect(write); + } + } + + if (hasPathEffect() && pathEffectsEnabled()) { + for (PathEffectList::iterator it = this->path_effect_list->begin(); it != this->path_effect_list->end(); ++it) + { + LivePathEffectObject *lpeobj = (*it)->lpeobject; + + if (lpeobj && lpeobj->get_lpe()) { + lpeobj->get_lpe()->doBeforeEffect_impl(this); + } + } + + sp_group_perform_patheffect(this, this, write); + + for (PathEffectList::iterator it = this->path_effect_list->begin(); it != this->path_effect_list->end(); ++it) + { + LivePathEffectObject *lpeobj = (*it)->lpeobject; + + if (lpeobj && lpeobj->get_lpe()) { + lpeobj->get_lpe()->doAfterEffect(this); + } + } + } +} + +static void +sp_group_perform_patheffect(SPGroup *group, SPGroup *top_group, bool write) +{ + std::vector const item_list = sp_item_group_item_list(group); + + for ( std::vector::const_iterator iter=item_list.begin();iter!=item_list.end();++iter) { + SPObject *sub_item = *iter; + + SPGroup *sub_group = dynamic_cast(sub_item); + if (sub_group) { + sp_group_perform_patheffect(sub_group, top_group, write); + } else { + SPShape *sub_shape = dynamic_cast(sub_item); + if (sub_shape) { + SPCurve * c = NULL; + // If item is a SPRect, convert it to path first: + if ( dynamic_cast(sub_shape) ) { + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop) { + Inkscape::Selection *sel = desktop->getSelection(); + if ( sel && !sel->isEmpty() ) { + sel->clear(); + sel->add(SP_ITEM(sub_shape)); + sel->toCurves(); + sub_item = sel->singleItem(); + sub_shape = dynamic_cast(sub_item); + if (!sub_shape) { + continue; + } + sel->clear(); + sel->add(SP_ITEM(top_group)); + } + } + } + c = sub_shape->getCurve(); + bool success = false; + // only run LPEs when the shape has a curve defined + if (c) { + c->transform(i2anc_affine(sub_item, top_group)); + success = top_group->performPathEffect(c, sub_shape); + c->transform(i2anc_affine(sub_item, top_group).inverse()); + Inkscape::XML::Node *repr = sub_item->getRepr(); + if (c && success) { + SPPath *sub_path = dynamic_cast(sub_item); + if (!sub_path) { + sub_shape->setCurveInsync( sub_shape->getCurveBeforeLPE(), TRUE); + sub_shape->setCurve(c, TRUE); + sub_shape->setCurveInsync( c, TRUE); + } + if (write) { + gchar *str = sp_svg_write_path(c->get_pathvector()); + repr->setAttribute("d", str); +#ifdef GROUP_VERBOSE + g_message("sp_group_perform_patheffect writes 'd' attribute"); +#endif + g_free(str); + } + c->unref(); + } else { + // LPE was unsuccessful or doeffect stack return null. Read the old 'd'-attribute. + if (gchar const * value = repr->attribute("d")) { + Geom::PathVector pv = sp_svg_read_pathv(value); + SPCurve *oldcurve = new (std::nothrow) SPCurve(pv); + if (oldcurve) { + sub_shape->setCurve(oldcurve, TRUE); + oldcurve->unref(); + } + } + } + } + } + } + } +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-item-group.h b/src/object/sp-item-group.h new file mode 100644 index 000000000..a96d77aa9 --- /dev/null +++ b/src/object/sp-item-group.h @@ -0,0 +1,125 @@ +#ifndef SEEN_SP_ITEM_GROUP_H +#define SEEN_SP_ITEM_GROUP_H + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 authors + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include "sp-lpe-item.h" + +#define SP_GROUP(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_GROUP(obj) (dynamic_cast((SPObject*)obj) != NULL) + +#define SP_IS_LAYER(obj) (SP_IS_GROUP(obj) && SP_GROUP(obj)->layerMode() == SPGroup::LAYER) + +namespace Inkscape { + +class Drawing; +class DrawingItem; + +} // namespace Inkscape + +class SPGroup : public SPLPEItem { +public: + SPGroup(); + virtual ~SPGroup(); + + enum LayerMode { GROUP, LAYER, MASK_HELPER }; + + bool _expanded; + bool _insert_bottom; + LayerMode _layer_mode; + std::map _display_modes; + + LayerMode layerMode() const { return _layer_mode; } + void setLayerMode(LayerMode mode); + + bool expanded() const { return _expanded; } + void setExpanded(bool isexpanded); + + bool insertBottom() const { return _insert_bottom; } + void setInsertBottom(bool insertbottom); + + LayerMode effectiveLayerMode(unsigned int display_key) const { + if ( _layer_mode == LAYER ) { + return LAYER; + } else { + return layerDisplayMode(display_key); + } + } + + LayerMode layerDisplayMode(unsigned int display_key) const; + void setLayerDisplayMode(unsigned int display_key, LayerMode mode); + void translateChildItems(Geom::Translate const &tr); + void scaleChildItemsRec(Geom::Scale const &sc, Geom::Point const &p, bool noRecurse); + + int getItemCount() const; + virtual void _showChildren (Inkscape::Drawing &drawing, Inkscape::DrawingItem *ai, unsigned int key, unsigned int flags); + +private: + void _updateLayerMode(unsigned int display_key=0); + +public: + virtual void build(SPDocument *document, Inkscape::XML::Node *repr); + virtual void release(); + + virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref); + virtual void remove_child(Inkscape::XML::Node *child); + virtual void order_changed(Inkscape::XML::Node *child, Inkscape::XML::Node *old_ref, Inkscape::XML::Node *new_ref); + + virtual void update(SPCtx *ctx, unsigned int flags); + virtual void modified(unsigned int flags); + virtual void set(unsigned int key, char const* value); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); + + virtual Geom::OptRect bbox(Geom::Affine const &transform, SPItem::BBoxType bboxtype) const; + virtual void print(SPPrintContext *ctx); + virtual const char* displayName() const; + virtual char *description() const; + virtual Inkscape::DrawingItem *show (Inkscape::Drawing &drawing, unsigned int key, unsigned int flags); + virtual void hide (unsigned int key); + + virtual void snappoints(std::vector &p, Inkscape::SnapPreferences const *snapprefs) const; + + virtual void update_patheffect(bool write); +}; + + +/** + * finds clones of a child of the group going out of the group; and inverse the group transform on its clones + * Also called when moving objects between different layers + * @param group current group + * @param parent original parent + * @param g transform + */ +void sp_item_group_ungroup_handle_clones(SPItem *parent, Geom::Affine const g); + +void sp_item_group_ungroup (SPGroup *group, std::vector &children, bool do_done = true); + + +std::vector sp_item_group_item_list (SPGroup *group); + +SPObject *sp_item_group_get_child_by_name (SPGroup *group, SPObject *ref, const char *name); + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-item-rm-unsatisfied-cns.cpp b/src/object/sp-item-rm-unsatisfied-cns.cpp new file mode 100644 index 000000000..516c88672 --- /dev/null +++ b/src/object/sp-item-rm-unsatisfied-cns.cpp @@ -0,0 +1,45 @@ + +#include +#include <2geom/coord.h> + +#include "remove-last.h" +#include "sp-guide.h" +#include "sp-item-rm-unsatisfied-cns.h" + +using std::vector; + +void sp_item_rm_unsatisfied_cns(SPItem &item) +{ + if (item.constraints.empty()) { + return; + } + std::vector snappoints; + item.getSnappoints(snappoints, NULL); + for (unsigned i = item.constraints.size(); i--;) { + g_assert( i < item.constraints.size() ); + SPGuideConstraint const &cn = item.constraints[i]; + int const snappoint_ix = cn.snappoint_ix; + g_assert( snappoint_ix < int(snappoints.size()) ); + + if (!Geom::are_near(cn.g->getDistanceFrom(snappoints[snappoint_ix].getPoint()), 0, 1e-2)) { + + remove_last(cn.g->attached_items, SPGuideAttachment(&item, cn.snappoint_ix)); + + g_assert( i < item.constraints.size() ); + + item.constraints.erase(item.constraints.begin() + i); + } + } +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-item-rm-unsatisfied-cns.h b/src/object/sp-item-rm-unsatisfied-cns.h new file mode 100644 index 000000000..62f688b51 --- /dev/null +++ b/src/object/sp-item-rm-unsatisfied-cns.h @@ -0,0 +1,20 @@ +#ifndef SEEN_SP_ITEM_RM_UNSATISFIED_CNS_H +#define SEEN_SP_ITEM_RM_UNSATISFIED_CNS_H + +class SPItem; + +void sp_item_rm_unsatisfied_cns(SPItem &item); + + +#endif // SEEN_SP_ITEM_RM_UNSATISFIED_CNS_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-item-transform.cpp b/src/object/sp-item-transform.cpp new file mode 100644 index 000000000..3675323ca --- /dev/null +++ b/src/object/sp-item-transform.cpp @@ -0,0 +1,429 @@ +/* + * Transforming single items + * + * Authors: + * Lauris Kaplinski + * Frank Felfe + * bulia byak + * Johan Engelen + * Abhishek Sharma + * Diederik van Lierop + * + * Copyright (C) 1999-2011 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <2geom/transforms.h> +#include "sp-item.h" +#include "sp-item-transform.h" + +#include + +void sp_item_rotate_rel(SPItem *item, Geom::Rotate const &rotation) +{ + Geom::Point center = item->getCenter(); + Geom::Translate const s(item->getCenter()); + Geom::Affine affine = Geom::Affine(s).inverse() * Geom::Affine(rotation) * Geom::Affine(s); + + // Rotate item. + item->set_i2d_affine(item->i2dt_affine() * (Geom::Affine)affine); + // Use each item's own transform writer, consistent with sp_selection_apply_affine() + item->doWriteTransform(item->transform); + + // Restore the center position (it's changed because the bbox center changed) + if (item->isCenterSet()) { + item->setCenter(center * affine); + item->updateRepr(); + } +} + +void sp_item_scale_rel(SPItem *item, Geom::Scale const &scale) +{ + Geom::OptRect bbox = item->desktopVisualBounds(); + if (bbox) { + Geom::Translate const s(bbox->midpoint()); // use getCenter? + item->set_i2d_affine(item->i2dt_affine() * s.inverse() * scale * s); + item->doWriteTransform(item->transform); + } +} + +void sp_item_skew_rel(SPItem *item, double skewX, double skewY) +{ + Geom::Point center = item->getCenter(); + Geom::Translate const s(item->getCenter()); + + Geom::Affine const skew(1, skewY, skewX, 1, 0, 0); + Geom::Affine affine = Geom::Affine(s).inverse() * skew * Geom::Affine(s); + + item->set_i2d_affine(item->i2dt_affine() * affine); + item->doWriteTransform(item->transform); + + // Restore the center position (it's changed because the bbox center changed) + if (item->isCenterSet()) { + item->setCenter(center * affine); + item->updateRepr(); + } +} + +void sp_item_move_rel(SPItem *item, Geom::Translate const &tr) +{ + item->set_i2d_affine(item->i2dt_affine() * tr); + + item->doWriteTransform(item->transform); +} + +/** + * Calculate the affine transformation required to transform one visual bounding box into another, accounting for a uniform strokewidth. + * + * PS: This function will only return accurate results for the visual bounding box of a selection of one or more objects, all having + * the same strokewidth. If the stroke width varies from object to object in this selection, then the function + * get_scale_transform_for_variable_stroke() should be called instead + * + * When scaling or stretching an object using the selector, e.g. by dragging the handles or by entering a value, we will + * need to calculate the affine transformation for the old dimensions to the new dimensions. When using a geometric bounding + * box this is very straightforward, but when using a visual bounding box this become more tricky as we need to account for + * the strokewidth, which is either constant or scales width the area of the object. This function takes care of the calculation + * of the affine transformation: + * @param bbox_visual Current visual bounding box + * @param stroke_x Apparent strokewidth in horizontal direction + * @param stroke_y Apparent strokewidth in vertical direction + * @param transform_stroke If true then the stroke will be scaled proportional to the square root of the area of the geometric bounding box + * @param preserve If true then the transform element will be preserved in XML, and evaluated after stroke is applied + * @param x0 Coordinate of the target visual bounding box + * @param y0 Coordinate of the target visual bounding box + * @param x1 Coordinate of the target visual bounding box + * @param y1 Coordinate of the target visual bounding box + * PS: we have to pass each coordinate individually, to find out if we are mirroring the object; Using a Geom::Rect() instead is + * not possible here because it will only allow for a positive width and height, and therefore cannot mirror + * @return + */ +Geom::Affine get_scale_transform_for_uniform_stroke(Geom::Rect const &bbox_visual, gdouble stroke_x, gdouble stroke_y, bool transform_stroke, bool preserve, gdouble x0, gdouble y0, gdouble x1, gdouble y1) +{ + Geom::Affine p2o = Geom::Translate (-bbox_visual.min()); + Geom::Affine o2n = Geom::Translate (x0, y0); + + Geom::Affine scale = Geom::Scale (1, 1); + Geom::Affine unbudge = Geom::Translate (0, 0); // moves the object(s) to compensate for the drift caused by stroke width change + + // 1) We start with a visual bounding box (w0, h0) which we want to transfer into another visual bounding box (w1, h1) + // 2) The stroke is r0, equal for all edges, if preserve transforms is false + // 3) Given this visual bounding box we can calculate the geometric bounding box by subtracting half the stroke from each side; + // -> The width and height of the geometric bounding box will therefore be (w0 - 2*0.5*r0) and (h0 - 2*0.5*r0) + // 4) If preserve transforms is true, then stroke_x != stroke_y, since these are the apparent stroke widths, after transforming + + if ((stroke_x == Geom::infinity()) || (fabs(stroke_x) < 1e-6)) stroke_x = 0; + if ((stroke_y == Geom::infinity()) || (fabs(stroke_y) < 1e-6)) stroke_y = 0; + + gdouble w0 = bbox_visual.width(); // will return a value >= 0, as required further down the road + gdouble h0 = bbox_visual.height(); + + // We also know the width and height of the new visual bounding box + gdouble w1 = x1 - x0; // can have any sign + gdouble h1 = y1 - y0; + // The new visual bounding box will have a stroke r1 + + // Here starts the calculation you've been waiting for; first do some preparation + int flip_x = (w1 > 0) ? 1 : -1; + int flip_y = (h1 > 0) ? 1 : -1; + + // w1 and h1 will be negative when mirroring, but if so then e.g. w1-r0 won't make sense + // Therefore we will use the absolute values from this point on + w1 = fabs(w1); + h1 = fabs(h1); + // w0 and h0 will always be positive due to the definition of the width() and height() methods. + + // Check whether the stroke is negative; i.e. the geometric bounding box is larger than the visual bounding box, which + // occurs for example for clipped objects (see launchpad bug #811819) + if (stroke_x < 0 || stroke_y < 0) { + Geom::Affine direct = Geom::Scale(flip_x * w1 / w0, flip_y* h1 / h0); // Scaling of the visual bounding box + // How should we handle the stroke width scaling of clipped object? I don't know if we can/should handle this, + // so for now we simply return the direct scaling + return (p2o * direct * o2n); + } + gdouble r0 = sqrt(stroke_x*stroke_y); // r0 is redundant, used only for those cases where stroke_x = stroke_y + + // We will now try to calculate the affine transformation required to transform the first visual bounding box into + // the second one, while accounting for strokewidth + + if ((fabs(w0 - stroke_x) < 1e-6) && (fabs(h0 - stroke_y) < 1e-6)) { + return Geom::Affine(); + } + + gdouble scale_x = 1; + gdouble scale_y = 1; + gdouble r1; + + if ((fabs(w0 - stroke_x) < 1e-6) || w1 == 0) { // We have a vertical line at hand + scale_y = h1/h0; + scale_x = transform_stroke ? 1 : scale_y; + unbudge *= Geom::Translate (-flip_x * 0.5 * (scale_x - 1.0) * w0, 0); + unbudge *= Geom::Translate ( flip_x * 0.5 * (w1 - w0), 0); // compensate for the fact that this operation cannot be performed + } else if ((fabs(h0 - stroke_y) < 1e-6) || h1 == 0) { // We have a horizontal line at hand + scale_x = w1/w0; + scale_y = transform_stroke ? 1 : scale_x; + unbudge *= Geom::Translate (0, -flip_y * 0.5 * (scale_y - 1.0) * h0); + unbudge *= Geom::Translate (0, flip_y * 0.5 * (h1 - h0)); // compensate for the fact that this operation cannot be performed + } else { // We have a true 2D object at hand + if (transform_stroke && !preserve) { + /* Initial area of the geometric bounding box: A0 = (w0-r0)*(h0-r0) + * Desired area of the geometric bounding box: A1 = (w1-r1)*(h1-r1) + * This is how the stroke should scale: r1^2 / A1 = r0^2 / A0 + * So therefore we will need to solve this equation: + * + * r1^2 * (w0-r0) * (h0-r0) = r0^2 * (w1-r1) * (h1-r1) + * + * This is a quadratic equation in r1, of which the roots can be found using the ABC formula + * */ + gdouble A = -w0*h0 + r0*(w0 + h0); + gdouble B = -(w1 + h1) * r0*r0; + gdouble C = w1 * h1 * r0*r0; + if (B*B - 4*A*C < 0) { + g_message("stroke scaling error : %d, %f, %f, %f, %f, %f", preserve, r0, w0, h0, w1, h1); + } else { + r1 = -C/B; + if (!Geom::are_near(A*C/B/B, 0.0, Geom::EPSILON)) + r1 = fabs((-B - sqrt(B*B - 4*A*C))/(2*A)); + // If w1 < 0 then the scale will be wrong if we just assume that scale_x = (w1 - r1)/(w0 - r0); + // Therefore we here need the absolute values of w0, w1, h0, h1, and r0, as taken care of earlier + scale_x = (w1 - r1)/(w0 - r0); + scale_y = (h1 - r1)/(h0 - r0); + // Make sure that the lower-left corner of the visual bounding box stays where it is, even though the stroke width has changed + unbudge *= Geom::Translate (-flip_x * 0.5 * (r0 * scale_x - r1), -flip_y * 0.5 * (r0 * scale_y - r1)); + } + } else if (!transform_stroke && !preserve) { // scale the geometric bbox with constant stroke + scale_x = (w1 - r0) / (w0 - r0); + scale_y = (h1 - r0) / (h0 - r0); + unbudge *= Geom::Translate (-flip_x * 0.5 * r0 * (scale_x - 1), -flip_y * 0.5 * r0 * (scale_y - 1)); + } else if (!transform_stroke) { // 'Preserve Transforms' was chosen. + // geometric mean of stroke_x and stroke_y will be preserved + // new_stroke_x = stroke_x*sqrt(scale_x/scale_y) + // new_stroke_y = stroke_y*sqrt(scale_y/scale_x) + // scale_x = (w1 - new_stroke_x)/(w0 - stroke_x) + // scale_y = (h1 - new_stroke_y)/(h0 - stroke_y) + gdouble A = h1*(w0 - stroke_x); + gdouble B = (h0*stroke_x - w0*stroke_y); + gdouble C = -w1*(h0 - stroke_y); + gdouble Sx_div_Sy; // Sx_div_Sy = sqrt(scale_x/scale_y) + if (B*B - 4*A*C < 0) { + g_message("stroke scaling error : %d, %f, %f, %f, %f, %f, %f", preserve, stroke_x, stroke_y, w0, h0, w1, h1); + } else { + Sx_div_Sy = (-B + sqrt(B*B - 4*A*C))/2/A; + scale_x = (w1 - stroke_x*Sx_div_Sy)/(w0 - stroke_x); + scale_y = (h1 - stroke_y/Sx_div_Sy)/(h0 - stroke_y); + unbudge *= Geom::Translate (-flip_x * 0.5 * stroke_x * scale_x * (1.0 - sqrt(1.0/scale_x/scale_y)), -flip_y * 0.5 * stroke_y * scale_y * (1.0 - sqrt(1.0/scale_x/scale_y))); + } + } else { // 'Preserve Transforms' was chosen, and stroke is scaled + scale_x = w1 / w0; + scale_y = h1 / h0; + } + } + + // Now we account for mirroring by flipping if needed + scale *= Geom::Scale(flip_x * scale_x, flip_y * scale_y); + + return (p2o * scale * unbudge * o2n); +} + +/** + * Calculate the affine transformation required to transform one visual bounding box into another, accounting for a VARIABLE strokewidth. + * + * Note: Please try to understand get_scale_transform_for_uniform_stroke() first, and read all it's comments carefully. This function + * (get_scale_transform_for_variable_stroke) is a bit different because it will allow for a strokewidth that's different for each + * side of the visual bounding box. Such a situation will arise when transforming the visual bounding box of a selection of objects, + * each having a different stroke width. In fact this function is a generalized version of get_scale_transform_for_uniform_stroke(), but + * will not (yet) replace it because it has not been tested as carefully, and because the old function is can serve as an introduction to + * understand the new one. + * + * When scaling or stretching an object using the selector, e.g. by dragging the handles or by entering a value, we will + * need to calculate the affine transformation for the old dimensions to the new dimensions. When using a geometric bounding + * box this is very straightforward, but when using a visual bounding box this become more tricky as we need to account for + * the strokewidth, which is either constant or scales width the area of the object. This function takes care of the calculation + * of the affine transformation: + * + * @param bbox_visual Current visual bounding box + * @param bbox_geometric Current geometric bounding box (allows for calculating the strokewidth of each edge) + * @param transform_stroke If true then the stroke will be scaled proportional to the square root of the area of the geometric bounding box + * @param preserve If true then the transform element will be preserved in XML, and evaluated after stroke is applied + * @param x0 Coordinate of the target visual bounding box + * @param y0 Coordinate of the target visual bounding box + * @param x1 Coordinate of the target visual bounding box + * @param y1 Coordinate of the target visual bounding box + * PS: we have to pass each coordinate individually, to find out if we are mirroring the object; Using a Geom::Rect() instead is + * not possible here because it will only allow for a positive width and height, and therefore cannot mirror + * @return + */ +Geom::Affine get_scale_transform_for_variable_stroke(Geom::Rect const &bbox_visual, Geom::Rect const &bbox_geom, bool transform_stroke, bool preserve, gdouble x0, gdouble y0, gdouble x1, gdouble y1) +{ + Geom::Affine p2o = Geom::Translate (-bbox_visual.min()); + Geom::Affine o2n = Geom::Translate (x0, y0); + + Geom::Affine scale = Geom::Scale (1, 1); + Geom::Affine unbudge = Geom::Translate (0, 0); // moves the object(s) to compensate for the drift caused by stroke width change + + // 1) We start with a visual bounding box (w0, h0) which we want to transfer into another visual bounding box (w1, h1) + // 2) We will also know the geometric bounding box, which can be used to calculate the strokewidth. The strokewidth will however + // be different for each of the four sides (left/right/top/bottom: r0l, r0r, r0t, r0b) + + gdouble w0 = bbox_visual.width(); // will return a value >= 0, as required further down the road + gdouble h0 = bbox_visual.height(); + + // We also know the width and height of the new visual bounding box + gdouble w1 = x1 - x0; // can have any sign + gdouble h1 = y1 - y0; + // The new visual bounding box will have strokes r1l, r1r, r1t, and r1b + + // We will now try to calculate the affine transformation required to transform the first visual bounding box into + // the second one, while accounting for strokewidth + gdouble r0w = w0 - bbox_geom.width(); // r0w is the average strokewidth of the left and right edges, i.e. 0.5*(r0l + r0r) + gdouble r0h = h0 - bbox_geom.height(); // r0h is the average strokewidth of the top and bottom edges, i.e. 0.5*(r0t + r0b) + if ((r0w == Geom::infinity()) || (fabs(r0w) < 1e-6)) r0w = 0; + if ((r0h == Geom::infinity()) || (fabs(r0h) < 1e-6)) r0h = 0; + + int flip_x = (w1 > 0) ? 1 : -1; + int flip_y = (h1 > 0) ? 1 : -1; + + // w1 and h1 will be negative when mirroring, but if so then e.g. w1-r0 won't make sense + // Therefore we will use the absolute values from this point on + w1 = fabs(w1); + h1 = fabs(h1); + // w0 and h0 will always be positive due to the definition of the width() and height() methods. + + if ((fabs(w0 - r0w) < 1e-6) && (fabs(h0 - r0h) < 1e-6)) { + return Geom::Affine(); + } + + // Check whether the stroke is negative; i.e. the geometric bounding box is larger than the visual bounding box, which + // occurs for example for clipped objects (see launchpad bug #811819) + if (r0w < 0 || r0h < 0) { + Geom::Affine direct = Geom::Scale(flip_x * w1 / w0, flip_y* h1 / h0); // Scaling of the visual bounding box + // How should we handle the stroke width scaling of clipped object? I don't know if we can/should handle this, + // so for now we simply return the direct scaling + return (p2o * direct * o2n); + } + + // The calculation of the new strokewidth will only use the average stroke for each of the dimensions; To find the new stroke for each + // of the edges individually though, we will use the boundary condition that the ratio of the left/right strokewidth will not change due to the + // scaling. The same holds for the ratio of the top/bottom strokewidth. + gdouble stroke_ratio_w = fabs(r0w) < 1e-6 ? 1 : (bbox_geom[Geom::X].min() - bbox_visual[Geom::X].min())/r0w; + gdouble stroke_ratio_h = fabs(r0h) < 1e-6 ? 1 : (bbox_geom[Geom::Y].min() - bbox_visual[Geom::Y].min())/r0h; + + gdouble scale_x = 1; + gdouble scale_y = 1; + gdouble r1h; + gdouble r1w; + + if ((fabs(w0 - r0w) < 1e-6) || w1 == 0) { // We have a vertical line at hand + scale_y = h1/h0; + scale_x = transform_stroke ? 1 : scale_y; + unbudge *= Geom::Translate (-flip_x * 0.5 * (scale_x - 1.0) * w0, 0); + unbudge *= Geom::Translate ( flip_x * 0.5 * (w1 - w0), 0); // compensate for the fact that this operation cannot be performed + } else if ((fabs(h0 - r0h) < 1e-6) || h1 == 0) { // We have a horizontal line at hand + scale_x = w1/w0; + scale_y = transform_stroke ? 1 : scale_x; + unbudge *= Geom::Translate (0, -flip_y * 0.5 * (scale_y - 1.0) * h0); + unbudge *= Geom::Translate (0, flip_y * 0.5 * (h1 - h0)); // compensate for the fact that this operation cannot be performed + } else { // We have a true 2D object at hand + if (transform_stroke && !preserve) { + /* Initial area of the geometric bounding box: A0 = (w0-r0w)*(h0-r0h) + * Desired area of the geometric bounding box: A1 = (w1-r1w)*(h1-r1h) + * This is how the stroke should scale: r1w^2 = A1/A0 * r0w^2, AND + * r1h^2 = A1/A0 * r0h^2 + * These can be re-expressed as : r1w/r0w = r1h/r0h + * and : r1w*r1w*(w0 - r0w)*(h0 - r0h) = r0w*r0w*(w1 - r1w)*(h1 - r1h) + * This leads to a quadratic equation in r1w, solved as follows: + * */ + + gdouble A = w0*h0 - r0h*w0 - r0w*h0; + gdouble B = r0h*w1 + r0w*h1; + gdouble C = -w1*h1; + + if (B*B - 4*A*C < 0) { + g_message("variable stroke scaling error : %d, %d, %f, %f, %f, %f, %f, %f", transform_stroke, preserve, r0w, r0h, w0, h0, w1, h1); + } else { + gdouble det = -C/B; + if (!Geom::are_near(A*C/B/B, 0.0, Geom::EPSILON)) + det = (-B + sqrt(B*B - 4*A*C))/(2*A); + r1w = r0w*det; + r1h = r0h*det; + // If w1 < 0 then the scale will be wrong if we just assume that scale_x = (w1 - r1)/(w0 - r0); + // Therefore we here need the absolute values of w0, w1, h0, h1, and r0, as taken care of earlier + scale_x = (w1 - r1w)/(w0 - r0w); + scale_y = (h1 - r1h)/(h0 - r0h); + // Make sure that the lower-left corner of the visual bounding box stays where it is, even though the stroke width has changed + unbudge *= Geom::Translate (-flip_x * stroke_ratio_w * (r0w * scale_x - r1w), -flip_y * stroke_ratio_h * (r0h * scale_y - r1h)); + } + } else if (!transform_stroke && !preserve) { // scale the geometric bbox with constant stroke + scale_x = (w1 - r0w) / (w0 - r0w); + scale_y = (h1 - r0h) / (h0 - r0h); + unbudge *= Geom::Translate (-flip_x * stroke_ratio_w * r0w * (scale_x - 1), -flip_y * stroke_ratio_h * r0h * (scale_y - 1)); + } else if (!transform_stroke) { // 'Preserve Transforms' was chosen. + // geometric mean of r0w and r0h will be preserved + // new_r0w = r0w*sqrt(scale_x/scale_y) + // new_r0h = r0h*sqrt(scale_y/scale_x) + // scale_x = (w1 - new_r0w)/(w0 - r0w) + // scale_y = (h1 - new_r0h)/(h0 - r0h) + gdouble A = h1*(w0 - r0w); + gdouble B = (h0*r0w - w0*r0h); + gdouble C = -w1*(h0 - r0h); + gdouble Sx_div_Sy; // Sx_div_Sy = sqrt(scale_x/scale_y) + if (B*B - 4*A*C < 0) { + g_message("variable stroke scaling error : %d, %d, %f, %f, %f, %f, %f, %f", transform_stroke, preserve, r0w, r0h, w0, h0, w1, h1); + } else { + Sx_div_Sy = (-B + sqrt(B*B - 4*A*C))/2/A; + scale_x = (w1 - r0w*Sx_div_Sy)/(w0 - r0w); + scale_y = (h1 - r0h/Sx_div_Sy)/(h0 - r0h); + unbudge *= Geom::Translate (-flip_x * stroke_ratio_w * r0w * scale_x * (1.0 - sqrt(1.0/scale_x/scale_y)), -flip_y * stroke_ratio_h * r0h * scale_y * (1.0 - sqrt(1.0/scale_x/scale_y))); + } + } else { // 'Preserve Transforms' was chosen, and stroke is scaled + scale_x = w1 / w0; + scale_y = h1 / h0; + } + } + + // Now we account for mirroring by flipping if needed + scale *= Geom::Scale(flip_x * scale_x, flip_y * scale_y); + + return (p2o * scale * unbudge * o2n); +} + +Geom::Rect get_visual_bbox(Geom::OptRect const &initial_geom_bbox, Geom::Affine const &abs_affine, gdouble const initial_strokewidth, bool const transform_stroke) +{ + g_assert(initial_geom_bbox); + + // Find the new geometric bounding box; Do this by transforming each corner of + // the initial geometric bounding box individually and fitting a new boundingbox + // around the transformerd corners + Geom::Point const p0 = Geom::Point(initial_geom_bbox->corner(0)) * abs_affine; + Geom::Rect new_geom_bbox(p0, p0); + for (unsigned i = 1 ; i < 4 ; i++) { + new_geom_bbox.expandTo(Geom::Point(initial_geom_bbox->corner(i)) * abs_affine); + } + + Geom::Rect new_visual_bbox = new_geom_bbox; + if (initial_strokewidth > 0 && initial_strokewidth < Geom::infinity()) { + if (transform_stroke) { + // scale stroke by: sqrt (((w1-r0)/(w0-r0))*((h1-r0)/(h0-r0))) (for visual bboxes, see get_scale_transform_for_stroke) + // equals scaling by: sqrt ((w1/w0)*(h1/h0)) for geometrical bboxes + // equals scaling by: sqrt (area1/area0) for geometrical bboxes + gdouble const new_strokewidth = initial_strokewidth * sqrt (new_geom_bbox.area() / initial_geom_bbox->area()); + new_visual_bbox.expandBy(0.5 * new_strokewidth); + } else { + // Do not transform the stroke + new_visual_bbox.expandBy(0.5 * initial_strokewidth); + } + } + + return new_visual_bbox; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-item-transform.h b/src/object/sp-item-transform.h new file mode 100644 index 000000000..d563c9768 --- /dev/null +++ b/src/object/sp-item-transform.h @@ -0,0 +1,29 @@ +#ifndef SEEN_SP_ITEM_TRANSFORM_H +#define SEEN_SP_ITEM_TRANSFORM_H + +#include <2geom/forward.h> + +class SPItem; + +void sp_item_rotate_rel(SPItem *item, Geom::Rotate const &rotation); +void sp_item_scale_rel (SPItem *item, Geom::Scale const &scale); +void sp_item_skew_rel (SPItem *item, double skewX, double skewY); +void sp_item_move_rel(SPItem *item, Geom::Translate const &tr); + +Geom::Affine get_scale_transform_for_uniform_stroke (Geom::Rect const &bbox_visual, double stroke_x, double stroke_y, bool transform_stroke, bool preserve, double x0, double y0, double x1, double y1); +Geom::Affine get_scale_transform_for_variable_stroke (Geom::Rect const &bbox_visual, Geom::Rect const &bbox_geom, bool transform_stroke, bool preserve, double x0, double y0, double x1, double y1); +Geom::Rect get_visual_bbox (Geom::OptRect const &initial_geom_bbox, Geom::Affine const &abs_affine, double const initial_strokewidth, bool const transform_stroke); + + +#endif // SEEN_SP_ITEM_TRANSFORM_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-item-update-cns.cpp b/src/object/sp-item-update-cns.cpp new file mode 100644 index 000000000..077931d52 --- /dev/null +++ b/src/object/sp-item-update-cns.cpp @@ -0,0 +1,47 @@ + + +#include "satisfied-guide-cns.h" + +#include "sp-item-update-cns.h" +#include "sp-guide.h" + +using std::find; +using std::vector; + +void sp_item_update_cns(SPItem &item, SPDesktop const &desktop) +{ + std::vector snappoints; + item.getSnappoints(snappoints, NULL); + /* TODO: Implement the ordering. */ + vector found_cns; + satisfied_guide_cns(desktop, snappoints, found_cns); + /* effic: It might be nice to avoid an n^2 algorithm, but in practice n will be + small enough that it's still usually more efficient. */ + + for (vector::const_iterator fi(found_cns.begin()), + fiEnd(found_cns.end()); + fi != fiEnd; ++fi) + { + SPGuideConstraint const &cn = *fi; + if ( find(item.constraints.begin(), + item.constraints.end(), + cn) + == item.constraints.end() ) + { + item.constraints.push_back(cn); + cn.g->attached_items.push_back(SPGuideAttachment(&item, cn.snappoint_ix)); + } + } +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-item-update-cns.h b/src/object/sp-item-update-cns.h new file mode 100644 index 000000000..d0b080552 --- /dev/null +++ b/src/object/sp-item-update-cns.h @@ -0,0 +1,23 @@ +#ifndef SEEN_SP_ITEM_UPDATE_CNS_H +#define SEEN_SP_ITEM_UPDATE_CNS_H + +#include <2geom/forward.h> + +class SPDesktop; +class SPItem; + +void sp_item_update_cns(SPItem &item, SPDesktop const &desktop); + + +#endif // SEEN_SP_ITEM_UPDATE_CNS_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-item.cpp b/src/object/sp-item.cpp new file mode 100644 index 000000000..f7a4ff672 --- /dev/null +++ b/src/object/sp-item.cpp @@ -0,0 +1,1739 @@ +/* + * Authors: + * Lauris Kaplinski + * bulia byak + * Johan Engelen + * Abhishek Sharma + * Jon A. Cruz + * + * Copyright (C) 2001-2006 authors + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "sp-item.h" + +#include + +#include "bad-uri-exception.h" +#include "svg/svg.h" +#include "print.h" +#include "display/drawing-item.h" +#include "attributes.h" +#include "document.h" + +#include "inkscape.h" +#include "desktop.h" + +#include "gradient-chemistry.h" +#include "conn-avoid-ref.h" +#include "conditions.h" +#include "filter-chemistry.h" + +#include "sp-clippath.h" +#include "sp-desc.h" +#include "sp-guide.h" +#include "sp-item-rm-unsatisfied-cns.h" +#include "sp-mask.h" +#include "sp-pattern.h" +#include "sp-root.h" +#include "sp-rect.h" +#include "sp-switch.h" +#include "sp-text.h" +#include "sp-textpath.h" +#include "sp-title.h" +#include "sp-use.h" + +#include "style.h" +#include "uri.h" + + +#include "util/find-last-if.h" +#include "util/reverse-list.h" + +#include "extract-uri.h" + +#include "live_effects/lpeobject.h" +#include "live_effects/effect.h" +#include "live_effects/lpeobject-reference.h" + +#include "util/units.h" + +#define noSP_ITEM_DEBUG_IDLE + +//#define OBJECT_TRACE + +static SPItemView* sp_item_view_list_remove(SPItemView *list, + SPItemView *view); + + +SPItem::SPItem() : SPObject() { + + sensitive = TRUE; + bbox_valid = FALSE; + + _highlightColor = NULL; + + transform_center_x = 0; + transform_center_y = 0; + + freeze_stroke_width = false; + _is_evaluated = true; + _evaluated_status = StatusUnknown; + + transform = Geom::identity(); + // doc_bbox = Geom::OptRect(); + + display = NULL; + + clip_ref = new SPClipPathReference(this); + clip_ref->changedSignal().connect(sigc::bind(sigc::ptr_fun(clip_ref_changed), this)); + + mask_ref = new SPMaskReference(this); + mask_ref->changedSignal().connect(sigc::bind(sigc::ptr_fun(mask_ref_changed), this)); + + style->signal_fill_ps_changed.connect(sigc::bind(sigc::ptr_fun(fill_ps_ref_changed), this)); + style->signal_stroke_ps_changed.connect(sigc::bind(sigc::ptr_fun(stroke_ps_ref_changed), this)); + + avoidRef = new SPAvoidRef(this); +} + +SPItem::~SPItem() { +} + +bool SPItem::isVisibleAndUnlocked() const { + return (!isHidden() && !isLocked()); +} + +bool SPItem::isVisibleAndUnlocked(unsigned display_key) const { + return (!isHidden(display_key) && !isLocked()); +} + +bool SPItem::isLocked() const { + for (SPObject const *o = this; o != NULL; o = o->parent) { + SPItem const *item = dynamic_cast(o); + if (item && !(item->sensitive)) { + return true; + } + } + return false; +} + +void SPItem::setLocked(bool locked) { + setAttribute("sodipodi:insensitive", + ( locked ? "1" : NULL )); + updateRepr(); + document->_emitModified(); +} + +bool SPItem::isHidden() const { + if (!isEvaluated()) + return true; + return style->display.computed == SP_CSS_DISPLAY_NONE; +} + +void SPItem::setHidden(bool hide) { + style->display.set = TRUE; + style->display.value = ( hide ? SP_CSS_DISPLAY_NONE : SP_CSS_DISPLAY_INLINE ); + style->display.computed = style->display.value; + style->display.inherit = FALSE; + updateRepr(); +} + +bool SPItem::isHidden(unsigned display_key) const { + if (!isEvaluated()) + return true; + for ( SPItemView *view(display) ; view ; view = view->next ) { + if ( view->key == display_key ) { + g_assert(view->arenaitem != NULL); + for ( Inkscape::DrawingItem *arenaitem = view->arenaitem ; + arenaitem ; arenaitem = arenaitem->parent() ) + { + if (!arenaitem->visible()) { + return true; + } + } + return false; + } + } + return true; +} + +bool SPItem::isHighlightSet() const { + return _highlightColor != NULL; +} + +guint32 SPItem::highlight_color() const { + if (_highlightColor) + { + return atoi(_highlightColor) | 0x00000000; + } + else { + SPItem const *item = dynamic_cast(parent); + if (parent && (parent != this) && item) + { + return item->highlight_color(); + } + else + { + static Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + return prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff) | 0x00000000; + } + } +} + +void SPItem::setEvaluated(bool evaluated) { + _is_evaluated = evaluated; + _evaluated_status = StatusSet; +} + +void SPItem::resetEvaluated() { + if ( StatusCalculated == _evaluated_status ) { + _evaluated_status = StatusUnknown; + bool oldValue = _is_evaluated; + if ( oldValue != isEvaluated() ) { + requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + } + } if ( StatusSet == _evaluated_status ) { + SPSwitch *switchItem = dynamic_cast(parent); + if (switchItem) { + switchItem->resetChildEvaluated(); + } + } +} + +bool SPItem::isEvaluated() const { + if ( StatusUnknown == _evaluated_status ) { + _is_evaluated = sp_item_evaluate(this); + _evaluated_status = StatusCalculated; + } + return _is_evaluated; +} + +bool SPItem::isExplicitlyHidden() const +{ + return (style->display.set + && style->display.value == SP_CSS_DISPLAY_NONE); +} + +void SPItem::setExplicitlyHidden(bool val) { + style->display.set = val; + style->display.value = ( val ? SP_CSS_DISPLAY_NONE : SP_CSS_DISPLAY_INLINE ); + style->display.computed = style->display.value; + updateRepr(); +} + +void SPItem::setCenter(Geom::Point const &object_centre) { + document->ensureUpToDate(); + + // Copied from DocumentProperties::onDocUnitChange() + gdouble viewscale = 1.0; + Geom::Rect vb = this->document->getRoot()->viewBox; + if ( !vb.hasZeroArea() ) { + gdouble viewscale_w = this->document->getWidth().value("px") / vb.width(); + gdouble viewscale_h = this->document->getHeight().value("px")/ vb.height(); + viewscale = std::min(viewscale_h, viewscale_w); + } + + // FIXME this is seriously wrong + Geom::OptRect bbox = desktopGeometricBounds(); + if (bbox) { + // object centre is document coordinates (i.e. in pixels), so we need to consider the viewbox + // to translate to user units; transform_center_x/y is in user units + transform_center_x = (object_centre[Geom::X] - bbox->midpoint()[Geom::X])/viewscale; + if (Geom::are_near(transform_center_x, 0)) // rounding error + transform_center_x = 0; + transform_center_y = (object_centre[Geom::Y] - bbox->midpoint()[Geom::Y])/viewscale; + if (Geom::are_near(transform_center_y, 0)) // rounding error + transform_center_y = 0; + } +} + +void +SPItem::unsetCenter() { + transform_center_x = 0; + transform_center_y = 0; +} + +bool SPItem::isCenterSet() const { + return (transform_center_x != 0 || transform_center_y != 0); +} + +// Get the item's transformation center in document coordinates (i.e. in pixels) +Geom::Point SPItem::getCenter() const { + document->ensureUpToDate(); + + // Copied from DocumentProperties::onDocUnitChange() + gdouble viewscale = 1.0; + Geom::Rect vb = this->document->getRoot()->viewBox; + if ( !vb.hasZeroArea() ) { + gdouble viewscale_w = this->document->getWidth().value("px") / vb.width(); + gdouble viewscale_h = this->document->getHeight().value("px")/ vb.height(); + viewscale = std::min(viewscale_h, viewscale_w); + } + + // FIXME this is seriously wrong + Geom::OptRect bbox = desktopGeometricBounds(); + if (bbox) { + // transform_center_x/y are stored in user units, so we have to take the viewbox into account to translate to document coordinates + return bbox->midpoint() + Geom::Point (transform_center_x*viewscale, transform_center_y*viewscale); + + } else { + return Geom::Point(0, 0); // something's wrong! + } + +} + +void +SPItem::scaleCenter(Geom::Scale const &sc) { + transform_center_x *= sc[Geom::X]; + transform_center_y *= sc[Geom::Y]; +} + +namespace { + +bool is_item(SPObject const &object) { + return dynamic_cast(&object) != NULL; +} + +} + +void SPItem::raiseToTop() { + using Inkscape::Algorithms::find_last_if; + + auto topmost = find_last_if(++parent->children.iterator_to(*this), parent->children.end(), &is_item); + if (topmost != parent->children.end()) { + getRepr()->parent()->changeOrder( getRepr(), topmost->getRepr() ); + } +} + +bool SPItem::raiseOne() { + auto next_higher = std::find_if(++parent->children.iterator_to(*this), parent->children.end(), &is_item); + if (next_higher != parent->children.end()) { + Inkscape::XML::Node *ref = next_higher->getRepr(); + getRepr()->parent()->changeOrder(getRepr(), ref); + return true; + } + return false; +} + +bool SPItem::lowerOne() { + using Inkscape::Algorithms::find_last_if; + + auto next_lower = find_last_if(parent->children.begin(), parent->children.iterator_to(*this), &is_item); + if (next_lower != parent->children.iterator_to(*this)) { + Inkscape::XML::Node *ref = nullptr; + if (next_lower != parent->children.begin()) { + next_lower--; + ref = next_lower->getRepr(); + } + getRepr()->parent()->changeOrder(getRepr(), ref); + return true; + } + return false; +} + +void SPItem::lowerToBottom() { + auto bottom = std::find_if(parent->children.begin(), parent->children.iterator_to(*this), &is_item); + if (bottom != parent->children.iterator_to(*this)) { + Inkscape::XML::Node *ref = nullptr; + if (bottom != parent->children.begin()) { + bottom--; + ref = bottom->getRepr(); + } + parent->getRepr()->changeOrder(getRepr(), ref); + } +} + +void SPItem::moveTo(SPItem *target, bool intoafter) { + + Inkscape::XML::Node *target_ref = ( target ? target->getRepr() : NULL ); + Inkscape::XML::Node *our_ref = getRepr(); + + if (!target_ref) { + // Assume move to the "first" in the top node, find the top node + intoafter = false; + SPObject* bottom = this->document->getObjectByRepr(our_ref->root())->firstChild(); + while(!dynamic_cast(bottom->getNext())){ + bottom = bottom->getNext(); + } + target_ref = bottom->getRepr(); + } + + if (target_ref == our_ref) { + // Move to ourself ignore + return; + } + + if (intoafter) { + // Move this inside of the target at the end + our_ref->parent()->removeChild(our_ref); + target_ref->addChild(our_ref, NULL); + } else if (target_ref->parent() != our_ref->parent()) { + // Change in parent, need to remove and add + our_ref->parent()->removeChild(our_ref); + target_ref->parent()->addChild(our_ref, target_ref); + } else { + // Same parent, just move + our_ref->parent()->changeOrder(our_ref, target_ref); + } +} + +void SPItem::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPItem* object = this; + + object->readAttr( "style" ); + object->readAttr( "transform" ); + object->readAttr( "clip-path" ); + object->readAttr( "mask" ); + object->readAttr( "sodipodi:insensitive" ); + object->readAttr( "inkscape:transform-center-x" ); + object->readAttr( "inkscape:transform-center-y" ); + object->readAttr( "inkscape:connector-avoid" ); + object->readAttr( "inkscape:connection-points" ); + object->readAttr( "inkscape:highlight-color" ); + + SPObject::build(document, repr); +} + +void SPItem::release() { + SPItem* item = this; + + // Note: do this here before the clip_ref is deleted, since calling + // ensureUpToDate() for triggered routing may reference + // the deleted clip_ref. + delete item->avoidRef; + + // we do NOT disconnect from the changed signal of those before deletion. + // The destructor will call *_ref_changed with NULL as the new value, + // which will cause the hide() function to be called. + delete item->clip_ref; + delete item->mask_ref; + + SPObject::release(); + + SPPaintServer *fill_ps = style->getFillPaintServer(); + SPPaintServer *stroke_ps = style->getStrokePaintServer(); + while (item->display) { + if (fill_ps) { + fill_ps->hide(item->display->arenaitem->key()); + } + if (stroke_ps) { + stroke_ps->hide(item->display->arenaitem->key()); + } + item->display = sp_item_view_list_remove(item->display, item->display); + } + + //item->_transformed_signal.~signal(); +} + +void SPItem::set(unsigned int key, gchar const* value) { + SPItem *item = this; + SPItem* object = item; + + switch (key) { + case SP_ATTR_TRANSFORM: { + Geom::Affine t; + if (value && sp_svg_transform_read(value, &t)) { + item->set_item_transform(t); + } else { + item->set_item_transform(Geom::identity()); + } + break; + } + case SP_PROP_CLIP_PATH: { + gchar *uri = extract_uri(value); + if (uri) { + try { + item->clip_ref->attach(Inkscape::URI(uri)); + } catch (Inkscape::BadURIException &e) { + g_warning("%s", e.what()); + item->clip_ref->detach(); + } + g_free(uri); + } else { + item->clip_ref->detach(); + } + + break; + } + case SP_PROP_MASK: { + gchar *uri = extract_uri(value); + if (uri) { + try { + item->mask_ref->attach(Inkscape::URI(uri)); + } catch (Inkscape::BadURIException &e) { + g_warning("%s", e.what()); + item->mask_ref->detach(); + } + g_free(uri); + } else { + item->mask_ref->detach(); + } + + break; + } + case SP_ATTR_SODIPODI_INSENSITIVE: + { + item->sensitive = !value; + for (SPItemView *v = item->display; v != NULL; v = v->next) { + v->arenaitem->setSensitive(item->sensitive); + } + break; + } + case SP_ATTR_INKSCAPE_HIGHLIGHT_COLOR: + { + g_free(item->_highlightColor); + if (value) { + item->_highlightColor = g_strdup(value); + } else { + item->_highlightColor = NULL; + } + break; + } + case SP_ATTR_CONNECTOR_AVOID: + item->avoidRef->setAvoid(value); + break; + case SP_ATTR_TRANSFORM_CENTER_X: + if (value) { + item->transform_center_x = g_strtod(value, NULL); + } else { + item->transform_center_x = 0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_TRANSFORM_CENTER_Y: + if (value) { + item->transform_center_y = g_strtod(value, NULL); + } else { + item->transform_center_y = 0; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_PROP_SYSTEM_LANGUAGE: + case SP_PROP_REQUIRED_FEATURES: + case SP_PROP_REQUIRED_EXTENSIONS: + { + item->resetEvaluated(); + // pass to default handler + } + default: + if (SP_ATTRIBUTE_IS_CSS(key)) { + style->readFromObject( this ); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + } else { + SPObject::set(key, value); + } + break; + } +} + +void SPItem::clip_ref_changed(SPObject *old_clip, SPObject *clip, SPItem *item) +{ + item->bbox_valid = FALSE; // force a re-evaluation + if (old_clip) { + SPItemView *v; + /* Hide clippath */ + for (v = item->display; v != NULL; v = v->next) { + SPClipPath *oldPath = dynamic_cast(old_clip); + g_assert(oldPath != NULL); + oldPath->hide(v->arenaitem->key()); + } + } + SPClipPath *clipPath = dynamic_cast(clip); + if (clipPath) { + Geom::OptRect bbox = item->geometricBounds(); + for (SPItemView *v = item->display; v != NULL; v = v->next) { + if (!v->arenaitem->key()) { + v->arenaitem->setKey(SPItem::display_key_new(3)); + } + Inkscape::DrawingItem *ai = clipPath->show( + v->arenaitem->drawing(), + v->arenaitem->key()); + v->arenaitem->setClip(ai); + clipPath->setBBox(v->arenaitem->key(), bbox); + clip->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } + } + item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +void SPItem::mask_ref_changed(SPObject *old_mask, SPObject *mask, SPItem *item) +{ + if (old_mask) { + /* Hide mask */ + for (SPItemView *v = item->display; v != NULL; v = v->next) { + SPMask *maskItem = dynamic_cast(old_mask); + g_assert(maskItem != NULL); + maskItem->sp_mask_hide(v->arenaitem->key()); + } + } + SPMask *maskItem = dynamic_cast(mask); + if (maskItem) { + Geom::OptRect bbox = item->geometricBounds(); + for (SPItemView *v = item->display; v != NULL; v = v->next) { + if (!v->arenaitem->key()) { + v->arenaitem->setKey(SPItem::display_key_new(3)); + } + Inkscape::DrawingItem *ai = maskItem->sp_mask_show( + v->arenaitem->drawing(), + v->arenaitem->key()); + v->arenaitem->setMask(ai); + maskItem->sp_mask_set_bbox(v->arenaitem->key(), bbox); + mask->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } + } +} + +void SPItem::fill_ps_ref_changed(SPObject *old_ps, SPObject *ps, SPItem *item) { + SPPaintServer *old_fill_ps = dynamic_cast(old_ps); + if (old_fill_ps) { + for (SPItemView *v =item->display; v != NULL; v = v->next) { + old_fill_ps->hide(v->arenaitem->key()); + } + } + + SPPaintServer *new_fill_ps = dynamic_cast(ps); + if (new_fill_ps) { + Geom::OptRect bbox = item->geometricBounds(); + for (SPItemView *v = item->display; v != NULL; v = v->next) { + if (!v->arenaitem->key()) { + v->arenaitem->setKey(SPItem::display_key_new(3)); + } + Inkscape::DrawingPattern *pi = new_fill_ps->show( + v->arenaitem->drawing(), v->arenaitem->key(), bbox); + v->arenaitem->setFillPattern(pi); + if (pi) { + new_fill_ps->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } + } + } +} + +void SPItem::stroke_ps_ref_changed(SPObject *old_ps, SPObject *ps, SPItem *item) { + SPPaintServer *old_stroke_ps = dynamic_cast(old_ps); + if (old_stroke_ps) { + for (SPItemView *v =item->display; v != NULL; v = v->next) { + old_stroke_ps->hide(v->arenaitem->key()); + } + } + + SPPaintServer *new_stroke_ps = dynamic_cast(ps); + if (new_stroke_ps) { + Geom::OptRect bbox = item->geometricBounds(); + for (SPItemView *v = item->display; v != NULL; v = v->next) { + if (!v->arenaitem->key()) { + v->arenaitem->setKey(SPItem::display_key_new(3)); + } + Inkscape::DrawingPattern *pi = new_stroke_ps->show( + v->arenaitem->drawing(), v->arenaitem->key(), bbox); + v->arenaitem->setStrokePattern(pi); + if (pi) { + new_stroke_ps->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } + } + } +} + +void SPItem::update(SPCtx* ctx, guint flags) { + + SPItemCtx const *ictx = reinterpret_cast(ctx); + + // Any of the modifications defined in sp-object.h might change bbox, + // so we invalidate it unconditionally + bbox_valid = FALSE; + + viewport = ictx->viewport; // Cache viewport + + if (flags & (SP_OBJECT_CHILD_MODIFIED_FLAG | + SP_OBJECT_MODIFIED_FLAG | + SP_OBJECT_STYLE_MODIFIED_FLAG) ) { + if (flags & SP_OBJECT_MODIFIED_FLAG) { + for (SPItemView *v = display; v != NULL; v = v->next) { + v->arenaitem->setTransform(transform); + } + } + + SPClipPath *clip_path = clip_ref ? clip_ref->getObject() : NULL; + SPMask *mask = mask_ref ? mask_ref->getObject() : NULL; + + if ( clip_path || mask ) { + Geom::OptRect bbox = geometricBounds(); + if (clip_path) { + for (SPItemView *v = display; v != NULL; v = v->next) { + clip_path->setBBox(v->arenaitem->key(), bbox); + } + } + if (mask) { + for (SPItemView *v = display; v != NULL; v = v->next) { + mask->sp_mask_set_bbox(v->arenaitem->key(), bbox); + } + } + } + + if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) { + for (SPItemView *v = display; v != NULL; v = v->next) { + v->arenaitem->setOpacity(SP_SCALE24_TO_FLOAT(style->opacity.value)); + v->arenaitem->setAntialiasing(style->shape_rendering.computed == SP_CSS_SHAPE_RENDERING_CRISPEDGES ? 0 : 2); + v->arenaitem->setIsolation( style->isolation.value ); + v->arenaitem->setBlendMode( style->mix_blend_mode.value ); + v->arenaitem->setVisible(!isHidden()); + } + } + } + /* Update bounding box in user space, used for filter and objectBoundingBox units */ + if (style->filter.set && display) { + Geom::OptRect item_bbox = geometricBounds(); + SPItemView *itemview = display; + do { + if (itemview->arenaitem) + itemview->arenaitem->setItemBounds(item_bbox); + } while ( (itemview = itemview->next) ); + } + + // Update libavoid with item geometry (for connector routing). + if (avoidRef && document) { + avoidRef->handleSettingChange(); + } +} + +void SPItem::modified(unsigned int /*flags*/) +{ +#ifdef OBJECT_TRACE + objectTrace( "SPItem::modified" ); + objectTrace( "SPItem::modified", false ); +#endif +} + +Inkscape::XML::Node* SPItem::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + SPItem *item = this; + SPItem* object = item; + + // in the case of SP_OBJECT_WRITE_BUILD, the item should always be newly created, + // so we need to add any children from the underlying object to the new repr + if (flags & SP_OBJECT_WRITE_BUILD) { + std::vectorl; + for (auto& child: object->children) { + if (dynamic_cast(&child) || dynamic_cast(&child)) { + Inkscape::XML::Node *crepr = child.updateRepr(xml_doc, NULL, flags); + if (crepr) { + l.push_back(crepr); + } + } + } + for (auto i = l.rbegin(); i!= l.rend(); ++i) { + repr->addChild(*i, NULL); + Inkscape::GC::release(*i); + } + } else { + for (auto& child: object->children) { + if (dynamic_cast(&child) || dynamic_cast(&child)) { + child.updateRepr(flags); + } + } + } + + gchar *c = sp_svg_transform_write(item->transform); + repr->setAttribute("transform", c); + g_free(c); + + if (flags & SP_OBJECT_WRITE_EXT) { + repr->setAttribute("sodipodi:insensitive", ( item->sensitive ? NULL : "true" )); + if (item->transform_center_x != 0) + sp_repr_set_svg_double (repr, "inkscape:transform-center-x", item->transform_center_x); + else + repr->setAttribute ("inkscape:transform-center-x", NULL); + if (item->transform_center_y != 0) + sp_repr_set_svg_double (repr, "inkscape:transform-center-y", item->transform_center_y); + else + repr->setAttribute ("inkscape:transform-center-y", NULL); + } + + if (item->clip_ref){ + if (item->clip_ref->getObject()) { + gchar *uri = item->clip_ref->getURI()->toString(); + const gchar *value = g_strdup_printf ("url(%s)", uri); + repr->setAttribute ("clip-path", value); + g_free ((void *) value); + g_free ((void *) uri); + } + } + if (item->mask_ref){ + if (item->mask_ref->getObject()) { + gchar *uri = item->mask_ref->getURI()->toString(); + const gchar *value = g_strdup_printf ("url(%s)", uri); + repr->setAttribute ("mask", value); + g_free ((void *) value); + g_free ((void *) uri); + } + } + if (item->_highlightColor){ + repr->setAttribute("inkscape:highlight-color", item->_highlightColor); + } else { + repr->setAttribute("inkscape:highlight-color", NULL); + } + + SPObject::write(xml_doc, repr, flags); + + return repr; +} + +// CPPIFY: make pure virtual +Geom::OptRect SPItem::bbox(Geom::Affine const & /*transform*/, SPItem::BBoxType /*type*/) const { + //throw; + return Geom::OptRect(); +} + +Geom::OptRect SPItem::geometricBounds(Geom::Affine const &transform) const +{ + Geom::OptRect bbox; + + // call the subclass method + // CPPIFY + //bbox = this->bbox(transform, SPItem::GEOMETRIC_BBOX); + bbox = const_cast(this)->bbox(transform, SPItem::GEOMETRIC_BBOX); + + return bbox; +} + +Geom::OptRect SPItem::visualBounds(Geom::Affine const &transform) const +{ + using Geom::X; + using Geom::Y; + + Geom::OptRect bbox; + + + SPFilter *filter = (style && style->filter.href) ? dynamic_cast(style->getFilter()) : NULL; + if ( filter ) { + // call the subclass method + // CPPIFY + //bbox = this->bbox(Geom::identity(), SPItem::VISUAL_BBOX); + bbox = const_cast(this)->bbox(Geom::identity(), SPItem::GEOMETRIC_BBOX); // see LP Bug 1229971 + + // default filer area per the SVG spec: + SVGLength x, y, w, h; + Geom::Point minp, maxp; + x.set(SVGLength::PERCENT, -0.10, 0); + y.set(SVGLength::PERCENT, -0.10, 0); + w.set(SVGLength::PERCENT, 1.20, 0); + h.set(SVGLength::PERCENT, 1.20, 0); + + // if area is explicitly set, override: + if (filter->x._set) + x = filter->x; + if (filter->y._set) + y = filter->y; + if (filter->width._set) + w = filter->width; + if (filter->height._set) + h = filter->height; + + double len_x = bbox ? bbox->width() : 0; + double len_y = bbox ? bbox->height() : 0; + + x.update(12, 6, len_x); + y.update(12, 6, len_y); + w.update(12, 6, len_x); + h.update(12, 6, len_y); + + if (filter->filterUnits == SP_FILTER_UNITS_OBJECTBOUNDINGBOX && bbox) { + minp[X] = bbox->left() + x.computed * (x.unit == SVGLength::PERCENT ? 1.0 : len_x); + maxp[X] = minp[X] + w.computed * (w.unit == SVGLength::PERCENT ? 1.0 : len_x); + minp[Y] = bbox->top() + y.computed * (y.unit == SVGLength::PERCENT ? 1.0 : len_y); + maxp[Y] = minp[Y] + h.computed * (h.unit == SVGLength::PERCENT ? 1.0 : len_y); + } else if (filter->filterUnits == SP_FILTER_UNITS_USERSPACEONUSE) { + minp[X] = x.computed; + maxp[X] = minp[X] + w.computed; + minp[Y] = y.computed; + maxp[Y] = minp[Y] + h.computed; + } + bbox = Geom::OptRect(minp, maxp); + *bbox *= transform; + } else { + // call the subclass method + // CPPIFY + //bbox = this->bbox(transform, SPItem::VISUAL_BBOX); + bbox = const_cast(this)->bbox(transform, SPItem::VISUAL_BBOX); + } + if (clip_ref->getObject()) { + SPItem *ownerItem = dynamic_cast(clip_ref->getOwner()); + g_assert(ownerItem != NULL); + ownerItem->bbox_valid = FALSE; // LP Bug 1349018 + bbox.intersectWith(clip_ref->getObject()->geometricBounds(transform)); + } + + return bbox; +} + +Geom::OptRect SPItem::bounds(BBoxType type, Geom::Affine const &transform) const +{ + if (type == GEOMETRIC_BBOX) { + return geometricBounds(transform); + } else { + return visualBounds(transform); + } +} + +Geom::OptRect SPItem::documentPreferredBounds() const +{ + if (Inkscape::Preferences::get()->getInt("/tools/bounding_box") == 0) { + return documentBounds(SPItem::VISUAL_BBOX); + } else { + return documentBounds(SPItem::GEOMETRIC_BBOX); + } +} + + + +Geom::OptRect SPItem::documentGeometricBounds() const +{ + return geometricBounds(i2doc_affine()); +} + +Geom::OptRect SPItem::documentVisualBounds() const +{ + if (!bbox_valid) { + doc_bbox = visualBounds(i2doc_affine()); + bbox_valid = true; + } + return doc_bbox; +} +Geom::OptRect SPItem::documentBounds(BBoxType type) const +{ + if (type == GEOMETRIC_BBOX) { + return documentGeometricBounds(); + } else { + return documentVisualBounds(); + } +} + +Geom::OptRect SPItem::desktopGeometricBounds() const +{ + return geometricBounds(i2dt_affine()); +} + +Geom::OptRect SPItem::desktopVisualBounds() const +{ + /// @fixme hardcoded desktop transform + Geom::Affine m = Geom::Scale(1, -1) * Geom::Translate(0, document->getHeight().value("px")); + Geom::OptRect ret = documentVisualBounds(); + if (ret) *ret *= m; + return ret; +} + +Geom::OptRect SPItem::desktopPreferredBounds() const +{ + if (Inkscape::Preferences::get()->getInt("/tools/bounding_box") == 0) { + return desktopBounds(SPItem::VISUAL_BBOX); + } else { + return desktopBounds(SPItem::GEOMETRIC_BBOX); + } +} + +Geom::OptRect SPItem::desktopBounds(BBoxType type) const +{ + if (type == GEOMETRIC_BBOX) { + return desktopGeometricBounds(); + } else { + return desktopVisualBounds(); + } +} + +unsigned int SPItem::pos_in_parent() const { + g_assert(parent != NULL); + g_assert(SP_IS_OBJECT(parent)); + + unsigned int pos = 0; + + for (auto& iter: parent->children) { + if (&iter == this) { + return pos; + } + + if (dynamic_cast(&iter)) { + pos++; + } + } + + g_assert_not_reached(); + return 0; +} + +// CPPIFY: make pure virtual, see below! +void SPItem::snappoints(std::vector & /*p*/, Inkscape::SnapPreferences const */*snapprefs*/) const { + //throw; +} + /* This will only be called if the derived class doesn't override this. + * see for example sp_genericellipse_snappoints in sp-ellipse.cpp + * We don't know what shape we could be dealing with here, so we'll just + * do nothing + */ + +void SPItem::getSnappoints(std::vector &p, Inkscape::SnapPreferences const *snapprefs) const +{ + // Get the snappoints of the item + // CPPIFY + //this->snappoints(p, snapprefs); + const_cast(this)->snappoints(p, snapprefs); + + // Get the snappoints at the item's center + if (snapprefs != NULL && snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_ROTATION_CENTER)) { + p.push_back(Inkscape::SnapCandidatePoint(getCenter(), Inkscape::SNAPSOURCE_ROTATION_CENTER, Inkscape::SNAPTARGET_ROTATION_CENTER)); + } + + // Get the snappoints of clipping paths and mask, if any + std::list clips_and_masks; + + clips_and_masks.push_back(clip_ref->getObject()); + clips_and_masks.push_back(mask_ref->getObject()); + + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + for (std::list::const_iterator o = clips_and_masks.begin(); o != clips_and_masks.end(); ++o) { + if (*o) { + // obj is a group object, the children are the actual clippers + for(auto& child: (*o)->children) { + SPItem *item = dynamic_cast(const_cast(&child)); + if (item) { + std::vector p_clip_or_mask; + // Please note the recursive call here! + item->getSnappoints(p_clip_or_mask, snapprefs); + // Take into account the transformation of the item being clipped or masked + for (std::vector::const_iterator p_orig = p_clip_or_mask.begin(); p_orig != p_clip_or_mask.end(); ++p_orig) { + // All snappoints are in desktop coordinates, but the item's transformation is + // in document coordinates. Hence the awkward construction below + Geom::Point pt = desktop->dt2doc((*p_orig).getPoint()) * i2dt_affine(); + p.push_back(Inkscape::SnapCandidatePoint(pt, (*p_orig).getSourceType(), (*p_orig).getTargetType())); + } + } + } + } + } +} + +// CPPIFY: make pure virtual +void SPItem::print(SPPrintContext* /*ctx*/) { + //throw; +} + +void SPItem::invoke_print(SPPrintContext *ctx) +{ + if ( !isHidden() ) { + if (!transform.isIdentity() || style->opacity.value != SP_SCALE24_MAX) { + sp_print_bind(ctx, transform, SP_SCALE24_TO_FLOAT(style->opacity.value)); + this->print(ctx); + sp_print_release(ctx); + } else { + this->print(ctx); + } + } +} + +const char* SPItem::displayName() const { + return _("Object"); +} + +gchar* SPItem::description() const { + return g_strdup(""); +} + +gchar *SPItem::detailedDescription() const { + gchar* s = g_strdup_printf("%s %s", + this->displayName(), this->description()); + + if (s && clip_ref->getObject()) { + gchar *snew = g_strdup_printf (_("%s; clipped"), s); + g_free (s); + s = snew; + } + + if (s && mask_ref->getObject()) { + gchar *snew = g_strdup_printf (_("%s; masked"), s); + g_free (s); + s = snew; + } + + if ( style && style->filter.href && style->filter.href->getObject() ) { + const gchar *label = style->filter.href->getObject()->label(); + gchar *snew = 0; + + if (label) { + snew = g_strdup_printf (_("%s; filtered (%s)"), s, _(label)); + } else { + snew = g_strdup_printf (_("%s; filtered"), s); + } + + g_free (s); + s = snew; + } + + return s; +} + +bool SPItem::isFiltered() const { + return (style && style->filter.href && style->filter.href->getObject()); +} + +unsigned SPItem::display_key_new(unsigned numkeys) +{ + static unsigned dkey = 0; + + dkey += numkeys; + + return dkey - numkeys; +} + +// CPPIFY: make pure virtual +Inkscape::DrawingItem* SPItem::show(Inkscape::Drawing& /*drawing*/, unsigned int /*key*/, unsigned int /*flags*/) { + //throw; + return 0; +} + +Inkscape::DrawingItem *SPItem::invoke_show(Inkscape::Drawing &drawing, unsigned key, unsigned flags) +{ + Inkscape::DrawingItem *ai = NULL; + + ai = this->show(drawing, key, flags); + + if (ai != NULL) { + Geom::OptRect item_bbox = geometricBounds(); + + display = sp_item_view_new_prepend(display, this, flags, key, ai); + ai->setTransform(transform); + ai->setOpacity(SP_SCALE24_TO_FLOAT(style->opacity.value)); + ai->setIsolation( style->isolation.value ); + ai->setBlendMode( style->mix_blend_mode.value ); + //ai->setCompositeOperator( style->composite_op.value ); + ai->setVisible(!isHidden()); + ai->setSensitive(sensitive); + if (clip_ref->getObject()) { + SPClipPath *cp = clip_ref->getObject(); + + if (!display->arenaitem->key()) { + display->arenaitem->setKey(display_key_new(3)); + } + int clip_key = display->arenaitem->key(); + + // Show and set clip + Inkscape::DrawingItem *ac = cp->show(drawing, clip_key); + ai->setClip(ac); + + // Update bbox, in case the clip uses bbox units + cp->setBBox(clip_key, item_bbox); + cp->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } + if (mask_ref->getObject()) { + SPMask *mask = mask_ref->getObject(); + + if (!display->arenaitem->key()) { + display->arenaitem->setKey(display_key_new(3)); + } + int mask_key = display->arenaitem->key(); + + // Show and set mask + Inkscape::DrawingItem *ac = mask->sp_mask_show(drawing, mask_key); + ai->setMask(ac); + + // Update bbox, in case the mask uses bbox units + mask->sp_mask_set_bbox(mask_key, item_bbox); + mask->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } + + SPPaintServer *fill_ps = style->getFillPaintServer(); + if (fill_ps) { + if (!display->arenaitem->key()) { + display->arenaitem->setKey(display_key_new(3)); + } + int fill_key = display->arenaitem->key(); + + Inkscape::DrawingPattern *ap = fill_ps->show(drawing, fill_key, item_bbox); + ai->setFillPattern(ap); + if (ap) { + fill_ps->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } + } + SPPaintServer *stroke_ps = style->getStrokePaintServer(); + if (stroke_ps) { + if (!display->arenaitem->key()) { + display->arenaitem->setKey(display_key_new(3)); + } + int stroke_key = display->arenaitem->key(); + + Inkscape::DrawingPattern *ap = stroke_ps->show(drawing, stroke_key, item_bbox); + ai->setStrokePattern(ap); + if (ap) { + stroke_ps->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } + } + ai->setData(this); + ai->setItemBounds(geometricBounds()); + } + + return ai; +} + +// CPPIFY: make pure virtual +void SPItem::hide(unsigned int /*key*/) { + //throw; +} + +void SPItem::invoke_hide(unsigned key) +{ + this->hide(key); + + SPItemView *ref = NULL; + SPItemView *v = display; + while (v != NULL) { + SPItemView *next = v->next; + if (v->key == key) { + if (clip_ref->getObject()) { + (clip_ref->getObject())->hide(v->arenaitem->key()); + v->arenaitem->setClip(NULL); + } + if (mask_ref->getObject()) { + mask_ref->getObject()->sp_mask_hide(v->arenaitem->key()); + v->arenaitem->setMask(NULL); + } + SPPaintServer *fill_ps = style->getFillPaintServer(); + if (fill_ps) { + fill_ps->hide(v->arenaitem->key()); + } + SPPaintServer *stroke_ps = style->getStrokePaintServer(); + if (stroke_ps) { + stroke_ps->hide(v->arenaitem->key()); + } + if (!ref) { + display = v->next; + } else { + ref->next = v->next; + } + delete v->arenaitem; + g_free(v); + } else { + ref = v; + } + v = next; + } +} + +// Adjusters + +void SPItem::adjust_pattern(Geom::Affine const &postmul, bool set, PatternTransform pt) +{ + bool fill = (pt == TRANSFORM_FILL || pt == TRANSFORM_BOTH); + if (fill && style && (style->fill.isPaintserver())) { + SPObject *server = style->getFillPaintServer(); + SPPattern *serverPatt = dynamic_cast(server); + if ( serverPatt ) { + SPPattern *pattern = serverPatt->clone_if_necessary(this, "fill"); + pattern->transform_multiply(postmul, set); + } + } + + bool stroke = (pt == TRANSFORM_STROKE || pt == TRANSFORM_BOTH); + if (stroke && style && (style->stroke.isPaintserver())) { + SPObject *server = style->getStrokePaintServer(); + SPPattern *serverPatt = dynamic_cast(server); + if ( serverPatt ) { + SPPattern *pattern = serverPatt->clone_if_necessary(this, "stroke"); + pattern->transform_multiply(postmul, set); + } + } +} + +void SPItem::adjust_gradient( Geom::Affine const &postmul, bool set ) +{ + if ( style && style->fill.isPaintserver() ) { + SPPaintServer *server = style->getFillPaintServer(); + SPGradient *serverGrad = dynamic_cast(server); + if ( serverGrad ) { + + /** + * \note Bbox units for a gradient are generally a bad idea because + * with them, you cannot preserve the relative position of the + * object and its gradient after rotation or skew. So now we + * convert them to userspace units which are easy to keep in sync + * just by adding the object's transform to gradientTransform. + * \todo FIXME: convert back to bbox units after transforming with + * the item, so as to preserve the original units. + */ + SPGradient *gradient = sp_gradient_convert_to_userspace( serverGrad, this, "fill" ); + + sp_gradient_transform_multiply( gradient, postmul, set ); + } + } + + if ( style && style->stroke.isPaintserver() ) { + SPPaintServer *server = style->getStrokePaintServer(); + SPGradient *serverGrad = dynamic_cast(server); + if ( serverGrad ) { + SPGradient *gradient = sp_gradient_convert_to_userspace( serverGrad, this, "stroke"); + sp_gradient_transform_multiply( gradient, postmul, set ); + } + } +} + +void SPItem::adjust_stroke( gdouble ex ) +{ + if (freeze_stroke_width) { + return; + } + + SPStyle *style = this->style; + + if (style && !Geom::are_near(ex, 1.0, Geom::EPSILON)) { + style->stroke_width.computed *= ex; + style->stroke_width.set = TRUE; + + if ( !style->stroke_dasharray.values.empty() ) { + for (unsigned i = 0; i < style->stroke_dasharray.values.size(); i++) { + style->stroke_dasharray.values[i] *= ex; + } + style->stroke_dashoffset.value *= ex; + } + + updateRepr(); + } +} + +/** + * Find out the inverse of previous transform of an item (from its repr) + */ +static Geom::Affine sp_item_transform_repr (SPItem *item) +{ + Geom::Affine t_old(Geom::identity()); + gchar const *t_attr = item->getRepr()->attribute("transform"); + if (t_attr) { + Geom::Affine t; + if (sp_svg_transform_read(t_attr, &t)) { + t_old = t; + } + } + + return t_old; +} + + +void SPItem::adjust_stroke_width_recursive(double expansion) +{ + adjust_stroke (expansion); + +// A clone's child is the ghost of its original - we must not touch it, skip recursion + if ( !dynamic_cast(this) ) { + for (auto& o: children) { + SPItem *item = dynamic_cast(&o); + if (item) { + item->adjust_stroke_width_recursive(expansion); + } + } + } +} + +void SPItem::freeze_stroke_width_recursive(bool freeze) +{ + freeze_stroke_width = freeze; + +// A clone's child is the ghost of its original - we must not touch it, skip recursion + if ( !dynamic_cast(this) ) { + for (auto& o: children) { + SPItem *item = dynamic_cast(&o); + if (item) { + item->freeze_stroke_width_recursive(freeze); + } + } + } +} + +/** + * Recursively adjust rx and ry of rects. + */ +static void +sp_item_adjust_rects_recursive(SPItem *item, Geom::Affine advertized_transform) +{ + SPRect *rect = dynamic_cast(item); + if (rect) { + rect->compensateRxRy(advertized_transform); + } + + for(auto& o: item->children) { + SPItem *itm = dynamic_cast(&o); + if (itm) { + sp_item_adjust_rects_recursive(itm, advertized_transform); + } + } +} + +void SPItem::adjust_paint_recursive (Geom::Affine advertized_transform, Geom::Affine t_ancestors, bool is_pattern) +{ +// _Before_ full pattern/gradient transform: t_paint * t_item * t_ancestors +// _After_ full pattern/gradient transform: t_paint_new * t_item * t_ancestors * advertised_transform +// By equating these two expressions we get t_paint_new = t_paint * paint_delta, where: + Geom::Affine t_item = sp_item_transform_repr (this); + Geom::Affine paint_delta = t_item * t_ancestors * advertized_transform * t_ancestors.inverse() * t_item.inverse(); + +// Within text, we do not fork gradients, and so must not recurse to avoid double compensation; +// also we do not recurse into clones, because a clone's child is the ghost of its original - +// we must not touch it + if (!(dynamic_cast(this) || dynamic_cast(this))) { + for (auto& o: children) { + SPItem *item = dynamic_cast(&o); + if (item) { +// At the level of the transformed item, t_ancestors is identity; +// below it, it is the accmmulated chain of transforms from this level to the top level + item->adjust_paint_recursive (advertized_transform, t_item * t_ancestors, is_pattern); + } + } + } + +// We recursed into children first, and are now adjusting this object second; +// this is so that adjustments in a tree are done from leaves up to the root, +// and paintservers on leaves inheriting their values from ancestors could adjust themselves properly +// before ancestors themselves are adjusted, probably differently (bug 1286535) + + if (is_pattern) { + adjust_pattern(paint_delta); + } else { + adjust_gradient(paint_delta); + } +} + +void SPItem::adjust_livepatheffect (Geom::Affine const &postmul, bool set) +{ + SPLPEItem *lpeitem = dynamic_cast(this); + if ( lpeitem && lpeitem->hasPathEffect() ) { + lpeitem->forkPathEffectsIfNecessary(); + // now that all LPEs are forked_if_necessary, we can apply the transform + PathEffectList effect_list = lpeitem->getEffectList(); + for (PathEffectList::iterator it = effect_list.begin(); it != effect_list.end(); ++it) + { + LivePathEffectObject *lpeobj = (*it)->lpeobject; + if (lpeobj) { + Inkscape::LivePathEffect::Effect * lpe = lpeobj->get_lpe(); + if (lpe && lpe->isReady()) { + lpe->transform_multiply(postmul, set); + } + } + } + } +} + +// CPPIFY:: make pure virtual? +// Not all SPItems must necessarily have a set transform method! +Geom::Affine SPItem::set_transform(Geom::Affine const &transform) { +// throw; + return transform; +} + +void SPItem::doWriteTransform(Geom::Affine const &transform, Geom::Affine const *adv, bool compensate) +{ + // calculate the relative transform, if not given by the adv attribute + Geom::Affine advertized_transform; + if (adv != NULL) { + advertized_transform = *adv; + } else { + advertized_transform = sp_item_transform_repr (this).inverse() * transform; + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (compensate) { + // recursively compensating for stroke scaling will not always work, because it can be scaled to zero or infinite + // from which we cannot ever recover by applying an inverse scale; therefore we temporarily block any changes + // to the strokewidth in such a case instead, and unblock these after the transformation + // (as reported in https://bugs.launchpad.net/inkscape/+bug/825840/comments/4) + if (!prefs->getBool("/options/transform/stroke", true)) { + double const expansion = 1. / advertized_transform.descrim(); + if (expansion < 1e-9 || expansion > 1e9) { + freeze_stroke_width_recursive(true); + // This will only work if the item has a set_transform method (in this method adjust_stroke() will be called) + // We will still have to apply the inverse scaling to other items, not having a set_transform method + // such as ellipses and stars + // PS: We cannot use this freeze_stroke_width_recursive() trick in all circumstances. For example, it will + // break pasting objects within their group (because in such a case the transformation of the group will affect + // the strokewidth, and has to be compensated for. See https://bugs.launchpad.net/inkscape/+bug/959223/comments/10) + } else { + adjust_stroke_width_recursive(expansion); + } + } + + // recursively compensate rx/ry of a rect if requested + if (!prefs->getBool("/options/transform/rectcorners", true)) { + sp_item_adjust_rects_recursive(this, advertized_transform); + } + + // recursively compensate pattern fill if it's not to be transformed + if (!prefs->getBool("/options/transform/pattern", true)) { + adjust_paint_recursive (advertized_transform.inverse(), Geom::identity(), true); + } + /// \todo FIXME: add the same else branch as for gradients below, to convert patterns to userSpaceOnUse as well + /// recursively compensate gradient fill if it's not to be transformed + if (!prefs->getBool("/options/transform/gradient", true)) { + adjust_paint_recursive (advertized_transform.inverse(), Geom::identity(), false); + } else { + // this converts the gradient/pattern fill/stroke, if any, to userSpaceOnUse; we need to do + // it here _before_ the new transform is set, so as to use the pre-transform bbox + adjust_paint_recursive (Geom::identity(), Geom::identity(), false); + } + + } // endif(compensate) + + gint preserve = prefs->getBool("/options/preservetransform/value", 0); + Geom::Affine transform_attr (transform); + + // CPPIFY: check this code. + // If onSetTransform is not overridden, CItem::onSetTransform will return the transform it was given as a parameter. + // onSetTransform cannot be pure due to the fact that not all visible Items are transformable. + + if ( // run the object's set_transform (i.e. embed transform) only if: + (dynamic_cast(this) && firstChild() && dynamic_cast(firstChild())) || + (!preserve && // user did not chose to preserve all transforms + (!clip_ref || !clip_ref->getObject()) && // the object does not have a clippath + (!mask_ref || !mask_ref->getObject()) && // the object does not have a mask + !(!transform.isTranslation() && style && style->getFilter())) // the object does not have a filter, or the transform is translation (which is supposed to not affect filters) + ) + { + transform_attr = this->set_transform(transform); + + if (freeze_stroke_width) { + freeze_stroke_width_recursive(false); + } + } else { + SPLPEItem * lpeitem = SP_LPE_ITEM(this); + if (lpeitem && lpeitem->hasPathEffectRecursive()) { + lpeitem->adjust_livepatheffect(transform_attr); + } + if (freeze_stroke_width) { + freeze_stroke_width_recursive(false); + if (compensate) { + if (!prefs->getBool("/options/transform/stroke", true)) { + // Recursively compensate for stroke scaling, depending on user preference + // (As to why we need to do this, see the comment a few lines above near the freeze_stroke_width_recursive(true) call) + double const expansion = 1. / advertized_transform.descrim(); + adjust_stroke_width_recursive(expansion); + } + } + } + } + set_item_transform(transform_attr); + + + + // Note: updateRepr comes before emitting the transformed signal since + // it causes clone SPUse's copy of the original object to brought up to + // date with the original. Otherwise, sp_use_bbox returns incorrect + // values if called in code handling the transformed signal. + updateRepr(); + + // send the relative transform with a _transformed_signal + _transformed_signal.emit(&advertized_transform, this); +} + +// CPPIFY: see below, do not make pure? +gint SPItem::event(SPEvent* /*event*/) { + return FALSE; +} + +gint SPItem::emitEvent(SPEvent &event) +{ + return this->event(&event); +} + +void SPItem::set_item_transform(Geom::Affine const &transform_matrix) +{ + if (!Geom::are_near(transform_matrix, transform, 1e-18)) { + transform = transform_matrix; + /* The SP_OBJECT_USER_MODIFIED_FLAG_B is used to mark the fact that it's only a + transformation. It's apparently not used anywhere else. */ + requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_USER_MODIFIED_FLAG_B); + sp_item_rm_unsatisfied_cns(*this); + } +} + +//void SPItem::convert_to_guides() const { +// // CPPIFY: If not overridden, call SPItem::convert_to_guides() const, see below! +// this->convert_to_guides(); +//} + + +Geom::Affine i2anc_affine(SPObject const *object, SPObject const *const ancestor) { + Geom::Affine ret(Geom::identity()); + g_return_val_if_fail(object != NULL, ret); + + /* stop at first non-renderable ancestor */ + while ( object != ancestor && dynamic_cast(object) ) { + SPRoot const *root = dynamic_cast(object); + if (root) { + ret *= root->c2p; + } else { + SPItem const *item = dynamic_cast(object); + g_assert(item != NULL); + ret *= item->transform; + } + object = object->parent; + } + return ret; +} + +Geom::Affine +i2i_affine(SPObject const *src, SPObject const *dest) { + g_return_val_if_fail(src != NULL && dest != NULL, Geom::identity()); + SPObject const *ancestor = src->nearestCommonAncestor(dest); + return i2anc_affine(src, ancestor) * i2anc_affine(dest, ancestor).inverse(); +} + +Geom::Affine SPItem::getRelativeTransform(SPObject const *dest) const { + return i2i_affine(this, dest); +} + +Geom::Affine SPItem::i2doc_affine() const +{ + return i2anc_affine(this, NULL); +} + +Geom::Affine SPItem::i2dt_affine() const +{ + Geom::Affine ret; + SPDesktop const *desktop = SP_ACTIVE_DESKTOP; + if ( desktop ) { + ret = i2doc_affine() * desktop->doc2dt(); + } else { + // TODO temp code to prevent crashing on command-line launch: + ret = i2doc_affine() + * Geom::Scale(1, -1) + * Geom::Translate(0, document->getHeight().value("px")); + } + return ret; +} + +void SPItem::set_i2d_affine(Geom::Affine const &i2dt) +{ + Geom::Affine dt2p; /* desktop to item parent transform */ + if (parent) { + dt2p = static_cast(parent)->i2dt_affine().inverse(); + } else { + SPDesktop *dt = SP_ACTIVE_DESKTOP; + dt2p = dt->dt2doc(); + } + + Geom::Affine const i2p( i2dt * dt2p ); + set_item_transform(i2p); +} + + +Geom::Affine SPItem::dt2i_affine() const +{ + /* fixme: Implement the right way (Lauris) */ + return i2dt_affine().inverse(); +} + +/* Item views */ + +SPItemView *SPItem::sp_item_view_new_prepend(SPItemView *list, SPItem *item, unsigned flags, unsigned key, Inkscape::DrawingItem *drawing_item) +{ + g_assert(item != NULL); + g_assert(dynamic_cast(item) != NULL); + g_assert(drawing_item != NULL); + + SPItemView *new_view = g_new(SPItemView, 1); + + new_view->next = list; + new_view->flags = flags; + new_view->key = key; + new_view->arenaitem = drawing_item; + + return new_view; +} + +static SPItemView* +sp_item_view_list_remove(SPItemView *list, SPItemView *view) +{ + SPItemView *ret = list; + if (view == list) { + ret = list->next; + } else { + SPItemView *prev; + prev = list; + while (prev->next != view) prev = prev->next; + prev->next = view->next; + } + + delete view->arenaitem; + g_free(view); + + return ret; +} + +Inkscape::DrawingItem *SPItem::get_arenaitem(unsigned key) +{ + for ( SPItemView *iv = display ; iv ; iv = iv->next ) { + if ( iv->key == key ) { + return iv->arenaitem; + } + } + + return NULL; +} + +int sp_item_repr_compare_position(SPItem const *first, SPItem const *second) +{ + return sp_repr_compare_position(first->getRepr(), + second->getRepr()); +} + +SPItem const *sp_item_first_item_child(SPObject const *obj) +{ + return sp_item_first_item_child( const_cast(obj) ); +} + +SPItem *sp_item_first_item_child(SPObject *obj) +{ + SPItem *child = 0; + for (auto& iter: obj->children) { + SPItem *tmp = dynamic_cast(&iter); + if ( tmp ) { + child = tmp; + break; + } + } + return child; +} + +void SPItem::convert_to_guides() const { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int prefs_bbox = prefs->getInt("/tools/bounding_box", 0); + + Geom::OptRect bbox = (prefs_bbox == 0) ? desktopVisualBounds() : desktopGeometricBounds(); + if (!bbox) { + g_warning ("Cannot determine item's bounding box during conversion to guides.\n"); + return; + } + + std::list > pts; + + Geom::Point A((*bbox).min()); + Geom::Point C((*bbox).max()); + Geom::Point B(A[Geom::X], C[Geom::Y]); + Geom::Point D(C[Geom::X], A[Geom::Y]); + + pts.push_back(std::make_pair(A, B)); + pts.push_back(std::make_pair(B, C)); + pts.push_back(std::make_pair(C, D)); + pts.push_back(std::make_pair(D, A)); + + sp_guide_pt_pairs_to_guides(document, pts); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-item.h b/src/object/sp-item.h new file mode 100644 index 000000000..36af02edc --- /dev/null +++ b/src/object/sp-item.h @@ -0,0 +1,434 @@ +#ifndef SEEN_SP_ITEM_H +#define SEEN_SP_ITEM_H + +/** + * @file + * Some things pertinent to all visible shapes: SPItem, SPItemView, SPItemCtx, SPItemClass, SPEvent. + */ + +/* + * Authors: + * Lauris Kaplinski + * bulia byak + * Johan Engelen + * Abhishek Sharma + * + * Copyright (C) 1999-2006 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * Copyright (C) 2004 Monash University + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <2geom/forward.h> +#include <2geom/affine.h> +#include <2geom/rect.h> +#include + +#include "sp-object.h" +#include "snap-preferences.h" +#include "snap-candidate.h" + +//class SPGuideConstraint; +#include "sp-guide-constraint.h" + +class SPClipPathReference; +class SPMaskReference; +class SPAvoidRef; +class SPPattern; +struct SPPrintContext; +typedef unsigned int guint32; + +namespace Inkscape { + +class Drawing; +class DrawingItem; +class URIReference; + +} + +enum { + SP_EVENT_INVALID, + SP_EVENT_NONE, + SP_EVENT_ACTIVATE, + SP_EVENT_MOUSEOVER, + SP_EVENT_MOUSEOUT +}; + +// TODO make a completely new function that transforms either the fill or +// stroke of any SPItem without adding an extra parameter to adjust_pattern. +enum PatternTransform { + TRANSFORM_BOTH, + TRANSFORM_FILL, + TRANSFORM_STROKE +}; + +/** + * Event structure. + * + * @todo This is just placeholder. Plan: + * We do extensible event structure, that hold applicable (ui, non-ui) + * data pointers. So it is up to given object/arena implementation + * to process correct ones in meaningful way. + * Also, this probably goes to SPObject base class. + * + */ +class SPEvent { +public: + unsigned int type; + void* data; +}; + +class SPItemView { +public: + SPItemView *next; + unsigned int flags; + unsigned int key; + Inkscape::DrawingItem *arenaitem; +}; + +/* flags */ + +#define SP_ITEM_BBOX_VISUAL 1 + +#define SP_ITEM_SHOW_DISPLAY (1 << 0) + +/** + * Flag for referenced views (i.e. markers, clippaths, masks and patterns); + * currently unused, does the same as DISPLAY + */ +#define SP_ITEM_REFERENCE_FLAGS (1 << 1) + +/** + * Contains transformations to document/viewport and the viewport size. + */ +class SPItemCtx : public SPCtx { +public: + /** Item to document transformation */ + Geom::Affine i2doc; + + /** Viewport size */ + Geom::Rect viewport; + + /** Item to viewport transformation */ + Geom::Affine i2vp; +}; + +#define SP_ITEM(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_ITEM(obj) (dynamic_cast((SPObject*)obj) != NULL) + +/** + * Base class for visual SVG elements. + * SPItem is an abstract base class for all graphic (visible) SVG nodes. It + * is a subclass of SPObject, with great deal of specific functionality. + */ +class SPItem : public SPObject { +public: + enum BBoxType { + // legacy behavior: includes crude stroke, markers; excludes long miters, blur margin; is known to be wrong for caps + APPROXIMATE_BBOX, + // includes only the bare path bbox, no stroke, no nothing + GEOMETRIC_BBOX, + // includes everything: correctly done stroke (with proper miters and caps), markers, filter margins (e.g. blur) + VISUAL_BBOX + }; + + SPItem(); + virtual ~SPItem(); + + unsigned int sensitive : 1; + unsigned int stop_paint: 1; + mutable unsigned bbox_valid : 1; + double transform_center_x; + double transform_center_y; + bool freeze_stroke_width; + + Geom::Affine transform; + mutable Geom::OptRect doc_bbox; + Geom::Rect viewport; // Cache viewport information + + SPClipPathReference *clip_ref; + SPMaskReference *mask_ref; + + // Used for object-avoiding connectors + SPAvoidRef *avoidRef; + + SPItemView *display; + + std::vector constraints; + + sigc::signal _transformed_signal; + + bool isLocked() const; + void setLocked(bool lock); + + bool isHidden() const; + void setHidden(bool hidden); + + // Objects dialogue + bool isSensitive() const { + return sensitive; + }; + + bool isHighlightSet() const; + guint32 highlight_color() const; + + void setHighlightColor(guint32 color); + + void unsetHighlightColor(); + //==================== + + bool isEvaluated() const; + void setEvaluated(bool visible); + void resetEvaluated(); + + bool isHidden(unsigned display_key) const; + + /** + * Returns something suitable for the `Hide' checkbox in the Object Properties dialog box. + * Corresponds to setExplicitlyHidden. + */ + bool isExplicitlyHidden() const; + + /** + * Sets the display CSS property to `hidden' if \a val is true, + * otherwise makes it unset. + */ + void setExplicitlyHidden(bool val); + + /** + * Sets the transform_center_x and transform_center_y properties to retain the rotation center + */ + void setCenter(Geom::Point const &object_centre); + + void unsetCenter(); + bool isCenterSet() const; + Geom::Point getCenter() const; + void scaleCenter(Geom::Scale const &sc); + + bool isVisibleAndUnlocked() const; + + bool isVisibleAndUnlocked(unsigned display_key) const; + + Geom::Affine getRelativeTransform(SPObject const *obj) const; + + bool raiseOne(); + bool lowerOne(); + void raiseToTop(); + void lowerToBottom(); + + /** + * Move this SPItem into or after another SPItem in the doc. + * + * @param target the SPItem to move into or after. + * @param intoafter move to after the target (false), move inside (sublayer) of the target (true). + */ + void moveTo(SPItem *target, bool intoafter); + + sigc::connection connectTransformed(sigc::slot slot) { + return _transformed_signal.connect(slot); + } + + /** + * Get item's geometric bounding box in this item's coordinate system. + * + * The geometric bounding box includes only the path, disregarding all style attributes. + */ + Geom::OptRect geometricBounds(Geom::Affine const &transform = Geom::identity()) const; + + /** + * Get item's visual bounding box in this item's coordinate system. + * + * The visual bounding box includes the stroke and the filter region. + */ + Geom::OptRect visualBounds(Geom::Affine const &transform = Geom::identity()) const; + + Geom::OptRect bounds(BBoxType type, Geom::Affine const &transform = Geom::identity()) const; + + /** + * Get item's geometric bbox in document coordinate system. + * Document coordinates are the default coordinates of the root element: + * the origin is at the top left, X grows to the right and Y grows downwards. + */ + Geom::OptRect documentGeometricBounds() const; + + /** + * Get item's visual bbox in document coordinate system. + */ + Geom::OptRect documentVisualBounds() const; + + Geom::OptRect documentBounds(BBoxType type) const; + Geom::OptRect documentPreferredBounds() const; + + /** + * Get item's geometric bbox in desktop coordinate system. + * Desktop coordinates should be user defined. Currently they are hardcoded: + * origin is at bottom left, X grows to the right and Y grows upwards. + */ + Geom::OptRect desktopGeometricBounds() const; + + /** + * Get item's visual bbox in desktop coordinate system. + */ + Geom::OptRect desktopVisualBounds() const; + + Geom::OptRect desktopPreferredBounds() const; + Geom::OptRect desktopBounds(BBoxType type) const; + + unsigned int pos_in_parent() const; + + /** + * Returns a string suitable for status bar, formatted in pango markup language. + * + * Must be freed by caller. + */ + char *detailedDescription() const; + + /** + * Returns true if the item is filtered, false otherwise. + * Used with groups/lists to determine how many, or if any, are filtered. + */ + bool isFiltered() const; + + void invoke_print(SPPrintContext *ctx); + + /** + * Allocates unique integer keys. + * + * @param numkeys Number of keys required. + * @return First allocated key; hence if the returned key is n + * you can use n, n + 1, ..., n + (numkeys - 1) + */ + static unsigned int display_key_new(unsigned int numkeys); + + Inkscape::DrawingItem *invoke_show(Inkscape::Drawing &drawing, unsigned int key, unsigned int flags); + void invoke_hide(unsigned int key); + void getSnappoints(std::vector &p, Inkscape::SnapPreferences const *snapprefs=0) const; + void adjust_pattern(/* Geom::Affine const &premul, */ Geom::Affine const &postmul, bool set = false, PatternTransform = TRANSFORM_BOTH); + void adjust_gradient(/* Geom::Affine const &premul, */ Geom::Affine const &postmul, bool set = false); + void adjust_stroke(double ex); + + /** + * Recursively scale stroke width in \a item and its children by \a expansion. + */ + void adjust_stroke_width_recursive(double ex); + + void freeze_stroke_width_recursive(bool freeze); + + /** + * Recursively compensate pattern or gradient transform. + */ + void adjust_paint_recursive(Geom::Affine advertized_transform, Geom::Affine t_ancestors, bool is_pattern); + + void adjust_livepatheffect(Geom::Affine const &postmul, bool set = false); + + /** + * Set a new transform on an object. + * + * Compensate for stroke scaling and gradient/pattern fill transform, if + * necessary. Call the object's set_transform method if transforms are + * stored optimized. Send _transformed_signal. Invoke _write method so that + * the repr is updated with the new transform. + */ + void doWriteTransform(Geom::Affine const &transform, Geom::Affine const *adv = NULL, bool compensate = true); + + /** + * Sets item private transform (not propagated to repr), without compensating stroke widths, + * gradients, patterns as sp_item_write_transform does. + */ + void set_item_transform(Geom::Affine const &transform_matrix); + + int emitEvent (SPEvent &event); + + /** + * Return the arenaitem corresponding to the given item in the display + * with the given key + */ + Inkscape::DrawingItem *get_arenaitem(unsigned int key); + + /** + * Returns the accumulated transformation of the item and all its ancestors, including root's viewport. + * @pre (item != NULL) and SP_IS_ITEM(item). + */ + Geom::Affine i2doc_affine() const; + + /** + * Returns the transformation from item to desktop coords + */ + Geom::Affine i2dt_affine() const; + + void set_i2d_affine(Geom::Affine const &transform); + + /** + * should rather be named "sp_item_d2i_affine" to match "sp_item_i2d_affine" (or vice versa). + */ + Geom::Affine dt2i_affine() const; + + char *_highlightColor; + +private: + enum EvaluatedStatus + { + StatusUnknown, StatusCalculated, StatusSet + }; + + mutable bool _is_evaluated; + mutable EvaluatedStatus _evaluated_status; + + static SPItemView *sp_item_view_new_prepend(SPItemView *list, SPItem *item, unsigned flags, unsigned key, Inkscape::DrawingItem *arenaitem); + static void clip_ref_changed(SPObject *old_clip, SPObject *clip, SPItem *item); + static void mask_ref_changed(SPObject *old_clip, SPObject *clip, SPItem *item); + static void fill_ps_ref_changed(SPObject *old_clip, SPObject *clip, SPItem *item); + static void stroke_ps_ref_changed(SPObject *old_clip, SPObject *clip, SPItem *item); + +public: + virtual void build(SPDocument *document, Inkscape::XML::Node *repr); + virtual void release(); + virtual void set(unsigned int key, char const* value); + virtual void update(SPCtx *ctx, unsigned int flags); + virtual void modified(unsigned int flags); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); + + virtual Geom::OptRect bbox(Geom::Affine const &transform, SPItem::BBoxType type) const; + virtual void print(SPPrintContext *ctx); + virtual const char* displayName() const; + virtual char* description() const; + virtual Inkscape::DrawingItem* show(Inkscape::Drawing &drawing, unsigned int key, unsigned int flags); + virtual void hide(unsigned int key); + virtual void snappoints(std::vector &p, Inkscape::SnapPreferences const *snapprefs) const; + virtual Geom::Affine set_transform(Geom::Affine const &transform); + + virtual void convert_to_guides() const; + + virtual int event(SPEvent *event); +}; + + +// Utility + +/** + * @pre \a ancestor really is an ancestor (\>=) of \a object, or NULL. + * ("Ancestor (\>=)" here includes as far as \a object itself.) + */ +Geom::Affine i2anc_affine(SPObject const *item, SPObject const *ancestor); + +Geom::Affine i2i_affine(SPObject const *src, SPObject const *dest); + +/* fixme: - these are evil, but OK */ + +int sp_item_repr_compare_position(SPItem const *first, SPItem const *second); +SPItem *sp_item_first_item_child (SPObject *obj); +SPItem const *sp_item_first_item_child (SPObject const *obj); + +#endif // SEEN_SP_ITEM_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-line.cpp b/src/object/sp-line.cpp new file mode 100644 index 000000000..09ffd1f17 --- /dev/null +++ b/src/object/sp-line.cpp @@ -0,0 +1,170 @@ +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * Abhishek Sharma + * Jon A. Cruz + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "attributes.h" +#include "style.h" +#include "sp-line.h" +#include "sp-guide.h" +#include "display/curve.h" +#include +#include "document.h" +#include "inkscape.h" + +SPLine::SPLine() : SPShape() { + this->x1.unset(); + this->y1.unset(); + this->x2.unset(); + this->y2.unset(); +} + +SPLine::~SPLine() { +} + +void SPLine::build(SPDocument * document, Inkscape::XML::Node * repr) { + SPShape::build(document, repr); + + this->readAttr( "x1" ); + this->readAttr( "y1" ); + this->readAttr( "x2" ); + this->readAttr( "y2" ); +} + +void SPLine::set(unsigned int key, const gchar* value) { + /* fixme: we should really collect updates */ + + switch (key) { + case SP_ATTR_X1: + this->x1.readOrUnset(value); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_Y1: + this->y1.readOrUnset(value); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_X2: + this->x2.readOrUnset(value); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_Y2: + this->y2.readOrUnset(value); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + default: + SPShape::set(key, value); + break; + } +} + +void SPLine::update(SPCtx *ctx, guint flags) { + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + SPStyle const *style = this->style; + SPItemCtx const *ictx = (SPItemCtx const *) ctx; + double const w = ictx->viewport.width(); + double const h = ictx->viewport.height(); + double const em = style->font_size.computed; + double const ex = em * 0.5; // fixme: get from pango or libnrtype. + + this->x1.update(em, ex, w); + this->x2.update(em, ex, w); + this->y1.update(em, ex, h); + this->y2.update(em, ex, h); + + this->set_shape(); + } + + SPShape::update(ctx, flags); +} + +Inkscape::XML::Node* SPLine::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:line"); + } + + if (repr != this->getRepr()) { + repr->mergeFrom(this->getRepr(), "id"); + } + + sp_repr_set_svg_double(repr, "x1", this->x1.computed); + sp_repr_set_svg_double(repr, "y1", this->y1.computed); + sp_repr_set_svg_double(repr, "x2", this->x2.computed); + sp_repr_set_svg_double(repr, "y2", this->y2.computed); + + SPShape::write(xml_doc, repr, flags); + + return repr; +} + +const char* SPLine::displayName() const { + return _("Line"); +} + +void SPLine::convert_to_guides() const { + Geom::Point points[2]; + Geom::Affine const i2dt(this->i2dt_affine()); + + points[0] = Geom::Point(this->x1.computed, this->y1.computed)*i2dt; + points[1] = Geom::Point(this->x2.computed, this->y2.computed)*i2dt; + + SPGuide::createSPGuide(this->document, points[0], points[1]); +} + + +Geom::Affine SPLine::set_transform(Geom::Affine const &transform) { + Geom::Point points[2]; + + points[0] = Geom::Point(this->x1.computed, this->y1.computed); + points[1] = Geom::Point(this->x2.computed, this->y2.computed); + + points[0] *= transform; + points[1] *= transform; + + this->x1.computed = points[0][Geom::X]; + this->y1.computed = points[0][Geom::Y]; + this->x2.computed = points[1][Geom::X]; + this->y2.computed = points[1][Geom::Y]; + + this->adjust_stroke(transform.descrim()); + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + + return Geom::identity(); +} + +void SPLine::set_shape() { + SPCurve *c = new SPCurve(); + + c->moveto(this->x1.computed, this->y1.computed); + c->lineto(this->x2.computed, this->y2.computed); + + this->setCurveInsync(c, TRUE); // *_insync does not call update, avoiding infinite recursion when set_shape is called by update + this->setCurveBeforeLPE(c); + + // LPE's cannot be applied to lines. (the result can (generally) not be represented as SPLine) + + c->unref(); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-line.h b/src/object/sp-line.h new file mode 100644 index 000000000..6c720d403 --- /dev/null +++ b/src/object/sp-line.h @@ -0,0 +1,55 @@ +#ifndef SEEN_SP_LINE_H +#define SEEN_SP_LINE_H + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * Abhishek Sharma + * Jon A. Cruz + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "svg/svg-length.h" +#include "sp-shape.h" + +#define SP_LINE(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_LINE(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPLine : public SPShape { +public: + SPLine(); + virtual ~SPLine(); + + SVGLength x1; + SVGLength y1; + SVGLength x2; + SVGLength y2; + + virtual void build(SPDocument *document, Inkscape::XML::Node *repr); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); + virtual void set(unsigned int key, char const* value); + + virtual const char* displayName() const; + virtual Geom::Affine set_transform(Geom::Affine const &transform); + virtual void convert_to_guides() const; + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual void set_shape(); +}; + +#endif // SEEN_SP_LINE_H +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-linear-gradient.cpp b/src/object/sp-linear-gradient.cpp new file mode 100644 index 000000000..29579b617 --- /dev/null +++ b/src/object/sp-linear-gradient.cpp @@ -0,0 +1,100 @@ +#include + +#include "sp-linear-gradient.h" + +#include "attributes.h" +#include "xml/repr.h" + +/* + * Linear Gradient + */ +SPLinearGradient::SPLinearGradient() : SPGradient() { + this->x1.unset(SVGLength::PERCENT, 0.0, 0.0); + this->y1.unset(SVGLength::PERCENT, 0.0, 0.0); + this->x2.unset(SVGLength::PERCENT, 1.0, 1.0); + this->y2.unset(SVGLength::PERCENT, 0.0, 0.0); +} + +SPLinearGradient::~SPLinearGradient() { +} + +void SPLinearGradient::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPGradient::build(document, repr); + + this->readAttr( "x1" ); + this->readAttr( "y1" ); + this->readAttr( "x2" ); + this->readAttr( "y2" ); +} + +/** + * Callback: set attribute. + */ +void SPLinearGradient::set(unsigned int key, const gchar* value) { + switch (key) { + case SP_ATTR_X1: + this->x1.readOrUnset(value, SVGLength::PERCENT, 0.0, 0.0); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_Y1: + this->y1.readOrUnset(value, SVGLength::PERCENT, 0.0, 0.0); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_X2: + this->x2.readOrUnset(value, SVGLength::PERCENT, 1.0, 1.0); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_Y2: + this->y2.readOrUnset(value, SVGLength::PERCENT, 0.0, 0.0); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + default: + SPGradient::set(key, value); + break; + } +} + +/** + * Callback: write attributes to associated repr. + */ +Inkscape::XML::Node* SPLinearGradient::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:linearGradient"); + } + + if ((flags & SP_OBJECT_WRITE_ALL) || this->x1._set) { + sp_repr_set_svg_double(repr, "x1", this->x1.computed); + } + + if ((flags & SP_OBJECT_WRITE_ALL) || this->y1._set) { + sp_repr_set_svg_double(repr, "y1", this->y1.computed); + } + + if ((flags & SP_OBJECT_WRITE_ALL) || this->x2._set) { + sp_repr_set_svg_double(repr, "x2", this->x2.computed); + } + + if ((flags & SP_OBJECT_WRITE_ALL) || this->y2._set) { + sp_repr_set_svg_double(repr, "y2", this->y2.computed); + } + + SPGradient::write(xml_doc, repr, flags); + + return repr; +} + +cairo_pattern_t* SPLinearGradient::pattern_new(cairo_t * /*ct*/, Geom::OptRect const &bbox, double opacity) { + this->ensureVector(); + + cairo_pattern_t *cp = cairo_pattern_create_linear( + this->x1.computed, this->y1.computed, + this->x2.computed, this->y2.computed); + + sp_gradient_pattern_common_setup(cp, this, bbox, opacity); + + return cp; +} diff --git a/src/object/sp-linear-gradient.h b/src/object/sp-linear-gradient.h new file mode 100644 index 000000000..a152e7fe2 --- /dev/null +++ b/src/object/sp-linear-gradient.h @@ -0,0 +1,44 @@ +#ifndef SP_LINEAR_GRADIENT_H +#define SP_LINEAR_GRADIENT_H + +/** \file + * SPLinearGradient: SVG implementation + */ + +#include "sp-gradient.h" +#include "svg/svg-length.h" + +#define SP_LINEARGRADIENT(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_LINEARGRADIENT(obj) (dynamic_cast((SPObject*)obj) != NULL) + +/** Linear gradient. */ +class SPLinearGradient : public SPGradient { +public: + SPLinearGradient(); + virtual ~SPLinearGradient(); + + SVGLength x1; + SVGLength y1; + SVGLength x2; + SVGLength y2; + + virtual cairo_pattern_t* pattern_new(cairo_t *ct, Geom::OptRect const &bbox, double opacity); + +protected: + virtual void build(SPDocument *document, Inkscape::XML::Node *repr); + virtual void set(unsigned key, char const *value); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); +}; + +#endif /* !SP_LINEAR_GRADIENT_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-lpe-item.cpp b/src/object/sp-lpe-item.cpp new file mode 100644 index 000000000..f3cc841fb --- /dev/null +++ b/src/object/sp-lpe-item.cpp @@ -0,0 +1,1071 @@ +/** \file + * Base class for live path effect items + */ +/* + * Authors: + * Johan Engelen + * Bastien Bouclet + * Abhishek Sharma + * + * Copyright (C) 2008 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +#endif + +#include + +#include "bad-uri-exception.h" +#include "ui/tool/multi-path-manipulator.h" + +#include "live_effects/effect.h" +#include "live_effects/lpe-path_length.h" +#include "live_effects/lpeobject.h" +#include "live_effects/lpeobject-reference.h" +#include "live_effects/lpe-mirror_symmetry.h" +#include "live_effects/lpe-copy_rotate.h" + +#include "sp-path.h" +#include "sp-item-group.h" +#include "attributes.h" +#include "uri.h" +#include "message-stack.h" +#include "inkscape.h" +#include "desktop.h" +#include "ui/shape-editor.h" +#include "path-chemistry.h" +#include "sp-ellipse.h" +#include "display/curve.h" +#include "svg/svg.h" +#include "sp-clippath.h" +#include "sp-mask.h" +#include "ui/tools-switch.h" +#include "ui/tools/node-tool.h" + +/* LPEItem base class */ +static void sp_lpe_item_enable_path_effects(SPLPEItem *lpeitem, bool enable); + +static void lpeobject_ref_modified(SPObject *href, guint flags, SPLPEItem *lpeitem); + +static void sp_lpe_item_create_original_path_recursive(SPLPEItem *lpeitem); +static void sp_lpe_item_cleanup_original_path_recursive(SPLPEItem *lpeitem, bool keep_paths); + +typedef std::list HRefList; +static std::string patheffectlist_svg_string(PathEffectList const & list); +static std::string hreflist_svg_string(HRefList const & list); + +SPLPEItem::SPLPEItem() + : SPItem() + , path_effects_enabled(1) + , path_effect_list(new PathEffectList()) + , lpe_modified_connection_list(new std::list()) + , current_path_effect(NULL) + , lpe_helperpaths() +{ +} + +SPLPEItem::~SPLPEItem() { +} + +void SPLPEItem::build(SPDocument *document, Inkscape::XML::Node *repr) { + this->readAttr( "inkscape:path-effect" ); + + SPItem::build(document, repr); +} + +void SPLPEItem::release() { + // disconnect all modified listeners: + + for (std::list::iterator mod_it = this->lpe_modified_connection_list->begin(); + mod_it != this->lpe_modified_connection_list->end(); ++mod_it) + { + mod_it->disconnect(); + } + + delete this->lpe_modified_connection_list; + this->lpe_modified_connection_list = NULL; + + PathEffectList::iterator it = this->path_effect_list->begin(); + + while ( it != this->path_effect_list->end() ) { + // unlink and delete all references in the list + (*it)->unlink(); + delete *it; + it = this->path_effect_list->erase(it); + } + + // delete the list itself + delete this->path_effect_list; + this->path_effect_list = NULL; + + SPItem::release(); +} + +void SPLPEItem::set(unsigned int key, gchar const* value) { + switch (key) { + case SP_ATTR_INKSCAPE_PATH_EFFECT: + { + this->current_path_effect = NULL; + + // Disable the path effects while populating the LPE list + sp_lpe_item_enable_path_effects(this, false); + + // disconnect all modified listeners: + for ( std::list::iterator mod_it = this->lpe_modified_connection_list->begin(); + mod_it != this->lpe_modified_connection_list->end(); + ++mod_it) + { + mod_it->disconnect(); + } + + this->lpe_modified_connection_list->clear(); + // Clear the path effect list + PathEffectList::iterator it = this->path_effect_list->begin(); + while ( it != this->path_effect_list->end()) { + (*it)->unlink(); + delete *it; + it = this->path_effect_list->erase(it); + } + + // Parse the contents of "value" to rebuild the path effect reference list + if ( value ) { + std::istringstream iss(value); + std::string href; + + while (std::getline(iss, href, ';')) + { + Inkscape::LivePathEffect::LPEObjectReference *path_effect_ref = new Inkscape::LivePathEffect::LPEObjectReference(this); + + try { + path_effect_ref->link(href.c_str()); + } catch (Inkscape::BadURIException &e) { + g_warning("BadURIException when trying to find LPE: %s", e.what()); + path_effect_ref->unlink(); + delete path_effect_ref; + path_effect_ref = NULL; + } + + this->path_effect_list->push_back(path_effect_ref); + + if ( path_effect_ref->lpeobject && path_effect_ref->lpeobject->get_lpe() ) { + // connect modified-listener + this->lpe_modified_connection_list->push_back( + path_effect_ref->lpeobject->connectModified(sigc::bind(sigc::ptr_fun(&lpeobject_ref_modified), this)) ); + } else { + // something has gone wrong in finding the right patheffect. + g_warning("Unknown LPE type specified, LPE stack effectively disabled"); + // keep the effect in the lpestack, so the whole stack is effectively disabled but maintained + } + } + } + + sp_lpe_item_enable_path_effects(this, true); + } + break; + + default: + SPItem::set(key, value); + break; + } +} + +void SPLPEItem::update(SPCtx* ctx, unsigned int flags) { + SPItem::update(ctx, flags); + + // update the helperpaths of all LPEs applied to the item + // TODO: re-add for the new node tool +} + +void SPLPEItem::modified(unsigned int flags) { + if (SP_IS_GROUP(this) && (flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_USER_MODIFIED_FLAG_B)) { + sp_lpe_item_update_patheffect(this, true, true); + } + +// SPItem::onModified(flags); +} + +Inkscape::XML::Node* SPLPEItem::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if (flags & SP_OBJECT_WRITE_EXT) { + if ( hasPathEffect() ) { + repr->setAttribute("inkscape:path-effect", patheffectlist_svg_string(*this->path_effect_list)); + } else { + repr->setAttribute("inkscape:path-effect", NULL); + } + } + + SPItem::write(xml_doc, repr, flags); + + return repr; +} + +/** + * returns true when LPE was successful. + */ +bool SPLPEItem::hasPathEffectOnClipOrMask() const +{ + bool has_clipormask_lpe = false; + if (this->hasPathEffect() && this->pathEffectsEnabled()) { + for (PathEffectList::iterator it = this->path_effect_list->begin(); it != this->path_effect_list->end(); ++it) + { + LivePathEffectObject *lpeobj = (*it)->lpeobject; + if (!lpeobj) { + return false; + } + Inkscape::LivePathEffect::Effect *lpe = lpeobj->get_lpe(); + if (!lpe) { + return false; + } + if (lpe->isVisible()) { + if (lpe->acceptsNumClicks() > 0 && !lpe->isReady()) { + return false; + } + if (lpe->apply_to_clippath_and_mask) { + has_clipormask_lpe = true; + } + } + } + } + return has_clipormask_lpe; +} + +bool SPLPEItem::hasPathEffectOnClipOrMaskRecursive() const +{ + if (parent && SP_IS_LPE_ITEM(parent)) { + return hasPathEffectOnClipOrMask() || SP_LPE_ITEM(parent)->hasPathEffectOnClipOrMaskRecursive(); + } + else { + return hasPathEffectOnClipOrMask(); + } +} + +/** + * returns true when LPE was successful. + */ +bool SPLPEItem::performPathEffect(SPCurve *curve, SPShape *current, bool is_clip_or_mask) { + + if (!curve) { + return false; + } + bool has_clipormask_lpe = false; + if (this->hasPathEffect() && this->pathEffectsEnabled()) { + for (PathEffectList::iterator it = this->path_effect_list->begin(); it != this->path_effect_list->end(); ++it) + { + LivePathEffectObject *lpeobj = (*it)->lpeobject; + if (!lpeobj) { + /** \todo Investigate the cause of this. + * For example, this happens when copy pasting an object with LPE applied. Probably because the object is pasted while the effect is not yet pasted to defs, and cannot be found. + */ + g_warning("SPLPEItem::performPathEffect - NULL lpeobj in list!"); + return false; + } + Inkscape::LivePathEffect::Effect *lpe = lpeobj->get_lpe(); + if (!lpe) { + /** \todo Investigate the cause of this. + * Not sure, but I think this can happen when an unknown effect type is specified... + */ + g_warning("SPLPEItem::performPathEffect - lpeobj with invalid lpe in the stack!"); + return false; + } + if (lpe->isVisible()) { + if (lpe->acceptsNumClicks() > 0 && !lpe->isReady()) { + // if the effect expects mouse input before being applied and the input is not finished + // yet, we don't alter the path + return false; + } + if (lpe->apply_to_clippath_and_mask) { + has_clipormask_lpe = true; + } + if (!is_clip_or_mask || (is_clip_or_mask && lpe->apply_to_clippath_and_mask)) { + // Groups have their doBeforeEffect called elsewhere + if (current) { + lpe->setCurrentShape(current); + } + if (!SP_IS_GROUP(this)) { + lpe->doBeforeEffect_impl(this); + } + + try { + lpe->doEffect(curve); + } + catch (std::exception & e) { + g_warning("Exception during LPE %s execution. \n %s", lpe->getName().c_str(), e.what()); + if (SP_ACTIVE_DESKTOP && SP_ACTIVE_DESKTOP->messageStack()) { + SP_ACTIVE_DESKTOP->messageStack()->flash( Inkscape::WARNING_MESSAGE, + _("An exception occurred during execution of the Path Effect.") ); + } + return false; + } + if (!SP_IS_GROUP(this)) { + lpe->pathvector_after_effect = curve->get_pathvector(); + lpe->doAfterEffect(this); + } + } + } + } + if(!SP_IS_GROUP(this) && !is_clip_or_mask && has_clipormask_lpe){ + this->applyToClipPath(this); + this->applyToMask(this); + } + } + return true; +} + +// CPPIFY: make pure virtual +void SPLPEItem::update_patheffect(bool /*write*/) { + //throw; +} + +/** + * Calls any registered handlers for the update_patheffect action + */ +void +sp_lpe_item_update_patheffect (SPLPEItem *lpeitem, bool wholetree, bool write) +{ +#ifdef SHAPE_VERBOSE + g_message("sp_lpe_item_update_patheffect: %p\n", lpeitem); +#endif + g_return_if_fail (lpeitem != NULL); + g_return_if_fail (SP_IS_LPE_ITEM (lpeitem)); + + if (!lpeitem->pathEffectsEnabled()) + return; + + SPLPEItem *top = NULL; + + if (wholetree) { + SPLPEItem *prev_parent = lpeitem; + SPLPEItem *parent = dynamic_cast(prev_parent->parent); + while (parent && parent->hasPathEffectRecursive()) { + prev_parent = parent; + parent = dynamic_cast(prev_parent->parent); + } + top = prev_parent; + } + else { + top = lpeitem; + } + + top->update_patheffect(write); +} + +/** + * Gets called when any of the lpestack's lpeobject repr contents change: i.e. parameter change in any of the stacked LPEs + */ +static void +lpeobject_ref_modified(SPObject */*href*/, guint /*flags*/, SPLPEItem *lpeitem) +{ +#ifdef SHAPE_VERBOSE + g_message("lpeobject_ref_modified"); +#endif + sp_lpe_item_update_patheffect (lpeitem, true, true); +} + +static void +sp_lpe_item_create_original_path_recursive(SPLPEItem *lpeitem) +{ + g_return_if_fail(lpeitem != NULL); + + SPMask * mask = lpeitem->mask_ref->getObject(); + if(mask) + { + sp_lpe_item_create_original_path_recursive(SP_LPE_ITEM(mask->firstChild())); + } + SPClipPath * clip_path = lpeitem->clip_ref->getObject(); + if(clip_path) + { + sp_lpe_item_create_original_path_recursive(SP_LPE_ITEM(clip_path->firstChild())); + } + if (SP_IS_GROUP(lpeitem)) { + std::vector item_list = sp_item_group_item_list(SP_GROUP(lpeitem)); + for ( std::vector::const_iterator iter=item_list.begin();iter!=item_list.end();++iter) { + SPObject *subitem = *iter; + if (SP_IS_LPE_ITEM(subitem)) { + sp_lpe_item_create_original_path_recursive(SP_LPE_ITEM(subitem)); + } + } + } + else if (SP_IS_PATH(lpeitem)) { + Inkscape::XML::Node *pathrepr = lpeitem->getRepr(); + if ( !pathrepr->attribute("inkscape:original-d") ) { + pathrepr->setAttribute("inkscape:original-d", pathrepr->attribute("d")); + } + } +} + +static void +sp_lpe_item_cleanup_original_path_recursive(SPLPEItem *lpeitem, bool keep_paths) +{ + g_return_if_fail(lpeitem != NULL); + if (SP_IS_GROUP(lpeitem)) { + if (!lpeitem->hasPathEffectOnClipOrMaskRecursive()) { + SPMask * mask = lpeitem->mask_ref->getObject(); + if(mask) + { + sp_lpe_item_cleanup_original_path_recursive(SP_LPE_ITEM(mask->firstChild()), keep_paths); + } + SPClipPath * clip_path = lpeitem->clip_ref->getObject(); + if(clip_path) + { + sp_lpe_item_cleanup_original_path_recursive(SP_LPE_ITEM(clip_path->firstChild()), keep_paths); + } + } + std::vector item_list = sp_item_group_item_list(SP_GROUP(lpeitem)); + for ( std::vector::const_iterator iter=item_list.begin();iter!=item_list.end();++iter) { + SPObject *subitem = *iter; + if (SP_IS_LPE_ITEM(subitem)) { + sp_lpe_item_cleanup_original_path_recursive(SP_LPE_ITEM(subitem), keep_paths); + } + } + } else if (SP_IS_PATH(lpeitem)) { + Inkscape::XML::Node *repr = lpeitem->getRepr(); + SPMask * mask = lpeitem->mask_ref->getObject(); + if(mask) { + sp_lpe_item_cleanup_original_path_recursive(SP_LPE_ITEM(mask->firstChild()), keep_paths); + } + SPClipPath * clip_path = lpeitem->clip_ref->getObject(); + if(clip_path) { + sp_lpe_item_cleanup_original_path_recursive(SP_LPE_ITEM(clip_path->firstChild()), keep_paths); + } + mask = dynamic_cast(lpeitem->parent); + clip_path = dynamic_cast(lpeitem->parent); + if ((!lpeitem->hasPathEffectRecursive() && repr->attribute("inkscape:original-d")) || + ((mask || clip_path) && !lpeitem->hasPathEffectOnClipOrMaskRecursive() && repr->attribute("inkscape:original-d"))) + { + if (!keep_paths) { + repr->setAttribute("d", repr->attribute("inkscape:original-d")); + } + repr->setAttribute("inkscape:original-d", NULL); + } else { + if (!keep_paths) { + sp_lpe_item_update_patheffect(lpeitem, true, true); + } + } + } else if (SP_IS_SHAPE(lpeitem)) { + Inkscape::XML::Node *repr = lpeitem->getRepr(); + SPMask * mask = lpeitem->mask_ref->getObject(); + if(mask) { + sp_lpe_item_cleanup_original_path_recursive(SP_LPE_ITEM(mask->firstChild()), keep_paths); + } + SPClipPath * clip_path = lpeitem->clip_ref->getObject(); + if(clip_path) { + sp_lpe_item_cleanup_original_path_recursive(SP_LPE_ITEM(clip_path->firstChild()), keep_paths); + } + mask = dynamic_cast(lpeitem->parent); + clip_path = dynamic_cast(lpeitem->parent); + if ((!lpeitem->hasPathEffectRecursive() && repr->attribute("d")) || + ((mask || clip_path) && !lpeitem->hasPathEffectOnClipOrMaskRecursive() && repr->attribute("d"))) + { + if (!keep_paths) { + repr->setAttribute("d", NULL); + } else { + SPDesktop * desktop = SP_ACTIVE_DESKTOP; + if (desktop) { + std::vector items; + items.push_back(SP_ITEM(lpeitem)); + std::vector selected = items; + std::vector to_select; + sp_item_list_to_curves(items, selected, to_select, true); + } + } + } else { + if (!keep_paths) { + sp_lpe_item_update_patheffect(lpeitem, true, true); + } + } + + } +} + +void SPLPEItem::addPathEffect(std::string value, bool reset) +{ + if (!value.empty()) { + // Apply the path effects here because in the casse of a group, lpe->resetDefaults + // needs that all the subitems have their effects applied + sp_lpe_item_update_patheffect(this, false, true); + + // Disable the path effects while preparing the new lpe + sp_lpe_item_enable_path_effects(this, false); + + // Add the new reference to the list of LPE references + HRefList hreflist; + for (PathEffectList::const_iterator it = this->path_effect_list->begin(); it != this->path_effect_list->end(); ++it) + { + hreflist.push_back( std::string((*it)->lpeobject_href) ); + } + hreflist.push_back(value); // C++11: should be emplace_back std::move'd (also the reason why passed by value to addPathEffect) + + this->getRepr()->setAttribute("inkscape:path-effect", hreflist_svg_string(hreflist)); + + // Make sure that ellipse is stored as + if( SP_IS_GENERICELLIPSE(this)) { + SP_GENERICELLIPSE(this)->write( this->getRepr()->document(), this->getRepr(), SP_OBJECT_WRITE_EXT ); + } + // make sure there is an original-d for paths!!! + sp_lpe_item_create_original_path_recursive(this); + + LivePathEffectObject *lpeobj = this->path_effect_list->back()->lpeobject; + if (lpeobj && lpeobj->get_lpe()) { + Inkscape::LivePathEffect::Effect *lpe = lpeobj->get_lpe(); + // Ask the path effect to reset itself if it doesn't have parameters yet + if (reset) { + // has to be called when all the subitems have their lpes applied + lpe->resetDefaults(this); + } + + // perform this once when the effect is applied + lpe->doOnApply(this); + + // indicate that all necessary preparations are done and the effect can be performed + lpe->setReady(); + } + + //Enable the path effects now that everything is ready to apply the new path effect + sp_lpe_item_enable_path_effects(this, true); + + // Apply the path effect + sp_lpe_item_update_patheffect(this, true, true); + SPMask * mask = mask_ref->getObject(); + if(mask && !hasPathEffectOnClipOrMaskRecursive()) + { + sp_lpe_item_cleanup_original_path_recursive(SP_LPE_ITEM(mask->firstChild()), false); + } + SPClipPath * clip_path = clip_ref->getObject(); + if(clip_path && !hasPathEffectOnClipOrMaskRecursive()) + { + sp_lpe_item_cleanup_original_path_recursive(SP_LPE_ITEM(clip_path->firstChild()), false); + } + //fix bug 1219324 + if (SP_ACTIVE_DESKTOP ) { + Inkscape::UI::Tools::ToolBase *ec = SP_ACTIVE_DESKTOP->event_context; + if (INK_IS_NODE_TOOL(ec)) { + tools_switch(SP_ACTIVE_DESKTOP, TOOLS_SELECT); //mhh + tools_switch(SP_ACTIVE_DESKTOP, TOOLS_NODES); + } + } + } +} + +void SPLPEItem::addPathEffect(LivePathEffectObject * new_lpeobj) +{ + const gchar * repr_id = new_lpeobj->getRepr()->attribute("id"); + gchar *hrefstr = g_strdup_printf("#%s", repr_id); + this->addPathEffect(hrefstr, false); + g_free(hrefstr); +} + +/** + * If keep_path is true, the item should not be updated, effectively 'flattening' the LPE. + */ +void SPLPEItem::removeCurrentPathEffect(bool keep_paths) +{ + Inkscape::LivePathEffect::LPEObjectReference* lperef = this->getCurrentLPEReference(); + if (!lperef) + return; + + if (Inkscape::LivePathEffect::Effect* effect_ = this->getCurrentLPE()) { + effect_->keep_paths = keep_paths; + effect_->doOnRemove(this); + } + PathEffectList new_list = *this->path_effect_list; + new_list.remove(lperef); //current lpe ref is always our 'own' pointer from the path_effect_list + this->getRepr()->setAttribute("inkscape:path-effect", patheffectlist_svg_string(new_list)); + if (!keep_paths) { + // Make sure that ellipse is stored as or if possible. + if( SP_IS_GENERICELLIPSE(this)) { + SP_GENERICELLIPSE(this)->write( this->getRepr()->document(), this->getRepr(), SP_OBJECT_WRITE_EXT ); + } + } + sp_lpe_item_cleanup_original_path_recursive(this, keep_paths); +} + +/** + * If keep_path is true, the item should not be updated, effectively 'flattening' the LPE. + */ +void SPLPEItem::removeAllPathEffects(bool keep_paths) +{ + if (keep_paths) { + if (path_effect_list->empty()) { + return; + } + } + + PathEffectList::iterator it = this->path_effect_list->begin(); + + while ( it != this->path_effect_list->end() ) { + LivePathEffectObject *lpeobj = (*it)->lpeobject; + if (lpeobj) { + Inkscape::LivePathEffect::Effect * lpe = lpeobj->get_lpe(); + if (lpe) { + lpe->keep_paths = keep_paths; + lpe->doOnRemove(this); + } + } + // unlink and delete all references in the list + (*it)->unlink(); + ++it; + } + this->path_effect_list->clear(); + this->getRepr()->setAttribute("inkscape:path-effect", NULL); + + if (!keep_paths) { + // Make sure that ellipse is stored as or if possible. + if (SP_IS_GENERICELLIPSE(this)) { + SP_GENERICELLIPSE(this)->write(this->getRepr()->document(), this->getRepr(), SP_OBJECT_WRITE_EXT); + } + } + sp_lpe_item_cleanup_original_path_recursive(this, keep_paths); +} + +void SPLPEItem::downCurrentPathEffect() +{ + Inkscape::LivePathEffect::LPEObjectReference* lperef = getCurrentLPEReference(); + if (!lperef) + return; + + PathEffectList new_list = *this->path_effect_list; + PathEffectList::iterator cur_it = find( new_list.begin(), new_list.end(), lperef ); + if (cur_it != new_list.end()) { + PathEffectList::iterator down_it = cur_it; + ++down_it; + if (down_it != new_list.end()) { // perhaps current effect is already last effect + std::iter_swap(cur_it, down_it); + } + } + + this->getRepr()->setAttribute("inkscape:path-effect", patheffectlist_svg_string(new_list)); + + sp_lpe_item_cleanup_original_path_recursive(this, false); +} + +void SPLPEItem::upCurrentPathEffect() +{ + Inkscape::LivePathEffect::LPEObjectReference* lperef = getCurrentLPEReference(); + if (!lperef) + return; + + PathEffectList new_list = *this->path_effect_list; + PathEffectList::iterator cur_it = find( new_list.begin(), new_list.end(), lperef ); + if (cur_it != new_list.end() && cur_it != new_list.begin()) { + PathEffectList::iterator up_it = cur_it; + --up_it; + std::iter_swap(cur_it, up_it); + } + + this->getRepr()->setAttribute("inkscape:path-effect", patheffectlist_svg_string(new_list)); + + sp_lpe_item_cleanup_original_path_recursive(this, false); +} + +/** used for shapes so they can see if they should also disable shape calculation and read from d= */ +bool SPLPEItem::hasBrokenPathEffect() const +{ + if (path_effect_list->empty()) { + return false; + } + + // go through the list; if some are unknown or invalid, return true + for (PathEffectList::const_iterator it = path_effect_list->begin(); it != path_effect_list->end(); ++it) + { + LivePathEffectObject *lpeobj = (*it)->lpeobject; + if (!lpeobj || !lpeobj->get_lpe()) { + return true; + } + } + + return false; +} + + +bool SPLPEItem::hasPathEffect() const +{ + if (!path_effect_list || path_effect_list->empty()) { + return false; + } + + // go through the list; if some are unknown or invalid, we are not an LPE item! + for (PathEffectList::const_iterator it = path_effect_list->begin(); it != path_effect_list->end(); ++it) + { + LivePathEffectObject *lpeobj = (*it)->lpeobject; + if (!lpeobj || !lpeobj->get_lpe()) { + return false; + } + } + + return true; +} + +bool SPLPEItem::hasPathEffectOfType(int const type, bool is_ready) const +{ + if (path_effect_list->empty()) { + return false; + } + + for (PathEffectList::const_iterator it = path_effect_list->begin(); it != path_effect_list->end(); ++it) + { + LivePathEffectObject const *lpeobj = (*it)->lpeobject; + if (lpeobj) { + Inkscape::LivePathEffect::Effect const* lpe = lpeobj->get_lpe(); + if (lpe && (lpe->effectType() == type)) { + if (is_ready || lpe->isReady()) { + return true; + } + } + } + } + + return false; +} + +bool SPLPEItem::hasPathEffectRecursive() const +{ + if (parent && SP_IS_LPE_ITEM(parent)) { + return hasPathEffect() || SP_LPE_ITEM(parent)->hasPathEffectRecursive(); + } + else { + return hasPathEffect(); + } +} + +void +SPLPEItem::applyToClipPath(SPItem *item) +{ + SPClipPath *clip_path = item->clip_ref->getObject(); + if(clip_path) { + std::vector clip_path_list = clip_path->childList(true); + for ( std::vector::const_iterator iter=clip_path_list.begin();iter!=clip_path_list.end();++iter) { + SPObject * clip_data = *iter; + applyToClipPathOrMask(SP_ITEM(clip_data), item); + } + } + if(SP_IS_GROUP(item)){ + std::vector item_list = sp_item_group_item_list(SP_GROUP(item)); + for ( std::vector::const_iterator iter=item_list.begin();iter!=item_list.end();++iter) { + SPObject *subitem = *iter; + applyToClipPath(SP_ITEM(subitem)); + } + } +} + +void +SPLPEItem::applyToMask(SPItem *item) +{ + SPMask *mask = item->mask_ref->getObject(); + if(mask) { + std::vector mask_list = mask->childList(true); + for ( std::vector::const_iterator iter=mask_list.begin();iter!=mask_list.end();++iter) { + SPObject * mask_data = *iter; + applyToClipPathOrMask(SP_ITEM(mask_data), item); + } + } + if(SP_IS_GROUP(item)){ + std::vector item_list = sp_item_group_item_list(SP_GROUP(item)); + for ( std::vector::const_iterator iter=item_list.begin();iter!=item_list.end();++iter) { + SPObject *subitem = *iter; + applyToMask(SP_ITEM(subitem)); + } + } +} + +void +SPLPEItem::applyToClipPathOrMask(SPItem *clip_mask, SPItem *item) +{ + if (SP_IS_GROUP(clip_mask)) { + std::vector item_list = sp_item_group_item_list(SP_GROUP(clip_mask)); + for ( std::vector::const_iterator iter=item_list.begin();iter!=item_list.end();++iter) { + SPItem *subitem = *iter; + applyToClipPathOrMask(subitem, item); + } + } else if (SP_IS_SHAPE(clip_mask)) { + SPCurve * c = NULL; + + if (SP_IS_PATH(clip_mask)) { + c = SP_PATH(clip_mask)->get_original_curve(); + } else { + c = SP_SHAPE(clip_mask)->getCurve(); + } + if (c) { + bool success = false; + try { + if(SP_IS_GROUP(this)){ + c->transform(i2anc_affine(SP_GROUP(item), SP_GROUP(this))); + success = this->performPathEffect(c, SP_SHAPE(clip_mask), true); + c->transform(i2anc_affine(SP_GROUP(item), SP_GROUP(this)).inverse()); + } else { + success = this->performPathEffect(c, SP_SHAPE(clip_mask), true); + } + } catch (std::exception & e) { + g_warning("Exception during LPE execution. \n %s", e.what()); + if (SP_ACTIVE_DESKTOP && SP_ACTIVE_DESKTOP->messageStack()) { + SP_ACTIVE_DESKTOP->messageStack()->flash( Inkscape::WARNING_MESSAGE, + _("An exception occurred during execution of the Path Effect.") ); + } + success = false; + } + Inkscape::XML::Node *repr = clip_mask->getRepr(); + // This c check allow to not apply LPE if curve is NULL after performPathEffect used in clone.obgets LPE + if (success && c) { + gchar *str = sp_svg_write_path(c->get_pathvector()); + repr->setAttribute("d", str); + g_free(str); + } else { + // LPE was unsuccessful or doeffect stack return null.. Read the old 'd'-attribute. + if (gchar const * value = repr->attribute("d")) { + Geom::PathVector pv = sp_svg_read_pathv(value); + SPCurve *oldcurve = new (std::nothrow) SPCurve(pv); + if (oldcurve) { + SP_SHAPE(clip_mask)->setCurve(oldcurve, TRUE); + oldcurve->unref(); + } + } + } + if (c) { + c->unref(); + } + } + } +} + +Inkscape::LivePathEffect::Effect* +SPLPEItem::getPathEffectOfType(int type) +{ + std::list::iterator i; + for (i = path_effect_list->begin(); i != path_effect_list->end(); ++i) { + LivePathEffectObject *lpeobj = (*i)->lpeobject; + if (lpeobj) { + Inkscape::LivePathEffect::Effect* lpe = lpeobj->get_lpe(); + if (lpe && (lpe->effectType() == type)) { + return lpe; + } + } + } + return NULL; +} + +Inkscape::LivePathEffect::Effect const* +SPLPEItem::getPathEffectOfType(int type) const +{ + std::list::const_iterator i; + for (i = path_effect_list->begin(); i != path_effect_list->end(); ++i) { + LivePathEffectObject const *lpeobj = (*i)->lpeobject; + if (lpeobj) { + Inkscape::LivePathEffect::Effect const *lpe = lpeobj->get_lpe(); + if (lpe && (lpe->effectType() == type)) { + return lpe; + } + } + } + return NULL; +} + +void SPLPEItem::editNextParamOncanvas(SPDesktop *dt) +{ + Inkscape::LivePathEffect::LPEObjectReference *lperef = this->getCurrentLPEReference(); + if (lperef && lperef->lpeobject && lperef->lpeobject->get_lpe()) { + lperef->lpeobject->get_lpe()->editNextParamOncanvas(this, dt); + } +} + +void SPLPEItem::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) { + SPItem::child_added(child, ref); + + if (this->hasPathEffectRecursive()) { + SPObject *ochild = this->get_child_by_repr(child); + + if ( ochild && SP_IS_LPE_ITEM(ochild) ) { + sp_lpe_item_create_original_path_recursive(SP_LPE_ITEM(ochild)); + } + } +} +void SPLPEItem::remove_child(Inkscape::XML::Node * child) { + if (this->hasPathEffectRecursive()) { + SPObject *ochild = this->get_child_by_repr(child); + + if ( ochild && SP_IS_LPE_ITEM(ochild) ) { + sp_lpe_item_cleanup_original_path_recursive(SP_LPE_ITEM(ochild), false); + } + } + + SPItem::remove_child(child); +} + +static std::string patheffectlist_svg_string(PathEffectList const & list) +{ + HRefList hreflist; + + for (PathEffectList::const_iterator it = list.begin(); it != list.end(); ++it) + { + hreflist.push_back( std::string((*it)->lpeobject_href) ); // C++11: use emplace_back + } + + return hreflist_svg_string(hreflist); +} + +/** + * THE function that should be used to generate any patheffectlist string. + * one of the methods to change the effect list: + * - create temporary href list + * - populate the templist with the effects from the old list that you want to have and their order + * - call this function with temp list as param + */ +static std::string hreflist_svg_string(HRefList const & list) +{ + std::string r; + bool semicolon_first = false; + + for (HRefList::const_iterator it = list.begin(); it != list.end(); ++it) + { + if (semicolon_first) { + r += ';'; + } + + semicolon_first = true; + + r += (*it); + } + + return r; +} + +// Return a copy of the effect list +PathEffectList SPLPEItem::getEffectList() +{ + return *path_effect_list; +} + +// Return a copy of the effect list +PathEffectList const SPLPEItem::getEffectList() const +{ + return *path_effect_list; +} + +Inkscape::LivePathEffect::LPEObjectReference* SPLPEItem::getCurrentLPEReference() +{ + if (!this->current_path_effect && !this->path_effect_list->empty()) { + setCurrentPathEffect(this->path_effect_list->back()); + } + + return this->current_path_effect; +} + +Inkscape::LivePathEffect::Effect* SPLPEItem::getCurrentLPE() +{ + Inkscape::LivePathEffect::LPEObjectReference* lperef = getCurrentLPEReference(); + + if (lperef && lperef->lpeobject) + return lperef->lpeobject->get_lpe(); + else + return NULL; +} + +bool SPLPEItem::setCurrentPathEffect(Inkscape::LivePathEffect::LPEObjectReference* lperef) +{ + for (PathEffectList::iterator it = path_effect_list->begin(); it != path_effect_list->end(); ++it) { + if ((*it)->lpeobject_repr == lperef->lpeobject_repr) { + this->current_path_effect = (*it); // current_path_effect should always be a pointer from the path_effect_list ! + return true; + } + } + + return false; +} + +/** + * Writes a new "inkscape:path-effect" string to xml, where the old_lpeobjects are substituted by the new ones. + * Note that this method messes up the item's \c PathEffectList. + */ +void SPLPEItem::replacePathEffects( std::vector const &old_lpeobjs, + std::vector const &new_lpeobjs ) +{ + HRefList hreflist; + for (PathEffectList::const_iterator it = this->path_effect_list->begin(); it != this->path_effect_list->end(); ++it) + { + LivePathEffectObject const * current_lpeobj = (*it)->lpeobject; + std::vector::const_iterator found_it(std::find(old_lpeobjs.begin(), old_lpeobjs.end(), current_lpeobj)); + + if ( found_it != old_lpeobjs.end() ) { + std::vector::difference_type found_index = std::distance (old_lpeobjs.begin(), found_it); + const gchar * repr_id = new_lpeobjs[found_index]->getRepr()->attribute("id"); + gchar *hrefstr = g_strdup_printf("#%s", repr_id); + hreflist.push_back( std::string(hrefstr) ); + g_free(hrefstr); + } + else { + hreflist.push_back( std::string((*it)->lpeobject_href) ); + } + } + + this->getRepr()->setAttribute("inkscape:path-effect", hreflist_svg_string(hreflist)); +} + +/** + * Check all effects in the stack if they are used by other items, and fork them if so. + * It is not recommended to fork the effects by yourself calling LivePathEffectObject::fork_private_if_necessary, + * use this method instead. + * Returns true if one or more effects were forked; returns false if nothing was done. + */ +bool SPLPEItem::forkPathEffectsIfNecessary(unsigned int nr_of_allowed_users) +{ + bool forked = false; + + if ( this->hasPathEffect() ) { + // If one of the path effects is used by 2 or more items, fork it + // so that each object has its own independent copy of the effect. + // Note: replacing path effects messes up the path effect list + + // Clones of the LPEItem will increase the refcount of the lpeobjects. + // Therefore, nr_of_allowed_users should be increased with the number of clones (i.e. refs to the lpeitem) + nr_of_allowed_users += this->hrefcount; + + std::vector old_lpeobjs, new_lpeobjs; + PathEffectList effect_list = this->getEffectList(); + for (PathEffectList::iterator it = effect_list.begin(); it != effect_list.end(); ++it) + { + LivePathEffectObject *lpeobj = (*it)->lpeobject; + if (lpeobj) { + LivePathEffectObject *forked_lpeobj = lpeobj->fork_private_if_necessary(nr_of_allowed_users); + if (forked_lpeobj != lpeobj) { + forked = true; + old_lpeobjs.push_back(lpeobj); + new_lpeobjs.push_back(forked_lpeobj); + } + } + } + + if (forked) { + this->replacePathEffects(old_lpeobjs, new_lpeobjs); + } + } + + return forked; +} + +// Enable or disable the path effects of the item. +// The counter allows nested calls +static void sp_lpe_item_enable_path_effects(SPLPEItem *lpeitem, bool enable) +{ + if (enable) { + lpeitem->path_effects_enabled++; + } + else { + lpeitem->path_effects_enabled--; + } +} + +// Are the path effects enabled on this item ? +bool SPLPEItem::pathEffectsEnabled() const +{ + return path_effects_enabled > 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-lpe-item.h b/src/object/sp-lpe-item.h new file mode 100644 index 000000000..82f3940c1 --- /dev/null +++ b/src/object/sp-lpe-item.h @@ -0,0 +1,117 @@ +#ifndef SP_LPE_ITEM_H_SEEN +#define SP_LPE_ITEM_H_SEEN + +/** \file + * Base class for live path effect items + */ +/* + * Authors: + * Johan Engelen + * Bastien Bouclet + * + * Copyright (C) 2008 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include "sp-item.h" + +#define SP_LPE_ITEM(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_LPE_ITEM(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class LivePathEffectObject; +class SPCurve; +class SPShape; +class SPDesktop; + +namespace Inkscape{ + namespace Display { + class TemporaryItem; + } + namespace LivePathEffect{ + class LPEObjectReference; + class Effect; + } +} + +typedef std::list PathEffectList; + +class SPLPEItem : public SPItem { +public: + SPLPEItem(); + virtual ~SPLPEItem(); + + int path_effects_enabled; + + PathEffectList* path_effect_list; + std::list *lpe_modified_connection_list; // this list contains the connections for listening to lpeobject parameter changes + + Inkscape::LivePathEffect::LPEObjectReference* current_path_effect; + std::vector lpe_helperpaths; + + void replacePathEffects( std::vector const &old_lpeobjs, + std::vector const &new_lpeobjs ); + + + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void set(unsigned int key, char const* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + virtual void modified(unsigned int flags); + + virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref); + virtual void remove_child(Inkscape::XML::Node* child); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); + + virtual void update_patheffect(bool write); + + bool hasPathEffectOnClipOrMask() const; + bool hasPathEffectOnClipOrMaskRecursive() const; + bool performPathEffect(SPCurve *curve, SPShape *current = NULL, bool is_clip_or_mask = false); + + bool pathEffectsEnabled() const; + bool hasPathEffect() const; + bool hasPathEffectOfType(int const type, bool is_ready = true) const; + bool hasPathEffectRecursive() const; + Inkscape::LivePathEffect::Effect* getPathEffectOfType(int type); + Inkscape::LivePathEffect::Effect const* getPathEffectOfType(int type) const; + bool hasBrokenPathEffect() const; + + PathEffectList getEffectList(); + PathEffectList const getEffectList() const; + + void downCurrentPathEffect(); + void upCurrentPathEffect(); + Inkscape::LivePathEffect::LPEObjectReference* getCurrentLPEReference(); + Inkscape::LivePathEffect::Effect* getCurrentLPE(); + bool setCurrentPathEffect(Inkscape::LivePathEffect::LPEObjectReference* lperef); + void removeCurrentPathEffect(bool keep_paths); + void removeAllPathEffects(bool keep_paths); + void addPathEffect(std::string value, bool reset); + void addPathEffect(LivePathEffectObject * new_lpeobj); + void applyToMask(SPItem * item); + void applyToClipPath(SPItem * item); + void applyToClipPathOrMask(SPItem * clip_mask, SPItem * item); + bool forkPathEffectsIfNecessary(unsigned int nr_of_allowed_users = 1); + + void editNextParamOncanvas(SPDesktop *dt); +}; +void sp_lpe_item_update_patheffect (SPLPEItem *lpeitem, bool wholetree, bool write); // careful, class already has method with *very* similar name! + +#endif /* !SP_LPE_ITEM_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-marker-loc.h b/src/object/sp-marker-loc.h new file mode 100644 index 000000000..b6877e5aa --- /dev/null +++ b/src/object/sp-marker-loc.h @@ -0,0 +1,31 @@ +#ifndef SEEN_SP_MARKER_LOC_H +#define SEEN_SP_MARKER_LOC_H + +/** + * These enums are to allow us to have 4-element arrays that represent a set of marker locations + * (all, start, mid, and end). This allows us to iterate through the array in places where we need + * to do a process across all of the markers, instead of separate code stanzas for each. + * + * IMPORTANT: the code assumes that the locations have the values as written below! so don't change the values!!! + */ +enum SPMarkerLoc { + SP_MARKER_LOC = 0, + SP_MARKER_LOC_START = 1, + SP_MARKER_LOC_MID = 2, + SP_MARKER_LOC_END = 3, + SP_MARKER_LOC_QTY = 4 +}; + + +#endif /* !SEEN_SP_MARKER_LOC_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-marker.cpp b/src/object/sp-marker.cpp new file mode 100644 index 000000000..e5ddb91b5 --- /dev/null +++ b/src/object/sp-marker.cpp @@ -0,0 +1,508 @@ +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * Bryce Harrington + * Abhishek Sharma + * Jon A. Cruz + * + * Copyright (C) 1999-2003 Lauris Kaplinski + * 2004-2006 Bryce Harrington + * 2008 Johan Engelen + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include + +#include <2geom/affine.h> +#include <2geom/transforms.h> +#include "svg/svg.h" +#include "display/drawing-group.h" +#include "xml/repr.h" +#include "attributes.h" +#include "document.h" +#include "document-private.h" +#include "preferences.h" + +#include "sp-marker.h" +#include "sp-defs.h" + +class SPMarkerView { + +public: + + SPMarkerView() {}; + ~SPMarkerView() { + for (unsigned int i = 0; i < items.size(); ++i) { + delete items[i]; + } + items.clear(); + } + std::vector items; +}; + +SPMarker::SPMarker() : SPGroup(), SPViewBox(), + markerUnits_set(0), + markerUnits(0), + refX(), + refY(), + markerWidth(), + markerHeight(), + orient_set(0), + orient_mode(MARKER_ORIENT_ANGLE) +{ + // cppcheck-suppress useInitializationList + orient = 0; +} + +/** + * Initializes an SPMarker object. This notes the marker's viewBox is + * not set and initializes the marker's c2p identity matrix. + */ + +SPMarker::~SPMarker() { +} + +/** + * Virtual build callback for SPMarker. + * + * This is to be invoked immediately after creation of an SPMarker. This + * method fills an SPMarker object with its SVG attributes, and calls the + * parent class' build routine to attach the object to its document and + * repr. The result will be creation of the whole document tree. + * + * \see SPObject::build() + */ +void SPMarker::build(SPDocument *document, Inkscape::XML::Node *repr) { + this->readAttr( "markerUnits" ); + this->readAttr( "refX" ); + this->readAttr( "refY" ); + this->readAttr( "markerWidth" ); + this->readAttr( "markerHeight" ); + this->readAttr( "orient" ); + this->readAttr( "viewBox" ); + this->readAttr( "preserveAspectRatio" ); + + SPGroup::build(document, repr); +} + + +/** + * Removes, releases and unrefs all children of object + * + * This is the inverse of sp_marker_build(). It must be invoked as soon + * as the marker is removed from the tree, even if it is still referenced + * by other objects. It hides and removes any views of the marker, then + * calls the parent classes' release function to deregister the object + * and release its SPRepr bindings. The result will be the destruction + * of the entire document tree. + * + * \see SPObject::release() + */ +void SPMarker::release() { + + std::map::iterator it; + for (it = views_map.begin(); it != views_map.end(); ++it) { + SPGroup::hide( it->first ); + } + views_map.clear(); + + SPGroup::release(); +} + + +void SPMarker::set(unsigned int key, const gchar* value) { + switch (key) { + case SP_ATTR_MARKERUNITS: + this->markerUnits_set = FALSE; + this->markerUnits = SP_MARKER_UNITS_STROKEWIDTH; + + if (value) { + if (!strcmp (value, "strokeWidth")) { + this->markerUnits_set = TRUE; + } else if (!strcmp (value, "userSpaceOnUse")) { + this->markerUnits = SP_MARKER_UNITS_USERSPACEONUSE; + this->markerUnits_set = TRUE; + } + } + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + break; + + case SP_ATTR_REFX: + this->refX.readOrUnset(value); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_REFY: + this->refY.readOrUnset(value); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_MARKERWIDTH: + this->markerWidth.readOrUnset(value, SVGLength::NONE, 3.0, 3.0); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_MARKERHEIGHT: + this->markerHeight.readOrUnset(value, SVGLength::NONE, 3.0, 3.0); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_ORIENT: + this->orient_set = FALSE; + this->orient_mode = MARKER_ORIENT_ANGLE; + this->orient = 0.0; + + if (value) { + if (!strcmp (value, "auto")) { + this->orient_mode = MARKER_ORIENT_AUTO; + this->orient_set = TRUE; + } else if (!strcmp (value, "auto-start-reverse")) { + this->orient_mode = MARKER_ORIENT_AUTO_START_REVERSE; + this->orient_set = TRUE; + } else { + orient.readOrUnset(value); + if (orient._set) { + this->orient_mode = MARKER_ORIENT_ANGLE; + this->orient_set = orient._set; + } + } + } + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_VIEWBOX: + set_viewBox( value ); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + break; + + case SP_ATTR_PRESERVEASPECTRATIO: + set_preserveAspectRatio( value ); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + break; + + default: + SPGroup::set(key, value); + break; + } +} + +void SPMarker::update(SPCtx *ctx, guint flags) { + + SPItemCtx ictx; + + // Copy parent context + ictx.flags = ctx->flags; + + // Initialize transformations + ictx.i2doc = Geom::identity(); + ictx.i2vp = Geom::identity(); + + // Set up viewport + ictx.viewport = Geom::Rect::from_xywh(0, 0, this->markerWidth.computed, this->markerHeight.computed); + + SPItemCtx rctx = get_rctx( &ictx ); + + // Shift according to refX, refY + Geom::Point ref( this->refX.computed, this->refY.computed ); + ref *= c2p; + this->c2p = this->c2p * Geom::Translate( -ref ); + + // And invoke parent method + SPGroup::update((SPCtx *) &rctx, flags); + + // As last step set additional transform of drawing group + std::map::iterator it; + for (it = views_map.begin(); it != views_map.end(); ++it) { + for (unsigned i = 0 ; i < it->second.items.size() ; ++i) { + if (it->second.items[i]) { + Inkscape::DrawingGroup *g = dynamic_cast(it->second.items[i]); + g->setChildTransform(this->c2p); + } + } + } +} + +Inkscape::XML::Node* SPMarker::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:marker"); + } + + if (this->markerUnits_set) { + if (this->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { + repr->setAttribute("markerUnits", "strokeWidth"); + } else { + repr->setAttribute("markerUnits", "userSpaceOnUse"); + } + } else { + repr->setAttribute("markerUnits", NULL); + } + + if (this->refX._set) { + sp_repr_set_svg_double(repr, "refX", this->refX.computed); + } else { + repr->setAttribute("refX", NULL); + } + + if (this->refY._set) { + sp_repr_set_svg_double (repr, "refY", this->refY.computed); + } else { + repr->setAttribute("refY", NULL); + } + + if (this->markerWidth._set) { + sp_repr_set_svg_double (repr, "markerWidth", this->markerWidth.computed); + } else { + repr->setAttribute("markerWidth", NULL); + } + + if (this->markerHeight._set) { + sp_repr_set_svg_double (repr, "markerHeight", this->markerHeight.computed); + } else { + repr->setAttribute("markerHeight", NULL); + } + + if (this->orient_set) { + if (this->orient_mode == MARKER_ORIENT_AUTO) { + repr->setAttribute("orient", "auto"); + } else if (this->orient_mode == MARKER_ORIENT_AUTO_START_REVERSE) { + repr->setAttribute("orient", "auto-start-reverse"); + } else { + sp_repr_set_css_double(repr, "orient", this->orient.computed); + } + } else { + repr->setAttribute("orient", NULL); + } + + /* fixme: */ + //XML Tree being used directly here while it shouldn't be.... + repr->setAttribute("viewBox", this->getRepr()->attribute("viewBox")); + //XML Tree being used directly here while it shouldn't be.... + repr->setAttribute("preserveAspectRatio", this->getRepr()->attribute("preserveAspectRatio")); + + SPGroup::write(xml_doc, repr, flags); + + return repr; +} + +Inkscape::DrawingItem* SPMarker::show(Inkscape::Drawing &/*drawing*/, unsigned int /*key*/, unsigned int /*flags*/) { + // Markers in tree are never shown directly even if outside of . + return 0; +} + +Inkscape::DrawingItem* SPMarker::private_show(Inkscape::Drawing &drawing, unsigned int key, unsigned int flags) { + return SPGroup::show(drawing, key, flags); +} + +void SPMarker::hide(unsigned int key) { + // CPPIFY: correct? + SPGroup::hide(key); +} + +Geom::OptRect SPMarker::bbox(Geom::Affine const &/*transform*/, SPItem::BBoxType /*type*/) const { + return Geom::OptRect(); +} + +void SPMarker::print(SPPrintContext* /*ctx*/) { + +} + +/* fixme: Remove link if zero-sized (Lauris) */ + +/** + * Removes any SPMarkerViews that a marker has with a specific key. + * Set up the DrawingItem array's size in the specified SPMarker's SPMarkerView. + * This is called from sp_shape_update() for shapes that have markers. It + * removes the old view of the marker and establishes a new one, registering + * it with the marker's list of views for future updates. + * + * \param marker Marker to create views in. + * \param key Key to give each SPMarkerView. + * \param size Number of DrawingItems to put in the SPMarkerView. + */ +// If marker views are always created in order, then this function could be eliminated +// by doing the push_back in sp_marker_show_instance. +void +sp_marker_show_dimension (SPMarker *marker, unsigned int key, unsigned int size) +{ + std::map::iterator it = marker->views_map.find(key); + if (it != marker->views_map.end()) { + if (it->second.items.size() != size ) { + // Need to change size of vector! (We should not really need to do this.) + marker->hide(key); + it->second.items.clear(); + for (unsigned int i = 0; i < size; ++i) { + it->second.items.push_back(NULL); + } + } + } else { + marker->views_map[key] = SPMarkerView(); + for (unsigned int i = 0; i < size; ++i) { + marker->views_map[key].items.push_back(NULL); + } + } +} + +/** + * Shows an instance of a marker. This is called during sp_shape_update_marker_view() + * show and transform a child item in the drawing for all views with the given key. + */ +Inkscape::DrawingItem * +sp_marker_show_instance ( SPMarker *marker, Inkscape::DrawingItem *parent, + unsigned int key, unsigned int pos, + Geom::Affine const &base, float linewidth) +{ + // Do not show marker if linewidth == 0 and markerUnits == strokeWidth + // otherwise Cairo will fail to render anything on the tile + // that contains the "degenerate" marker. + if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH && linewidth == 0) { + return NULL; + } + + std::map::iterator it = marker->views_map.find(key); + if (it == marker->views_map.end()) { + // Key not found + return NULL; + } + + SPMarkerView *view = &(it->second); + if (pos >= view->items.size() ) { + // Position index too large, doesn't exist. + return NULL; + } + + // If not already created + if (view->items[pos] == NULL) { + + /* Parent class ::show method */ + view->items[pos] = marker->private_show(parent->drawing(), key, SP_ITEM_REFERENCE_FLAGS); + + if (view->items[pos]) { + /* fixme: Position (Lauris) */ + parent->prependChild(view->items[pos]); + Inkscape::DrawingGroup *g = dynamic_cast(view->items[pos]); + if (g) g->setChildTransform(marker->c2p); + } + } + + if (view->items[pos]) { + Geom::Affine m; + if (marker->orient_mode == MARKER_ORIENT_AUTO) { + m = base; + } else if (marker->orient_mode == MARKER_ORIENT_AUTO_START_REVERSE) { + // m = Geom::Rotate::from_degrees( 180.0 ) * base; + // Rotating is done at rendering time if necessary + m = base; + } else { + /* fixme: Orient units (Lauris) */ + m = Geom::Rotate::from_degrees(marker->orient.computed); + m *= Geom::Translate(base.translation()); + } + if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { + m = Geom::Scale(linewidth) * m; + } + view->items[pos]->setTransform(m); + } + + return view->items[pos]; +} + +/** + * Hides/removes all views of the given marker that have key 'key'. + * This replaces SPItem implementation because we have our own views + * \param key SPMarkerView key to hide. + */ +void +sp_marker_hide (SPMarker *marker, unsigned int key) +{ + marker->hide(key); + marker->views_map.erase(key); +} + + +const gchar *generate_marker(std::vector &reprs, Geom::Rect bounds, SPDocument *document, Geom::Point center, Geom::Affine move) +{ + Inkscape::XML::Document *xml_doc = document->getReprDoc(); + Inkscape::XML::Node *defsrepr = document->getDefs()->getRepr(); + + Inkscape::XML::Node *repr = xml_doc->createElement("svg:marker"); + + // Uncommenting this will make the marker fixed-size independent of stroke width. + // Commented out for consistency with standard markers which scale when you change + // stroke width: + //repr->setAttribute("markerUnits", "userSpaceOnUse"); + + sp_repr_set_svg_double(repr, "markerWidth", bounds.dimensions()[Geom::X]); + sp_repr_set_svg_double(repr, "markerHeight", bounds.dimensions()[Geom::Y]); + sp_repr_set_svg_double(repr, "refX", center[Geom::X]); + sp_repr_set_svg_double(repr, "refY", center[Geom::Y]); + + repr->setAttribute("orient", "auto"); + + defsrepr->appendChild(repr); + const gchar *mark_id = repr->attribute("id"); + SPObject *mark_object = document->getObjectById(mark_id); + + for (std::vector::const_iterator i=reprs.begin();i!=reprs.end();++i){ + Inkscape::XML::Node *node = *i; + SPItem *copy = SP_ITEM(mark_object->appendChildRepr(node)); + + Geom::Affine dup_transform; + if (!sp_svg_transform_read (node->attribute("transform"), &dup_transform)) + dup_transform = Geom::identity(); + dup_transform *= move; + + copy->doWriteTransform(dup_transform); + } + + Inkscape::GC::release(repr); + return mark_id; +} + +SPObject *sp_marker_fork_if_necessary(SPObject *marker) +{ + if (marker->hrefcount < 2) { + return marker; + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gboolean colorStock = prefs->getBool("/options/markers/colorStockMarkers", true); + gboolean colorCustom = prefs->getBool("/options/markers/colorCustomMarkers", false); + const gchar *stock = marker->getRepr()->attribute("inkscape:isstock"); + gboolean isStock = (!stock || !strcmp(stock,"true")); + + if (isStock ? !colorStock : !colorCustom) { + return marker; + } + + SPDocument *doc = marker->document; + Inkscape::XML::Document *xml_doc = doc->getReprDoc(); + // Turn off garbage-collectable or it might be collected before we can use it + marker->getRepr()->setAttribute("inkscape:collect", NULL); + Inkscape::XML::Node *mark_repr = marker->getRepr()->duplicate(xml_doc); + doc->getDefs()->getRepr()->addChild(mark_repr, NULL); + if (!mark_repr->attribute("inkscape:stockid")) { + mark_repr->setAttribute("inkscape:stockid", mark_repr->attribute("id")); + } + marker->getRepr()->setAttribute("inkscape:collect", "always"); + + SPObject *marker_new = static_cast(doc->getObjectByRepr(mark_repr)); + Inkscape::GC::release(mark_repr); + return marker_new; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-marker.h b/src/object/sp-marker.h new file mode 100644 index 000000000..bae13243b --- /dev/null +++ b/src/object/sp-marker.h @@ -0,0 +1,107 @@ +#ifndef SEEN_SP_MARKER_H +#define SEEN_SP_MARKER_H + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2003 Lauris Kaplinski + * Copyright (C) 2008 Johan Engelen + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +/* + * This is quite similar in logic to + * Maybe we should merge them somehow (Lauris) + */ + +#define SP_TYPE_MARKER (sp_marker_get_type ()) +#define SP_MARKER(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_MARKER(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPMarkerView; + +#include + +#include <2geom/rect.h> +#include <2geom/affine.h> + +#include "enums.h" +#include "svg/svg-length.h" +#include "svg/svg-angle.h" +#include "sp-item-group.h" +#include "uri-references.h" +#include "viewbox.h" + +enum markerOrient { + MARKER_ORIENT_ANGLE, + MARKER_ORIENT_AUTO, + MARKER_ORIENT_AUTO_START_REVERSE +}; + +class SPMarker : public SPGroup, public SPViewBox { +public: + SPMarker(); + virtual ~SPMarker(); + + /* units */ + unsigned int markerUnits_set : 1; + unsigned int markerUnits : 1; + + /* reference point */ + SVGLength refX; + SVGLength refY; + + /* dimensions */ + SVGLength markerWidth; + SVGLength markerHeight; + + /* orient */ + unsigned int orient_set : 1; + markerOrient orient_mode : 2; + SVGAngle orient; + + /* Private views indexed by key that corresponds to a + * particular marker type (start, mid, end) on a particular + * path. SPMarkerView is a wrapper for a vector of pointers to + * Inkscape::DrawingItem instances, one pointer for each + * rendered marker. + */ + std::map views_map; + + virtual void build(SPDocument *document, Inkscape::XML::Node *repr); + virtual void release(); + virtual void set(unsigned int key, gchar const* value); + virtual void update(SPCtx *ctx, guint flags); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags); + + virtual Inkscape::DrawingItem* show(Inkscape::Drawing &drawing, unsigned int key, unsigned int flags); + virtual Inkscape::DrawingItem* private_show(Inkscape::Drawing &drawing, unsigned int key, unsigned int flags); + virtual void hide(unsigned int key); + + virtual Geom::OptRect bbox(Geom::Affine const &transform, SPItem::BBoxType type) const; + virtual void print(SPPrintContext *ctx); +}; + +class SPMarkerReference : public Inkscape::URIReference { + SPMarkerReference(SPObject *obj) : URIReference(obj) {} + SPMarker *getObject() const { + return static_cast(URIReference::getObject()); + } +protected: + virtual bool _acceptObject(SPObject *obj) const { + return SP_IS_MARKER(obj) && URIReference::_acceptObject(obj); + } +}; + +void sp_marker_show_dimension (SPMarker *marker, unsigned int key, unsigned int size); +Inkscape::DrawingItem *sp_marker_show_instance (SPMarker *marker, Inkscape::DrawingItem *parent, + unsigned int key, unsigned int pos, + Geom::Affine const &base, float linewidth); +void sp_marker_hide (SPMarker *marker, unsigned int key); +const char *generate_marker (std::vector &reprs, Geom::Rect bounds, SPDocument *document, Geom::Point center, Geom::Affine move); +SPObject *sp_marker_fork_if_necessary(SPObject *marker); + +#endif diff --git a/src/object/sp-mask.cpp b/src/object/sp-mask.cpp new file mode 100644 index 000000000..b65c145fc --- /dev/null +++ b/src/object/sp-mask.cpp @@ -0,0 +1,319 @@ +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 2003 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include <2geom/transforms.h> + +#include "display/drawing.h" +#include "display/drawing-group.h" +#include "xml/repr.h" + +#include "enums.h" +#include "attributes.h" +#include "document.h" +#include "document-private.h" +#include "style.h" +#include "attributes.h" + +#include "sp-defs.h" +#include "sp-item.h" +#include "sp-mask.h" + +struct SPMaskView { + SPMaskView *next; + unsigned int key; + Inkscape::DrawingItem *arenaitem; + Geom::OptRect bbox; +}; + +SPMaskView *sp_mask_view_new_prepend (SPMaskView *list, unsigned int key, Inkscape::DrawingItem *arenaitem); +SPMaskView *sp_mask_view_list_remove (SPMaskView *list, SPMaskView *view); + +SPMask::SPMask() : SPObjectGroup() { + this->maskUnits_set = FALSE; + this->maskUnits = SP_CONTENT_UNITS_OBJECTBOUNDINGBOX; + + this->maskContentUnits_set = FALSE; + this->maskContentUnits = SP_CONTENT_UNITS_USERSPACEONUSE; + + this->display = NULL; +} + +SPMask::~SPMask() { +} + +void SPMask::build(SPDocument* doc, Inkscape::XML::Node* repr) { + SPObjectGroup::build(doc, repr); + + this->readAttr( "maskUnits" ); + this->readAttr( "maskContentUnits" ); + + /* Register ourselves */ + doc->addResource("mask", this); +} + +void SPMask::release() { + if (this->document) { + // Unregister ourselves + this->document->removeResource("mask", this); + } + + while (this->display) { + // We simply unref and let item manage this in handler + this->display = sp_mask_view_list_remove(this->display, this->display); + } + + SPObjectGroup::release(); +} + +void SPMask::set(unsigned int key, const gchar* value) { + switch (key) { + case SP_ATTR_MASKUNITS: + this->maskUnits = SP_CONTENT_UNITS_OBJECTBOUNDINGBOX; + this->maskUnits_set = FALSE; + + if (value) { + if (!strcmp (value, "userSpaceOnUse")) { + this->maskUnits = SP_CONTENT_UNITS_USERSPACEONUSE; + this->maskUnits_set = TRUE; + } else if (!strcmp (value, "objectBoundingBox")) { + this->maskUnits_set = TRUE; + } + } + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_MASKCONTENTUNITS: + this->maskContentUnits = SP_CONTENT_UNITS_USERSPACEONUSE; + this->maskContentUnits_set = FALSE; + + if (value) { + if (!strcmp (value, "userSpaceOnUse")) { + this->maskContentUnits_set = TRUE; + } else if (!strcmp (value, "objectBoundingBox")) { + this->maskContentUnits = SP_CONTENT_UNITS_OBJECTBOUNDINGBOX; + this->maskContentUnits_set = TRUE; + } + } + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + default: + SPObjectGroup::set(key, value); + break; + } +} + +void SPMask::child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref) { + /* Invoke SPObjectGroup implementation */ + SPObjectGroup::child_added(child, ref); + + /* Show new object */ + SPObject *ochild = this->document->getObjectByRepr(child); + + if (SP_IS_ITEM (ochild)) { + for (SPMaskView *v = this->display; v != NULL; v = v->next) { + Inkscape::DrawingItem *ac = SP_ITEM (ochild)->invoke_show(v->arenaitem->drawing(), v->key, SP_ITEM_REFERENCE_FLAGS); + + if (ac) { + v->arenaitem->prependChild(ac); + } + } + } +} + + +void SPMask::update(SPCtx* ctx, unsigned int flags) { + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + std::vector children = this->childList(true); + + for (std::vector::const_iterator child = children.begin();child != children.end();++child) { + if (flags || ((*child)->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + (*child)->updateDisplay(ctx, flags); + } + + sp_object_unref(*child); + } + + for (SPMaskView *v = this->display; v != NULL; v = v->next) { + Inkscape::DrawingGroup *g = dynamic_cast(v->arenaitem); + + if (this->maskContentUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX && v->bbox) { + Geom::Affine t = Geom::Scale(v->bbox->dimensions()); + t.setTranslation(v->bbox->min()); + g->setChildTransform(t); + } else { + g->setChildTransform(Geom::identity()); + } + } +} + +void SPMask::modified(unsigned int flags) { + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + std::vector children = this->childList(true); + + for (std::vector::const_iterator child = children.begin();child != children.end();++child) { + if (flags || ((*child)->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + (*child)->emitModified(flags); + } + + sp_object_unref(*child); + } +} + +Inkscape::XML::Node* SPMask::write(Inkscape::XML::Document* xml_doc, Inkscape::XML::Node* repr, guint flags) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:mask"); + } + + SPObjectGroup::write(xml_doc, repr, flags); + + return repr; +} + +// Create a mask element (using passed elements), add it to +const gchar * +sp_mask_create (std::vector &reprs, SPDocument *document) +{ + Inkscape::XML::Node *defsrepr = document->getDefs()->getRepr(); + + Inkscape::XML::Document *xml_doc = document->getReprDoc(); + Inkscape::XML::Node *repr = xml_doc->createElement("svg:mask"); + repr->setAttribute("maskUnits", "userSpaceOnUse"); + + defsrepr->appendChild(repr); + const gchar *mask_id = repr->attribute("id"); + SPObject *mask_object = document->getObjectById(mask_id); + + for (std::vector::const_iterator it = reprs.begin(); it != reprs.end(); ++it) { + Inkscape::XML::Node *node = (*it); + mask_object->appendChildRepr(node); + } + + if (repr != defsrepr->lastChild()) + defsrepr->changeOrder(repr, defsrepr->lastChild()); // workaround for bug 989084 + + Inkscape::GC::release(repr); + return mask_id; +} + +Inkscape::DrawingItem *SPMask::sp_mask_show(Inkscape::Drawing &drawing, unsigned int key) { + g_return_val_if_fail (this != NULL, NULL); + g_return_val_if_fail (SP_IS_MASK (this), NULL); + + Inkscape::DrawingGroup *ai = new Inkscape::DrawingGroup(drawing); + this->display = sp_mask_view_new_prepend (this->display, key, ai); + + for (auto& child: children) { + if (SP_IS_ITEM (&child)) { + Inkscape::DrawingItem *ac = SP_ITEM (&child)->invoke_show (drawing, key, SP_ITEM_REFERENCE_FLAGS); + + if (ac) { + ai->prependChild(ac); + } + } + } + + if (this->maskContentUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX && this->display->bbox) { + Geom::Affine t = Geom::Scale(this->display->bbox->dimensions()); + t.setTranslation(this->display->bbox->min()); + ai->setChildTransform(t); + } + + return ai; +} + +void SPMask::sp_mask_hide(unsigned int key) { + g_return_if_fail (this != NULL); + g_return_if_fail (SP_IS_MASK (this)); + + for (auto& child: children) { + if (SP_IS_ITEM (&child)) { + SP_ITEM(&child)->invoke_hide (key); + } + } + + for (SPMaskView *v = this->display; v != NULL; v = v->next) { + if (v->key == key) { + /* We simply unref and let item to manage this in handler */ + this->display = sp_mask_view_list_remove (this->display, v); + return; + } + } + + g_assert_not_reached (); +} + +void SPMask::sp_mask_set_bbox(unsigned int key, Geom::OptRect const &bbox) { + for (SPMaskView *v = this->display; v != NULL; v = v->next) { + if (v->key == key) { + v->bbox = bbox; + break; + } + } +} + +/* Mask views */ + +SPMaskView * +sp_mask_view_new_prepend (SPMaskView *list, unsigned int key, Inkscape::DrawingItem *arenaitem) +{ + SPMaskView *new_mask_view = g_new (SPMaskView, 1); + + new_mask_view->next = list; + new_mask_view->key = key; + new_mask_view->arenaitem = arenaitem; + new_mask_view->bbox = Geom::OptRect(); + + return new_mask_view; +} + +SPMaskView * +sp_mask_view_list_remove (SPMaskView *list, SPMaskView *view) +{ + if (view == list) { + list = list->next; + } else { + SPMaskView *prev; + prev = list; + while (prev->next != view) prev = prev->next; + prev->next = view->next; + } + + delete view->arenaitem; + g_free (view); + + return list; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-mask.h b/src/object/sp-mask.h new file mode 100644 index 000000000..02d37b82b --- /dev/null +++ b/src/object/sp-mask.h @@ -0,0 +1,113 @@ +#ifndef SEEN_SP_MASK_H +#define SEEN_SP_MASK_H + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * Abhishek Sharma + * + * Copyright (C) 2003 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <2geom/rect.h> +#include "sp-object-group.h" +#include "uri-references.h" +#include "xml/node.h" + +#define SP_MASK(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_MASK(obj) (dynamic_cast((SPObject*)obj) != NULL) + +struct SPMaskView; + +namespace Inkscape { + +class Drawing; +class DrawingItem; + +} // namespace Inkscape + + +class SPMask : public SPObjectGroup { +public: + SPMask(); + virtual ~SPMask(); + + unsigned int maskUnits_set : 1; + unsigned int maskUnits : 1; + + unsigned int maskContentUnits_set : 1; + unsigned int maskContentUnits : 1; + + SPMaskView *display; + + Inkscape::DrawingItem *sp_mask_show(Inkscape::Drawing &drawing, unsigned int key); + void sp_mask_hide(unsigned int key); + + void sp_mask_set_bbox(unsigned int key, Geom::OptRect const &bbox); + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref); + + virtual void set(unsigned int key, const char* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + virtual void modified(unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); +}; + +class SPMaskReference : public Inkscape::URIReference { +public: + SPMaskReference(SPObject *obj) : URIReference(obj) {} + SPMask *getObject() const { + return static_cast(URIReference::getObject()); + } +protected: + /** + * If the owner element of this reference (the element with <... mask="...">) + * is a child of the mask it refers to, return false. + * \return false if obj is not a mask or if obj is a parent of this + * reference's owner element. True otherwise. + */ + virtual bool _acceptObject(SPObject *obj) const { + if (!SP_IS_MASK(obj)) { + return false; + } + SPObject * const owner = this->getOwner(); + if (!URIReference::_acceptObject(obj)) { + //XML Tree being used directly here while it shouldn't be... + Inkscape::XML::Node * const owner_repr = owner->getRepr(); + //XML Tree being used directly here while it shouldn't be... + Inkscape::XML::Node * const obj_repr = obj->getRepr(); + char const * owner_name = ""; + char const * owner_mask = ""; + char const * obj_name = ""; + char const * obj_id = ""; + if (owner_repr != NULL) { + owner_name = owner_repr->name(); + owner_mask = owner_repr->attribute("mask"); + } + if (obj_repr != NULL) { + obj_name = obj_repr->name(); + obj_id = obj_repr->attribute("id"); + } + printf("WARNING: Ignoring recursive mask reference " + "<%s mask=\"%s\"> in <%s id=\"%s\">", + owner_name, owner_mask, + obj_name, obj_id); + return false; + } + return true; + } +}; + +const char *sp_mask_create (std::vector &reprs, SPDocument *document); + +#endif // SEEN_SP_MASK_H diff --git a/src/object/sp-mesh-array.cpp b/src/object/sp-mesh-array.cpp new file mode 100644 index 000000000..d958427f2 --- /dev/null +++ b/src/object/sp-mesh-array.cpp @@ -0,0 +1,3102 @@ +/** \file + A group of classes and functions for manipulating mesh gradients. + + A mesh is made up of an array of patches. Each patch has four sides and four corners. The sides can + be shared between two patches and the corners between up to four. + + The order of the points for each side always goes from left to right or top to bottom. + For sides 2 and 3 the points must be reversed when used (as in calls to cairo functions). + + Two patches: (C=corner, S=side, H=handle, T=tensor) + + C0 H1 H2 C1 C0 H1 H2 C1 + + ---------- + ---------- + + | S0 | S0 | + H1 | T0 T1 |H1 T0 T1 | H1 + |S3 S1|S3 S1| + H2 | T3 T2 |H2 T3 T2 | H2 + | S2 | S2 | + + ---------- + ---------- + + C3 H1 H2 C2 C3 H1 H2 C2 + + The mesh is stored internally as an array of nodes that includes the tensor nodes. + + Note: This code uses tensor points which are not part of the SVG2 plan at the moment. + Including tensor points was motivated by a desire to experiment with their usefulness + in smoothing color transitions. There doesn't seem to be much advantage for that + purpose. However including them internally allows for storing all the points in + an array which simplifies things like inserting new rows or columns. +*/ + +/* + * Authors: + * Tavmjong Bah + * + * Copyright (C) 2012, 2015 Tavmjong Bah + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include + +// For color picking +#include "display/drawing.h" +#include "display/drawing-context.h" +#include "display/cairo-utils.h" +#include "document.h" +#include "sp-root.h" + +#include "sp-mesh-gradient.h" +#include "sp-mesh-array.h" +#include "sp-mesh-row.h" +#include "sp-mesh-patch.h" +#include "sp-stop.h" +#include "display/curve.h" + +// For new mesh creation +#include "preferences.h" +#include "sp-ellipse.h" +#include "sp-star.h" + +// For writing color/opacity to style +#include "svg/css-ostringstream.h" + +// For default color +#include "style.h" +#include "svg/svg-color.h" + + +// Includes bezier-curve.h, ray.h, crossing.h +#include "2geom/line.h" + +#include "xml/repr.h" +#include +#include + +enum { ROW, COL }; + +SPMeshPatchI::SPMeshPatchI( std::vector > * n, int r, int c ) { + + nodes = n; + row = r*3; // Convert from patch array to node array + col = c*3; + + guint i = 0; + if( row != 0 ) i = 1; + for( ; i < 4; ++i ) { + if( nodes->size() < row+i+1 ) { + std::vector< SPMeshNode* > row; + nodes->push_back( row ); + } + + guint j = 0; + if( col != 0 ) j = 1; + for( ; j < 4; ++j ) { + if( (*nodes)[row+i].size() < col+j+1 ){ + SPMeshNode* node = new SPMeshNode; + // Ensure all nodes know their type. + node->node_type = MG_NODE_TYPE_HANDLE; + if( (i == 0 || i == 3) && (j == 0 || j == 3 ) ) node->node_type = MG_NODE_TYPE_CORNER; + if( (i == 1 || i == 2) && (j == 1 || j == 2 ) ) node->node_type = MG_NODE_TYPE_TENSOR; + (*nodes)[row+i].push_back( node ); + } + } + } +} + +/** + Returns point for side in proper order for patch +*/ +Geom::Point SPMeshPatchI::getPoint( guint s, guint pt ) { + + assert( s < 4 ); + assert( pt < 4 ); + + Geom::Point p; + switch ( s ) { + case 0: + p = (*nodes)[ row ][ col+pt ]->p; + break; + case 1: + p = (*nodes)[ row+pt ][ col+3 ]->p; + break; + case 2: + p = (*nodes)[ row+3 ][ col+3-pt ]->p; + break; + case 3: + p = (*nodes)[ row+3-pt ][ col ]->p; + break; + } + return p; + +}; + +/** + Returns vector of points for a side in proper order for a patch (clockwise order). +*/ +std::vector< Geom::Point > SPMeshPatchI::getPointsForSide( guint i ) { + + assert( i < 4 ); + + std::vector< Geom::Point> points; + points.push_back( getPoint( i, 0 ) ); + points.push_back( getPoint( i, 1 ) ); + points.push_back( getPoint( i, 2 ) ); + points.push_back( getPoint( i, 3 ) ); + return points; +}; + + +/** + Set point for side in proper order for patch +*/ +void SPMeshPatchI::setPoint( guint s, guint pt, Geom::Point p, bool set ) { + + assert( s < 4 ); + assert( pt < 4 ); + + NodeType node_type = MG_NODE_TYPE_CORNER; + if( pt == 1 || pt == 2 ) node_type = MG_NODE_TYPE_HANDLE; + + // std::cout << "SPMeshPatchI::setPoint: s: " << s + // << " pt: " << pt + // << " p: " << p + // << " node_type: " << node_type + // << " set: " << set + // << " row: " << row + // << " col: " << col << std::endl; + switch ( s ) { + case 0: + (*nodes)[ row ][ col+pt ]->p = p; + (*nodes)[ row ][ col+pt ]->set = set; + (*nodes)[ row ][ col+pt ]->node_type = node_type; + break; + case 1: + (*nodes)[ row+pt ][ col+3 ]->p = p; + (*nodes)[ row+pt ][ col+3 ]->set = set; + (*nodes)[ row+pt ][ col+3 ]->node_type = node_type; + break; + case 2: + (*nodes)[ row+3 ][ col+3-pt ]->p = p; + (*nodes)[ row+3 ][ col+3-pt ]->set = set; + (*nodes)[ row+3 ][ col+3-pt ]->node_type = node_type; + break; + case 3: + (*nodes)[ row+3-pt ][ col ]->p = p; + (*nodes)[ row+3-pt ][ col ]->set = set; + (*nodes)[ row+3-pt ][ col ]->node_type = node_type; + break; + } + +}; + +/** + Get path type for side (stored in handle nodes). +*/ +gchar SPMeshPatchI::getPathType( guint s ) { + + assert( s < 4 ); + + gchar type = 'x'; + + switch ( s ) { + case 0: + type = (*nodes)[ row ][ col+1 ]->path_type; + break; + case 1: + type = (*nodes)[ row+1 ][ col+3 ]->path_type; + break; + case 2: + type = (*nodes)[ row+3 ][ col+2 ]->path_type; + break; + case 3: + type = (*nodes)[ row+2 ][ col ]->path_type; + break; + } + + return type; +}; + +/** + Set path type for side (stored in handle nodes). +*/ +void SPMeshPatchI::setPathType( guint s, gchar t ) { + + assert( s < 4 ); + + switch ( s ) { + case 0: + (*nodes)[ row ][ col+1 ]->path_type = t; + (*nodes)[ row ][ col+2 ]->path_type = t; + break; + case 1: + (*nodes)[ row+1 ][ col+3 ]->path_type = t; + (*nodes)[ row+2 ][ col+3 ]->path_type = t; + break; + case 2: + (*nodes)[ row+3 ][ col+1 ]->path_type = t; + (*nodes)[ row+3 ][ col+2 ]->path_type = t; + break; + case 3: + (*nodes)[ row+1 ][ col ]->path_type = t; + (*nodes)[ row+2 ][ col ]->path_type = t; + break; + } + +}; + +/** + Set tensor control point for "corner" i. + */ +void SPMeshPatchI::setTensorPoint( guint i, Geom::Point p ) { + + assert( i < 4 ); + switch ( i ) { + case 0: + (*nodes)[ row + 1 ][ col + 1 ]->p = p; + (*nodes)[ row + 1 ][ col + 1 ]->set = true; + (*nodes)[ row + 1 ][ col + 1 ]->node_type = MG_NODE_TYPE_TENSOR; + break; + case 1: + (*nodes)[ row + 1 ][ col + 2 ]->p = p; + (*nodes)[ row + 1 ][ col + 2 ]->set = true; + (*nodes)[ row + 1 ][ col + 2 ]->node_type = MG_NODE_TYPE_TENSOR; + break; + case 2: + (*nodes)[ row + 2 ][ col + 2 ]->p = p; + (*nodes)[ row + 2 ][ col + 2 ]->set = true; + (*nodes)[ row + 2 ][ col + 2 ]->node_type = MG_NODE_TYPE_TENSOR; + break; + case 3: + (*nodes)[ row + 2 ][ col + 1 ]->p = p; + (*nodes)[ row + 2 ][ col + 1 ]->set = true; + (*nodes)[ row + 2 ][ col + 1 ]->node_type = MG_NODE_TYPE_TENSOR; + break; + } +} + +/** + Return if any tensor control point is set. + */ +bool SPMeshPatchI::tensorIsSet() { + for( guint i = 0; i < 4; ++i ) { + if( tensorIsSet( i ) ) { + return true; + } + } + return false; +} + +/** + Return if tensor control point for "corner" i is set. + */ +bool SPMeshPatchI::tensorIsSet( unsigned int i ) { + + assert( i < 4 ); + + bool set = false; + switch ( i ) { + case 0: + set = (*nodes)[ row + 1 ][ col + 1 ]->set; + break; + case 1: + set = (*nodes)[ row + 1 ][ col + 2 ]->set; + break; + case 2: + set = (*nodes)[ row + 2 ][ col + 2 ]->set; + break; + case 3: + set = (*nodes)[ row + 2 ][ col + 1 ]->set; + break; + } + return set; +} + +/** + Return tensor control point for "corner" i. + If not set, returns calculated (Coons) point. + */ +Geom::Point SPMeshPatchI::getTensorPoint( guint k ) { + + assert( k < 4 ); + + guint i = 0; + guint j = 0; + + + switch ( k ) { + case 0: + i = 1; + j = 1; + break; + case 1: + i = 1; + j = 2; + break; + case 2: + i = 2; + j = 2; + break; + case 3: + i = 2; + j = 1; + break; + } + + Geom::Point p; + if( (*nodes)[ row + i ][ col + j ]->set ) { + p = (*nodes)[ row + i ][ col + j ]->p; + } else { + p = coonsTensorPoint( k ); + } + return p; +} + +/** + Find default tensor point (equivalent point to Coons Patch). + Formulas defined in PDF spec. + Equivalent to 1/3 of side length from corner for square patch. + */ +Geom::Point SPMeshPatchI::coonsTensorPoint( guint i ) { + + Geom::Point t; + Geom::Point p[4][4]; // Points in PDF notation + + p[0][0] = getPoint( 0, 0 ); + p[0][1] = getPoint( 0, 1 ); + p[0][2] = getPoint( 0, 2 ); + p[0][3] = getPoint( 0, 3 ); + p[1][0] = getPoint( 3, 2 ); + p[1][3] = getPoint( 1, 1 ); + p[2][0] = getPoint( 3, 1 ); + p[2][3] = getPoint( 1, 2 ); + p[3][0] = getPoint( 2, 3 ); + p[3][1] = getPoint( 2, 2 ); + p[3][2] = getPoint( 2, 1 ); + p[3][3] = getPoint( 2, 0 ); + + switch ( i ) { + case 0: + t = ( -4.0 * p[0][0] + + 6.0 * ( p[0][1] + p[1][0] ) + + -2.0 * ( p[0][3] + p[3][0] ) + + 3.0 * ( p[3][1] + p[1][3] ) + + -1.0 * p[3][3] ) / 9.0; + break; + + case 1: + t = ( -4.0 * p[0][3] + + 6.0 * ( p[0][2] + p[1][3] ) + + -2.0 * ( p[0][0] + p[3][3] ) + + 3.0 * ( p[3][2] + p[1][0] ) + + -1.0 * p[3][0] ) / 9.0; + break; + + case 2: + t = ( -4.0 * p[3][3] + + 6.0 * ( p[3][2] + p[2][3] ) + + -2.0 * ( p[3][0] + p[0][3] ) + + 3.0 * ( p[0][2] + p[2][0] ) + + -1.0 * p[0][0] ) / 9.0; + break; + + case 3: + t = ( -4.0 * p[3][0] + + 6.0 * ( p[3][1] + p[2][0] ) + + -2.0 * ( p[3][3] + p[0][0] ) + + 3.0 * ( p[0][1] + p[2][3] ) + + -1.0 * p[0][3] ) / 9.0; + break; + + default: + + g_warning( "Impossible!" ); + + } + return t; +} + +/** + Update default values for handle and tensor nodes. +*/ +void SPMeshPatchI::updateNodes() { + + // std::cout << "SPMeshPatchI::updateNodes: " << row << "," << col << std::endl; + // Handles first (tensors require update handles). + for( guint i = 0; i < 4; ++i ) { + for( guint j = 0; j < 4; ++j ) { + if( (*nodes)[ row + i ][ col + j ]->set == false ) { + + if( (*nodes)[ row + i ][ col + j ]->node_type == MG_NODE_TYPE_HANDLE ) { + + // If a handle is not set it is because the side is a line. + // Set node points 1/3 of the way between corners. + + if( i == 0 || i == 3 ) { + Geom::Point p0 = ( (*nodes)[ row + i ][ col ]->p ); + Geom::Point p3 = ( (*nodes)[ row + i ][ col + 3 ]->p ); + Geom::Point dp = (p3 - p0)/3.0; + if( j == 2 ) dp *= 2.0; + (*nodes)[ row + i ][ col + j ]->p = p0 + dp; + } + + if( j == 0 || j == 3 ) { + Geom::Point p0 = ( (*nodes)[ row ][ col + j ]->p ); + Geom::Point p3 = ( (*nodes)[ row + 3 ][ col + j ]->p ); + Geom::Point dp = (p3 - p0)/3.0; + if( i == 2 ) dp *= 2.0; + (*nodes)[ row + i ][ col + j ]->p = p0 + dp; + } + } + } + } + } + + // Update tensor nodes + for( guint i = 1; i < 3; ++i ) { + for( guint j = 1; j < 3; ++j ) { + if( (*nodes)[ row + i ][ col + j ]->set == false ) { + + (*nodes)[ row + i ][ col + j ]->node_type = MG_NODE_TYPE_TENSOR; + + guint t = 0; + if( i == 1 && j == 2 ) t = 1; + if( i == 2 && j == 2 ) t = 2; + if( i == 2 && j == 1 ) t = 3; + (*nodes)[ row + i ][ col + j ]->p = coonsTensorPoint( t ); + // std::cout << "Update node: " << i << ", " << j << " " << coonsTensorPoint( t ) << std::endl; + + } + } + } +} + +/** + Return color for corner of patch. +*/ +SPColor SPMeshPatchI::getColor( guint i ) { + + assert( i < 4 ); + + SPColor color; + switch ( i ) { + case 0: + color = (*nodes)[ row ][ col ]->color; + break; + case 1: + color = (*nodes)[ row ][ col+3 ]->color; + break; + case 2: + color = (*nodes)[ row+3 ][ col+3 ]->color; + break; + case 3: + color = (*nodes)[ row+3 ][ col ]->color; + break; + + } + + return color; + +}; + +/** + Set color for corner of patch. +*/ +void SPMeshPatchI::setColor( guint i, SPColor color ) { + + assert( i < 4 ); + + switch ( i ) { + case 0: + (*nodes)[ row ][ col ]->color = color; + break; + case 1: + (*nodes)[ row ][ col+3 ]->color = color; + break; + case 2: + (*nodes)[ row+3 ][ col+3 ]->color = color; + break; + case 3: + (*nodes)[ row+3 ][ col ]->color = color; + break; + } +}; + +/** + Return opacity for corner of patch. +*/ +gdouble SPMeshPatchI::getOpacity( guint i ) { + + assert( i < 4 ); + + gdouble opacity = 0.0; + switch ( i ) { + case 0: + opacity = (*nodes)[ row ][ col ]->opacity; + break; + case 1: + opacity = (*nodes)[ row ][ col+3 ]->opacity; + break; + case 2: + opacity = (*nodes)[ row+3 ][ col+3 ]->opacity; + break; + case 3: + opacity = (*nodes)[ row+3 ][ col ]->opacity; + break; + } + + return opacity; +}; + + +/** + Set opacity for corner of patch. +*/ +void SPMeshPatchI::setOpacity( guint i, gdouble opacity ) { + + assert( i < 4 ); + + switch ( i ) { + case 0: + (*nodes)[ row ][ col ]->opacity = opacity; + break; + case 1: + (*nodes)[ row ][ col+3 ]->opacity = opacity; + break; + case 2: + (*nodes)[ row+3 ][ col+3 ]->opacity = opacity; + break; + case 3: + (*nodes)[ row+3 ][ col ]->opacity = opacity; + break; + + } + +}; + + +/** + Return stop pointer for corner of patch. +*/ +SPStop* SPMeshPatchI::getStopPtr( guint i ) { + + assert( i < 4 ); + + SPStop* stop = nullptr; + switch ( i ) { + case 0: + stop = (*nodes)[ row ][ col ]->stop; + break; + case 1: + stop = (*nodes)[ row ][ col+3 ]->stop; + break; + case 2: + stop = (*nodes)[ row+3 ][ col+3 ]->stop; + break; + case 3: + stop = (*nodes)[ row+3 ][ col ]->stop; + break; + } + + return stop; +}; + + +/** + Set stop pointer for corner of patch. +*/ +void SPMeshPatchI::setStopPtr( guint i, SPStop* stop ) { + + assert( i < 4 ); + + switch ( i ) { + case 0: + (*nodes)[ row ][ col ]->stop = stop; + break; + case 1: + (*nodes)[ row ][ col+3 ]->stop = stop; + break; + case 2: + (*nodes)[ row+3 ][ col+3 ]->stop = stop; + break; + case 3: + (*nodes)[ row+3 ][ col ]->stop = stop; + break; + + } + +}; + + +SPMeshNodeArray::SPMeshNodeArray( SPMeshGradient *mg ) { + + read( mg ); + +}; + + +// Copy constructor +SPMeshNodeArray::SPMeshNodeArray( const SPMeshNodeArray& rhs ) { + + built = false; + mg = NULL; + draggers_valid = false; + + nodes = rhs.nodes; // This only copies the pointers but it does size the vector of vectors. + + for( unsigned i=0; i < nodes.size(); ++i ) { + for( unsigned j=0; j < nodes[i].size(); ++j ) { + nodes[i][j] = new SPMeshNode( *rhs.nodes[i][j] ); // Copy data. + } + } +}; + + +// Copy assignment operator +SPMeshNodeArray& SPMeshNodeArray::operator=( const SPMeshNodeArray& rhs ) { + + if( this == &rhs ) return *this; + + clear(); // Clear any existing array. + + built = false; + mg = NULL; + draggers_valid = false; + + nodes = rhs.nodes; // This only copies the pointers but it does size the vector of vectors. + + for( unsigned i=0; i < nodes.size(); ++i ) { + for( unsigned j=0; j < nodes[i].size(); ++j ) { + nodes[i][j] = new SPMeshNode( *rhs.nodes[i][j] ); // Copy data. + } + } + + return *this; +}; + +// Fill array with data from mesh objects. +// Returns true of array's dimensions unchanged. +bool SPMeshNodeArray::read( SPMeshGradient *mg_in ) { + + mg = mg_in; + SPMeshGradient* mg_array = dynamic_cast(mg->getArray()); + if (!mg_array) { + std::cerr << "SPMeshNodeArray::read: No mesh array!" << std::endl; + return false; + } + // std::cout << "SPMeshNodeArray::read: " << mg_in << " array: " << mg_array << std::endl; + + // Count rows and columns, if unchanged reuse array to keep draggers valid. + unsigned cols = 0; + unsigned rows = 0; + for (auto& ro: mg_array->children) { + if (SP_IS_MESHROW(&ro)) { + ++rows; + if (rows == 1 ) { + for (auto& po: ro.children) { + if (SP_IS_MESHPATCH(&po)) { + ++cols; + } + } + } + } + } + bool same_size = true; + if (cols != patch_columns() || rows != patch_rows() ) { + // Draggers will be invalidated. + same_size = false; + clear(); + draggers_valid = false; + } + + Geom::Point current_p( mg->x.computed, mg->y.computed ); + // std::cout << "SPMeshNodeArray::read: p: " << current_p << std::endl; + + guint max_column = 0; + guint irow = 0; // Corresponds to top of patch being read in. + for (auto& ro: mg_array->children) { + + if (SP_IS_MESHROW(&ro)) { + + guint icolumn = 0; // Corresponds to left of patch being read in. + for (auto& po: ro.children) { + + if (SP_IS_MESHPATCH(&po)) { + + SPMeshpatch *patch = SP_MESHPATCH(&po); + + // std::cout << "SPMeshNodeArray::read: row size: " << nodes.size() << std::endl; + SPMeshPatchI new_patch( &nodes, irow, icolumn ); // Adds new nodes. + // std::cout << " after: " << nodes.size() << std::endl; + + gint istop = 0; + + // Only 'top' side defined for first row. + if( irow != 0 ) ++istop; + + for (auto& so: po.children) { + if (SP_IS_STOP(&so)) { + + if( istop > 3 ) { + // std::cout << " Mesh Gradient: Too many stops: " << istop << std::endl; + break; + } + + SPStop *stop = SP_STOP(&so); + + // Handle top of first row. + if( istop == 0 && icolumn == 0 ) { + // First patch in mesh. + new_patch.setPoint( 0, 0, current_p ); + } + // First point is always already defined by previous side (stop). + current_p = new_patch.getPoint( istop, 0 ); + + // If side closes patch, then we read one less point. + bool closed = false; + if( icolumn == 0 && istop == 3 ) closed = true; + if( icolumn > 0 && istop == 2 ) closed = true; + + + // Copy path and then replace commas by spaces so we can use stringstream to parse + std::string path_string = *(stop->path_string); + std::replace(path_string.begin(),path_string.end(),',',' '); + + // std::cout << " path_string: " << path_string << std::endl; + // std::cout << " current_p: " << current_p << std::endl; + + std::stringstream os( path_string ); + + // Determine type of path + char path_type; + os >> path_type; + new_patch.setPathType( istop, path_type ); + + gdouble x, y; + Geom::Point p, dp; + guint max; + switch ( path_type ) { + case 'l': + if( !closed ) { + os >> x >> y; + if( !os.fail() ) { + dp = Geom::Point( x, y ); + new_patch.setPoint( istop, 3, current_p + dp ); + } else { + std::cerr << "Failed to read l" << std::endl; + } + } + // To facilitate some side operations, set handles to 1/3 and + // 2/3 distance between corner points but flag as unset. + p = new_patch.getPoint( istop, 3 ); + dp = (p - current_p)/3.0; // Calculate since may not be set if closed. + // std::cout << " istop: " << istop + // << " dp: " << dp + // << " p: " << p + // << " current_p: " << current_p + // << std::endl; + new_patch.setPoint( istop, 1, current_p + dp, false ); + new_patch.setPoint( istop, 2, current_p + 2.0 * dp, false ); + break; + case 'L': + if( !closed ) { + os >> x >> y; + if( !os.fail() ) { + p = Geom::Point( x, y ); + new_patch.setPoint( istop, 3, p ); + } else { + std::cerr << "Failed to read L" << std::endl; + } + } + // To facilitate some side operations, set handles to 1/3 and + // 2/3 distance between corner points but flag as unset. + p = new_patch.getPoint( istop, 3 ); + dp = (p - current_p)/3.0; + new_patch.setPoint( istop, 1, current_p + dp, false ); + new_patch.setPoint( istop, 2, current_p + 2.0 * dp, false ); + break; + case 'c': + max = 4; + if( closed ) max = 3; + for( guint i = 1; i < max; ++i ) { + os >> x >> y; + if( !os.fail() ) { + p = Geom::Point( x, y ); + p += current_p; + new_patch.setPoint( istop, i, p ); + } else { + std::cerr << "Failed to read c: " << i << std::endl; + } + } + break; + case 'C': + max = 4; + if( closed ) max = 3; + for( guint i = 1; i < max; ++i ) { + os >> x >> y; + if( !os.fail() ) { + p = Geom::Point( x, y ); + new_patch.setPoint( istop, i, p ); + } else { + std::cerr << "Failed to read C: " << i << std::endl; + } + } + break; + default: + // should not reach + std::cerr << "Path Error: unhandled path type: " << path_type << std::endl; + } + current_p = new_patch.getPoint( istop, 3 ); + + // Color + if( (istop == 0 && irow == 0 && icolumn > 0) || (istop == 1 && irow > 0 ) ) { + // skip + } else { + SPColor color = stop->getEffectiveColor(); + double opacity = stop->opacity; + new_patch.setColor( istop, color ); + new_patch.setOpacity( istop, opacity ); + new_patch.setStopPtr( istop, stop ); + } + + } + ++istop; + } // Loop over stops + + // Read in tensor string after stops since tensor nodes defined relative to corner nodes. + + // Copy string and then replace commas by spaces so we can use stringstream to parse XXXX + if( patch->tensor_string ) { + std::string tensor_string = *(patch->tensor_string); + std::replace(tensor_string.begin(),tensor_string.end(),',',' '); + + // std::cout << " tensor_string: " << tensor_string << std::endl; + + std::stringstream os( tensor_string ); + for( guint i = 0; i < 4; ++i ) { + double x = 0.0; + double y = 0.0; + os >> x >> y; + if( !os.fail() ) { + new_patch.setTensorPoint( i, new_patch.getPoint( i, 0 ) + Geom::Point( x, y ) ); + } else { + std::cerr << "Failed to read p: " << i << std::endl; + break; + } + } + } + } + + ++icolumn; + if( max_column < icolumn ) max_column = icolumn; + } + } + ++irow; + } + + // Insure we have a true array. + for( guint i = 0; i < nodes.size(); ++i ) { + nodes[ i ].resize( max_column * 3 + 1 ); + } + + // Set node edge. + for( guint i = 0; i < nodes.size(); ++i ) { + for( guint j = 0; j < nodes[i].size(); ++j ) { + nodes[i][j]->node_edge = MG_NODE_EDGE_NONE; + if( i == 0 ) nodes[i][j]->node_edge |= MG_NODE_EDGE_TOP; + if( i == nodes.size() - 1 ) nodes[i][j]->node_edge |= MG_NODE_EDGE_BOTTOM; + if( j == 0 ) nodes[i][j]->node_edge |= MG_NODE_EDGE_RIGHT; + if( j == nodes[i].size() - 1 ) nodes[i][j]->node_edge |= MG_NODE_EDGE_LEFT; + } + } + + // std::cout << "SPMeshNodeArray::Read: result:" << std::endl; + // print(); + + built = true; + + return same_size; +}; + +/** + Write repr using our array. +*/ +void SPMeshNodeArray::write( SPMeshGradient *mg ) { + + // std::cout << "SPMeshNodeArray::write: entrance:" << std::endl; + // print(); + using Geom::X; + using Geom::Y; + + SPMeshGradient* mg_array = dynamic_cast(mg->getArray()); + if (!mg_array) { + // std::cerr << "SPMeshNodeArray::write: missing patches!" << std::endl; + mg_array = mg; + } + + // First we must delete reprs for old mesh rows and patches. We only need to call the + // deleteObject() method, which in turn calls sp_repr_unparent. Since iterators do not play + // well with boost::intrusive::list (which ChildrenList derive from) we need to iterate over a + // copy of the pointers to the objects. + std::vector children_pointers; + for (auto& row : mg_array->children) { + children_pointers.push_back(&row); + } + + for (auto i : children_pointers) { + i->deleteObject(); + } + + // Now we build new reprs + Inkscape::XML::Node *mesh = mg->getRepr(); + Inkscape::XML::Node *mesh_array = mg_array->getRepr(); + + SPMeshNodeArray* array = &(mg_array->array); + SPMeshPatchI patch0( &(array->nodes), 0, 0 ); + Geom::Point current_p = patch0.getPoint( 0, 0 ); // Side 0, point 0 + + sp_repr_set_svg_double( mesh, "x", current_p[X] ); + sp_repr_set_svg_double( mesh, "y", current_p[Y] ); + + Geom::Point current_p2( mg->x.computed, mg->y.computed ); + + Inkscape::XML::Document *xml_doc = mesh->document(); + guint rows = array->patch_rows(); + for( guint i = 0; i < rows; ++i ) { + + // Write row + Inkscape::XML::Node *row = xml_doc->createElement("svg:meshrow"); + mesh_array->appendChild( row ); // No attributes + + guint columns = array->patch_columns(); + for( guint j = 0; j < columns; ++j ) { + + // Write patch + Inkscape::XML::Node *patch = xml_doc->createElement("svg:meshpatch"); + + SPMeshPatchI patchi( &(array->nodes), i, j ); + + // Add tensor + if( patchi.tensorIsSet() ) { + + std::stringstream is; + + for( guint k = 0; k < 4; ++k ) { + Geom::Point p = patchi.getTensorPoint( k ) - patchi.getPoint( k, 0 ); + is << p[X] << "," << p[Y]; + if( k < 3 ) is << " "; + } + + patch->setAttribute("tensor", is.str().c_str()); + // std::cout << " SPMeshNodeArray::write: tensor: " << is.str() << std::endl; + } + + row->appendChild( patch ); + + // Write sides + for( guint k = 0; k < 4; ++k ) { + + // Only first row has top stop + if( k == 0 && i != 0 ) continue; + + // Only first column has left stop + if( k == 3 && j != 0 ) continue; + + Inkscape::XML::Node *stop = xml_doc->createElement("svg:stop"); + + // Add path + std::stringstream is; + char path_type = patchi.getPathType( k ); + is << path_type; + + std::vector< Geom::Point> p = patchi.getPointsForSide( k ); + current_p = patchi.getPoint( k, 0 ); + + switch ( path_type ) { + case 'l': + is << " " + << ( p[3][X] - current_p[X] ) << "," + << ( p[3][Y] - current_p[Y] ); + break; + case 'L': + is << " " + << p[3][X] << "," + << p[3][Y]; + break; + case 'c': + is << " " + << ( p[1][X] - current_p[X] ) << "," + << ( p[1][Y] - current_p[Y] ) << " " + << ( p[2][X] - current_p[X] ) << "," + << ( p[2][Y] - current_p[Y] ) << " " + << ( p[3][X] - current_p[X] ) << "," + << ( p[3][Y] - current_p[Y] ); + break; + case 'C': + is << " " + << p[1][X] << "," + << p[1][Y] << " " + << p[2][X] << "," + << p[2][Y] << " " + << p[3][X] << "," + << p[3][Y]; + break; + case 'z': + case 'Z': + std::cerr << "SPMeshNodeArray::write(): bad path type" << path_type << std::endl; + break; + default: + std::cerr << "SPMeshNodeArray::write(): unhandled path type" << path_type << std::endl; + } + stop->setAttribute("path", is.str().c_str()); + // std::cout << "SPMeshNodeArray::write: path: " << is.str().c_str() << std::endl; + // Add stop-color + if( ( k == 0 && i == 0 && j == 0 ) || + ( k == 1 && i == 0 ) || + ( k == 2 ) || + ( k == 3 && j == 0 ) ) { + + // Why are we setting attribute and not style? + //stop->setAttribute("stop-color", patchi.getColor(k).toString().c_str() ); + //stop->setAttribute("stop-opacity", patchi.getOpacity(k) ); + + Inkscape::CSSOStringStream os; + os << "stop-color:" << patchi.getColor(k).toString() << ";stop-opacity:" << patchi.getOpacity(k); + stop->setAttribute("style", os.str().c_str()); + } + patch->appendChild( stop ); + } + } + } +} + +/** + * Find default color based on colors in existing fill. + */ +static SPColor default_color( SPItem *item ) { + + SPColor color( 0.5, 0.0, 0.5 ); + + if ( item->style ) { + SPIPaint const &paint = ( item->style->fill ); // Could pick between style.fill/style.stroke + if ( paint.isColor() ) { + color = paint.value.color; + } else if ( paint.isPaintserver() ) { + SPObject const *server = item->style->getFillPaintServer(); + if ( SP_IS_GRADIENT(server) && SP_GRADIENT(server)->getVector() ) { + SPStop *firstStop = SP_GRADIENT(server)->getVector()->getFirstStop(); + if ( firstStop ) { + color = firstStop->getEffectiveColor(); + } + } + } + } else { + std::cerr << " SPMeshNodeArray: default_color(): No style" << std::endl; + } + + return color; +} + +/** + Create a default mesh. +*/ +void SPMeshNodeArray::create( SPMeshGradient *mg, SPItem *item, Geom::OptRect bbox ) { + + // std::cout << "SPMeshNodeArray::create: Entrance" << std::endl; + + if( !bbox ) { + // Set default size to bounding box if size not given. + std::cerr << "SPMeshNodeArray::create(): bbox empty" << std::endl; + bbox = item->geometricBounds(); + + if( !bbox ) { + std::cerr << "SPMeshNodeArray::create: ERROR: No bounding box!" << std::endl; + return; + } + } + + Geom::Coord const width = bbox->dimensions()[Geom::X]; + Geom::Coord const height = bbox->dimensions()[Geom::Y]; + Geom::Point center = bbox->midpoint(); + + // Must keep repr and array in sync. We have two choices: + // Build the repr first and then "read" it. + // Construct the array and then "write" it. + // We'll do the second. + + // Remove any existing mesh. We could choose to simply scale an existing mesh... + //clear(); + + // We get called twice when a new mesh is created...WHY? + // return if we've already constructed the mesh. + if( !nodes.empty() ) return; + + // Set 'gradientUnits'. Our calculations assume "userSpaceOnUse". + Inkscape::XML::Node *repr = mg->getRepr(); + repr->setAttribute("gradientUnits", "userSpaceOnUse"); + + // Get default color + SPColor color = default_color( item ); + + // Set some corners to white so we can see the mesh. + SPColor white( 1.0, 1.0, 1.0 ); + if (color == white) { + // If default color is white, set other color to black. + white = SPColor( 0.0, 0.0, 0.0 ); + } + + // Get preferences + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + guint prows = prefs->getInt("/tools/mesh/mesh_rows", 1); + guint pcols = prefs->getInt("/tools/mesh/mesh_cols", 1); + + SPMeshGeometry mesh_type = + (SPMeshGeometry) prefs->getInt("/tools/mesh/mesh_geometry", SP_MESH_GEOMETRY_NORMAL); + + if( mesh_type == SP_MESH_GEOMETRY_CONICAL ) { + + // Conical gradient.. for any shape/path using geometric bounding box. + + gdouble rx = width/2.0; + gdouble ry = height/2.0; + + // Start and end angles + gdouble start = 0.0; + gdouble end = 2.0 * M_PI; + + if ( SP_IS_STAR( item ) ) { + // But if it is a star... use star parameters! + SPStar* star = SP_STAR( item ); + center = star->center; + rx = star->r[0]; + ry = star->r[0]; + start = star->arg[0]; + end = start + 2.0 * M_PI; + } + + if ( SP_IS_GENERICELLIPSE( item ) ) { + // For arcs use set start/stop + SPGenericEllipse* arc = SP_GENERICELLIPSE( item ); + center[Geom::X] = arc->cx.computed; + center[Geom::Y] = arc->cy.computed; + rx = arc->rx.computed; + ry = arc->ry.computed; + start = arc->start; + end = arc->end; + if( end <= start ) { + end += 2.0 * M_PI; + } + } + + // std::cout << " start: " << start << " end: " << end << std::endl; + + // IS THIS NECESSARY? + sp_repr_set_svg_double( repr, "x", center[Geom::X] + rx * cos(start) ); + sp_repr_set_svg_double( repr, "y", center[Geom::Y] + ry * sin(start) ); + + guint sections = pcols; + + // If less sections, arc approximation error too great. (Check!) + if( sections < 4 ) sections = 4; + + double arc = (end - start) / (double)sections; + + // See: http://en.wikipedia.org/wiki/B%C3%A9zier_curve + gdouble kappa = 4.0/3.0 * tan(arc/4.0); + gdouble lenx = rx * kappa; + gdouble leny = ry * kappa; + + gdouble s = start; + for( guint i = 0; i < sections; ++i ) { + + SPMeshPatchI patch( &nodes, 0, i ); + + gdouble x0 = center[Geom::X] + rx * cos(s); + gdouble y0 = center[Geom::Y] + ry * sin(s); + gdouble x1 = x0 - lenx * sin(s); + gdouble y1 = y0 + leny * cos(s); + + s += arc; + gdouble x3 = center[Geom::X] + rx * cos(s); + gdouble y3 = center[Geom::Y] + ry * sin(s); + gdouble x2 = x3 + lenx * sin(s); + gdouble y2 = y3 - leny * cos(s); + + patch.setPoint( 0, 0, Geom::Point( x0, y0 ) ); + patch.setPoint( 0, 1, Geom::Point( x1, y1 ) ); + patch.setPoint( 0, 2, Geom::Point( x2, y2 ) ); + patch.setPoint( 0, 3, Geom::Point( x3, y3 ) ); + + patch.setPoint( 2, 0, center ); + patch.setPoint( 3, 0, center ); + + for( guint k = 0; k < 4; ++k ) { + patch.setPathType( k, 'l' ); + patch.setColor( k, (i+k)%2 ? color : white ); + patch.setOpacity( k, 1.0 ); + } + patch.setPathType( 0, 'c' ); + + // Set handle and tensor nodes. + patch.updateNodes(); + + } + + split_row( 0, prows ); + + } else { + + // Normal grid meshes + + if( SP_IS_GENERICELLIPSE( item ) ) { + + // std::cout << "We've got ourselves an arc!" << std::endl; + + SPGenericEllipse* arc = SP_GENERICELLIPSE( item ); + center[Geom::X] = arc->cx.computed; + center[Geom::Y] = arc->cy.computed; + gdouble rx = arc->rx.computed; + gdouble ry = arc->ry.computed; + + gdouble s = -3.0/2.0 * M_PI_2; + + sp_repr_set_svg_double( repr, "x", center[Geom::X] + rx * cos(s) ); + sp_repr_set_svg_double( repr, "y", center[Geom::Y] + ry * sin(s) ); + + gdouble lenx = rx * 4*tan(M_PI_2/4)/3; + gdouble leny = ry * 4*tan(M_PI_2/4)/3; + + SPMeshPatchI patch( &nodes, 0, 0 ); + for( guint i = 0; i < 4; ++i ) { + + gdouble x0 = center[Geom::X] + rx * cos(s); + gdouble y0 = center[Geom::Y] + ry * sin(s); + gdouble x1 = x0 + lenx * cos(s + M_PI_2); + gdouble y1 = y0 + leny * sin(s + M_PI_2); + + s += M_PI_2; + gdouble x3 = center[Geom::X] + rx * cos(s); + gdouble y3 = center[Geom::Y] + ry * sin(s); + gdouble x2 = x3 + lenx * cos(s - M_PI_2); + gdouble y2 = y3 + leny * sin(s - M_PI_2); + + Geom::Point p1( x1, y1 ); + Geom::Point p2( x2, y2 ); + Geom::Point p3( x3, y3 ); + patch.setPoint( i, 1, p1 ); + patch.setPoint( i, 2, p2 ); + patch.setPoint( i, 3, p3 ); + + patch.setPathType( i, 'c' ); + + patch.setColor( i, i%2 ? color : white ); + patch.setOpacity( i, 1.0 ); + } + + // Fill out tensor points + patch.updateNodes(); + + split_row( 0, prows ); + split_column( 0, pcols ); + + // END Arc + + } else if ( SP_IS_STAR( item ) ) { + + // Do simplest thing... assume star is not rounded or randomized. + // (It should be easy to handle the rounded/randomized cases by making + // the appropriate star class function public.) + SPStar* star = SP_STAR( item ); + guint sides = star->sides; + + // std::cout << "We've got ourselves an star! Sides: " << sides << std::endl; + + Geom::Point p0 = sp_star_get_xy( star, SP_STAR_POINT_KNOT1, 0 ); + sp_repr_set_svg_double( repr, "x", p0[Geom::X] ); + sp_repr_set_svg_double( repr, "y", p0[Geom::Y] ); + + for( guint i = 0; i < sides; ++i ) { + + if( star->flatsided ) { + + SPMeshPatchI patch( &nodes, 0, i ); + + patch.setPoint( 0, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT1, i ) ); + guint ii = i+1; + if( ii == sides ) ii = 0; + patch.setPoint( 1, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT1, ii ) ); + patch.setPoint( 2, 0, star->center ); + patch.setPoint( 3, 0, star->center ); + + for( guint s = 0; s < 4; ++s ) { + patch.setPathType( s, 'l' ); + patch.setColor( s, (i+s)%2 ? color : white ); + patch.setOpacity( s, 1.0 ); + } + + // Set handle and tensor nodes. + patch.updateNodes(); + + } else { + + SPMeshPatchI patch0( &nodes, 0, 2*i ); + + patch0.setPoint( 0, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT1, i ) ); + patch0.setPoint( 1, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT2, i ) ); + patch0.setPoint( 2, 0, star->center ); + patch0.setPoint( 3, 0, star->center ); + + guint ii = i+1; + if( ii == sides ) ii = 0; + + SPMeshPatchI patch1( &nodes, 0, 2*i+1 ); + + patch1.setPoint( 0, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT2, i ) ); + patch1.setPoint( 1, 0, sp_star_get_xy( star, SP_STAR_POINT_KNOT1, ii ) ); + patch1.setPoint( 2, 0, star->center ); + patch1.setPoint( 3, 0, star->center ); + + for( guint s = 0; s < 4; ++s ) { + patch0.setPathType( s, 'l' ); + patch0.setColor( s, s%2 ? color : white ); + patch0.setOpacity( s, 1.0 ); + patch1.setPathType( s, 'l' ); + patch1.setColor( s, s%2 ? white : color ); + patch1.setOpacity( s, 1.0 ); + } + + // Set handle and tensor nodes. + patch0.updateNodes(); + patch1.updateNodes(); + + } + } + + //print(); + + split_row( 0, prows ); + //split_column( 0, pcols ); + + } else { + + // Generic + + sp_repr_set_svg_double(repr, "x", bbox->min()[Geom::X]); + sp_repr_set_svg_double(repr, "y", bbox->min()[Geom::Y]); + + // Get node array size + guint nrows = prows * 3 + 1; + guint ncols = pcols * 3 + 1; + + gdouble dx = width / (gdouble)(ncols-1.0); + gdouble dy = height / (gdouble)(nrows-1.0); + + Geom::Point p0( mg->x.computed, mg->y.computed ); + + for( guint i = 0; i < nrows; ++i ) { + std::vector< SPMeshNode* > row; + for( guint j = 0; j < ncols; ++j ) { + SPMeshNode* node = new SPMeshNode; + node->p = p0 + Geom::Point( j * dx, i * dy ); + + node->node_edge = MG_NODE_EDGE_NONE; + if( i == 0 ) node->node_edge |= MG_NODE_EDGE_TOP; + if( i == nrows -1 ) node->node_edge |= MG_NODE_EDGE_BOTTOM; + if( j == 0 ) node->node_edge |= MG_NODE_EDGE_LEFT; + if( j == ncols -1 ) node->node_edge |= MG_NODE_EDGE_RIGHT; + + if( i%3 == 0 ) { + + if( j%3 == 0) { + // Corner + node->node_type = MG_NODE_TYPE_CORNER; + node->set = true; + node->color = (i+j)%2 ? color : white; + node->opacity = 1.0; + + } else { + // Side + node->node_type = MG_NODE_TYPE_HANDLE; + node->set = true; + node->path_type = 'c'; + } + + } else { + + if( j%3 == 0) { + // Side + node->node_type = MG_NODE_TYPE_HANDLE; + node->set = true; + node->path_type = 'c'; + } else { + // Tensor + node->node_type = MG_NODE_TYPE_TENSOR; + node->set = false; + } + + } + + row.push_back( node ); + } + nodes.push_back( row ); + } + // End normal + } + + } // If conical + + //print(); + + // Write repr + write( mg ); +} + + +/** + Clear mesh gradient. +*/ +void SPMeshNodeArray::clear() { + + for( guint i = 0; i < nodes.size(); ++i ) { + for( guint j = 0; j < nodes[i].size(); ++j ) { + if( nodes[i][j] ) { + delete nodes[i][j]; + } + } + } + nodes.clear(); +}; + + +/** + Print mesh gradient (for debugging). +*/ +void SPMeshNodeArray::print() { + for( guint i = 0; i < nodes.size(); ++i ) { + std::cout << "New node row:" << std::endl; + for( guint j = 0; j < nodes[i].size(); ++j ) { + if( nodes[i][j] ) { + std::cout.width(4); + std::cout << " Node: " << i << "," << j << ": " + << nodes[i][j]->p + << " Node type: " << nodes[i][j]->node_type + << " Node edge: " << nodes[i][j]->node_edge + << " Set: " << nodes[i][j]->set + << " Path type: " << nodes[i][j]->path_type + << " Stop: " << nodes[i][j]->stop + << std::endl; + } else { + std::cout << "Error: missing mesh node." << std::endl; + } + } // Loop over patches + } // Loop over rows +}; + + + +/* +double hermite( const double p0, const double p1, const double m0, const double m1, const double t ) { + double t2 = t*t; + double t3 = t2*t; + + double result = (2.0*t3 - 3.0*t2 +1.0) * p0 + + (t3 - 2.0*t2 + t) * m0 + + (-2.0*t3 + 3.0*t2) * p1 + + (t3 -t2) * m1; + + return result; +} +*/ + +class SPMeshSmoothCorner { + +public: + SPMeshSmoothCorner() { + for( unsigned i = 0; i < 3; ++i ) { + for( unsigned j = 0; j < 4; ++j ) { + g[i][j] = 0; + } + } + } + + double g[3][8]; // 3 colors, 8 parameters: see enum. + Geom::Point p; // Location of point +}; + +// Find slope at point 1 given values at previous and next points +// Return value is slope in user space +double find_slope1( const double &p0, const double &p1, const double &p2, + const double &d01, const double &d12 ) { + + double slope = 0; + + if( d01 > 0 && d12 > 0 ) { + slope = 0.5 * ( (p1 - p0)/d01 + (p2 - p1)/d12 ); + + if( ( p0 > p1 && p1 < p2 ) || + ( p0 < p1 && p1 > p2 ) ) { + // At minimum or maximum, use slope of zero + slope = 0; + } else { + // Ensure we don't overshoot + if( fabs(slope) > fabs(3*(p1-p0)/d01) ) { + slope = 3*(p1-p0)/d01; + } + if( fabs(slope) > fabs(3*(p2-p1)/d12) ) { + slope = 3*(p2-p1)/d12; + } + } + } else { + // Do something clever + } + return slope; +}; + + +/* +// Find slope at point 0 given values at previous and next points +// TO DO: TAKE DISTANCE BETWEEN POINTS INTO ACCOUNT +double find_slope2( double pmm, double ppm, double pmp, double ppp, double p0 ) { + + // pmm == d[i-1][j-1], ... 'm' is minus, 'p' is plus + double slope = (ppp - ppm - pmp + pmm)/2.0; + if( (ppp > p0 && ppm > p0 && pmp > p0 && pmm > 0) || + (ppp < p0 && ppm < p0 && pmp < p0 && pmm < 0) ) { + // At minimum or maximum, use slope of zero + slope = 0; + } else { + // Don't really know what to do here + if( fabs(slope) > fabs(3*(ppp-p0)) ) { + slope = 3*(ppp-p0); + } + if( fabs(slope) > fabs(3*(pmp-p0)) ) { + slope = 3*(pmp-p0); + } + if( fabs(slope) > fabs(3*(ppm-p0)) ) { + slope = 3*(ppm-p0); + } + if( fabs(slope) > fabs(3*(pmm-p0)) ) { + slope = 3*(pmm-p0); + } + } + return slope; +} +*/ + +// https://en.wikipedia.org/wiki/Bicubic_interpolation +void invert( const double v[16], double alpha[16] ) { + + const double A[16][16] = { + + { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + {-3, 3, 0, 0, -2,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 2,-2, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, -3, 3, 0, 0, -2,-1, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 2,-2, 0, 0, 1, 1, 0, 0 }, + {-3, 0, 3, 0, 0, 0, 0, 0, -2, 0,-1, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, -3, 0, 3, 0, 0, 0, 0, 0, -2, 0,-1, 0 }, + { 9,-9,-9, 9, 6, 3,-6,-3, 6,-6, 3,-3, 4, 2, 2, 1 }, + {-6, 6, 6,-6, -3,-3, 3, 3, -4, 4,-2, 2, -2,-2,-1,-1 }, + { 2, 0,-2, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 2, 0,-2, 0, 0, 0, 0, 0, 1, 0, 1, 0 }, + {-6, 6, 6,-6, -4,-2, 4, 2, -3, 3,-3, 3, -2,-1,-2,-1 }, + { 4,-4,-4, 4, 2, 2,-2,-2, 2,-2, 2,-2, 1, 1, 1, 1 } + }; + + for( unsigned i = 0; i < 16; ++i ) { + alpha[i] = 0; + for( unsigned j = 0; j < 16; ++j ) { + alpha[i] += A[i][j]*v[j]; + } + } +} + +double sum( const double alpha[16], const double& x, const double& y ) { + + double result = 0; + + double xx = x*x; + double xxx = xx * x; + double yy = y*y; + double yyy = yy * y; + + result += alpha[ 0 ]; + result += alpha[ 1 ] * x; + result += alpha[ 2 ] * xx; + result += alpha[ 3 ] * xxx; + result += alpha[ 4 ] * y; + result += alpha[ 5 ] * y * x; + result += alpha[ 6 ] * y * xx; + result += alpha[ 7 ] * y * xxx; + result += alpha[ 8 ] * yy; + result += alpha[ 9 ] * yy * x; + result += alpha[ 10 ] * yy * xx; + result += alpha[ 11 ] * yy * xxx; + result += alpha[ 12 ] * yyy; + result += alpha[ 13 ] * yyy * x; + result += alpha[ 14 ] * yyy * xx; + result += alpha[ 15 ] * yyy * xxx; + + return result; +} + +/** + Fill 'smooth' with a smoothed version of the array by subdividing each patch into smaller patches. +*/ +void SPMeshNodeArray::bicubic( SPMeshNodeArray* smooth, SPMeshType type ) { + + + *smooth = *this; // Deep copy via copy assignment constructor, smooth cleared before copy + // std::cout << "SPMeshNodeArray::smooth2(): " << this->patch_rows() << " " << smooth->patch_columns() << std::endl; + // std::cout << " " << smooth << " " << this << std::endl; + + // Find derivatives at corners + + // Create array of corner points + std::vector< std::vector > d; + d.resize( smooth->patch_rows() + 1 ); + for( unsigned i = 0; i < d.size(); ++i ) { + d[i].resize( smooth->patch_columns() + 1 ); + for( unsigned j = 0; j < d[i].size(); ++j ) { + float rgb_color[3]; + sp_color_get_rgb_floatv( &this->nodes[ i*3 ][ j*3 ]->color, rgb_color ); + d[i][j].g[0][0] = rgb_color[ 0 ]; + d[i][j].g[1][0] = rgb_color[ 1 ]; + d[i][j].g[2][0] = rgb_color[ 2 ]; + d[i][j].p = this->nodes[ i*3 ][ j*3 ]->p; + } + } + + // Calculate interior derivatives + for( unsigned i = 0; i < d.size(); ++i ) { + for( unsigned j = 0; j < d[i].size(); ++j ) { + for( unsigned k = 0; k < 3; ++k ) { // Loop over colors + + // dx + + if( i != 0 && i != d.size()-1 ) { + double lm = Geom::distance( d[i-1][j].p, d[i][j].p ); + double lp = Geom::distance( d[i+1][j].p, d[i][j].p ); + d[i][j].g[k][1] = find_slope1( d[i-1][j].g[k][0], d[i][j].g[k][0], d[i+1][j].g[k][0], lm, lp ); + } + + // dy + if( j != 0 && j != d[i].size()-1 ) { + double lm = Geom::distance( d[i][j-1].p, d[i][j].p ); + double lp = Geom::distance( d[i][j+1].p, d[i][j].p ); + d[i][j].g[k][2] = find_slope1( d[i][j-1].g[k][0], d[i][j].g[k][0], d[i][j+1].g[k][0], lm, lp ); + } + + // dxdy if needed, need to take lengths into account + // if( i != 0 && i != d.size()-1 && j != 0 && j != d[i].size()-1 ) { + // d[i][j].g[k][3] = find_slope2( d[i-1][j-1].g[k][0], d[i+1][j-1].g[k][0], + // d[i-1][j+1].g[k][0], d[i-1][j-1].g[k][0], + // d[i][j].g[k][0] ); + // } + + } + } + } + + // Calculate exterior derivatives + // We need to do this after calculating interior derivatives as we need to already + // have the non-exterior derivative calculated for finding the parabola. + for( unsigned j = 0; j< d[0].size(); ++j ) { + for( unsigned k = 0; k < 3; ++k ) { // Loop over colors + + // Parabolic + double d0 = Geom::distance( d[1][j].p, d[0 ][j].p ); + if( d0 > 0 ) { + d[0][j].g[k][1] = 2.0*(d[1][j].g[k][0] - d[0 ][j].g[k][0])/d0 - d[1][j].g[k][1]; + } else { + d[0][j].g[k][1] = 0; + } + + unsigned z = d.size()-1; + double dz = Geom::distance( d[z][j].p, d[z-1][j].p ); + if( dz > 0 ) { + d[z][j].g[k][1] = 2.0*(d[z][j].g[k][0] - d[z-1][j].g[k][0])/dz - d[z-1][j].g[k][1]; + } else { + d[z][j].g[k][1] = 0; + } + } + } + + for( unsigned i = 0; i< d.size(); ++i ) { + for( unsigned k = 0; k < 3; ++k ) { // Loop over colors + + // Parabolic + double d0 = Geom::distance( d[i][1].p, d[i][0 ].p ); + if( d0 > 0 ) { + d[i][0].g[k][2] = 2.0*(d[i][1].g[k][0] - d[i][0 ].g[k][0])/d0 - d[i][1].g[k][2]; + } else { + d[i][0].g[k][2] = 0; + } + + unsigned z = d[0].size()-1; + double dz = Geom::distance( d[i][z].p, d[i][z-1].p ); + if( dz > 0 ) { + d[i][z].g[k][2] = 2.0*(d[i][z].g[k][0] - d[i][z-1].g[k][0])/dz - d[i][z-1].g[k][2]; + } else { + d[i][z].g[k][2] = 0; + } + } + } + + // Leave outside corner cross-derivatives at zero. + + // Next split each patch into 8x8 smaller patches. + + // Split each row into eight rows. + // Must do it from end so inserted rows don't mess up indexing + for( int i = smooth->patch_rows() - 1; i >= 0; --i ) { + smooth->split_row( i, unsigned(8) ); + } + + // Split each column into eight columns. + // Must do it from end so inserted columns don't mess up indexing + for( int i = smooth->patch_columns() - 1; i >= 0; --i ) { + smooth->split_column( i, (unsigned)8 ); + } + + // Fill new patches + for( unsigned i = 0; i < this->patch_rows(); ++i ) { + for( unsigned j = 0; j < this->patch_columns(); ++j ) { + + double dx0 = Geom::distance( d[i ][j ].p, d[i+1][j ].p ); + double dx1 = Geom::distance( d[i ][j+1].p, d[i+1][j+1].p ); + double dy0 = Geom::distance( d[i ][j ].p, d[i ][j+1].p ); + double dy1 = Geom::distance( d[i+1][j ].p, d[i+1][j+1].p ); + + // Temp loop over 0..8 to get last column/row edges + float r[3][9][9]; // result + for( unsigned m = 0; m < 3; ++m ) { + + double v[16]; + v[ 0] = d[i ][j ].g[m][0]; + v[ 1] = d[i+1][j ].g[m][0]; + v[ 2] = d[i ][j+1].g[m][0]; + v[ 3] = d[i+1][j+1].g[m][0]; + v[ 4] = d[i ][j ].g[m][1]*dx0; + v[ 5] = d[i+1][j ].g[m][1]*dx0; + v[ 6] = d[i ][j+1].g[m][1]*dx1; + v[ 7] = d[i+1][j+1].g[m][1]*dx1; + v[ 8] = d[i ][j ].g[m][2]*dy0; + v[ 9] = d[i+1][j ].g[m][2]*dy1; + v[10] = d[i ][j+1].g[m][2]*dy0; + v[11] = d[i+1][j+1].g[m][2]*dy1; + v[12] = d[i ][j ].g[m][3]; + v[13] = d[i+1][j ].g[m][3]; + v[14] = d[i ][j+1].g[m][3]; + v[15] = d[i+1][j+1].g[m][3]; + + double alpha[16]; + invert( v, alpha ); + + for( unsigned k = 0; k < 9; ++k ) { + for( unsigned l = 0; l < 9; ++l ) { + double x = k/8.0; + double y = l/8.0; + r[m][k][l] = sum( alpha, x, y ); + // Clamp to allowed values + if( r[m][k][l] > 1.0 ) + r[m][k][l] = 1.0; + if( r[m][k][l] < 0.0 ) + r[m][k][l] = 0.0; + } + } + + } // Loop over colors + + for( unsigned k = 0; k < 9; ++k ) { + for( unsigned l = 0; l < 9; ++l ) { + // Every third node is a corner node + smooth->nodes[ (i*8+k)*3 ][(j*8+l)*3 ]->color.set( r[0][k][l], r[1][k][l], r[2][k][l] ); + } + } + } + } +} + +/** + Number of patch rows. +*/ +guint SPMeshNodeArray::patch_rows() { + + return nodes.size()/3; +} + +/** + Number of patch columns. +*/ +guint SPMeshNodeArray::patch_columns() { + if (nodes.empty()) { + return 0; + } + return nodes[0].size()/3; +} + +/** + Inputs: + i, j: Corner draggable indices. + Returns: + true if corners adjacent. + n[] is array of nodes in top/bottom or left/right order. +*/ +bool SPMeshNodeArray::adjacent_corners( guint i, guint j, SPMeshNode* n[4] ) { + + // This works as all corners have indices and they + // are numbered in order by row and column (and + // the node array is rectangular). + + bool adjacent = false; + + guint c1 = i; + guint c2 = j; + if( j < i ) { + c1 = j; + c2 = i; + } + + // Number of corners in a row of patches. + guint ncorners = patch_columns() + 1; + + guint crow1 = c1 / ncorners; + guint crow2 = c2 / ncorners; + guint ccol1 = c1 % ncorners; + guint ccol2 = c2 % ncorners; + + guint nrow = crow1 * 3; + guint ncol = ccol1 * 3; + + // std::cout << " i: " << i + // << " j: " << j + // << " ncorners: " << ncorners + // << " c1: " << c1 + // << " crow1: " << crow1 + // << " ccol1: " << ccol1 + // << " c2: " << c2 + // << " crow2: " << crow2 + // << " ccol2: " << ccol2 + // << " nrow: " << nrow + // << " ncol: " << ncol + // << std::endl; + + // Check for horizontal neighbors + if ( crow1 == crow2 && (ccol2 - ccol1) == 1 ) { + adjacent = true; + for( guint k = 0; k < 4; ++k ) { + n[k] = nodes[nrow][ncol+k]; + } + } + + // Check for vertical neighbors + if ( ccol1 == ccol2 && (crow2 - crow1) == 1 ) { + adjacent = true; + for( guint k = 0; k < 4; ++k ) { + n[k] = nodes[nrow+k][ncol]; + } + } + + return adjacent; +} + +/** + Toggle sides between lineto and curve to if both corners selected. + Input is a list of selected corner draggable indices. +*/ +guint SPMeshNodeArray::side_toggle( std::vector corners ) { + + guint toggled = 0; + + if( corners.size() < 2 ) return 0; + + for( guint i = 0; i < corners.size()-1; ++i ) { + for( guint j = i+1; j < corners.size(); ++j ) { + + SPMeshNode* n[4]; + if( adjacent_corners( corners[i], corners[j], n ) ) { + + gchar path_type = n[1]->path_type; + switch (path_type) + { + case 'L': + n[1]->path_type = 'C'; + n[2]->path_type = 'C'; + n[1]->set = true; + n[2]->set = true; + break; + + case 'l': + n[1]->path_type = 'c'; + n[2]->path_type = 'c'; + n[1]->set = true; + n[2]->set = true; + break; + + case 'C': { + n[1]->path_type = 'L'; + n[2]->path_type = 'L'; + n[1]->set = false; + n[2]->set = false; + // 'L' acts as if handles are 1/3 of path length from corners. + Geom::Point dp = (n[3]->p - n[0]->p)/3.0; + n[1]->p = n[0]->p + dp; + n[2]->p = n[3]->p - dp; + break; + } + case 'c': { + n[1]->path_type = 'l'; + n[2]->path_type = 'l'; + n[1]->set = false; + n[2]->set = false; + // 'l' acts as if handles are 1/3 of path length from corners. + Geom::Point dp = (n[3]->p - n[0]->p)/3.0; + n[1]->p = n[0]->p + dp; + n[2]->p = n[3]->p - dp; + // std::cout << "Toggle sides: " + // << n[0]->p << " " + // << n[1]->p << " " + // << n[2]->p << " " + // << n[3]->p << " " + // << dp << std::endl; + break; + } + default: + std::cout << "Toggle sides: Invalid path type: " << path_type << std::endl; + } + ++toggled; + } + } + } + if( toggled > 0 ) built = false; + return toggled; +} + +/** + * Converts generic Beziers to Beziers approximating elliptical arcs, preserving handle direction. + * There are infinite possible solutions. The solution chosen here is to generate a section of an + * ellipse that is centered on the intersection of the two lines passing through the two nodes but + * parallel to the other node's handle direction. This is the section of an ellipse that + * corresponds to a quarter of a circle squished and then skewed. + */ +guint SPMeshNodeArray::side_arc( std::vector corners ) { + + if( corners.size() < 2 ) return 0; + + guint arced = 0; + for( guint i = 0; i < corners.size()-1; ++i ) { + for( guint j = i+1; j < corners.size(); ++j ) { + + SPMeshNode* n[4]; + if( adjacent_corners( corners[i], corners[j], n ) ) { + + gchar path_type = n[1]->path_type; + switch (path_type) + { + case 'L': + case 'l': + std::cerr << "SPMeshNodeArray::side_arc: Can't convert straight lines to arcs." << std::endl; + break; + + case 'C': + case 'c': { + + Geom::Ray ray1( n[0]->p, n[1]->p ); + Geom::Ray ray2( n[3]->p, n[2]->p ); + if( !are_parallel( (Geom::Line)ray1, (Geom::Line)ray2 ) ) { + + Geom::OptCrossing crossing = intersection( ray1, ray2 ); + + if( crossing ) { + + Geom::Point intersection = ray1.pointAt( (*crossing).ta ); + + const double f = 4.0/3.0 * tan( M_PI/2.0/4.0 ); + + Geom::Point h1 = intersection - n[0]->p; + Geom::Point h2 = intersection - n[3]->p; + + n[1]->p = n[0]->p + f*h1; + n[2]->p = n[3]->p + f*h2; + ++arced; + + } else { + std::cerr << "SPMeshNodeArray::side_arc: No crossing, can't turn into arc." << std::endl; + } + } else { + std::cerr << "SPMeshNodeArray::side_arc: Handles parallel, can't turn into arc." << std::endl; + } + break; + } + default: + std::cerr << "SPMeshNodeArray::side_arc: Invalid path type: " << n[1]->path_type << std::endl; + } + } + } + } + if( arced > 0 ) built = false; + return arced; +} + +/** + Toggle sides between lineto and curve to if both corners selected. + Input is a list of selected corner draggable indices. +*/ +guint SPMeshNodeArray::tensor_toggle( std::vector corners ) { + + // std::cout << "SPMeshNodeArray::tensor_toggle" << std::endl; + + if( corners.size() < 4 ) return 0; + + guint toggled = 0; + + // Number of corners in a row of patches. + guint ncorners = patch_columns() + 1; + + for( guint i = 0; i < corners.size()-3; ++i ) { + for( guint j = i+1; j < corners.size()-2; ++j ) { + for( guint k = j+1; k < corners.size()-1; ++k ) { + for( guint l = k+1; l < corners.size(); ++l ) { + + guint c[4]; + c[0] = corners[i]; + c[1] = corners[j]; + c[2] = corners[k]; + c[3] = corners[l]; + std::sort( c, c+4 ); + + // Check we have four corners of one patch selected + if( c[1]-c[0] == 1 && + c[3]-c[2] == 1 && + c[2]-c[0] == ncorners && + c[3]-c[1] == ncorners && + c[0] % ncorners < ncorners - 1 ) { + + // Patch + guint prow = c[0] / ncorners; + guint pcol = c[0] % ncorners; + + // Upper left node of patch + guint irow = prow * 3; + guint jcol = pcol * 3; + + // std::cout << "tensor::toggle: " + // << c[0] << ", " + // << c[1] << ", " + // << c[2] << ", " + // << c[3] << std::endl; + + // std::cout << "tensor::toggle: " + // << " irow: " << irow + // << " jcol: " << jcol + // << " prow: " << prow + // << " pcol: " << pcol + // << std::endl; + + SPMeshPatchI patch( &nodes, prow, pcol ); + patch.updateNodes(); + + if( patch.tensorIsSet() ) { + // Unset tensor points + nodes[irow+1][jcol+1]->set = false; + nodes[irow+1][jcol+2]->set = false; + nodes[irow+2][jcol+1]->set = false; + nodes[irow+2][jcol+2]->set = false; + } else { + // Set tensor points + nodes[irow+1][jcol+1]->set = true; + nodes[irow+1][jcol+2]->set = true; + nodes[irow+2][jcol+1]->set = true; + nodes[irow+2][jcol+2]->set = true; + } + + ++toggled; + } + } + } + } + } + if( toggled > 0 ) built = false; + return toggled; +} + +/** + Atempts to smooth color transitions across corners. + Input is a list of selected corner draggable indices. +*/ +guint SPMeshNodeArray::color_smooth( std::vector corners ) { + + // std::cout << "SPMeshNodeArray::color_smooth" << std::endl; + + guint smoothed = 0; + + // Number of corners in a row of patches. + guint ncorners = patch_columns() + 1; + + // Number of node rows and columns + guint ncols = patch_columns() * 3 + 1; + guint nrows = patch_rows() * 3 + 1; + + for( guint i = 0; i < corners.size(); ++i ) { + + guint corner = corners[i]; + // std::cout << "SPMeshNodeArray::color_smooth: " << i << " " << corner << std::endl; + + // Node row & col + guint nrow = (corner / ncorners) * 3; + guint ncol = (corner % ncorners) * 3; + + SPMeshNode* n[7]; + for( guint s = 0; s < 2; ++s ) { + + bool smooth = false; + + // Find neighboring nodes + if( s == 0 ) { + + // Horizontal + if( ncol > 2 && ncol+3 < ncols) { + for( guint j = 0; j < 7; ++j ) { + n[j] = nodes[ nrow ][ ncol - 3 + j ]; + } + smooth = true; + } + + } else { + + // Vertical + if( nrow > 2 && nrow+3 < nrows) { + for( guint j = 0; j < 7; ++j ) { + n[j] = nodes[ nrow - 3 + j ][ ncol ]; + } + smooth = true; + } + } + + if( smooth ) { + + // Let the smoothing begin + // std::cout << " checking: " << ncol << " " << nrow << std::endl; + + // Get initial slopes using closest handles. + double slope[2][3]; + double slope_ave[3]; + double slope_diff[3]; + + // Color of corners + SPColor color0 = n[0]->color; + SPColor color3 = n[3]->color; + SPColor color6 = n[6]->color; + + // Distance nodes from selected corner + Geom::Point d[7]; + for( guint k = 0; k < 7; ++k ) { + d[k]= n[k]->p - n[3]->p; + // std::cout << " d[" << k << "]: " << d[k].length() << std::endl; + } + + double sdm = -1.0; // Slope Diff Max + guint cdm = 0; // Color Diff Max (Which color has the maximum difference in slopes) + for( guint c = 0; c < 3; ++c ) { + if( d[2].length() != 0.0 ) { + slope[0][c] = (color3.v.c[c] - color0.v.c[c]) / d[2].length(); + } + if( d[4].length() != 0.0 ) { + slope[1][c] = (color6.v.c[c] - color3.v.c[c]) / d[4].length(); + } + slope_ave[c] = (slope[0][c]+slope[1][c]) / 2.0; + slope_diff[c] = (slope[0][c]-slope[1][c]); + // std::cout << " color: " << c << " :" + // << color0.v.c[c] << " " + // << color3.v.c[c] << " " + // << color6.v.c[c] + // << " slope: " + // << slope[0][c] << " " + // << slope[1][c] + // << " slope_ave: " << slope_ave[c] + // << " slope_diff: " << slope_diff[c] + // << std::endl; + + // Find color with maximum difference + if( std::abs( slope_diff[c] ) > sdm ) { + sdm = std::abs( slope_diff[c] ); + cdm = c; + } + } + // std::cout << " cdm: " << cdm << std::endl; + + // Find new handle positions: + double length_left = d[0].length(); + double length_right = d[6].length(); + if( slope_ave[ cdm ] != 0.0 ) { + length_left = std::abs( (color3.v.c[cdm] - color0.v.c[cdm]) / slope_ave[ cdm ] ); + length_right = std::abs( (color6.v.c[cdm] - color3.v.c[cdm]) / slope_ave[ cdm ] ); + } + + // Move closest handle a maximum of mid point... but don't shorten + double max = 0.8; + if( length_left > max * d[0].length() && length_left > d[2].length() ) { + std::cout << " Can't smooth left side" << std::endl; + length_left = std::max( max * d[0].length(), d[2].length() ); + } + if( length_right > max * d[6].length() && length_right > d[4].length() ) { + std::cout << " Can't smooth right side" << std::endl; + length_right = std::max( max * d[6].length(), d[4].length() ); + } + + if( d[2].length() != 0.0 ) d[2] *= length_left/d[2].length(); + if( d[4].length() != 0.0 ) d[4] *= length_right/d[4].length(); + + // std::cout << " length_left: " << length_left + // << " d[0]: " << d[0].length() + // << " length_right: " << length_right + // << " d[6]: " << d[6].length() + // << std::endl; + + n[2]->p = n[3]->p + d[2]; + n[4]->p = n[3]->p + d[4]; + + ++smoothed; + } + } + + } + + if( smoothed > 0 ) built = false; + return smoothed; +} + +/** + Pick color from background for selected corners. +*/ +guint SPMeshNodeArray::color_pick( std::vector icorners, SPItem* item ) { + + // std::cout << "SPMeshNodeArray::color_pick" << std::endl; + + guint picked = 0; + + // Code inspired from clone tracing + + // Setup... + + // We need a copy of the drawing so we can hide the mesh. + Inkscape::Drawing *pick_drawing = new Inkscape::Drawing(); + unsigned pick_visionkey = SPItem::display_key_new(1); + + SPDocument *pick_doc = mg->document; + + pick_drawing->setRoot(pick_doc->getRoot()->invoke_show(*pick_drawing, pick_visionkey, SP_ITEM_SHOW_DISPLAY)); + + item->invoke_hide(pick_visionkey); + + pick_doc->getRoot()->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + pick_doc->ensureUpToDate(); + + //gdouble pick_zoom = 1.0; // zoom; + //pick_drawing->root()->setTransform(Geom::Scale(pick_zoom)); + pick_drawing->update(); + + // std::cout << " transform: " << std::endl; + // std::cout << item->transform << std::endl; + // std::cout << " i2doc: " << std::endl; + // std::cout << item->i2doc_affine() << std::endl; + // std::cout << " i2dt: " << std::endl; + // std::cout << item->i2dt_affine() << std::endl; + // std::cout << " dt2i: " << std::endl; + // std::cout << item->dt2i_affine() << std::endl; + SPGradient* gr = SP_GRADIENT( mg ); + // if( gr->gradientTransform_set ) { + // std::cout << " gradient transform set: " << std::endl; + // std::cout << gr->gradientTransform << std::endl; + // } else { + // std::cout << " gradient transform not set! " << std::endl; + // } + + // Do picking + for( guint i = 0; i < icorners.size(); ++i ) { + + guint corner = icorners[i]; + + SPMeshNode* n = corners[ corner ]; + + // Region to average over + Geom::Point p = n->p; + // std::cout << " before transform: p: " << p << std::endl; + p *= gr->gradientTransform; + // std::cout << " after transform: p: " << p << std::endl; + p *= item->i2doc_affine(); + // std::cout << " after transform: p: " << p << std::endl; + + // If on edge, move inward + guint cols = patch_columns()+1; + guint rows = patch_rows()+1; + guint col = corner % cols; + guint row = corner / cols; + guint ncol = col * 3; + guint nrow = row * 3; + + const double size = 3.0; + + // Top edge + if( row == 0 ) { + Geom::Point dp = nodes[nrow+1][ncol]->p - p; + p += unit_vector( dp ) * size; + } + // Right edge + if( col == cols-1 ) { + Geom::Point dp = nodes[nrow][ncol-1]->p - p; + p += unit_vector( dp ) * size; + } + // Bottom edge + if( row == rows-1 ) { + Geom::Point dp = nodes[nrow-1][ncol]->p - p; + p += unit_vector( dp ) * size; + } + // Left edge + if( col == 0 ) { + Geom::Point dp = nodes[nrow][ncol+1]->p - p; + p += unit_vector( dp ) * size; + } + + Geom::Rect box( p[Geom::X]-size/2.0, p[Geom::Y]-size/2.0, + p[Geom::X]+size/2.0, p[Geom::Y]+size/2.0 ); + + /* Item integer bbox in points */ + Geom::IntRect ibox = box.roundOutwards(); + + /* Find visible area */ + cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, ibox.width(), ibox.height()); + Inkscape::DrawingContext dc(s, ibox.min()); + + /* Render copy and pick color */ + pick_drawing->render(dc, ibox); + double R = 0, G = 0, B = 0, A = 0; + ink_cairo_surface_average_color(s, R, G, B, A); + cairo_surface_destroy(s); + + // std::cout << " p: " << p + // << " box: " << ibox + // << " R: " << R + // << " G: " << G + // << " B: " << B + // << std::endl; + n->color.set( R, G, B ); + } + + pick_doc->getRoot()->invoke_hide(pick_visionkey); + delete pick_drawing; + + picked = 1; // Picking always happens + if( picked > 0 ) built = false; + return picked; +} + +/** + Splits selected rows and/or columns in half (according to the path 't' parameter). + Input is a list of selected corner draggable indices. +*/ +guint SPMeshNodeArray::insert( std::vector corners ) { + + guint inserted = 0; + + if( corners.size() < 2 ) return 0; + + std::set columns; + std::set rows; + + for( guint i = 0; i < corners.size()-1; ++i ) { + for( guint j = i+1; j < corners.size(); ++j ) { + + // This works as all corners have indices and they + // are numbered in order by row and column (and + // the node array is rectangular). + + guint c1 = corners[i]; + guint c2 = corners[j]; + if (c2 < c1) { + c1 = corners[j]; + c2 = corners[i]; + } + + // Number of corners in a row of patches. + guint ncorners = patch_columns() + 1; + + guint crow1 = c1 / ncorners; + guint crow2 = c2 / ncorners; + guint ccol1 = c1 % ncorners; + guint ccol2 = c2 % ncorners; + + // Check for horizontal neighbors + if ( crow1 == crow2 && (ccol2 - ccol1) == 1 ) { + columns.insert( ccol1 ); + } + + // Check for vertical neighbors + if ( ccol1 == ccol2 && (crow2 - crow1) == 1 ) { + rows.insert( crow1 ); + } + } + } + + // Iterate backwards so column/row numbers are not invalidated. + std::set::reverse_iterator rit; + for (rit=columns.rbegin(); rit != columns.rend(); ++rit) { + split_column( *rit, 0.5); + ++inserted; + } + for (rit=rows.rbegin(); rit != rows.rend(); ++rit) { + split_row( *rit, 0.5); + ++inserted; + } + + if( inserted > 0 ) built = false; + return inserted; +} + +/** + Moves handles in response to a corner node move. + p_old: original position of moved corner node. + corner: the corner node moved (draggable index, i.e. point_i). + selected: list of all corners selected (draggable indices). + op: how other corners should be moved. + Corner node must already have been moved! +*/ +void SPMeshNodeArray::update_handles( guint corner, std::vector< guint > /*selected*/, Geom::Point p_old, MeshNodeOperation /*op*/ ) +{ + if (!draggers_valid) { + std::cerr << "SPMeshNodeArray::update_handles: Draggers not valid!" << std::endl; + return; + } + // assert( draggers_valid ); + + // std::cout << "SPMeshNodeArray::update_handles: " + // << " corner: " << corner + // << " op: " << op + // << std::endl; + + // Find number of patch rows and columns + guint mrow = patch_rows(); + guint mcol = patch_columns(); + + // Number of corners in a row of patches. + guint ncorners = mcol + 1; + + // Find corner row/column + guint crow = corner / ncorners; + guint ccol = corner % ncorners; + + // Find node row/column + guint nrow = crow * 3; + guint ncol = ccol * 3; + + // std::cout << " mrow: " << mrow + // << " mcol: " << mcol + // << " crow: " << crow + // << " ccol: " << ccol + // << " ncorners: " << ncorners + // << " nrow: " << nrow + // << " ncol: " << ncol + // << std::endl; + + // New corner mesh coordinate. + Geom::Point p_new = nodes[nrow][ncol]->p; + + // Corner point move dpg in mesh coordinate system. + Geom::Point dp = p_new - p_old; + + // std::cout << " p_old: " << p_old << std::endl; + // std::cout << " p_new: " << p_new << std::endl; + // std::cout << " dp: " << dp << std::endl; + + // STEP 1: ONLY DO DIRECT MOVE + bool patch[4]; + patch[0] = patch[1] = patch[2] = patch[3] = false; + if( ccol > 0 && crow > 0 ) patch[0] = true; + if( ccol < mcol && crow > 0 ) patch[1] = true; + if( ccol < mcol && crow < mrow ) patch[2] = true; + if( ccol > 0 && crow < mrow ) patch[3] = true; + + // std::cout << patch[0] << " " + // << patch[1] << " " + // << patch[2] << " " + // << patch[3] << std::endl; + + // Move handles + if( patch[0] || patch[1] ) { + if( nodes[nrow-1][ncol]->path_type == 'l' || + nodes[nrow-1][ncol]->path_type == 'L' ) { + Geom::Point s = (nodes[nrow-3][ncol]->p - nodes[nrow][ncol]->p)/3.0; + nodes[nrow-1][ncol ]->p = nodes[nrow][ncol]->p + s; + nodes[nrow-2][ncol ]->p = nodes[nrow-3][ncol]->p - s; + } else { + nodes[nrow-1][ncol ]->p += dp; + } + } + + if( patch[1] || patch[2] ) { + if( nodes[nrow ][ncol+1]->path_type == 'l' || + nodes[nrow ][ncol+1]->path_type == 'L' ) { + Geom::Point s = (nodes[nrow][ncol+3]->p - nodes[nrow][ncol]->p)/3.0; + nodes[nrow ][ncol+1]->p = nodes[nrow][ncol]->p + s; + nodes[nrow ][ncol+2]->p = nodes[nrow][ncol+3]->p - s; + } else { + nodes[nrow ][ncol+1]->p += dp; + } + } + + if( patch[2] || patch[3] ) { + if( nodes[nrow+1][ncol ]->path_type == 'l' || + nodes[nrow+1][ncol ]->path_type == 'L' ) { + Geom::Point s = (nodes[nrow+3][ncol]->p - nodes[nrow][ncol]->p)/3.0; + nodes[nrow+1][ncol ]->p = nodes[nrow][ncol]->p + s; + nodes[nrow+2][ncol ]->p = nodes[nrow+3][ncol]->p - s; + } else { + nodes[nrow+1][ncol ]->p += dp; + } + } + + if( patch[3] || patch[0] ) { + if( nodes[nrow ][ncol-1]->path_type == 'l' || + nodes[nrow ][ncol-1]->path_type == 'L' ) { + Geom::Point s = (nodes[nrow][ncol-3]->p - nodes[nrow][ncol]->p)/3.0; + nodes[nrow ][ncol-1]->p = nodes[nrow][ncol]->p + s; + nodes[nrow ][ncol-2]->p = nodes[nrow][ncol-3]->p - s; + } else { + nodes[nrow ][ncol-1]->p += dp; + } + } + + + // Move tensors + if( patch[0] ) nodes[nrow-1][ncol-1]->p += dp; + if( patch[1] ) nodes[nrow-1][ncol+1]->p += dp; + if( patch[2] ) nodes[nrow+1][ncol+1]->p += dp; + if( patch[3] ) nodes[nrow+1][ncol-1]->p += dp; + + // // Check if neighboring corners are selected. + + // bool do_scale = false; + + // bool do_scale_xp = do_scale; + // bool do_scale_xn = do_scale; + // bool do_scale_yp = do_scale; + // bool do_scale_yn = do_scale; + + // if( ccol < mcol+1 ) { + // if( std::find( sc.begin(), sc.end(), point_i + 1 ) != sc.end() ) { + // do_scale_xp = false; + // std::cout << " Not scaling x+" << std::endl; + // } + // } + + // if( ccol > 0 ) { + // if( std::find( sc.begin(), sc.end(), point_i - 1 ) != sc.end() ) { + // do_scale_xn = false; + // std::cout << " Not scaling x-" << std::endl; + // } + // } + + // if( crow < mrow+1 ) { + // if( std::find( sc.begin(), sc.end(), point_i + ncorners ) != sc.end() ) { + // do_scale_yp = false; + // std::cout << " Not scaling y+" << std::endl; + // } + // } + + // if( crow > 0 ) { + // if( std::find( sc.begin(), sc.end(), point_i - ncorners ) != sc.end() ) { + // do_scale_yn = false; + // std::cout << " Not scaling y-" << std::endl; + // } + // } + + // // We have four patches to adjust... + // for ( guint k = 0; k < 4; ++k ) { + + // bool do_scale_x = do_scale; + // bool do_scale_y = do_scale; + + // SPMeshNode* pnodes[4][4]; + + // // Load up matrix + // switch (k) { + + // case 0: + // if( crow < mrow+1 && ccol < mcol+1 ) { + // // Bottom right patch + + // do_scale_x = do_scale_xp; + // do_scale_y = do_scale_yp; + + // for( guint i = 0; i < 4; ++i ) { + // for( guint j = 0; j< 4; ++j ) { + // pnodes[i][j] = mg->array.nodes[nrow+i][nrow+j]; + // } + // } + // } + // break; + + // case 1: + // if( crow < mrow+1 && ccol > 0 ) { + // // Bottom left patch (note x, y swapped) + + // do_scale_y = do_scale_xn; + // do_scale_x = do_scale_yp; + + // for( guint i = 0; i < 4; ++i ) { + // for( guint j = 0; j< 4; ++j ) { + // pnodes[j][i] = mg->array.nodes[nrow+i][nrow-j]; + // } + // } + // } + // break; + + // case 2: + // if( crow > 0 && ccol > 0 ) { + // // Top left patch + + // do_scale_x = do_scale_xn; + // do_scale_y = do_scale_yn; + + // for( guint i = 0; i < 4; ++i ) { + // for( guint j = 0; j< 4; ++j ) { + // pnodes[i][j] = mg->array.nodes[nrow-i][nrow-j]; + // } + // } + // } + // break; + + // case 3: + // if( crow > 0 && ccol < mcol+1 ) { + // // Top right patch (note x, y swapped) + + // do_scale_y = do_scale_xp; + // do_scale_x = do_scale_yn; + + // for( guint i = 0; i < 4; ++i ) { + // for( guint j = 0; j< 4; ++j ) { + // pnodes[j][i] = mg->array.nodes[nrow-i][nrow+j]; + // } + // } + // } + // break; + // } + + // // Now we must move points in both x and y. + // // There are upto six points to move: P01, P02, P11, P12, P21, P22. + // // (The points P10, P20 will be moved in another branch of the loop. + // // The points P03, P13, P23, P33, P32, P31, P30 are not moved.) + // // + // // P00 P01 P02 P03 + // // P10 P11 P12 P13 + // // P20 P21 P22 P23 + // // P30 P31 P32 P33 + // // + // // The goal is to preserve the direction of the handle! + + + // Geom::Point dsx_new = pnodes[0][3]->p - pnodes[0][0]->p; // New side x + // Geom::Point dsy_new = pnodes[3][0]->p - pnodes[0][0]->p; // New side y + // Geom::Point dsx_old = pnodes[0][3]->p - pcg_old; // Old side x + // Geom::Point dsy_old = pnodes[3][0]->p - pcg_old; // Old side y + + + // double scale_factor_x = 1.0; + // if( dsx_old.length() != 0.0 ) scale_factor_x = dsx_new.length()/dsx_old.length(); + + // double scale_factor_y = 1.0; + // if( dsy_old.length() != 0.0 ) scale_factor_y = dsy_new.length()/dsy_old.length(); + + + // if( do_scalex && do_scaley ) { + + // // We have six point to move. + + // // P01 + // Geom::Point dp01 = pnodes[0][1] - pcg_old; + // dp01 *= scale_factor_x; + // pnodes[0][1] = pnodes[0][0] + dp01; + + // // P02 + // Geom::Point dp02 = pnodes[0][2] - pnodes[0][3]; + // dp02 *= scale_factor_x; + // pnodes[0][2] = pnodes[0][3] + dp02; + + // // P11 + // Geom::Point dp11 = pnodes[1][1] - pcg_old; + // dp11 *= scale_factor_x; + // pnodes[1][1] = pnodes[0][0] + dp11; + + + + // // P21 + // Geom::Point dp21 = pnodes[2][1] - pnodes[3][0]; + // dp21 *= scale_factor_x; + // dp21 *= scale_factor_y; + // pnodes[2][1] = pnodes[3][0] + dp21; + + + // Geom::Point dsx1 = pnodes[0][1]->p - +} + + +SPCurve * SPMeshNodeArray::outline_path() { + + SPCurve *outline = new SPCurve(); + + if (nodes.empty() ) { + std::cerr << "SPMeshNodeArray::outline_path: empty array!" << std::endl; + return outline; + } + + outline->moveto( nodes[0][0]->p ); + + int ncol = nodes[0].size(); + int nrow = nodes.size(); + + // Top + for (int i = 1; i < ncol; i += 3 ) { + outline->curveto( nodes[0][i]->p, nodes[0][i+1]->p, nodes[0][i+2]->p); + } + + // Right + for (int i = 1; i < nrow; i += 3 ) { + outline->curveto( nodes[i][ncol-1]->p, nodes[i+1][ncol-1]->p, nodes[i+2][ncol-1]->p); + } + + // Bottom (right to left) + for (int i = 1; i < ncol; i += 3 ) { + outline->curveto( nodes[nrow-1][ncol-i-1]->p, nodes[nrow-1][ncol-i-2]->p, nodes[nrow-1][ncol-i-3]->p); + } + + // Left (bottom to top) + for (int i = 1; i < nrow; i += 3 ) { + outline->curveto( nodes[nrow-i-1][0]->p, nodes[nrow-i-2][0]->p, nodes[nrow-i-3][0]->p); + } + + outline->closepath(); + + return outline; +} + +void SPMeshNodeArray::transform(Geom::Affine const &m) { + + for (int i = 0; i < nodes[0].size(); ++i) { + for (int j = 0; j < nodes.size(); ++j) { + nodes[j][i]->p *= m; + } + } +} + +// Transform mesh to fill box. Return true if mesh transformed. +bool SPMeshNodeArray::fill_box(Geom::OptRect &box) { + + // If gradientTransfor is set (as happens when an object is transformed + // with the "optimized" preferences set true), we need to remove it. + if (mg->gradientTransform_set) { + Geom::Affine gt = mg->gradientTransform; + transform( gt ); + mg->gradientTransform_set = false; + mg->gradientTransform.setIdentity(); + } + + SPCurve *outline = outline_path(); + Geom::OptRect mesh_bbox = outline->get_pathvector().boundsExact(); + outline->unref(); + + if ((*mesh_bbox).width() == 0 || (*mesh_bbox).height() == 0) { + return false; + } + + double scale_x = (*box).width() /(*mesh_bbox).width() ; + double scale_y = (*box).height()/(*mesh_bbox).height(); + + Geom::Translate t1(-(*mesh_bbox).min()); + Geom::Scale scale(scale_x,scale_y); + Geom::Translate t2((*box).min()); + Geom::Affine trans = t1 * scale * t2; + if (!trans.isIdentity() ) { + transform(trans); + write( mg ); + mg->requestModified(SP_OBJECT_MODIFIED_FLAG); + return true; + } + + return false; +} + +// Defined in gradient-chemistry.cpp +guint32 average_color(guint32 c1, guint32 c2, gdouble p); + +/** + Split a row into n equal parts. +*/ +void SPMeshNodeArray::split_row( unsigned int row, unsigned int n ) { + + double nn = n; + if( n > 1 ) split_row( row, (nn-1)/nn ); + if( n > 2 ) split_row( row, n-1 ); +} + +/** + Split a column into n equal parts. +*/ +void SPMeshNodeArray::split_column( unsigned int col, unsigned int n ) { + + double nn = n; + if( n > 1 ) split_column( col, (nn-1)/nn ); + if( n > 2 ) split_column( col, n-1 ); +} + +/** + Split a row into two rows at coord (fraction of row height). +*/ +void SPMeshNodeArray::split_row( unsigned int row, double coord ) { + + // std::cout << "Splitting row: " << row << " at " << coord << std::endl; + // print(); + assert( coord >= 0.0 && coord <= 1.0 ); + assert( row < patch_rows() ); + + built = false; + + // First step is to ensure that handle and tensor points are up-to-date if they are not set. + // (We can't do this on the fly as we overwrite the necessary points to do the calculation + // during the update.) + for( guint j = 0; j < patch_columns(); ++ j ) { + SPMeshPatchI patch( &nodes, row, j ); + patch.updateNodes(); + } + + // Add three new rows of empty nodes + for( guint i = 0; i < 3; ++i ) { + std::vector< SPMeshNode* > new_row; + for( guint j = 0; j < nodes[0].size(); ++j ) { + SPMeshNode* new_node = new SPMeshNode; + new_row.push_back( new_node ); + } + nodes.insert( nodes.begin()+3*(row+1), new_row ); + } + + guint i = 3 * row; // Convert from patch row to node row + for( guint j = 0; j < nodes[i].size(); ++j ) { + + // std::cout << "Splitting row: column: " << j << std::endl; + + Geom::Point p[4]; + for( guint k = 0; k < 4; ++k ) { + guint n = k; + if( k == 3 ) n = 6; // Bottom patch row has been shifted by new rows + p[k] = nodes[i+n][j]->p; + // std::cout << p[k] << std::endl; + } + + Geom::BezierCurveN<3> b( p[0], p[1], p[2], p[3] ); + + std::pair, Geom::BezierCurveN<3> > b_new = + b.subdivide( coord ); + + // Update points + for( guint n = 0; n < 4; ++n ) { + nodes[i+n ][j]->p = b_new.first[n]; + nodes[i+n+3][j]->p = b_new.second[n]; + // std::cout << b_new.first[n] << " " << b_new.second[n] << std::endl; + } + + if( nodes[i][j]->node_type == MG_NODE_TYPE_CORNER ) { + // We are splitting a side + + // Path type stored in handles. + gchar path_type = nodes[i+1][j]->path_type; + nodes[i+4][j]->path_type = path_type; + nodes[i+5][j]->path_type = path_type; + bool set = nodes[i+1][j]->set; + nodes[i+4][j]->set = set; + nodes[i+5][j]->set = set; + nodes[i+4][j]->node_type = MG_NODE_TYPE_HANDLE; + nodes[i+5][j]->node_type = MG_NODE_TYPE_HANDLE; + + // Color stored in corners + guint c0 = nodes[i ][j]->color.toRGBA32( 1.0 ); + guint c1 = nodes[i+6][j]->color.toRGBA32( 1.0 ); + gdouble o0 = nodes[i ][j]->opacity; + gdouble o1 = nodes[i+6][j]->opacity; + guint cnew = average_color( c0, c1, coord ); + gdouble onew = o0 * (1.0 - coord) + o1 * coord; + nodes[i+3][j]->color.set( cnew ); + nodes[i+3][j]->opacity = onew; + nodes[i+3][j]->node_type = MG_NODE_TYPE_CORNER; + nodes[i+3][j]->set = true; + + } else { + // We are splitting a middle + + bool set = nodes[i+1][j]->set || nodes[i+2][j]->set; + nodes[i+4][j]->set = set; + nodes[i+5][j]->set = set; + nodes[i+4][j]->node_type = MG_NODE_TYPE_TENSOR; + nodes[i+5][j]->node_type = MG_NODE_TYPE_TENSOR; + + // Path type, if different, choose l -> L -> c -> C. + gchar path_type0 = nodes[i ][j]->path_type; + gchar path_type1 = nodes[i+6][j]->path_type; + gchar path_type = 'l'; + if( path_type0 == 'L' || path_type1 == 'L') path_type = 'L'; + if( path_type0 == 'c' || path_type1 == 'c') path_type = 'c'; + if( path_type0 == 'C' || path_type1 == 'C') path_type = 'C'; + nodes[i+3][j]->path_type = path_type; + nodes[i+3][j]->node_type = MG_NODE_TYPE_HANDLE; + if( path_type == 'c' || path_type == 'C' ) nodes[i+3][j]->set = true; + + } + + nodes[i+3][j]->node_edge = MG_NODE_EDGE_NONE; + nodes[i+4][j]->node_edge = MG_NODE_EDGE_NONE; + nodes[i+5][j]->node_edge = MG_NODE_EDGE_NONE;; + if( j == 0 ) { + nodes[i+3][j]->node_edge |= MG_NODE_EDGE_LEFT; + nodes[i+4][j]->node_edge |= MG_NODE_EDGE_LEFT; + nodes[i+5][j]->node_edge |= MG_NODE_EDGE_LEFT; + } + if( j == nodes[i].size() - 1 ) { + nodes[i+3][j]->node_edge |= MG_NODE_EDGE_RIGHT; + nodes[i+4][j]->node_edge |= MG_NODE_EDGE_RIGHT; + nodes[i+5][j]->node_edge |= MG_NODE_EDGE_RIGHT; + } + } + + // std::cout << "Splitting row: result:" << std::endl; + // print(); +} + + + +/** + Split a column into two columns at coord (fraction of column width). +*/ +void SPMeshNodeArray::split_column( unsigned int col, double coord ) { + + // std::cout << "Splitting column: " << col << " at " << coord << std::endl; + // print(); + assert( coord >= 0.0 && coord <= 1.0 ); + assert( col < patch_columns() ); + + built = false; + + // First step is to ensure that handle and tensor points are up-to-date if they are not set. + // (We can't do this on the fly as we overwrite the necessary points to do the calculation + // during the update.) + for( guint i = 0; i < patch_rows(); ++ i ) { + SPMeshPatchI patch( &nodes, i, col ); + patch.updateNodes(); + } + + guint j = 3 * col; // Convert from patch column to node column + for( guint i = 0; i < nodes.size(); ++i ) { + + // std::cout << "Splitting column: row: " << i << std::endl; + + Geom::Point p[4]; + for( guint k = 0; k < 4; ++k ) { + p[k] = nodes[i][j+k]->p; + } + + Geom::BezierCurveN<3> b( p[0], p[1], p[2], p[3] ); + + std::pair, Geom::BezierCurveN<3> > b_new = + b.subdivide( coord ); + + // Add three new nodes + for( guint n = 0; n < 3; ++n ) { + SPMeshNode* new_node = new SPMeshNode; + nodes[i].insert( nodes[i].begin()+j+3, new_node ); + } + + // Update points + for( guint n = 0; n < 4; ++n ) { + nodes[i][j+n]->p = b_new.first[n]; + nodes[i][j+n+3]->p = b_new.second[n]; + } + + if( nodes[i][j]->node_type == MG_NODE_TYPE_CORNER ) { + // We are splitting a side + + // Path type stored in handles. + gchar path_type = nodes[i][j+1]->path_type; + nodes[i][j+4]->path_type = path_type; + nodes[i][j+5]->path_type = path_type; + bool set = nodes[i][j+1]->set; + nodes[i][j+4]->set = set; + nodes[i][j+5]->set = set; + nodes[i][j+4]->node_type = MG_NODE_TYPE_HANDLE; + nodes[i][j+5]->node_type = MG_NODE_TYPE_HANDLE; + + // Color stored in corners + guint c0 = nodes[i][j ]->color.toRGBA32( 1.0 ); + guint c1 = nodes[i][j+6]->color.toRGBA32( 1.0 ); + gdouble o0 = nodes[i][j ]->opacity; + gdouble o1 = nodes[i][j+6]->opacity; + guint cnew = average_color( c0, c1, coord ); + gdouble onew = o0 * (1.0 - coord) + o1 * coord; + nodes[i][j+3]->color.set( cnew ); + nodes[i][j+3]->opacity = onew; + nodes[i][j+3]->node_type = MG_NODE_TYPE_CORNER; + nodes[i][j+3]->set = true; + + } else { + // We are splitting a middle + + bool set = nodes[i][j+1]->set || nodes[i][j+2]->set; + nodes[i][j+4]->set = set; + nodes[i][j+5]->set = set; + nodes[i][j+4]->node_type = MG_NODE_TYPE_TENSOR; + nodes[i][j+5]->node_type = MG_NODE_TYPE_TENSOR; + + // Path type, if different, choose l -> L -> c -> C. + gchar path_type0 = nodes[i][j ]->path_type; + gchar path_type1 = nodes[i][j+6]->path_type; + gchar path_type = 'l'; + if( path_type0 == 'L' || path_type1 == 'L') path_type = 'L'; + if( path_type0 == 'c' || path_type1 == 'c') path_type = 'c'; + if( path_type0 == 'C' || path_type1 == 'C') path_type = 'C'; + nodes[i][j+3]->path_type = path_type; + nodes[i][j+3]->node_type = MG_NODE_TYPE_HANDLE; + if( path_type == 'c' || path_type == 'C' ) nodes[i][j+3]->set = true; + + } + + nodes[i][j+3]->node_edge = MG_NODE_EDGE_NONE; + nodes[i][j+4]->node_edge = MG_NODE_EDGE_NONE; + nodes[i][j+5]->node_edge = MG_NODE_EDGE_NONE;; + if( i == 0 ) { + nodes[i][j+3]->node_edge |= MG_NODE_EDGE_TOP; + nodes[i][j+4]->node_edge |= MG_NODE_EDGE_TOP; + nodes[i][j+5]->node_edge |= MG_NODE_EDGE_TOP; + } + if( i == nodes.size() - 1 ) { + nodes[i][j+3]->node_edge |= MG_NODE_EDGE_BOTTOM; + nodes[i][j+4]->node_edge |= MG_NODE_EDGE_BOTTOM; + nodes[i][j+5]->node_edge |= MG_NODE_EDGE_BOTTOM; + } + + } + + // std::cout << "Splitting col: result:" << std::endl; + // print(); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-mesh-array.h b/src/object/sp-mesh-array.h new file mode 100644 index 000000000..df43638db --- /dev/null +++ b/src/object/sp-mesh-array.h @@ -0,0 +1,231 @@ +#ifndef SEEN_SP_MESH_ARRAY_H +#define SEEN_SP_MESH_ARRAY_H +/* + * Authors: + * Tavmjong Bah + * + * Copyrigt (C) 2012 Tavmjong Bah + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +/** + A group of classes and functions for manipulating mesh gradients. + + A mesh is made up of an array of patches. Each patch has four sides and four corners. The sides can + be shared between two patches and the corners between up to four. + + The order of the points for each side always goes from left to right or top to bottom. + For sides 2 and 3 the points must be reversed when used (as in calls to cairo functions). + + Two patches: (C=corner, S=side, H=handle, T=tensor) + + C0 H1 H2 C1 C0 H1 H2 C1 + + ---------- + ---------- + + | S0 | S0 | + H1 | T0 T1 |H1 T0 T1 | H1 + |S3 S1|S3 S1| + H2 | T3 T2 |H2 T3 T2 | H2 + | S2 | S2 | + + ---------- + ---------- + + C3 H1 H2 C2 C3 H1 H2 C2 + + The mesh is stored internally as an array of nodes that includes the tensor nodes. + + Note: This code uses tensor points which are not part of the SVG2 plan at the moment. + Including tensor points was motivated by a desire to experiment with their usefulness + in smoothing color transitions. There doesn't seem to be much advantage for that + purpose. However including them internally allows for storing all the points in + an array which simplifies things like inserting new rows or columns. +*/ + +#include <2geom/point.h> +#include "color.h" + +// For color picking +#include "sp-item.h" + +enum SPMeshType { + SP_MESH_TYPE_COONS, + SP_MESH_TYPE_BICUBIC +}; + +enum SPMeshGeometry { + SP_MESH_GEOMETRY_NORMAL, + SP_MESH_GEOMETRY_CONICAL +}; + +enum NodeType { + MG_NODE_TYPE_UNKNOWN, + MG_NODE_TYPE_CORNER, + MG_NODE_TYPE_HANDLE, + MG_NODE_TYPE_TENSOR +}; + +// Is a node along an edge? +enum NodeEdge { + MG_NODE_EDGE_NONE, + MG_NODE_EDGE_TOP = 1, + MG_NODE_EDGE_LEFT = 2, + MG_NODE_EDGE_BOTTOM = 4, + MG_NODE_EDGE_RIGHT = 8 +}; + +enum MeshCornerOperation { + MG_CORNER_SIDE_TOGGLE, + MG_CORNER_SIDE_ARC, + MG_CORNER_TENSOR_TOGGLE, + MG_CORNER_COLOR_SMOOTH, + MG_CORNER_COLOR_PICK, + MG_CORNER_INSERT +}; + +enum MeshNodeOperation { + MG_NODE_NO_SCALE, + MG_NODE_SCALE, + MG_NODE_SCALE_HANDLE +}; + +class SPStop; + +class SPMeshNode { +public: + SPMeshNode() { + node_type = MG_NODE_TYPE_UNKNOWN; + node_edge = MG_NODE_EDGE_NONE; + set = false; + draggable = -1; + path_type = 'u'; + opacity = 0.0; + stop = nullptr; + } + NodeType node_type; + unsigned int node_edge; + bool set; + Geom::Point p; + unsigned int draggable; // index of on-screen node + char path_type; + SPColor color; + double opacity; + SPStop *stop; // Stop corresponding to node. +}; + + +// I for Internal to distinguish it from the Object class +// This is a convenience class... +class SPMeshPatchI { + +private: + std::vector > *nodes; + int row; + int col; + +public: + SPMeshPatchI( std::vector > *n, int r, int c ); + Geom::Point getPoint( unsigned int side, unsigned int point ); + std::vector< Geom::Point > getPointsForSide( unsigned int i ); + void setPoint( unsigned int side, unsigned int point, Geom::Point p, bool set = true ); + char getPathType( unsigned int i ); + void setPathType( unsigned int, char t ); + Geom::Point getTensorPoint( unsigned int i ); + void setTensorPoint( unsigned int i, Geom::Point p ); + bool tensorIsSet(); + bool tensorIsSet( unsigned int i ); + Geom::Point coonsTensorPoint( unsigned int i ); + void updateNodes(); + SPColor getColor( unsigned int i ); + void setColor( unsigned int i, SPColor c ); + double getOpacity( unsigned int i ); + void setOpacity( unsigned int i, double o ); + SPStop* getStopPtr( unsigned int i ); + void setStopPtr( unsigned int i, SPStop* ); +}; + +class SPMeshGradient; +class SPCurve; + +// An array of mesh nodes. +class SPMeshNodeArray { + +// Should be private +public: + SPMeshGradient *mg; + std::vector< std::vector< SPMeshNode* > > nodes; + +public: + // Draggables to nodes + bool draggers_valid; + std::vector< SPMeshNode* > corners; + std::vector< SPMeshNode* > handles; + std::vector< SPMeshNode* > tensors; + +public: + + friend class SPMeshPatchI; + + SPMeshNodeArray() { built = false; mg = NULL; draggers_valid = false; }; + SPMeshNodeArray( SPMeshGradient *mg ); + SPMeshNodeArray( const SPMeshNodeArray& rhs ); + SPMeshNodeArray& operator=(const SPMeshNodeArray& rhs); + + ~SPMeshNodeArray() { clear(); }; + bool built; + + bool read( SPMeshGradient *mg ); + void write( SPMeshGradient *mg ); + void create( SPMeshGradient *mg, SPItem *item, Geom::OptRect bbox ); + void clear(); + void print(); + + // Fill 'smooth' with a smoothed version by subdividing each patch. + void bicubic( SPMeshNodeArray* smooth, SPMeshType type); + + // Get size of patch + unsigned int patch_rows(); + unsigned int patch_columns(); + + SPMeshNode * node( unsigned int i, unsigned int j ) { return nodes[i][j]; } + + // Operations on corners + bool adjacent_corners( unsigned int i, unsigned int j, SPMeshNode* n[4] ); + unsigned int side_toggle( std::vector< unsigned int > ); + unsigned int side_arc( std::vector< unsigned int > ); + unsigned int tensor_toggle( std::vector< unsigned int > ); + unsigned int color_smooth( std::vector< unsigned int > ); + unsigned int color_pick( std::vector< unsigned int >, SPItem* ); + unsigned int insert( std::vector< unsigned int > ); + + // Update other nodes in response to a node move. + void update_handles( unsigned int corner, std::vector< unsigned int > selected_corners, Geom::Point old_p, MeshNodeOperation op ); + + // Return outline path (don't forget to unref() when done with curve) + SPCurve * outline_path(); + + // Transform array + void transform(Geom::Affine const &m); + + // Transform mesh to fill box. Return true if not identity transform. + bool fill_box(Geom::OptRect &box); + + // Find bounding box + // Geom::OptRect findBoundingBox(); + + void split_row( unsigned int i, unsigned int n ); + void split_column( unsigned int j, unsigned int n ); + void split_row( unsigned int i, double coord ); + void split_column( unsigned int j, double coord ); +}; + +#endif /* !SEEN_SP_MESH_ARRAY_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + c-basic-offset:2 + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-mesh-gradient.cpp b/src/object/sp-mesh-gradient.cpp new file mode 100644 index 000000000..572131c60 --- /dev/null +++ b/src/object/sp-mesh-gradient.cpp @@ -0,0 +1,277 @@ +#include + +#include "attributes.h" +#include "display/cairo-utils.h" + +#include "sp-mesh-gradient.h" + +/* + * Mesh Gradient + */ +//#define MESH_DEBUG +//#define OBJECT_TRACE + +SPMeshGradient::SPMeshGradient() : SPGradient(), type( SP_MESH_TYPE_COONS ), type_set(false) { +#ifdef OBJECT_TRACE + objectTrace( "SPMeshGradient::SPMeshGradient" ); +#endif + + // Start coordinate of mesh + this->x.unset(SVGLength::NONE, 0.0, 0.0); + this->y.unset(SVGLength::NONE, 0.0, 0.0); + +#ifdef OBJECT_TRACE + objectTrace( "SPMeshGradient::SPMeshGradient", false ); +#endif +} + +SPMeshGradient::~SPMeshGradient() { +#ifdef OBJECT_TRACE + objectTrace( "SPMeshGradient::~SPMeshGradient (empty function)" ); + objectTrace( "SPMeshGradient::~SPMeshGradient", false ); +#endif +} + +void SPMeshGradient::build(SPDocument *document, Inkscape::XML::Node *repr) { +#ifdef OBJECT_TRACE + objectTrace( "SPMeshGradient::build" ); +#endif + + SPGradient::build(document, repr); + + // Start coordinate of meshgradient + this->readAttr( "x" ); + this->readAttr( "y" ); + + this->readAttr( "type" ); + +#ifdef OBJECT_TRACE + objectTrace( "SPMeshGradient::build", false ); +#endif +} + + +void SPMeshGradient::set(unsigned key, gchar const *value) { +#ifdef OBJECT_TRACE + objectTrace( "SPMeshGradient::set" ); +#endif + + switch (key) { + case SP_ATTR_X: + if (!this->x.read(value)) { + this->x.unset(SVGLength::NONE, 0.0, 0.0); + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_Y: + if (!this->y.read(value)) { + this->y.unset(SVGLength::NONE, 0.0, 0.0); + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_TYPE: + if (value) { + if (!strcmp(value, "coons")) { + this->type = SP_MESH_TYPE_COONS; + } else if (!strcmp(value, "bicubic")) { + this->type = SP_MESH_TYPE_BICUBIC; + } else { + std::cerr << "SPMeshGradient::set(): invalid value " << value << std::endl; + } + this->type_set = TRUE; + } else { + // std::cout << "SPMeshGradient::set() No value " << std::endl; + this->type = SP_MESH_TYPE_COONS; + this->type_set = FALSE; + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + default: + SPGradient::set(key, value); + break; + } + +#ifdef OBJECT_TRACE + objectTrace( "SPMeshGradient::set", false ); +#endif +} + +/** + * Write mesh gradient attributes to associated repr. + */ +Inkscape::XML::Node* SPMeshGradient::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { +#ifdef OBJECT_TRACE + objectTrace( "SPMeshGradient::write", false ); +#endif + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:meshgradient"); + } + + if ((flags & SP_OBJECT_WRITE_ALL) || this->x._set) { + sp_repr_set_svg_double(repr, "x", this->x.computed); + } + + if ((flags & SP_OBJECT_WRITE_ALL) || this->y._set) { + sp_repr_set_svg_double(repr, "y", this->y.computed); + } + + if ((flags & SP_OBJECT_WRITE_ALL) || this->type_set) { + switch (this->type) { + case SP_MESH_TYPE_COONS: + repr->setAttribute("type", "coons"); + break; + case SP_MESH_TYPE_BICUBIC: + repr->setAttribute("type", "bicubic"); + break; + default: + // Do nothing + break; + } + } + + SPGradient::write(xml_doc, repr, flags); + +#ifdef OBJECT_TRACE + objectTrace( "SPMeshGradient::write", false ); +#endif + return repr; +} + +cairo_pattern_t* SPMeshGradient::pattern_new(cairo_t * /*ct*/, +#if defined(MESH_DEBUG) || (CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 11, 4)) + Geom::OptRect const &bbox, + double opacity +#else + Geom::OptRect const & /*bbox*/, + double /*opacity*/ +#endif + ) +{ + using Geom::X; + using Geom::Y; + +#ifdef MESH_DEBUG + std::cout << "sp_meshgradient_create_pattern: " << (*bbox) << " " << opacity << std::endl; +#endif + + this->ensureArray(); + + cairo_pattern_t *cp = NULL; + +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 11, 4) + SPMeshNodeArray* my_array = &array; + + if( type_set ) { + switch (type) { + case SP_MESH_TYPE_COONS: + // std::cout << "SPMeshGradient::pattern_new: Coons" << std::endl; + break; + case SP_MESH_TYPE_BICUBIC: + array.bicubic( &array_smoothed, type ); + my_array = &array_smoothed; + break; + } + } + + cp = cairo_pattern_create_mesh(); + + for( unsigned int i = 0; i < my_array->patch_rows(); ++i ) { + for( unsigned int j = 0; j < my_array->patch_columns(); ++j ) { + + SPMeshPatchI patch( &(my_array->nodes), i, j ); + + cairo_mesh_pattern_begin_patch( cp ); + cairo_mesh_pattern_move_to( cp, patch.getPoint( 0, 0 )[X], patch.getPoint( 0, 0 )[Y] ); + + for( unsigned int k = 0; k < 4; ++k ) { +#ifdef DEBUG_MESH + std::cout << i << " " << j << " " + << patch.getPathType( k ) << " ("; + for( int p = 0; p < 4; ++p ) { + std::cout << patch.getPoint( k, p ); + } + std::cout << ") " + << patch.getColor( k ).toString() << std::endl; +#endif + + switch ( patch.getPathType( k ) ) { + case 'l': + case 'L': + case 'z': + case 'Z': + cairo_mesh_pattern_line_to( cp, + patch.getPoint( k, 3 )[X], + patch.getPoint( k, 3 )[Y] ); + break; + case 'c': + case 'C': + { + std::vector< Geom::Point > pts = patch.getPointsForSide( k ); + cairo_mesh_pattern_curve_to( cp, + pts[1][X], pts[1][Y], + pts[2][X], pts[2][Y], + pts[3][X], pts[3][Y] ); + break; + } + default: + // Shouldn't happen + std::cout << "sp_mesh_create_pattern: path error" << std::endl; + } + + if( patch.tensorIsSet(k) ) { + // Tensor point defined relative to corner. + Geom::Point t = patch.getTensorPoint(k); + cairo_mesh_pattern_set_control_point( cp, k, t[X], t[Y] ); + //std::cout << " sp_mesh_create_pattern: tensor " << k + // << " set to " << t << "." << std::endl; + } else { + // Geom::Point t = patch.coonsTensorPoint(k); + //std::cout << " sp_mesh_create_pattern: tensor " << k + // << " calculated as " << t << "." <gradientTransform; + if (this->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) { + Geom::Affine bbox2user(bbox->width(), 0, 0, bbox->height(), bbox->left(), bbox->top()); + gs2user *= bbox2user; + } + ink_cairo_pattern_set_matrix(cp, gs2user.inverse()); + +#else + static bool shown = false; + if( !shown ) { + std::cout << "sp_mesh_create_pattern: needs cairo >= 1.11.4, using " + << cairo_version_string() << std::endl; + shown = true; + } +#endif + + /* + cairo_pattern_t *cp = cairo_pattern_create_radial( + rg->fx.computed, rg->fy.computed, 0, + rg->cx.computed, rg->cy.computed, rg->r.computed); + sp_gradient_pattern_common_setup(cp, gr, bbox, opacity); + */ + + return cp; +} diff --git a/src/object/sp-mesh-gradient.h b/src/object/sp-mesh-gradient.h new file mode 100644 index 000000000..a221554a3 --- /dev/null +++ b/src/object/sp-mesh-gradient.h @@ -0,0 +1,43 @@ +#ifndef SP_MESH_GRADIENT_H +#define SP_MESH_GRADIENT_H + +/** \file + * SPMeshGradient: SVG implementation. + */ + +#include "svg/svg-length.h" +#include "sp-gradient.h" + +#define SP_MESHGRADIENT(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_MESHGRADIENT(obj) (dynamic_cast((SPObject*)obj) != NULL) + +/** Mesh gradient. */ +class SPMeshGradient : public SPGradient { +public: + SPMeshGradient(); + virtual ~SPMeshGradient(); + + SVGLength x; // Upper left corner of meshgradient + SVGLength y; // Upper right corner of mesh + SPMeshType type; + bool type_set; + virtual cairo_pattern_t* pattern_new(cairo_t *ct, Geom::OptRect const &bbox, double opacity); + +protected: + virtual void build(SPDocument *document, Inkscape::XML::Node *repr); + virtual void set(unsigned key, char const *value); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); +}; + +#endif /* !SP_MESH_GRADIENT_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-mesh-patch.cpp b/src/object/sp-mesh-patch.cpp new file mode 100644 index 000000000..04a121c7a --- /dev/null +++ b/src/object/sp-mesh-patch.cpp @@ -0,0 +1,138 @@ +/** @file + * @gradient meshpatch class. + */ +/* Authors: + * Lauris Kaplinski + * bulia byak + * Johan Engelen + * Jon A. Cruz + * Tavmjong Bah + * + * Copyright (C) 1999,2005 authors + * Copyright (C) 2010 Jon A. Cruz + * Copyright (C) 2012 Tavmjong Bah + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#include "sp-mesh-patch.h" +#include "style.h" + +#include "attributes.h" + +SPMeshpatch* SPMeshpatch::getNextMeshpatch() +{ + SPMeshpatch *result = 0; + + for (SPObject* obj = getNext(); obj && !result; obj = obj->getNext()) { + if (SP_IS_MESHPATCH(obj)) { + result = SP_MESHPATCH(obj); + } + } + + return result; +} + +SPMeshpatch* SPMeshpatch::getPrevMeshpatch() +{ + SPMeshpatch *result = 0; + + for (SPObject* obj = getPrev(); obj; obj = obj->getPrev()) { + // The closest previous SPObject that is an SPMeshpatch *should* be ourself. + if (SP_IS_MESHPATCH(obj)) { + SPMeshpatch* meshpatch = SP_MESHPATCH(obj); + // Sanity check to ensure we have a proper sibling structure. + if (meshpatch->getNextMeshpatch() == this) { + result = meshpatch; + } else { + g_warning("SPMeshpatch previous/next relationship broken"); + } + break; + } + } + + return result; +} + + +/* + * Mesh Patch + */ +SPMeshpatch::SPMeshpatch() : SPObject() { + this->tensor_string = NULL; +} + +SPMeshpatch::~SPMeshpatch() { +} + +void SPMeshpatch::build(SPDocument* doc, Inkscape::XML::Node* repr) { + SPObject::build(doc, repr); + + this->readAttr( "tensor" ); +} + +/** + * Virtual build: set meshpatch attributes from its associated XML node. + */ +void SPMeshpatch::set(unsigned int key, const gchar* value) { + switch (key) { + case SP_ATTR_TENSOR: { + if (value) { + this->tensor_string = new Glib::ustring( value ); + // std::cout << "sp_meshpatch_set: Tensor string: " << patch->tensor_string->c_str() << std::endl; + } + break; + } + default: { + // Do nothing + } + } +} + +/** + * modified + */ +void SPMeshpatch::modified(unsigned int flags) { + + flags &= SP_OBJECT_MODIFIED_CASCADE; + std::vector l; + for (auto& child: children) { + sp_object_ref(&child); + l.push_back(&child); + } + + for (auto child:l) { + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + sp_object_unref(child); + } +} + + +/** + * Virtual set: set attribute to value. + */ +Inkscape::XML::Node* SPMeshpatch::write(Inkscape::XML::Document* xml_doc, Inkscape::XML::Node* repr, guint flags) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:meshpatch"); + } + + SPObject::write(xml_doc, repr, flags); + + return repr; +} + +/** + * Virtual write: write object attributes to repr. + */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-mesh-patch.h b/src/object/sp-mesh-patch.h new file mode 100644 index 000000000..e018b81ea --- /dev/null +++ b/src/object/sp-mesh-patch.h @@ -0,0 +1,51 @@ +#ifndef SEEN_SP_MESHPATCH_H +#define SEEN_SP_MESHPATCH_H + +/** \file + * SPMeshpatch: SVG implementation. + */ +/* + * Authors: Tavmjong Bah + * + * Copyright (C) 2012 Tavmjong Bah + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include "sp-object.h" + +#define SP_MESHPATCH(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_MESHPATCH(obj) (dynamic_cast((SPObject*)obj) != NULL) + +/** Gradient Meshpatch. */ +class SPMeshpatch : public SPObject { +public: + SPMeshpatch(); + virtual ~SPMeshpatch(); + + SPMeshpatch* getNextMeshpatch(); + SPMeshpatch* getPrevMeshpatch(); + Glib::ustring * tensor_string; + //SVGLength tx[4]; // Tensor points + //SVGLength ty[4]; // Tensor points + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void set(unsigned int key, const char* value); + virtual void modified(unsigned int flags); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); +}; + +#endif /* !SEEN_SP_MESHPATCH_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-mesh-row.cpp b/src/object/sp-mesh-row.cpp new file mode 100644 index 000000000..8204aff65 --- /dev/null +++ b/src/object/sp-mesh-row.cpp @@ -0,0 +1,122 @@ +/** @file + * @gradient meshrow class. + */ +/* Authors: + * Lauris Kaplinski + * bulia byak + * Johan Engelen + * Jon A. Cruz + * Tavmjong Bah + * + * Copyright (C) 1999,2005 authors + * Copyright (C) 2010 Jon A. Cruz + * Copyright (C) 2012 Tavmjong Bah + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#include "sp-mesh-row.h" +#include "style.h" + +SPMeshrow* SPMeshrow::getNextMeshrow() +{ + SPMeshrow *result = 0; + + for (SPObject* obj = getNext(); obj && !result; obj = obj->getNext()) { + if (SP_IS_MESHROW(obj)) { + result = SP_MESHROW(obj); + } + } + + return result; +} + +SPMeshrow* SPMeshrow::getPrevMeshrow() +{ + SPMeshrow *result = 0; + + for (SPObject* obj = getPrev(); obj; obj = obj->getPrev()) { + // The closest previous SPObject that is an SPMeshrow *should* be ourself. + if (SP_IS_MESHROW(obj)) { + SPMeshrow* meshrow = SP_MESHROW(obj); + // Sanity check to ensure we have a proper sibling structure. + if (meshrow->getNextMeshrow() == this) { + result = meshrow; + } else { + g_warning("SPMeshrow previous/next relationship broken"); + } + break; + } + } + + return result; +} + + +/* + * Mesh Row + */ +SPMeshrow::SPMeshrow() : SPObject() { +} + +SPMeshrow::~SPMeshrow() { +} + +void SPMeshrow::build(SPDocument* doc, Inkscape::XML::Node* repr) { + SPObject::build(doc, repr); +} + + +/** + * Virtual build: set meshrow attributes from its associated XML node. + */ +void SPMeshrow::set(unsigned int /*key*/, const gchar* /*value*/) { +} + +/** + * modified + */ +void SPMeshrow::modified(unsigned int flags) { + + flags &= SP_OBJECT_MODIFIED_CASCADE; + std::vector l; + for (auto& child: children) { + sp_object_ref(&child); + l.push_back(&child); + } + + for (auto child:l) { + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + sp_object_unref(child); + } +} + + +/** + * Virtual set: set attribute to value. + */ +Inkscape::XML::Node* SPMeshrow::write(Inkscape::XML::Document* xml_doc, Inkscape::XML::Node* repr, guint flags) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:meshrow"); + } + + SPObject::write(xml_doc, repr, flags); + + return repr; +} + +/** + * Virtual write: write object attributes to repr. + */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-mesh-row.h b/src/object/sp-mesh-row.h new file mode 100644 index 000000000..40335e2b9 --- /dev/null +++ b/src/object/sp-mesh-row.h @@ -0,0 +1,46 @@ +#ifndef SEEN_SP_MESHROW_H +#define SEEN_SP_MESHROW_H + +/** \file + * SPMeshrow: SVG implementation. + */ +/* + * Authors: Tavmjong Bah + * Copyright (C) 2012 Tavmjong Bah + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-object.h" + +#define SP_MESHROW(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_MESHROW(obj) (dynamic_cast((SPObject*)obj) != NULL) + +/** Gradient Meshrow. */ +class SPMeshrow : public SPObject { +public: + SPMeshrow(); + virtual ~SPMeshrow(); + + SPMeshrow* getNextMeshrow(); + SPMeshrow* getPrevMeshrow(); + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void set(unsigned int key, const char* value); + virtual void modified(unsigned int flags); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); +}; + +#endif /* !SEEN_SP_MESHROW_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-metadata.cpp b/src/object/sp-metadata.cpp new file mode 100644 index 000000000..e7907e4f0 --- /dev/null +++ b/src/object/sp-metadata.cpp @@ -0,0 +1,147 @@ +/* + * SVG implementation + * + * Authors: + * Kees Cook + * + * Copyright (C) 2004 Kees Cook + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "sp-metadata.h" +#include "xml/node-iterators.h" +#include "document.h" + +#include "sp-item-group.h" +#include "sp-root.h" + +#define noDEBUG_METADATA +#ifdef DEBUG_METADATA +# define debug(f, a...) { g_print("%s(%d) %s:", \ + __FILE__,__LINE__,__FUNCTION__); \ + g_print(f, ## a); \ + g_print("\n"); \ + } +#else +# define debug(f, a...) /**/ +#endif + +/* Metadata base class */ + +SPMetadata::SPMetadata() : SPObject() { +} + +SPMetadata::~SPMetadata() { +} + +namespace { + +void strip_ids_recursively(Inkscape::XML::Node *node) { + using Inkscape::XML::NodeSiblingIterator; + if ( node->type() == Inkscape::XML::ELEMENT_NODE ) { + node->setAttribute("id", NULL); + } + for ( NodeSiblingIterator iter=node->firstChild() ; iter ; ++iter ) { + strip_ids_recursively(iter); + } +} + +} + + +void SPMetadata::build(SPDocument* doc, Inkscape::XML::Node* repr) { + using Inkscape::XML::NodeSiblingIterator; + + debug("0x%08x",(unsigned int)this); + + /* clean up our mess from earlier versions; elements under rdf:RDF should not + * have id= attributes... */ + static GQuark const rdf_root_name = g_quark_from_static_string("rdf:RDF"); + + for ( NodeSiblingIterator iter=repr->firstChild() ; iter ; ++iter ) { + if ( (GQuark)iter->code() == rdf_root_name ) { + strip_ids_recursively(iter); + } + } + + SPObject::build(doc, repr); +} + +void SPMetadata::release() { + debug("0x%08x",(unsigned int)this); + + // handle ourself + + SPObject::release(); +} + +void SPMetadata::set(unsigned int key, const gchar* value) { + debug("0x%08x %s(%u): '%s'",(unsigned int)this, + sp_attribute_name(key),key,value); + + // see if any parents need this value + SPObject::set(key, value); +} + +void SPMetadata::update(SPCtx* /*ctx*/, unsigned int flags) { + debug("0x%08x",(unsigned int)this); + //SPMetadata *metadata = SP_METADATA(object); + + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + + /* do something? */ + + } + +// SPObject::onUpdate(ctx, flags); +} + +Inkscape::XML::Node* SPMetadata::write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags) { + debug("0x%08x",(unsigned int)this); + + if ( repr != this->getRepr() ) { + if (repr) { + repr->mergeFrom(this->getRepr(), "id"); + } else { + repr = this->getRepr()->duplicate(doc); + } + } + + SPObject::write(doc, repr, flags); + + return repr; +} + +/** + * Retrieves the metadata object associated with a document. + */ +SPMetadata *sp_document_metadata(SPDocument *document) +{ + SPObject *nv; + + g_return_val_if_fail (document != NULL, NULL); + + nv = sp_item_group_get_child_by_name( document->getRoot(), NULL, + "metadata"); + g_assert (nv != NULL); + + return (SPMetadata *)nv; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-metadata.h b/src/object/sp-metadata.h new file mode 100644 index 000000000..a89020390 --- /dev/null +++ b/src/object/sp-metadata.h @@ -0,0 +1,39 @@ +#ifndef SEEN_SP_METADATA_H +#define SEEN_SP_METADATA_H + +/* + * SVG implementation + * + * Authors: + * Kees Cook + * + * Copyright (C) 2004 Kees Cook + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-object.h" + +/* Metadata base class */ + +#define SP_METADATA(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_METADATA(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPMetadata : public SPObject { +public: + SPMetadata(); + virtual ~SPMetadata(); + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void set(unsigned int key, const char* value); + virtual void update(SPCtx* ctx, unsigned int flags); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); +}; + +SPMetadata * sp_document_metadata (SPDocument *document); + +#endif +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-missing-glyph.cpp b/src/object/sp-missing-glyph.cpp new file mode 100644 index 000000000..f441b66d2 --- /dev/null +++ b/src/object/sp-missing-glyph.cpp @@ -0,0 +1,144 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +/* + * SVG element implementation + * + * Author: + * Felipe C. da S. Sanches + * Abhishek Sharma + * + * Copyright (C) 2008, Felipe C. da S. Sanches + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "xml/repr.h" +#include "attributes.h" +#include "sp-missing-glyph.h" +#include "document.h" + +SPMissingGlyph::SPMissingGlyph() : SPObject() { +//TODO: correct these values: + this->d = NULL; + this->horiz_adv_x = 0; + this->vert_origin_x = 0; + this->vert_origin_y = 0; + this->vert_adv_y = 0; +} + +SPMissingGlyph::~SPMissingGlyph() { +} + +void SPMissingGlyph::build(SPDocument* doc, Inkscape::XML::Node* repr) { + SPObject::build(doc, repr); + + this->readAttr( "d" ); + this->readAttr( "horiz-adv-x" ); + this->readAttr( "vert-origin-x" ); + this->readAttr( "vert-origin-y" ); + this->readAttr( "vert-adv-y" ); +} + +void SPMissingGlyph::release() { + SPObject::release(); +} + + +void SPMissingGlyph::set(unsigned int key, const gchar* value) { + switch (key) { + case SP_ATTR_D: + { + if (this->d) { + g_free(this->d); + } + this->d = g_strdup(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + case SP_ATTR_HORIZ_ADV_X: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + if (number != this->horiz_adv_x){ + this->horiz_adv_x = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_VERT_ORIGIN_X: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + if (number != this->vert_origin_x){ + this->vert_origin_x = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_VERT_ORIGIN_Y: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + if (number != this->vert_origin_y){ + this->vert_origin_y = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + case SP_ATTR_VERT_ADV_Y: + { + double number = value ? g_ascii_strtod(value, 0) : 0; + if (number != this->vert_adv_y){ + this->vert_adv_y = number; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + } + default: + { + SPObject::set(key, value); + break; + } + } +} + +#define COPY_ATTR(rd,rs,key) (rd)->setAttribute((key), rs->attribute(key)); + +Inkscape::XML::Node* SPMissingGlyph::write(Inkscape::XML::Document* xml_doc, Inkscape::XML::Node* repr, guint flags) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:glyph"); + } + + /* I am commenting out this part because I am not certain how does it work. I will have to study it later. Juca + repr->setAttribute("d", glyph->d); + sp_repr_set_svg_double(repr, "horiz-adv-x", glyph->horiz_adv_x); + sp_repr_set_svg_double(repr, "vert-origin-x", glyph->vert_origin_x); + sp_repr_set_svg_double(repr, "vert-origin-y", glyph->vert_origin_y); + sp_repr_set_svg_double(repr, "vert-adv-y", glyph->vert_adv_y); + */ + if (repr != this->getRepr()) { + + // TODO + // All the COPY_ATTR functions below use + // XML Tree directly while they shouldn't. + COPY_ATTR(repr, this->getRepr(), "d"); + COPY_ATTR(repr, this->getRepr(), "horiz-adv-x"); + COPY_ATTR(repr, this->getRepr(), "vert-origin-x"); + COPY_ATTR(repr, this->getRepr(), "vert-origin-y"); + COPY_ATTR(repr, this->getRepr(), "vert-adv-y"); + } + + SPObject::write(xml_doc, repr, flags); + + return repr; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-missing-glyph.h b/src/object/sp-missing-glyph.h new file mode 100644 index 000000000..06bc92231 --- /dev/null +++ b/src/object/sp-missing-glyph.h @@ -0,0 +1,40 @@ +#ifndef SEEN_SP_MISSING_GLYPH_H +#define SEEN_SP_MISSING_GLYPH_H + +/* + * SVG element implementation + * + * Authors: + * Felipe C. da S. Sanches + * + * Copyright (C) 2008 Felipe C. da S. Sanches + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-object.h" + +#define SP_MISSING_GLYPH(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_MISSING_GLYPH(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPMissingGlyph : public SPObject { +public: + SPMissingGlyph(); + virtual ~SPMissingGlyph(); + + char* d; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + virtual void set(unsigned int key, char const* value); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); + +private: + double horiz_adv_x; + double vert_origin_x; + double vert_origin_y; + double vert_adv_y; +}; + +#endif //#ifndef __SP_MISSING_GLYPH_H__ diff --git a/src/object/sp-namedview.cpp b/src/object/sp-namedview.cpp new file mode 100644 index 000000000..59c7129f6 --- /dev/null +++ b/src/object/sp-namedview.cpp @@ -0,0 +1,1188 @@ +/* + * implementation + * + * Authors: + * Lauris Kaplinski + * bulia byak + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 2006 Johan Engelen + * Copyright (C) 1999-2013 Authors + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include "event-log.h" +#include <2geom/transforms.h> + +#include "display/canvas-grid.h" +#include "util/units.h" +#include "svg/svg-color.h" +#include "xml/repr.h" +#include "attributes.h" +#include "document.h" +#include "document-undo.h" +#include "desktop-events.h" +#include "enums.h" +#include "ui/monitor.h" + +#include "sp-guide.h" +#include "sp-item-group.h" +#include "sp-namedview.h" +#include "preferences.h" +#include "desktop.h" +#include "conn-avoid-ref.h" // for defaultConnSpacing. +#include "sp-root.h" +#include + +using Inkscape::DocumentUndo; +using Inkscape::Util::unit_table; + +#define DEFAULTGRIDCOLOR 0x3f3fff25 +#define DEFAULTGRIDEMPCOLOR 0x3f3fff60 +#define DEFAULTGRIDEMPSPACING 5 +#define DEFAULTGUIDECOLOR 0x0000ff7f +#define DEFAULTGUIDEHICOLOR 0xff00007f +#define DEFAULTBORDERCOLOR 0x000000ff +#define DEFAULTPAGECOLOR 0xffffff00 + +static void sp_namedview_setup_guides(SPNamedView * nv); +static void sp_namedview_lock_guides(SPNamedView * nv); +static void sp_namedview_show_single_guide(SPGuide* guide, bool show); +static void sp_namedview_lock_single_guide(SPGuide* guide, bool show); + +static gboolean sp_str_to_bool(const gchar *str); +static gboolean sp_nv_read_opacity(const gchar *str, guint32 *color); + +SPNamedView::SPNamedView() : SPObjectGroup(), snap_manager(this) { + + this->zoom = 0; + this->guidecolor = 0; + this->guidehicolor = 0; + this->views.clear(); + this->borderlayer = 0; + this->page_size_units = NULL; + this->window_x = 0; + this->cy = 0; + this->window_y = 0; + this->display_units = NULL; + this->page_size_units = NULL; + this->pagecolor = 0; + this->cx = 0; + this->pageshadow = 0; + this->window_width = 0; + this->window_height = 0; + this->window_maximized = 0; + this->bordercolor = 0; + + this->editable = TRUE; + this->showguides = TRUE; + this->lockguides = false; + this->grids_visible = false; + this->showborder = TRUE; + this->pagecheckerboard = FALSE; + this->showpageshadow = TRUE; + + this->guides.clear(); + this->viewcount = 0; + this->grids.clear(); + + this->default_layer_id = 0; + + this->connector_spacing = defaultConnSpacing; +} + +SPNamedView::~SPNamedView() { +} + +static void sp_namedview_generate_old_grid(SPNamedView * /*nv*/, SPDocument *document, Inkscape::XML::Node *repr) { + bool old_grid_settings_present = false; + + // set old settings + const char* gridspacingx = "1px"; + const char* gridspacingy = "1px"; + const char* gridoriginy = "0px"; + const char* gridoriginx = "0px"; + const char* gridempspacing = "5"; + const char* gridcolor = "#3f3fff"; + const char* gridempcolor = "#3f3fff"; + const char* gridopacity = "0.15"; + const char* gridempopacity = "0.38"; + + const char* value = NULL; + if ((value = repr->attribute("gridoriginx"))) { + gridoriginx = value; + old_grid_settings_present = true; + } + if ((value = repr->attribute("gridoriginy"))) { + gridoriginy = value; + old_grid_settings_present = true; + } + if ((value = repr->attribute("gridspacingx"))) { + gridspacingx = value; + old_grid_settings_present = true; + } + if ((value = repr->attribute("gridspacingy"))) { + gridspacingy = value; + old_grid_settings_present = true; + } + if ((value = repr->attribute("gridcolor"))) { + gridcolor = value; + old_grid_settings_present = true; + } + if ((value = repr->attribute("gridempcolor"))) { + gridempcolor = value; + old_grid_settings_present = true; + } + if ((value = repr->attribute("gridempspacing"))) { + gridempspacing = value; + old_grid_settings_present = true; + } + if ((value = repr->attribute("gridopacity"))) { + gridopacity = value; + old_grid_settings_present = true; + } + if ((value = repr->attribute("gridempopacity"))) { + gridempopacity = value; + old_grid_settings_present = true; + } + + if (old_grid_settings_present) { + // generate new xy grid with the correct settings + // first create the child xml node, then hook it to repr. This order is important, to not set off listeners to repr before the new node is complete. + + Inkscape::XML::Document *xml_doc = document->getReprDoc(); + Inkscape::XML::Node *newnode = xml_doc->createElement("inkscape:grid"); + newnode->setAttribute("id", "GridFromPre046Settings"); + newnode->setAttribute("type", Inkscape::CanvasGrid::getSVGName(Inkscape::GRID_RECTANGULAR)); + newnode->setAttribute("originx", gridoriginx); + newnode->setAttribute("originy", gridoriginy); + newnode->setAttribute("spacingx", gridspacingx); + newnode->setAttribute("spacingy", gridspacingy); + newnode->setAttribute("color", gridcolor); + newnode->setAttribute("empcolor", gridempcolor); + newnode->setAttribute("opacity", gridopacity); + newnode->setAttribute("empopacity", gridempopacity); + newnode->setAttribute("empspacing", gridempspacing); + + repr->appendChild(newnode); + Inkscape::GC::release(newnode); + + // remove all old settings + repr->setAttribute("gridoriginx", NULL); + repr->setAttribute("gridoriginy", NULL); + repr->setAttribute("gridspacingx", NULL); + repr->setAttribute("gridspacingy", NULL); + repr->setAttribute("gridcolor", NULL); + repr->setAttribute("gridempcolor", NULL); + repr->setAttribute("gridopacity", NULL); + repr->setAttribute("gridempopacity", NULL); + repr->setAttribute("gridempspacing", NULL); + +// SPDocumentUndo::done(doc, SP_VERB_DIALOG_NAMEDVIEW, _("Create new grid from pre0.46 grid settings")); + } +} + +void SPNamedView::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPObjectGroup::build(document, repr); + + this->readAttr( "inkscape:document-units" ); + this->readAttr( "units" ); + this->readAttr( "viewonly" ); + this->readAttr( "showguides" ); + this->readAttr( "showgrid" ); + this->readAttr( "gridtolerance" ); + this->readAttr( "guidetolerance" ); + this->readAttr( "objecttolerance" ); + this->readAttr( "guidecolor" ); + this->readAttr( "guideopacity" ); + this->readAttr( "guidehicolor" ); + this->readAttr( "guidehiopacity" ); + this->readAttr( "showborder" ); + this->readAttr( "inkscape:showpageshadow" ); + this->readAttr( "borderlayer" ); + this->readAttr( "bordercolor" ); + this->readAttr( "borderopacity" ); + this->readAttr( "pagecolor" ); + this->readAttr( "inkscape:pagecheckerboard" ); + this->readAttr( "inkscape:pageopacity" ); + this->readAttr( "inkscape:pageshadow" ); + this->readAttr( "inkscape:zoom" ); + this->readAttr( "inkscape:cx" ); + this->readAttr( "inkscape:cy" ); + this->readAttr( "inkscape:window-width" ); + this->readAttr( "inkscape:window-height" ); + this->readAttr( "inkscape:window-x" ); + this->readAttr( "inkscape:window-y" ); + this->readAttr( "inkscape:window-maximized" ); + this->readAttr( "inkscape:snap-global" ); + this->readAttr( "inkscape:snap-bbox" ); + this->readAttr( "inkscape:snap-nodes" ); + this->readAttr( "inkscape:snap-others" ); + this->readAttr( "inkscape:snap-from-guide" ); + this->readAttr( "inkscape:snap-center" ); + this->readAttr( "inkscape:snap-smooth-nodes" ); + this->readAttr( "inkscape:snap-midpoints" ); + this->readAttr( "inkscape:snap-object-midpoints" ); + this->readAttr( "inkscape:snap-text-baseline" ); + this->readAttr( "inkscape:snap-bbox-edge-midpoints" ); + this->readAttr( "inkscape:snap-bbox-midpoints" ); + this->readAttr( "inkscape:snap-to-guides" ); + this->readAttr( "inkscape:snap-grids" ); + this->readAttr( "inkscape:snap-intersection-paths" ); + this->readAttr( "inkscape:object-paths" ); + this->readAttr( "inkscape:snap-perpendicular" ); + this->readAttr( "inkscape:snap-tangential" ); + this->readAttr( "inkscape:snap-path-clip" ); + this->readAttr( "inkscape:snap-path-mask" ); + this->readAttr( "inkscape:object-nodes" ); + this->readAttr( "inkscape:bbox-paths" ); + this->readAttr( "inkscape:bbox-nodes" ); + this->readAttr( "inkscape:snap-page" ); + this->readAttr( "inkscape:current-layer" ); + this->readAttr( "inkscape:connector-spacing" ); + this->readAttr( "inkscape:lockguides" ); + + /* Construct guideline list */ + for (auto& o: children) { + if (SP_IS_GUIDE(&o)) { + SPGuide * g = SP_GUIDE(&o); + this->guides.push_back(g); + //g_object_set(G_OBJECT(g), "color", nv->guidecolor, "hicolor", nv->guidehicolor, NULL); + g->setColor(this->guidecolor); + g->setHiColor(this->guidehicolor); + g->readAttr( "inkscape:color" ); + } + } + + // backwards compatibility with grid settings (pre 0.46) + sp_namedview_generate_old_grid(this, document, repr); +} + +void SPNamedView::release() { + this->guides.clear(); + + // delete grids: + for(std::vector::const_iterator it=this->grids.begin();it!=this->grids.end();++it ) + delete *it; + this->grids.clear(); + SPObjectGroup::release(); +} + +void SPNamedView::set(unsigned int key, const gchar* value) { + switch (key) { + case SP_ATTR_VIEWONLY: + this->editable = (!value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SHOWGUIDES: + if (!value) { // show guides if not specified, for backwards compatibility + this->showguides = TRUE; + } else { + this->showguides = sp_str_to_bool(value); + } + sp_namedview_setup_guides(this); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SHOWGRIDS: + if (!value) { // don't show grids if not specified, for backwards compatibility + this->grids_visible = false; + } else { + this->grids_visible = sp_str_to_bool(value); + } + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_GRIDTOLERANCE: + this->snap_manager.snapprefs.setGridTolerance(value ? g_ascii_strtod(value, NULL) : 10000); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_GUIDETOLERANCE: + this->snap_manager.snapprefs.setGuideTolerance(value ? g_ascii_strtod(value, NULL) : 20); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_OBJECTTOLERANCE: + this->snap_manager.snapprefs.setObjectTolerance(value ? g_ascii_strtod(value, NULL) : 20); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_GUIDECOLOR: + this->guidecolor = (this->guidecolor & 0xff) | (DEFAULTGUIDECOLOR & 0xffffff00); + + if (value) { + this->guidecolor = (this->guidecolor & 0xff) | sp_svg_read_color(value, this->guidecolor); + } + + for(std::vector::const_iterator it=this->guides.begin();it!=this->guides.end();++it ) { + (*it)->setColor(this->guidecolor); + (*it)->readAttr("inkscape:color"); + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_GUIDEOPACITY: + this->guidecolor = (this->guidecolor & 0xffffff00) | (DEFAULTGUIDECOLOR & 0xff); + sp_nv_read_opacity(value, &this->guidecolor); + + for(std::vector::const_iterator it=this->guides.begin();it!=this->guides.end();++it ) { + (*it)->setColor(this->guidecolor); + (*it)->readAttr("inkscape:color"); + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_GUIDEHICOLOR: + this->guidehicolor = (this->guidehicolor & 0xff) | (DEFAULTGUIDEHICOLOR & 0xffffff00); + + if (value) { + this->guidehicolor = (this->guidehicolor & 0xff) | sp_svg_read_color(value, this->guidehicolor); + } + for(std::vector::const_iterator it=this->guides.begin();it!=this->guides.end();++it ) { + (*it)->setHiColor(this->guidehicolor); + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_GUIDEHIOPACITY: + this->guidehicolor = (this->guidehicolor & 0xffffff00) | (DEFAULTGUIDEHICOLOR & 0xff); + sp_nv_read_opacity(value, &this->guidehicolor); + for(std::vector::const_iterator it=this->guides.begin();it!=this->guides.end();++it ) { + (*it)->setHiColor(this->guidehicolor); + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SHOWBORDER: + this->showborder = (value) ? sp_str_to_bool (value) : TRUE; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_BORDERLAYER: + this->borderlayer = SP_BORDER_LAYER_BOTTOM; + if (value && !strcasecmp(value, "true")) this->borderlayer = SP_BORDER_LAYER_TOP; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_BORDERCOLOR: + this->bordercolor = (this->bordercolor & 0xff) | (DEFAULTBORDERCOLOR & 0xffffff00); + if (value) { + this->bordercolor = (this->bordercolor & 0xff) | sp_svg_read_color (value, this->bordercolor); + } + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_BORDEROPACITY: + this->bordercolor = (this->bordercolor & 0xffffff00) | (DEFAULTBORDERCOLOR & 0xff); + sp_nv_read_opacity(value, &this->bordercolor); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_PAGECOLOR: + this->pagecolor = (this->pagecolor & 0xff) | (DEFAULTPAGECOLOR & 0xffffff00); + if (value) { + this->pagecolor = (this->pagecolor & 0xff) | sp_svg_read_color(value, this->pagecolor); + } + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_PAGECHECKERBOARD: + this->pagecheckerboard = (value) ? sp_str_to_bool (value) : false; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_PAGEOPACITY: + this->pagecolor = (this->pagecolor & 0xffffff00) | (DEFAULTPAGECOLOR & 0xff); + sp_nv_read_opacity(value, &this->pagecolor); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_PAGESHADOW: + this->pageshadow = value? atoi(value) : 2; // 2 is the default + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_SHOWPAGESHADOW: + this->showpageshadow = (value) ? sp_str_to_bool(value) : TRUE; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_ZOOM: + this->zoom = value ? g_ascii_strtod(value, NULL) : 0; // zero means not set + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_CX: + this->cx = value ? g_ascii_strtod(value, NULL) : HUGE_VAL; // HUGE_VAL means not set + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_CY: + this->cy = value ? g_ascii_strtod(value, NULL) : HUGE_VAL; // HUGE_VAL means not set + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_WINDOW_WIDTH: + this->window_width = value? atoi(value) : -1; // -1 means not set + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_WINDOW_HEIGHT: + this->window_height = value ? atoi(value) : -1; // -1 means not set + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_WINDOW_X: + this->window_x = value ? atoi(value) : 0; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_WINDOW_Y: + this->window_y = value ? atoi(value) : 0; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_WINDOW_MAXIMIZED: + this->window_maximized = value ? atoi(value) : 0; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_SNAP_GLOBAL: + this->snap_manager.snapprefs.setSnapEnabledGlobally(value ? sp_str_to_bool(value) : TRUE); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_SNAP_BBOX: + this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_BBOX_CATEGORY, value ? sp_str_to_bool(value) : FALSE); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_SNAP_NODE: + this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_NODE_CATEGORY, value ? sp_str_to_bool(value) : TRUE); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_SNAP_OTHERS: + this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_OTHERS_CATEGORY, value ? sp_str_to_bool(value) : TRUE); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_SNAP_ROTATION_CENTER: + this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_ROTATION_CENTER, value ? sp_str_to_bool(value) : FALSE); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_SNAP_GRID: + this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_GRID, value ? sp_str_to_bool(value) : TRUE); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_SNAP_GUIDE: + this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_GUIDE, value ? sp_str_to_bool(value) : TRUE); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_SNAP_NODE_SMOOTH: + this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_NODE_SMOOTH, value ? sp_str_to_bool(value) : FALSE); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_SNAP_LINE_MIDPOINT: + this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_LINE_MIDPOINT, value ? sp_str_to_bool(value) : FALSE); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_SNAP_OBJECT_MIDPOINT: + this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_OBJECT_MIDPOINT, value ? sp_str_to_bool(value) : FALSE); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_SNAP_TEXT_BASELINE: + this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_TEXT_BASELINE, value ? sp_str_to_bool(value) : FALSE); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_SNAP_BBOX_EDGE_MIDPOINT: + this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_BBOX_EDGE_MIDPOINT, value ? sp_str_to_bool(value) : FALSE); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_SNAP_BBOX_MIDPOINT: + this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_BBOX_MIDPOINT, value ? sp_str_to_bool(value) : FALSE); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_SNAP_PATH_INTERSECTION: + this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_PATH_INTERSECTION, value ? sp_str_to_bool(value) : FALSE); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_SNAP_PATH: + this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_PATH, value ? sp_str_to_bool(value) : FALSE); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_SNAP_PERP: + this->snap_manager.snapprefs.setSnapPerp(value ? sp_str_to_bool(value) : FALSE); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_SNAP_TANG: + this->snap_manager.snapprefs.setSnapTang(value ? sp_str_to_bool(value) : FALSE); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_SNAP_PATH_CLIP: + this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_PATH_CLIP, value ? sp_str_to_bool(value) : FALSE); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_SNAP_PATH_MASK: + this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_PATH_MASK, value ? sp_str_to_bool(value) : FALSE); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_SNAP_NODE_CUSP: + this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_NODE_CUSP, value ? sp_str_to_bool(value) : TRUE); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_SNAP_BBOX_EDGE: + this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_BBOX_EDGE, value ? sp_str_to_bool(value) : FALSE); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_SNAP_BBOX_CORNER: + this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_BBOX_CORNER, value ? sp_str_to_bool(value) : FALSE); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_SNAP_PAGE_BORDER: + this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_PAGE_BORDER, value ? sp_str_to_bool(value) : FALSE); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_CURRENT_LAYER: + this->default_layer_id = value ? g_quark_from_string(value) : 0; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_CONNECTOR_SPACING: + this->connector_spacing = value ? g_ascii_strtod(value, NULL) : + defaultConnSpacing; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_INKSCAPE_DOCUMENT_UNITS: { + /* The default display unit if the document doesn't override this: e.g. for files saved as + * `plain SVG', or non-inkscape files, or files created by an inkscape 0.40 & + * earlier. + * + * Note that these units are not the same as the units used for the values in SVG! + * + * We default to `px'. + */ + static Inkscape::Util::Unit const *px = unit_table.getUnit("px"); + Inkscape::Util::Unit const *new_unit = px; + + if (value && document->getRoot()->viewBox_set) { + Inkscape::Util::Unit const *const req_unit = unit_table.getUnit(value); + if ( !unit_table.hasUnit(value) ) { + g_warning("Unrecognized unit `%s'", value); + /* fixme: Document errors should be reported in the status bar or + * the like (e.g. as per + * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing); g_log + * should be only for programmer errors. */ + } else if ( req_unit->isAbsolute() ) { + new_unit = req_unit; + } else { + g_warning("Document units must be absolute like `mm', `pt' or `px', but found `%s'", + value); + /* fixme: Don't use g_log (see above). */ + } + } + this->display_units = new_unit; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + case SP_ATTR_UNITS: { + // Only used in "Custom size" section of Document Properties dialog + Inkscape::Util::Unit const *new_unit = NULL; + + if (value) { + Inkscape::Util::Unit const *const req_unit = unit_table.getUnit(value); + if ( !unit_table.hasUnit(value) ) { + g_warning("Unrecognized unit `%s'", value); + /* fixme: Document errors should be reported in the status bar or + * the like (e.g. as per + * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing); g_log + * should be only for programmer errors. */ + } else if ( req_unit->isAbsolute() ) { + new_unit = req_unit; + } else { + g_warning("Document units must be absolute like `mm', `pt' or `px', but found `%s'", + value); + /* fixme: Don't use g_log (see above). */ + } + } + this->page_size_units = new_unit; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + case SP_ATTR_INKSCAPE_LOCKGUIDES: + this->lockguides = value ? sp_str_to_bool(value) : FALSE; + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + default: + SPObjectGroup::set(key, value); + break; + } +} + +/** +* add a grid item from SVG-repr. Check if this namedview already has a gridobject for this one! If desktop=null, add grid-canvasitem to all desktops of this namedview, +* otherwise only add it to the specified desktop. +*/ +static Inkscape::CanvasGrid* +sp_namedview_add_grid(SPNamedView *nv, Inkscape::XML::Node *repr, SPDesktop *desktop) { + Inkscape::CanvasGrid* grid = NULL; + //check if namedview already has an object for this grid + for(std::vector::const_iterator it=nv->grids.begin();it!=nv->grids.end();++it ) { + if (repr == (*it)->repr) { + grid = (*it); + break; + } + } + + if (!grid) { + //create grid object + Inkscape::GridType gridtype = Inkscape::CanvasGrid::getGridTypeFromSVGName(repr->attribute("type")); + if (!nv->document) { + g_warning("sp_namedview_add_grid - how come doc is null here?!"); + return NULL; + } + grid = Inkscape::CanvasGrid::NewGrid(nv, repr, nv->document, gridtype); + nv->grids.push_back(grid); + } + + if (!desktop) { + //add canvasitem to all desktops + for(std::vector::const_iterator it=nv->views.begin();it!=nv->views.end();++it ) { + grid->createCanvasItem(*it); + } + } else { + //add canvasitem only for specified desktop + grid->createCanvasItem(desktop); + } + + return grid; +} + +void SPNamedView::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) { + SPObjectGroup::child_added(child, ref); + + if (!strcmp(child->name(), "inkscape:grid")) { + sp_namedview_add_grid(this, child, NULL); + } else { + SPObject *no = this->document->getObjectByRepr(child); + if ( !SP_IS_OBJECT(no) ) { + return; + } + + if (SP_IS_GUIDE(no)) { + SPGuide *g = (SPGuide *) no; + this->guides.push_back(g); + + //g_object_set(G_OBJECT(g), "color", this->guidecolor, "hicolor", this->guidehicolor, NULL); + g->setColor(this->guidecolor); + g->setHiColor(this->guidehicolor); + g->readAttr("inkscape:color"); + + if (this->editable) { + for(std::vector::const_iterator it=this->views.begin();it!=this->views.end();++it ) { + g->SPGuide::showSPGuide((*it)->guides, (GCallback) sp_dt_guide_event); + + if ((*it)->guides_active) { + g->sensitize((*it)->getCanvas(), TRUE); + } + + sp_namedview_show_single_guide(SP_GUIDE(g), this->showguides); + } + } + } + } +} + +void SPNamedView::remove_child(Inkscape::XML::Node *child) { + if (!strcmp(child->name(), "inkscape:grid")) { + for(std::vector::iterator it=this->grids.begin();it!=this->grids.end();++it ) { + if ( (*it)->repr == child ) { + delete (*it); + this->grids.erase(it); + break; + } + } + } else { + for(std::vector::iterator it=this->guides.begin();it!=this->guides.end();++it ) { + if ( (*it)->getRepr() == child ) { + this->guides.erase(it); + break; + } + } + } + + SPObjectGroup::remove_child(child); +} + +Inkscape::XML::Node* SPNamedView::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if ( ( flags & SP_OBJECT_WRITE_EXT ) && + repr != this->getRepr() ) + { + if (repr) { + repr->mergeFrom(this->getRepr(), "id"); + } else { + repr = this->getRepr()->duplicate(xml_doc); + } + } + + return repr; +} + +void SPNamedView::show(SPDesktop *desktop) +{ + for(std::vector::const_iterator it=this->guides.begin();it!=this->guides.end();++it ) { + (*it)->showSPGuide( desktop->guides, (GCallback) sp_dt_guide_event); + if (desktop->guides_active) { + (*it)->sensitize(desktop->getCanvas(), TRUE); + } + sp_namedview_show_single_guide((*it), showguides); + } + + views.push_back(desktop); + + // generate grids specified in SVG: + Inkscape::XML::Node *repr = this->getRepr(); + if (repr) { + for (Inkscape::XML::Node * child = repr->firstChild() ; child != NULL; child = child->next() ) { + if (!strcmp(child->name(), "inkscape:grid")) { + sp_namedview_add_grid(this, child, desktop); + } + } + } + + desktop->showGrids(grids_visible, false); +} + +/* + * Restores window geometry from the document settings or defaults in prefs + */ +void sp_namedview_window_from_document(SPDesktop *desktop) +{ + SPNamedView *nv = desktop->namedview; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int window_geometry = prefs->getInt("/options/savewindowgeometry/value", PREFS_WINDOW_GEOMETRY_NONE); + int default_size = prefs->getInt("/options/defaultwindowsize/value", PREFS_WINDOW_SIZE_NATURAL); + bool new_document = (nv->window_width <= 0) || (nv->window_height <= 0); + bool show_dialogs = true; + + // restore window size and position stored with the document + Gtk::Window *win = desktop->getToplevel(); + g_assert(win); + if (window_geometry == PREFS_WINDOW_GEOMETRY_LAST) { + // do nothing, as we already have code for that in interface.cpp + // TODO: Probably should not do similar things in two places + } else if ((window_geometry == PREFS_WINDOW_GEOMETRY_FILE && nv->window_maximized) || + (new_document && (default_size == PREFS_WINDOW_SIZE_MAXIMIZED))) { + win->maximize(); + } else { + const int MIN_WINDOW_SIZE = 600; + int w = 0; + int h = 0; + bool move_to_screen = false; + if (window_geometry == PREFS_WINDOW_GEOMETRY_FILE && !new_document) { + Gdk::Rectangle monitor_geometry = Inkscape::UI::get_monitor_geometry_at_point(nv->window_x, nv->window_y); + w = MIN(monitor_geometry.get_width(), nv->window_width); + h = MIN(monitor_geometry.get_height(), nv->window_height); + move_to_screen = true; + } else if (default_size == PREFS_WINDOW_SIZE_LARGE) { + Gdk::Rectangle monitor_geometry = Inkscape::UI::get_monitor_geometry_at_window(win->get_window()); + w = MAX(0.75 * monitor_geometry.get_width(), MIN_WINDOW_SIZE); + h = MAX(0.75 * monitor_geometry.get_height(), MIN_WINDOW_SIZE); + } else if (default_size == PREFS_WINDOW_SIZE_SMALL) { + w = h = MIN_WINDOW_SIZE; + } else if (default_size == PREFS_WINDOW_SIZE_NATURAL) { + // don't set size (i.e. keep the gtk+ default, which will be the natural size) + // unless gtk+ decided it would be a good idea to show a window that is larger than the screen + Gdk::Rectangle monitor_geometry = Inkscape::UI::get_monitor_geometry_at_window(win->get_window()); + int monitor_width = monitor_geometry.get_width(); + int monitor_height = monitor_geometry.get_height(); + int window_width, window_height; + win->get_size(window_width, window_height); + if (window_width > monitor_width || window_height > monitor_height) { + w = std::min(monitor_width, window_width); + h = std::min(monitor_height, window_height); + } + } + if ((w > 0) && (h > 0)) { +#ifndef WIN32 + gint dx= 0; + gint dy = 0; + gint dw = 0; + gint dh = 0; + desktop->getWindowGeometry(dx, dy, dw, dh); + if ((w != dw) || (h != dh)) { + // Don't show dialogs when window is initially resized on OSX/Linux due to gdl dock bug + // This will happen on sp_desktop_widget_size_allocate + show_dialogs = FALSE; + } +#endif + desktop->setWindowSize(w, h); + if (move_to_screen) { + win->hide(); + desktop->setWindowPosition(Geom::Point(nv->window_x, nv->window_y)); + win->show(); + } + } + } + + // Cancel any history of transforms up to this point (must be before call to zoom). + desktop->clear_transform_history(); + + // restore zoom and view + if (nv->zoom != 0 && nv->zoom != HUGE_VAL && !IS_NAN(nv->zoom) + && nv->cx != HUGE_VAL && !IS_NAN(nv->cx) + && nv->cy != HUGE_VAL && !IS_NAN(nv->cy)) { + desktop->zoom_absolute_center_point( Geom::Point(nv->cx, nv->cy), nv->zoom ); + } else if (desktop->getDocument()) { // document without saved zoom, zoom to its page + desktop->zoom_page(); + } + + if (show_dialogs) { + desktop->show_dialogs(); + } +} + +void SPNamedView::writeNewGrid(SPDocument *document,int gridtype) +{ + g_assert(this->getRepr() != NULL); + Inkscape::CanvasGrid::writeNewGridToRepr(this->getRepr(),document,static_cast(gridtype)); +} + +bool SPNamedView::getSnapGlobal() const +{ + return this->snap_manager.snapprefs.getSnapEnabledGlobally(); +} + +void SPNamedView::setSnapGlobal(bool v) +{ + g_assert(this->getRepr() != NULL); + sp_repr_set_boolean(this->getRepr(), "inkscape:snap-global", v); +} + +void sp_namedview_update_layers_from_document (SPDesktop *desktop) +{ + SPObject *layer = NULL; + SPDocument *document = desktop->doc(); + SPNamedView *nv = desktop->namedview; + if ( nv->default_layer_id != 0 ) { + layer = document->getObjectById(g_quark_to_string(nv->default_layer_id)); + } + // don't use that object if it's not at least group + if ( !layer || !SP_IS_GROUP(layer) ) { + layer = NULL; + } + // if that didn't work out, look for the topmost layer + if (!layer) { + for (auto& iter: document->getRoot()->children) { + if (desktop->isLayer(&iter)) { + layer = &iter; + } + } + } + if (layer) { + desktop->setCurrentLayer(layer); + } + + // FIXME: find a better place to do this + desktop->event_log->updateUndoVerbs(); +} + +void sp_namedview_document_from_window(SPDesktop *desktop) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int window_geometry = prefs->getInt("/options/savewindowgeometry/value", PREFS_WINDOW_GEOMETRY_NONE); + bool save_geometry_in_file = window_geometry == PREFS_WINDOW_GEOMETRY_FILE; + bool save_viewport_in_file = prefs->getBool("/options/savedocviewport/value", true); + Inkscape::XML::Node *view = desktop->namedview->getRepr(); + Geom::Rect const r = desktop->get_display_area(); + + // saving window geometry is not undoable + bool saved = DocumentUndo::getUndoSensitive(desktop->getDocument()); + DocumentUndo::setUndoSensitive(desktop->getDocument(), false); + + if (save_viewport_in_file) { + sp_repr_set_svg_double(view, "inkscape:zoom", desktop->current_zoom()); + sp_repr_set_svg_double(view, "inkscape:cx", r.midpoint()[Geom::X]); + sp_repr_set_svg_double(view, "inkscape:cy", r.midpoint()[Geom::Y]); + } + + if (save_geometry_in_file) { + gint w, h, x, y; + desktop->getWindowGeometry(x, y, w, h); + sp_repr_set_int(view, "inkscape:window-width", w); + sp_repr_set_int(view, "inkscape:window-height", h); + sp_repr_set_int(view, "inkscape:window-x", x); + sp_repr_set_int(view, "inkscape:window-y", y); + sp_repr_set_int(view, "inkscape:window-maximized", desktop->is_maximized()); + } + + view->setAttribute("inkscape:current-layer", desktop->currentLayer()->getId()); + + // restore undoability + DocumentUndo::setUndoSensitive(desktop->getDocument(), saved); +} + +void SPNamedView::hide(SPDesktop const *desktop) +{ + g_assert(desktop != NULL); + g_assert(std::find(views.begin(),views.end(),desktop)!=views.end()); + for(std::vector::iterator it=this->guides.begin();it!=this->guides.end();++it ) { + (*it)->hideSPGuide(desktop->getCanvas()); + } + views.erase(std::remove(views.begin(),views.end(),desktop),views.end()); +} + +void SPNamedView::activateGuides(void* desktop, bool active) +{ + g_assert(desktop != NULL); + g_assert(std::find(views.begin(),views.end(),desktop)!=views.end()); + + SPDesktop *dt = static_cast(desktop); + for(std::vector::iterator it=this->guides.begin();it!=this->guides.end();++it ) { + (*it)->sensitize(dt->getCanvas(), active); + } +} + +static void sp_namedview_setup_guides(SPNamedView *nv) +{ + for(std::vector::iterator it=nv->guides.begin();it!=nv->guides.end();++it ) { + sp_namedview_show_single_guide(*it, nv->showguides); + } +} + +static void sp_namedview_lock_guides(SPNamedView *nv) +{ + for(std::vector::iterator it=nv->guides.begin();it!=nv->guides.end();++it ) { + sp_namedview_lock_single_guide(*it, nv->lockguides); + } +} + +static void sp_namedview_show_single_guide(SPGuide* guide, bool show) +{ + if (show) { + guide->showSPGuide(); + } else { + guide->hideSPGuide(); + } +} + +static void sp_namedview_lock_single_guide(SPGuide* guide, bool locked) +{ + guide->set_locked(locked, true); +} + +void sp_namedview_toggle_guides(SPDocument *doc, Inkscape::XML::Node *repr) +{ + unsigned int v; + unsigned int set = sp_repr_get_boolean(repr, "showguides", &v); + if (!set) { // hide guides if not specified, for backwards compatibility + v = FALSE; + } else { + v = !v; + } + + bool saved = DocumentUndo::getUndoSensitive(doc); + DocumentUndo::setUndoSensitive(doc, false); + sp_repr_set_boolean(repr, "showguides", v); + DocumentUndo::setUndoSensitive(doc, saved); + + doc->setModifiedSinceSave(); +} + +void sp_namedview_guides_toggle_lock(SPDocument *doc, SPNamedView * namedview) +{ + unsigned int v; + Inkscape::XML::Node *repr = namedview->getRepr(); + unsigned int set = sp_repr_get_boolean(repr, "inkscape:lockguides", &v); + if (!set) { // hide guides if not specified, for backwards compatibility + v = true; + } else { + v = !v; + } + + bool saved = DocumentUndo::getUndoSensitive(doc); + DocumentUndo::setUndoSensitive(doc, false); + sp_repr_set_boolean(repr, "inkscape:lockguides", v); + sp_namedview_lock_guides(namedview); + DocumentUndo::setUndoSensitive(doc, saved); + doc->setModifiedSinceSave(); +} + +void sp_namedview_show_grids(SPNamedView * namedview, bool show, bool dirty_document) +{ + namedview->grids_visible = show; + + SPDocument *doc = namedview->document; + Inkscape::XML::Node *repr = namedview->getRepr(); + + bool saved = DocumentUndo::getUndoSensitive(doc); + DocumentUndo::setUndoSensitive(doc, false); + sp_repr_set_boolean(repr, "showgrid", namedview->grids_visible); + DocumentUndo::setUndoSensitive(doc, saved); + + /* we don't want the document to get dirty on startup; that's when + we call this function with dirty_document = false */ + if (dirty_document) { + doc->setModifiedSinceSave(); + } +} + +gchar const *SPNamedView::getName() const +{ + SPException ex; + SP_EXCEPTION_INIT(&ex); + return this->getAttribute("id", &ex); +} + +guint SPNamedView::getViewCount() +{ + return ++viewcount; +} + +std::vector const SPNamedView::getViewList() const +{ + return views; +} + +/* This should be moved somewhere */ + +static gboolean sp_str_to_bool(const gchar *str) +{ + if (str) { + if (!g_ascii_strcasecmp(str, "true") || + !g_ascii_strcasecmp(str, "yes") || + !g_ascii_strcasecmp(str, "y") || + (atoi(str) != 0)) { + return TRUE; + } + } + + return FALSE; +} + +static gboolean sp_nv_read_opacity(const gchar *str, guint32 *color) +{ + if (!str) { + return FALSE; + } + + gchar *u; + gdouble v = g_ascii_strtod(str, &u); + if (!u) { + return FALSE; + } + v = CLAMP(v, 0.0, 1.0); + + *color = (*color & 0xffffff00) | (guint32) floor(v * 255.9999); + + return TRUE; +} + +SPNamedView *sp_document_namedview(SPDocument *document, const gchar *id) +{ + g_return_val_if_fail(document != NULL, NULL); + + SPObject *nv = sp_item_group_get_child_by_name(document->getRoot(), NULL, "sodipodi:namedview"); + g_assert(nv != NULL); + + if (id == NULL) { + return (SPNamedView *) nv; + } + + while (nv && strcmp(nv->getId(), id)) { + nv = sp_item_group_get_child_by_name(document->getRoot(), nv, "sodipodi:namedview"); + } + + return (SPNamedView *) nv; +} + +SPNamedView const *sp_document_namedview(SPDocument const *document, const gchar *id) +{ + return sp_document_namedview(const_cast(document), id); // use a const_cast here to avoid duplicating code +} + +void SPNamedView::setGuides(bool v) +{ + g_assert(this->getRepr() != NULL); + sp_repr_set_boolean(this->getRepr(), "showguides", v); + sp_repr_set_boolean(this->getRepr(), "inkscape:guide-bbox", v); +} + +bool SPNamedView::getGuides() +{ + g_assert(this->getRepr() != NULL); + unsigned int v; + unsigned int set = sp_repr_get_boolean(this->getRepr(), "showguides", &v); + if (!set) { // hide guides if not specified, for backwards compatibility + v = FALSE; + } + + return v; +} +/** + * Gets page fitting margin information from the namedview node in the XML. + * \param nv_repr reference to this document's namedview + * \param key the same key used by the RegisteredScalarUnit in + * ui/widget/page-sizer.cpp + * \param margin_units units for the margin + * \param return_units units to return the result in + * \param width width in px (for percentage margins) + * \param height height in px (for percentage margins) + * \param use_width true if the this key is left or right margins, false + * otherwise. Used for percentage margins. + * \return the margin size in px, else 0.0 if anything is invalid. + */ +double SPNamedView::getMarginLength(gchar const * const key, + Inkscape::Util::Unit const * const margin_units, + Inkscape::Util::Unit const * const return_units, + double const width, + double const height, + bool const use_width) +{ + double value; + static Inkscape::Util::Unit const *percent = unit_table.getUnit("%"); + if(!this->storeAsDouble(key,&value)) { + return 0.0; + } + if (*margin_units == *percent) { + return (use_width)? width * value : height * value; + } + if (!margin_units->compatibleWith(return_units)) { + return 0.0; + } + return value; +} + +/** + * Returns namedview's default unit. + * If no default unit is set, "px" is returned + */ +Inkscape::Util::Unit const * SPNamedView::getDisplayUnit() const +{ + return display_units ? display_units : unit_table.getUnit("px"); +} + +/** + * Returns the first grid it could find that isEnabled(). Returns NULL, if none is enabled + */ +Inkscape::CanvasGrid * sp_namedview_get_first_enabled_grid(SPNamedView *namedview) +{ + for(std::vector::const_iterator it=namedview->grids.begin();it!=namedview->grids.end();++it ) { + if ((*it)->isEnabled()) + return (*it); + } + + return NULL; +} + +void SPNamedView::translateGuides(Geom::Translate const &tr) { + for(std::vector::iterator it=this->guides.begin();it!=this->guides.end();++it ) { + SPGuide &guide = *(*it); + Geom::Point point_on_line = guide.getPoint(); + point_on_line *= tr; + guide.moveto(point_on_line, true); + } +} + +void SPNamedView::translateGrids(Geom::Translate const &tr) { + for(std::vector::iterator it=this->grids.begin();it!=this->grids.end();++it ) { + (*it)->setOrigin((*it)->origin * tr); + } +} + +void SPNamedView::scrollAllDesktops(double dx, double dy, bool is_scrolling) { + for(std::vector::iterator it=this->views.begin();it!=this->views.end();++it ) { + (*it)->scroll_relative_in_svg_coords(dx, dy, is_scrolling); + } +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-namedview.h b/src/object/sp-namedview.h new file mode 100644 index 000000000..20d762bc4 --- /dev/null +++ b/src/object/sp-namedview.h @@ -0,0 +1,143 @@ +#ifndef INKSCAPE_SP_NAMEDVIEW_H +#define INKSCAPE_SP_NAMEDVIEW_H + +/* + * implementation + * + * Authors: + * Lauris Kaplinski + * Abhishek Sharma + * + * Copyright (C) 2006 Johan Engelen + * Copyright (C) Lauris Kaplinski 2000-2002 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define SP_NAMEDVIEW(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_NAMEDVIEW(obj) (dynamic_cast((SPObject*)obj) != NULL) + +#include "sp-object-group.h" +#include "snap.h" +#include "document.h" +#include "util/units.h" +#include + +namespace Inkscape { + class CanvasGrid; + namespace Util { + class Unit; + } +} + +typedef unsigned int guint32; +typedef guint32 GQuark; + +enum { + SP_BORDER_LAYER_BOTTOM, + SP_BORDER_LAYER_TOP +}; + +class SPNamedView : public SPObjectGroup { +public: + SPNamedView(); + virtual ~SPNamedView(); + + unsigned int editable : 1; + unsigned int showguides : 1; + unsigned int lockguides : 1; + unsigned int pagecheckerboard : 1; + unsigned int showborder : 1; + unsigned int showpageshadow : 1; + unsigned int borderlayer : 2; + + double zoom; + double cx; + double cy; + int window_width; + int window_height; + int window_x; + int window_y; + int window_maximized; + + SnapManager snap_manager; + std::vector grids; + bool grids_visible; + + Inkscape::Util::Unit const *display_units; // Units used for the UI (*not* the same as units of SVG coordinates) + Inkscape::Util::Unit const *page_size_units; // Only used in "Custom size" part of Document Properties dialog + + GQuark default_layer_id; + + double connector_spacing; + + guint32 guidecolor; + guint32 guidehicolor; + guint32 bordercolor; + guint32 pagecolor; + guint32 pageshadow; + + std::vector guides; + std::vector views; + + int viewcount; + + void show(SPDesktop *desktop); + void hide(SPDesktop const *desktop); + void activateGuides(void* desktop, bool active); + char const *getName() const; + unsigned int getViewCount(); + std::vector const getViewList() const; + Inkscape::Util::Unit const * getDisplayUnit() const; + + void translateGuides(Geom::Translate const &translation); + void translateGrids(Geom::Translate const &translation); + void scrollAllDesktops(double dx, double dy, bool is_scrolling); + void writeNewGrid(SPDocument *document,int gridtype); + bool getSnapGlobal() const; + void setSnapGlobal(bool v); + void setGuides(bool v); + bool getGuides(); + +private: + double getMarginLength(gchar const * const key,Inkscape::Util::Unit const * const margin_units,Inkscape::Util::Unit const * const return_units,double const width,double const height,bool const use_width); + friend class SPDocument; + +protected: + virtual void build(SPDocument *document, Inkscape::XML::Node *repr); + virtual void release(); + virtual void set(unsigned int key, char const* value); + + virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref); + virtual void remove_child(Inkscape::XML::Node* child); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); +}; + + +SPNamedView *sp_document_namedview(SPDocument *document, char const *name); +SPNamedView const *sp_document_namedview(SPDocument const *document, char const *name); + +void sp_namedview_window_from_document(SPDesktop *desktop); +void sp_namedview_document_from_window(SPDesktop *desktop); +void sp_namedview_update_layers_from_document (SPDesktop *desktop); + +void sp_namedview_toggle_guides(SPDocument *doc, Inkscape::XML::Node *repr); +void sp_namedview_guides_toggle_lock(SPDocument *doc, SPNamedView *namedview); +void sp_namedview_show_grids(SPNamedView *namedview, bool show, bool dirty_document); +Inkscape::CanvasGrid * sp_namedview_get_first_enabled_grid(SPNamedView *namedview); + + +#endif /* !INKSCAPE_SP_NAMEDVIEW_H */ + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-object-group.cpp b/src/object/sp-object-group.cpp new file mode 100644 index 000000000..f8ef855e3 --- /dev/null +++ b/src/object/sp-object-group.cpp @@ -0,0 +1,84 @@ +/* + * Abstract base class for non-item groups + * + * Authors: + * Lauris Kaplinski + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 1999-2003 Authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-object-group.h" +#include "xml/repr.h" +#include "document.h" + +SPObjectGroup::SPObjectGroup() : SPObject() { +} + +SPObjectGroup::~SPObjectGroup() { +} + +void SPObjectGroup::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) { + SPObject::child_added(child, ref); + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + + +void SPObjectGroup::remove_child(Inkscape::XML::Node *child) { + SPObject::remove_child(child); + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + + +void SPObjectGroup::order_changed(Inkscape::XML::Node *child, Inkscape::XML::Node *old_ref, Inkscape::XML::Node *new_ref) { + SPObject::order_changed(child, old_ref, new_ref); + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + + +Inkscape::XML::Node *SPObjectGroup::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if (flags & SP_OBJECT_WRITE_BUILD) { + if (!repr) { + repr = xml_doc->createElement("svg:g"); + } + + std::vector l; + for (auto& child: children) { + Inkscape::XML::Node *crepr = child.updateRepr(xml_doc, NULL, flags); + + if (crepr) { + l.push_back(crepr); + } + } + for (auto i=l.rbegin();i!=l.rend();++i) { + repr->addChild(*i, NULL); + Inkscape::GC::release(*i); + } + } else { + for (auto& child: children) { + child.updateRepr(flags); + } + } + + SPObject::write(xml_doc, repr, flags); + + return repr; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-object-group.h b/src/object/sp-object-group.h new file mode 100644 index 000000000..dcaa8a1d0 --- /dev/null +++ b/src/object/sp-object-group.h @@ -0,0 +1,46 @@ +#ifndef SEEN_SP_OBJECTGROUP_H +#define SEEN_SP_OBJECTGROUP_H + +/* + * Abstract base class for non-item groups + * + * Author: + * Lauris Kaplinski + * Abhishek Sharma + * + * Copyright (C) 1999-2003 Authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-object.h" + +#define SP_OBJECTGROUP(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_OBJECTGROUP(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPObjectGroup : public SPObject { +public: + SPObjectGroup(); + virtual ~SPObjectGroup(); + +protected: + virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref); + virtual void remove_child(Inkscape::XML::Node* child); + + virtual void order_changed(Inkscape::XML::Node* child, Inkscape::XML::Node* old, Inkscape::XML::Node* new_repr); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); +}; + +#endif // SEEN_SP_OBJECTGROUP_H +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-object.cpp b/src/object/sp-object.cpp new file mode 100644 index 000000000..bc930a430 --- /dev/null +++ b/src/object/sp-object.cpp @@ -0,0 +1,1702 @@ +/* + * SPObject implementation. + * + * Authors: + * Lauris Kaplinski + * bulia byak + * Stephen Silver + * Jon A. Cruz + * Abhishek Sharma + * Adrian Boguszewski + * + * Copyright (C) 1999-2016 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include + +#include "helper/sp-marshal.h" +#include "xml/node-event-vector.h" +#include "attributes.h" +#include "attribute-rel-util.h" +#include "color-profile.h" +#include "document.h" +#include "preferences.h" +#include "style.h" +#include "sp-factory.h" +#include "sp-paint-server.h" +#include "sp-root.h" +#include "sp-style-elem.h" +#include "sp-script.h" +#include "streq.h" +#include "strneq.h" +#include "xml/node-fns.h" +#include "debug/event-tracker.h" +#include "debug/simple-event.h" +#include "debug/demangle.h" +#include "util/format.h" +#include "util/longest-common-suffix.h" + +using std::memcpy; +using std::strchr; +using std::strcmp; +using std::strlen; +using std::strstr; + +#define noSP_OBJECT_DEBUG_CASCADE + +#define noSP_OBJECT_DEBUG + +#ifdef SP_OBJECT_DEBUG +# define debug(f, a...) { g_print("%s(%d) %s:", \ + __FILE__,__LINE__,__FUNCTION__); \ + g_print(f, ## a); \ + g_print("\n"); \ + } +#else +# define debug(f, a...) /* */ +#endif + +// Define to enable indented tracing of SPObject. +//#define OBJECT_TRACE +unsigned SPObject::indent_level = 0; + +guint update_in_progress = 0; // guard against update-during-update + +Inkscape::XML::NodeEventVector object_event_vector = { + SPObject::repr_child_added, + SPObject::repr_child_removed, + SPObject::repr_attr_changed, + SPObject::repr_content_changed, + SPObject::repr_order_changed +}; + +/** + * A friend class used to set internal members on SPObject so as to not expose settors in SPObject's public API + */ +class SPObjectImpl +{ +public: + +/** + * Null's the id member of an SPObject without attempting to free prior contents. + * + * @param[inout] obj Pointer to the object which's id shall be nulled. + */ + static void setIdNull( SPObject* obj ) { + if (obj) { + obj->id = 0; + } + } + +/** + * Sets the id member of an object, freeing any prior content. + * + * @param[inout] obj Pointer to the object which's id shall be set. + * @param[in] id New id + */ + static void setId( SPObject* obj, gchar const* id ) { + if (obj && (id != obj->id) ) { + if (obj->id) { + g_free(obj->id); + obj->id = 0; + } + if (id) { + obj->id = g_strdup(id); + } + } + } +}; + +/** + * Constructor, sets all attributes to default values. + */ +SPObject::SPObject() + : cloned(0), clone_original(NULL), uflags(0), mflags(0), hrefcount(0), _total_hrefcount(0), + document(NULL), parent(NULL), id(NULL), repr(NULL), refCount(1), hrefList(std::list()), + _successor(NULL), _collection_policy(SPObject::COLLECT_WITH_PARENT), + _label(NULL), _default_label(NULL) +{ + debug("id=%p, typename=%s",this, g_type_name_from_instance((GTypeInstance*)this)); + + //used XML Tree here. + this->getRepr(); // TODO check why this call is made + + SPObjectImpl::setIdNull(this); + + // FIXME: now we create style for all objects, but per SVG, only the following can have style attribute: + // vg, g, defs, desc, title, symbol, use, image, switch, path, rect, circle, ellipse, line, polyline, + // polygon, text, tspan, tref, textPath, altGlyph, glyphRef, marker, linearGradient, radialGradient, + // stop, pattern, clipPath, mask, filter, feImage, a, font, glyph, missing-glyph, foreignObject + this->style = new SPStyle( NULL, this ); // Is it necessary to call with "this"? + this->context_style = NULL; +} + +/** + * Destructor, frees the used memory and unreferences a potential successor of the object. + */ +SPObject::~SPObject() { + g_free(this->_label); + g_free(this->_default_label); + + this->_label = NULL; + this->_default_label = NULL; + + if (this->_successor) { + sp_object_unref(this->_successor, NULL); + this->_successor = NULL; + } + if (parent) { + parent->children.erase(parent->children.iterator_to(*this)); + } + + if( style == NULL ) { + // style pointer could be NULL if unreffed too many times. + // Conjecture: style pointer is never NULL. + std::cerr << "SPObject::~SPObject(): style pointer is NULL" << std::endl; + } else if( style->refCount() > 1 ) { + // Conjecture: style pointer should be unreffed by other classes before reaching here. + // Conjecture is false for SPTSpan where ref is held by InputStreamTextSource. + // As an additional note: + // The outer tspan of a nested tspan will result in a ref count of five: one for the + // TSpan itself, one for the InputStreamTextSource instance before the inner tspan and + // one for the one after, along with one for each corresponding DrawingText instance. + // std::cerr << "SPObject::~SPObject(): someone else still holding ref to style" << std::endl; + // + sp_style_unref( this->style ); + } else { + delete this->style; + } +} + +// CPPIFY: make pure virtual +void SPObject::read_content() { + //throw; +} + +void SPObject::update(SPCtx* /*ctx*/, unsigned int /*flags*/) { + //throw; +} + +void SPObject::modified(unsigned int /*flags*/) { +#ifdef OBJECT_TRACE + objectTrace( "SPObject::modified (default) (empty function)" ); + objectTrace( "SPObject::modified (default)", false ); +#endif + //throw; +} + +namespace { + +namespace Debug = Inkscape::Debug; +namespace Util = Inkscape::Util; + +typedef Debug::SimpleEvent BaseRefCountEvent; + +class RefCountEvent : public BaseRefCountEvent { +public: + RefCountEvent(SPObject *object, int bias, Util::ptr_shared name) + : BaseRefCountEvent(name) + { + _addProperty("object", Util::format("%p", object)); + _addProperty("class", Debug::demangle(g_type_name(G_TYPE_FROM_INSTANCE(object)))); + _addProperty("new-refcount", Util::format("%d", G_OBJECT(object)->ref_count + bias)); + } +}; + +class RefEvent : public RefCountEvent { +public: + RefEvent(SPObject *object) + : RefCountEvent(object, 1, Util::share_static_string("sp-object-ref")) + {} +}; + +class UnrefEvent : public RefCountEvent { +public: + UnrefEvent(SPObject *object) + : RefCountEvent(object, -1, Util::share_static_string("sp-object-unref")) + {} +}; + +} + +gchar const* SPObject::getId() const { + return id; +} + +Inkscape::XML::Node * SPObject::getRepr() { + return repr; +} + +Inkscape::XML::Node const* SPObject::getRepr() const{ + return repr; +} + + +SPObject *sp_object_ref(SPObject *object, SPObject *owner) +{ + g_return_val_if_fail(object != NULL, NULL); + g_return_val_if_fail(SP_IS_OBJECT(object), NULL); + g_return_val_if_fail(!owner || SP_IS_OBJECT(owner), NULL); + + Inkscape::Debug::EventTracker tracker(object); + //g_object_ref(G_OBJECT(object)); + object->refCount++; + return object; +} + +SPObject *sp_object_unref(SPObject *object, SPObject *owner) +{ + g_return_val_if_fail(object != NULL, NULL); + g_return_val_if_fail(SP_IS_OBJECT(object), NULL); + g_return_val_if_fail(!owner || SP_IS_OBJECT(owner), NULL); + + Inkscape::Debug::EventTracker tracker(object); + //g_object_unref(G_OBJECT(object)); + object->refCount--; + + if (object->refCount <= 0) { + delete object; + } + + return NULL; +} + +SPObject *sp_object_href(SPObject *object, SPObject* owner) +{ + g_return_val_if_fail(object != NULL, NULL); + g_return_val_if_fail(SP_IS_OBJECT(object), NULL); + + object->hrefcount++; + object->_updateTotalHRefCount(1); + + if(owner) + object->hrefList.push_front(owner); + + return object; +} + +SPObject *sp_object_hunref(SPObject *object, SPObject* owner) +{ + g_return_val_if_fail(object != NULL, NULL); + g_return_val_if_fail(SP_IS_OBJECT(object), NULL); + g_return_val_if_fail(object->hrefcount > 0, NULL); + + object->hrefcount--; + object->_updateTotalHRefCount(-1); + + if(owner) + object->hrefList.remove(owner); + + return NULL; +} + +void SPObject::_updateTotalHRefCount(int increment) { + SPObject *topmost_collectable = NULL; + for ( SPObject *iter = this ; iter ; iter = iter->parent ) { + iter->_total_hrefcount += increment; + if ( iter->_total_hrefcount < iter->hrefcount ) { + g_critical("HRefs overcounted"); + } + if ( iter->_total_hrefcount == 0 && + iter->_collection_policy != COLLECT_WITH_PARENT ) + { + topmost_collectable = iter; + } + } + if (topmost_collectable) { + topmost_collectable->requestOrphanCollection(); + } +} + +bool SPObject::isAncestorOf(SPObject const *object) const { + g_return_val_if_fail(object != NULL, false); + object = object->parent; + while (object) { + if ( object == this ) { + return true; + } + object = object->parent; + } + return false; +} + +namespace { + +bool same_objects(SPObject const &a, SPObject const &b) { + return &a == &b; +} + +} + +SPObject const *SPObject::nearestCommonAncestor(SPObject const *object) const { + g_return_val_if_fail(object != NULL, NULL); + + using Inkscape::Algorithms::longest_common_suffix; + return longest_common_suffix(this, object, NULL, &same_objects); +} + +static SPObject const *AncestorSon(SPObject const *obj, SPObject const *ancestor) { + SPObject const *result = 0; + if ( obj && ancestor ) { + if (obj->parent == ancestor) { + result = obj; + } else { + result = AncestorSon(obj->parent, ancestor); + } + } + return result; +} + +int sp_object_compare_position(SPObject const *first, SPObject const *second) +{ + int result = 0; + if (first != second) { + SPObject const *ancestor = first->nearestCommonAncestor(second); + // Need a common ancestor to be able to compare + if ( ancestor ) { + // we have an object and its ancestor (should not happen when sorting selection) + if (ancestor == first) { + result = 1; + } else if (ancestor == second) { + result = -1; + } else { + SPObject const *to_first = AncestorSon(first, ancestor); + SPObject const *to_second = AncestorSon(second, ancestor); + + g_assert(to_second->parent == to_first->parent); + + result = sp_repr_compare_position(to_first->getRepr(), to_second->getRepr()); + } + } + } + return result; +} + +bool sp_object_compare_position_bool(SPObject const *first, SPObject const *second){ + return sp_object_compare_position(first,second)<0; +} + + +SPObject *SPObject::appendChildRepr(Inkscape::XML::Node *repr) { + if ( !cloned ) { + getRepr()->appendChild(repr); + return document->getObjectByRepr(repr); + } else { + g_critical("Attempt to append repr as child of cloned object"); + return NULL; + } +} + +void SPObject::setCSS(SPCSSAttr *css, gchar const *attr) +{ + g_assert(this->getRepr() != NULL); + sp_repr_css_set(this->getRepr(), css, attr); +} + +void SPObject::changeCSS(SPCSSAttr *css, gchar const *attr) +{ + g_assert(this->getRepr() != NULL); + sp_repr_css_change(this->getRepr(), css, attr); +} + +std::vector SPObject::childList(bool add_ref, Action) { + std::vector l; + for (auto& child: children) { + if (add_ref) { + sp_object_ref(&child); + } + l.push_back(&child); + } + return l; + +} + +gchar const *SPObject::label() const { + return _label; +} + +gchar const *SPObject::defaultLabel() const { + if (_label) { + return _label; + } else { + if (!_default_label) { + if (getId()) { + _default_label = g_strdup_printf("#%s", getId()); + } else { + _default_label = g_strdup_printf("<%s>", getRepr()->name()); + } + } + return _default_label; + } +} + +void SPObject::setLabel(gchar const *label) +{ + getRepr()->setAttribute("inkscape:label", label, false); +} + + +void SPObject::requestOrphanCollection() { + g_return_if_fail(document != NULL); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + // do not remove style or script elements (Bug #276244) + if (dynamic_cast(this)) { + // leave it + } else if (dynamic_cast(this)) { + // leave it + + } else if ((! prefs->getBool("/options/cleanupswatches/value", false)) && SP_IS_PAINT_SERVER(this) && static_cast(this)->isSwatch() ) { + // leave it + } else if (IS_COLORPROFILE(this)) { + // leave it + } else { + document->queueForOrphanCollection(this); + + /** \todo + * This is a temporary hack added to make fill&stroke rebuild its + * gradient list when the defs are vacuumed. gradient-vector.cpp + * listens to the modified signal on defs, and now we give it that + * signal. Mental says that this should be made automatic by + * merging SPObjectGroup with SPObject; SPObjectGroup would issue + * this signal automatically. Or maybe just derive SPDefs from + * SPObjectGroup? + */ + + this->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG); + } +} + +void SPObject::_sendDeleteSignalRecursive() { + for (auto& child: children) { + child._delete_signal.emit(&child); + child._sendDeleteSignalRecursive(); + } +} + +void SPObject::deleteObject(bool propagate, bool propagate_descendants) +{ + sp_object_ref(this, NULL); + if ( SP_IS_LPE_ITEM(this) && SP_LPE_ITEM(this)->hasPathEffect()) { + SP_LPE_ITEM(this)->removeAllPathEffects(false); + } + if (propagate) { + _delete_signal.emit(this); + } + if (propagate_descendants) { + this->_sendDeleteSignalRecursive(); + } + + Inkscape::XML::Node *repr = getRepr(); + if (repr && repr->parent()) { + sp_repr_unparent(repr); + } + + if (_successor) { + _successor->deleteObject(propagate, propagate_descendants); + } + sp_object_unref(this, NULL); +} + +void SPObject::cropToObject(SPObject *except) +{ + std::vector toDelete; + for (auto& child: children) { + if (SP_IS_ITEM(&child)) { + if (child.isAncestorOf(except)) { + child.cropToObject(except); + } else if(&child != except) { + toDelete.push_back(&child); + } + } + } + for (std::size_t i = 0; i < toDelete.size(); ++i) { + (toDelete[i])->deleteObject(true, true); + } +} + +void SPObject::attach(SPObject *object, SPObject *prev) +{ + //g_return_if_fail(parent != NULL); + //g_return_if_fail(SP_IS_OBJECT(parent)); + g_return_if_fail(object != NULL); + g_return_if_fail(SP_IS_OBJECT(object)); + g_return_if_fail(!prev || SP_IS_OBJECT(prev)); + g_return_if_fail(!prev || prev->parent == this); + g_return_if_fail(!object->parent); + + sp_object_ref(object, this); + object->parent = this; + this->_updateTotalHRefCount(object->_total_hrefcount); + + auto it = children.begin(); + if (prev != nullptr) { + it = ++children.iterator_to(*prev); + } + children.insert(it, *object); + + if (!object->xml_space.set) + object->xml_space.value = this->xml_space.value; +} + +void SPObject::reorder(SPObject* obj, SPObject* prev) { + g_return_if_fail(obj != nullptr); + g_return_if_fail(obj->parent); + g_return_if_fail(obj->parent == this); + g_return_if_fail(obj != prev); + g_return_if_fail(!prev || prev->parent == obj->parent); + + auto it = children.begin(); + if (prev != nullptr) { + it = ++children.iterator_to(*prev); + } + + children.splice(it, children, children.iterator_to(*obj)); +} + +void SPObject::detach(SPObject *object) +{ + //g_return_if_fail(parent != NULL); + //g_return_if_fail(SP_IS_OBJECT(parent)); + g_return_if_fail(object != NULL); + g_return_if_fail(SP_IS_OBJECT(object)); + g_return_if_fail(object->parent == this); + + children.erase(children.iterator_to(*object)); + object->releaseReferences(); + + object->parent = NULL; + + this->_updateTotalHRefCount(-object->_total_hrefcount); + sp_object_unref(object, this); +} + +SPObject *SPObject::get_child_by_repr(Inkscape::XML::Node *repr) +{ + g_return_val_if_fail(repr != NULL, NULL); + SPObject *result = nullptr; + + if (children.size() > 0 && children.back().getRepr() == repr) { + result = &children.back(); // optimization for common scenario + } else { + for (auto& child: children) { + if (child.getRepr() == repr) { + result = &child; + break; + } + } + } + return result; +} + +void SPObject::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) { + SPObject* object = this; + + const std::string type_string = NodeTraits::get_type_string(*child); + + SPObject* ochild = SPFactory::createObject(type_string); + if (ochild == NULL) { + // Currenty, there are many node types that do not have + // corresponding classes in the SPObject tree. + // (rdf:RDF, inkscape:clipboard, ...) + // Thus, simply ignore this case for now. + return; + } + + SPObject *prev = ref ? object->get_child_by_repr(ref) : NULL; + object->attach(ochild, prev); + sp_object_unref(ochild, NULL); + + ochild->invoke_build(object->document, child, object->cloned); +} + +void SPObject::release() { + SPObject* object = this; + debug("id=%p, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object)); + auto tmp = children | boost::adaptors::transformed([](SPObject& obj){return &obj;}); + std::vector toRelease(tmp.begin(), tmp.end()); + + for (auto& p: toRelease) { + object->detach(p); + } +} + +void SPObject::remove_child(Inkscape::XML::Node* child) { + debug("id=%p, typename=%s", this, g_type_name_from_instance((GTypeInstance*)this)); + + SPObject *ochild = this->get_child_by_repr(child); + + // If the xml node has got a corresponding child in the object tree + if (ochild) { + this->detach(ochild); + } +} + +void SPObject::order_changed(Inkscape::XML::Node *child, Inkscape::XML::Node * /*old_ref*/, Inkscape::XML::Node *new_ref) { + SPObject* object = this; + + SPObject *ochild = object->get_child_by_repr(child); + g_return_if_fail(ochild != NULL); + SPObject *prev = new_ref ? object->get_child_by_repr(new_ref) : NULL; + object->reorder(ochild, prev); + ochild->_position_changed_signal.emit(ochild); +} + +void SPObject::build(SPDocument *document, Inkscape::XML::Node *repr) { + +#ifdef OBJECT_TRACE + objectTrace( "SPObject::build" ); +#endif + SPObject* object = this; + + /* Nothing specific here */ + debug("id=%p, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object)); + + object->readAttr("xml:space"); + object->readAttr("inkscape:label"); + object->readAttr("inkscape:collect"); + if(object->cloned && (repr->attribute("id")) ) // The cases where this happens are when the "original" has no id. This happens + // if it is a SPString (a TextNode, e.g. in a ), or when importing + // stuff externally modified to have no id. + object->clone_original = document->getObjectById(repr->attribute("id")); + + for (Inkscape::XML::Node *rchild = repr->firstChild() ; rchild != NULL; rchild = rchild->next()) { + const std::string typeString = NodeTraits::get_type_string(*rchild); + + SPObject* child = SPFactory::createObject(typeString); + if (child == NULL) { + // Currenty, there are many node types that do not have + // corresponding classes in the SPObject tree. + // (rdf:RDF, inkscape:clipboard, ...) + // Thus, simply ignore this case for now. + continue; + } + + object->attach(child, object->lastChild()); + sp_object_unref(child, NULL); + child->invoke_build(document, rchild, object->cloned); + } + +#ifdef OBJECT_TRACE + objectTrace( "SPObject::build", false ); +#endif +} + +void SPObject::invoke_build(SPDocument *document, Inkscape::XML::Node *repr, unsigned int cloned) +{ +#ifdef OBJECT_TRACE + objectTrace( "SPObject::invoke_build" ); +#endif + debug("id=%p, typename=%s", this, g_type_name_from_instance((GTypeInstance*)this)); + + //g_assert(object != NULL); + //g_assert(SP_IS_OBJECT(object)); + g_assert(document != NULL); + g_assert(repr != NULL); + + g_assert(this->document == NULL); + g_assert(this->repr == NULL); + g_assert(this->getId() == NULL); + + /* Bookkeeping */ + + this->document = document; + this->repr = repr; + if (!cloned) { + Inkscape::GC::anchor(repr); + } + this->cloned = cloned; + + /* Invoke derived methods, if any */ + this->build(document, repr); + + if ( !cloned ) { + this->document->bindObjectToRepr(this->repr, this); + + if (Inkscape::XML::id_permitted(this->repr)) { + /* If we are not cloned, and not seeking, force unique id */ + gchar const *id = this->repr->attribute("id"); + if (!document->isSeeking()) { + { + gchar *realid = sp_object_get_unique_id(this, id); + g_assert(realid != NULL); + + this->document->bindObjectToId(realid, this); + SPObjectImpl::setId(this, realid); + g_free(realid); + } + + /* Redefine ID, if required */ + if ((id == NULL) || (strcmp(id, this->getId()) != 0)) { + this->repr->setAttribute("id", this->getId()); + } + } else if (id) { + // bind if id, but no conflict -- otherwise, we can expect + // a subsequent setting of the id attribute + if (!this->document->getObjectById(id)) { + this->document->bindObjectToId(id, this); + SPObjectImpl::setId(this, id); + } + } + } + } else { + g_assert(this->getId() == NULL); + } + + + /* Signalling (should be connected AFTER processing derived methods */ + sp_repr_add_listener(repr, &object_event_vector, this); + +#ifdef OBJECT_TRACE + objectTrace( "SPObject::invoke_build", false ); +#endif +} + +int SPObject::getIntAttribute(char const *key, int def) +{ + sp_repr_get_int(getRepr(),key,&def); + return def; +} + +unsigned SPObject::getPosition(){ + g_assert(this->repr); + + return repr->position(); +} + +void SPObject::appendChild(Inkscape::XML::Node *child) { + g_assert(this->repr); + + repr->appendChild(child); +} + +SPObject* SPObject::nthChild(unsigned index) { + g_assert(this->repr); + if (hasChildren()) { + std::vector l; + unsigned counter = 0; + for (auto& child: children) { + if (counter == index) { + return &child; + } + counter++; + } + } + return NULL; +} + +void SPObject::addChild(Inkscape::XML::Node *child, Inkscape::XML::Node * prev) +{ + g_assert(this->repr); + + repr->addChild(child,prev); +} + +void SPObject::releaseReferences() { + g_assert(this->document); + g_assert(this->repr); + + sp_repr_remove_listener_by_data(this->repr, this); + + this->_release_signal.emit(this); + + this->release(); + + /* all hrefs should be released by the "release" handlers */ + g_assert(this->hrefcount == 0); + + if (!cloned) { + if (this->id) { + this->document->bindObjectToId(this->id, NULL); + } + g_free(this->id); + this->id = NULL; + + g_free(this->_default_label); + this->_default_label = NULL; + + this->document->bindObjectToRepr(this->repr, NULL); + + Inkscape::GC::release(this->repr); + } else { + g_assert(!this->id); + } + + // style belongs to SPObject, we should not need to unref here. + // if (this->style) { + // this->style = sp_style_unref(this->style); + // } + + this->document = NULL; + this->repr = NULL; +} + + +SPObject *SPObject::getPrev() +{ + SPObject *prev = nullptr; + if (parent && !parent->children.empty() && &parent->children.front() != this) { + prev = &*(--parent->children.iterator_to(*this)); + } + return prev; +} + +SPObject* SPObject::getNext() +{ + SPObject *next = nullptr; + if (parent && !parent->children.empty() && &parent->children.back() != this) { + next = &*(++parent->children.iterator_to(*this)); + } + return next; +} + +void SPObject::repr_child_added(Inkscape::XML::Node * /*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data) +{ + SPObject *object = SP_OBJECT(data); + + object->child_added(child, ref); +} + +void SPObject::repr_child_removed(Inkscape::XML::Node * /*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node * /*ref*/, gpointer data) +{ + SPObject *object = SP_OBJECT(data); + + object->remove_child(child); +} + +void SPObject::repr_order_changed(Inkscape::XML::Node * /*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node *old, Inkscape::XML::Node *newer, gpointer data) +{ + SPObject *object = SP_OBJECT(data); + + object->order_changed(child, old, newer); +} + +void SPObject::set(unsigned int key, gchar const* value) { + +#ifdef OBJECT_TRACE + std::stringstream temp; + temp << "SPObject::set: " << key << " " << (value?value:"null"); + objectTrace( temp.str() ); +#endif + + g_assert(key != SP_ATTR_INVALID); + + SPObject* object = this; + + switch (key) { + case SP_ATTR_ID: + + //XML Tree being used here. + if ( !object->cloned && object->getRepr()->type() == Inkscape::XML::ELEMENT_NODE ) { + SPDocument *document=object->document; + SPObject *conflict=NULL; + + gchar const *new_id = value; + + if (new_id) { + conflict = document->getObjectById((char const *)new_id); + } + + if ( conflict && conflict != object ) { + if (!document->isSeeking()) { + sp_object_ref(conflict, NULL); + // give the conflicting object a new ID + gchar *new_conflict_id = sp_object_get_unique_id(conflict, NULL); + conflict->getRepr()->setAttribute("id", new_conflict_id); + g_free(new_conflict_id); + sp_object_unref(conflict, NULL); + } else { + new_id = NULL; + } + } + + if (object->getId()) { + document->bindObjectToId(object->getId(), NULL); + SPObjectImpl::setId(object, 0); + } + + if (new_id) { + SPObjectImpl::setId(object, new_id); + document->bindObjectToId(object->getId(), object); + } + + g_free(object->_default_label); + object->_default_label = NULL; + } + break; + case SP_ATTR_INKSCAPE_LABEL: + g_free(object->_label); + if (value) { + object->_label = g_strdup(value); + } else { + object->_label = NULL; + } + g_free(object->_default_label); + object->_default_label = NULL; + break; + case SP_ATTR_INKSCAPE_COLLECT: + if ( value && !strcmp(value, "always") ) { + object->setCollectionPolicy(SPObject::ALWAYS_COLLECT); + } else { + object->setCollectionPolicy(SPObject::COLLECT_WITH_PARENT); + } + break; + case SP_ATTR_XML_SPACE: + if (value && !strcmp(value, "preserve")) { + object->xml_space.value = SP_XML_SPACE_PRESERVE; + object->xml_space.set = TRUE; + } else if (value && !strcmp(value, "default")) { + object->xml_space.value = SP_XML_SPACE_DEFAULT; + object->xml_space.set = TRUE; + } else if (object->parent) { + SPObject *parent; + parent = object->parent; + object->xml_space.value = parent->xml_space.value; + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + break; + case SP_ATTR_STYLE: + object->style->readFromObject( object ); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + break; + default: + break; + } +#ifdef OBJECT_TRACE + objectTrace( "SPObject::set", false ); +#endif +} + +void SPObject::setKeyValue(unsigned int key, gchar const *value) +{ + //g_assert(object != NULL); + //g_assert(SP_IS_OBJECT(object)); + + this->set(key, value); +} + +void SPObject::readAttr(gchar const *key) +{ + //g_assert(object != NULL); + //g_assert(SP_IS_OBJECT(object)); + g_assert(key != NULL); + + //XML Tree being used here. + g_assert(this->getRepr() != NULL); + + unsigned int keyid = sp_attribute_lookup(key); + if (keyid != SP_ATTR_INVALID) { + /* Retrieve the 'key' attribute from the object's XML representation */ + gchar const *value = getRepr()->attribute(key); + + setKeyValue(keyid, value); + } +} + +void SPObject::repr_attr_changed(Inkscape::XML::Node * /*repr*/, gchar const *key, gchar const * /*oldval*/, gchar const * /*newval*/, bool is_interactive, gpointer data) +{ + SPObject *object = SP_OBJECT(data); + + object->readAttr(key); + + // manual changes to extension attributes require the normal + // attributes, which depend on them, to be updated immediately + if (is_interactive) { + object->updateRepr(0); + } +} + +void SPObject::repr_content_changed(Inkscape::XML::Node * /*repr*/, gchar const * /*oldcontent*/, gchar const * /*newcontent*/, gpointer data) +{ + SPObject *object = SP_OBJECT(data); + + object->read_content(); +} + +/** + * Return string representation of space value. + */ +static gchar const *sp_xml_get_space_string(unsigned int space) +{ + switch (space) { + case SP_XML_SPACE_DEFAULT: + return "default"; + case SP_XML_SPACE_PRESERVE: + return "preserve"; + default: + return NULL; + } +} + +Inkscape::XML::Node* SPObject::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { +#ifdef OBJECT_TRACE + objectTrace( "SPObject::write" ); +#endif + + if (!repr && (flags & SP_OBJECT_WRITE_BUILD)) { + repr = this->getRepr()->duplicate(doc); + if (!( flags & SP_OBJECT_WRITE_EXT )) { + repr->setAttribute("inkscape:collect", NULL); + } + } else if (repr) { + repr->setAttribute("id", this->getId()); + + if (this->xml_space.set) { + char const *xml_space; + xml_space = sp_xml_get_space_string(this->xml_space.value); + repr->setAttribute("xml:space", xml_space); + } + + if ( flags & SP_OBJECT_WRITE_EXT && + this->collectionPolicy() == SPObject::ALWAYS_COLLECT ) + { + repr->setAttribute("inkscape:collect", "always"); + } else { + repr->setAttribute("inkscape:collect", NULL); + } + + if (style) { + // Write if property set by style attribute in this object + Glib::ustring s = + style->write(SP_STYLE_FLAG_IFSET | SP_STYLE_FLAG_IFSRC, SP_STYLE_SRC_STYLE_PROP); + + // Check for valid attributes. This may be time consuming. + // It is useful, though, for debugging Inkscape code. + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if( prefs->getBool("/options/svgoutput/check_on_editing") ) { + + unsigned int flags = sp_attribute_clean_get_prefs(); + Glib::ustring s_cleaned = sp_attribute_clean_style( repr, s.c_str(), flags ); + } + + if( s.empty() ) { + repr->setAttribute("style", NULL); + } else { + repr->setAttribute("style", s.c_str()); + } + + } else { + /** \todo I'm not sure what to do in this case. Bug #1165868 + * suggests that it can arise, but the submitter doesn't know + * how to do so reliably. The main two options are either + * leave repr's style attribute unchanged, or explicitly clear it. + * Must also consider what to do with property attributes for + * the element; see below. + */ + char const *style_str = repr->attribute("style"); + if (!style_str) { + style_str = "NULL"; + } + g_warning("Item's style is NULL; repr style attribute is %s", style_str); + } + + /** \note We treat this->style as authoritative. Its effects have + * been written to the style attribute above; any properties that are + * unset we take to be deliberately unset (e.g. so that clones can + * override the property). + * + * Note that the below has an undesirable consequence of changing the + * appearance on renderers that lack CSS support (e.g. SVG tiny); + * possibly we should write property attributes instead of a style + * attribute. + */ + // With the changes to preserves style source this is no longer needed + // and the above comment no longer applies. I leave it here until these + // change are well tested. + // sp_style_unset_property_attrs (this); + } + +#ifdef OBJECT_TRACE + objectTrace( "SPObject::write", false ); +#endif + return repr; +} + +Inkscape::XML::Node * SPObject::updateRepr(unsigned int flags) +{ +#ifdef OBJECT_TRACE + objectTrace( "SPObject::updateRepr 1" ); +#endif + + if ( !cloned ) { + Inkscape::XML::Node *repr = getRepr(); + if (repr) { +#ifdef OBJECT_TRACE + objectTrace( "SPObject::updateRepr 1", false ); +#endif + return updateRepr(repr->document(), repr, flags); + } else { + g_critical("Attempt to update non-existent repr"); +#ifdef OBJECT_TRACE + objectTrace( "SPObject::updateRepr 1", false ); +#endif + return NULL; + } + } else { + /* cloned objects have no repr */ +#ifdef OBJECT_TRACE + objectTrace( "SPObject::updateRepr 1", false ); +#endif + return NULL; + } +} + +Inkscape::XML::Node * SPObject::updateRepr(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, unsigned int flags) +{ +#ifdef OBJECT_TRACE + objectTrace( "SPObject::updateRepr 2" ); +#endif + + g_assert(doc != NULL); + + if (cloned) { + /* cloned objects have no repr */ +#ifdef OBJECT_TRACE + objectTrace( "SPObject::updateRepr 2", false ); +#endif + return NULL; + } + + if (!(flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = getRepr(); + } + +#ifdef OBJECT_TRACE + Inkscape::XML::Node *node = write(doc, repr, flags); + objectTrace( "SPObject::updateRepr 2", false ); + return node; +#else + return this->write(doc, repr, flags); +#endif + +} + +/* Modification */ + +void SPObject::requestDisplayUpdate(unsigned int flags) +{ + g_return_if_fail( this->document != NULL ); + + if (update_in_progress) { + g_print("WARNING: Requested update while update in progress, counter = %d\n", update_in_progress); + } + + /* requestModified must be used only to set one of SP_OBJECT_MODIFIED_FLAG or + * SP_OBJECT_CHILD_MODIFIED_FLAG */ + g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG)); + g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)); + g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG))); + +#ifdef OBJECT_TRACE + objectTrace( "SPObject::requestDisplayUpdate" ); +#endif + + bool already_propagated = (!(this->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))); + + this->uflags |= flags; + + /* If requestModified has already been called on this object or one of its children, then we + * don't need to set CHILD_MODIFIED on our ancestors because it's already been done. + */ + if (already_propagated) { + if (parent) { + parent->requestDisplayUpdate(SP_OBJECT_CHILD_MODIFIED_FLAG); + } else { + document->requestModified(); + } + } + +#ifdef OBJECT_TRACE + objectTrace( "SPObject::requestDisplayUpdate", false ); +#endif + +} + +void SPObject::updateDisplay(SPCtx *ctx, unsigned int flags) +{ + g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE)); + +#ifdef OBJECT_TRACE + objectTrace( "SPObject::updateDisplay" ); +#endif + + update_in_progress ++; + +#ifdef SP_OBJECT_DEBUG_CASCADE + g_print("Update %s:%s %x %x %x\n", g_type_name_from_instance((GTypeInstance *) this), getId(), flags, this->uflags, this->mflags); +#endif + + /* Get this flags */ + flags |= this->uflags; + /* Copy flags to modified cascade for later processing */ + this->mflags |= this->uflags; + /* We have to clear flags here to allow rescheduling update */ + this->uflags = 0; + + // Merge style if we have good reasons to think that parent style is changed */ + /** \todo + * I am not sure whether we should check only propagated + * flag. We are currently assuming that style parsing is + * done immediately. I think this is correct (Lauris). + */ + if ((flags & SP_OBJECT_STYLE_MODIFIED_FLAG) && (flags & SP_OBJECT_PARENT_MODIFIED_FLAG)) { + if (this->style && this->parent) { + style->cascade( this->parent->style ); + } + } + + try + { + this->update(ctx, flags); + } + catch(...) + { + /** \todo + * in case of catching an exception we need to inform the user somehow that the document is corrupted + * maybe by implementing an document flag documentOk + * or by a modal error dialog + */ + g_warning("SPObject::updateDisplay(SPCtx *ctx, unsigned int flags) : throw in ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update(this, ctx, flags);"); + } + + update_in_progress --; + +#ifdef OBJECT_TRACE + objectTrace( "SPObject::updateDisplay", false ); +#endif +} + +void SPObject::requestModified(unsigned int flags) +{ + g_return_if_fail( this->document != NULL ); + + /* requestModified must be used only to set one of SP_OBJECT_MODIFIED_FLAG or + * SP_OBJECT_CHILD_MODIFIED_FLAG */ + g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG)); + g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)); + g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG))); + +#ifdef OBJECT_TRACE + objectTrace( "SPObject::requestModified" ); +#endif + + bool already_propagated = (!(this->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))); + + this->mflags |= flags; + + /* If requestModified has already been called on this object or one of its children, then we + * don't need to set CHILD_MODIFIED on our ancestors because it's already been done. + */ + if (already_propagated) { + if (parent) { + parent->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG); + } else { + document->requestModified(); + } + } +#ifdef OBJECT_TRACE + objectTrace( "SPObject::requestModified", false ); +#endif +} + +void SPObject::emitModified(unsigned int flags) +{ + /* only the MODIFIED_CASCADE flag is legal here */ + g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE)); + +#ifdef OBJECT_TRACE + objectTrace( "SPObject::emitModified", true, flags ); +#endif + +#ifdef SP_OBJECT_DEBUG_CASCADE + g_print("Modified %s:%s %x %x %x\n", g_type_name_from_instance((GTypeInstance *) this), getId(), flags, this->uflags, this->mflags); +#endif + + flags |= this->mflags; + /* We have to clear mflags beforehand, as signal handlers may + * make changes and therefore queue new modification notifications + * themselves. */ + this->mflags = 0; + + sp_object_ref(this); + + this->modified(flags); + + _modified_signal.emit(this, flags); + sp_object_unref(this); + +#ifdef OBJECT_TRACE + objectTrace( "SPObject::emitModified", false ); +#endif +} + +gchar const *SPObject::getTagName(SPException *ex) const +{ + g_assert(repr != NULL); + /* If exception is not clear, return */ + if (!SP_EXCEPTION_IS_OK(ex)) { + return NULL; + } + + /// \todo fixme: Exception if object is NULL? */ + //XML Tree being used here. + return getRepr()->name(); +} + +gchar const *SPObject::getAttribute(gchar const *key, SPException *ex) const +{ + g_assert(this->repr != NULL); + /* If exception is not clear, return */ + if (!SP_EXCEPTION_IS_OK(ex)) { + return NULL; + } + + /// \todo fixme: Exception if object is NULL? */ + //XML Tree being used here. + return (gchar const *) getRepr()->attribute(key); +} + +void SPObject::setAttribute(gchar const *key, gchar const *value, SPException *ex) +{ + g_assert(this->repr != NULL); + /* If exception is not clear, return */ + g_return_if_fail(SP_EXCEPTION_IS_OK(ex)); + + /// \todo fixme: Exception if object is NULL? */ + //XML Tree being used here. + getRepr()->setAttribute(key, value, false); +} + +void SPObject::setAttribute(char const *key, Glib::ustring const &value, SPException *ex) +{ + setAttribute(key, value.empty() ? NULL : value.c_str(), ex); +} + +void SPObject::setAttribute(Glib::ustring const &key, Glib::ustring const &value, SPException *ex) +{ + setAttribute( key.empty() ? NULL : key.c_str(), + value.empty() ? NULL : value.c_str(), ex); +} + + +void SPObject::removeAttribute(gchar const *key, SPException *ex) +{ + /* If exception is not clear, return */ + g_return_if_fail(SP_EXCEPTION_IS_OK(ex)); + + /// \todo fixme: Exception if object is NULL? */ + //XML Tree being used here. + getRepr()->setAttribute(key, NULL, false); +} + +bool SPObject::storeAsDouble( gchar const *key, double *val ) const +{ + g_assert(this->getRepr()!= NULL); + return sp_repr_get_double(((Inkscape::XML::Node *)(this->getRepr())),key,val); +} + +/** Helper */ +gchar * +sp_object_get_unique_id(SPObject *object, + gchar const *id) +{ + static unsigned long count = 0; + + g_assert(SP_IS_OBJECT(object)); + + count++; + + //XML Tree being used here. + gchar const *name = object->getRepr()->name(); + g_assert(name != NULL); + + gchar const *local = strchr(name, ':'); + if (local) { + name = local + 1; + } + + if (id != NULL) { + if (object->document->getObjectById(id) == NULL) { + return g_strdup(id); + } + } + + size_t const name_len = strlen(name); + size_t const buflen = name_len + (sizeof(count) * 10 / 4) + 1; + gchar *const buf = (gchar *) g_malloc(buflen); + memcpy(buf, name, name_len); + gchar *const count_buf = buf + name_len; + size_t const count_buflen = buflen - name_len; + do { + ++count; + g_snprintf(count_buf, count_buflen, "%lu", count); + } while ( object->document->getObjectById(buf) != NULL ); + return buf; +} + +// Style + +gchar const * SPObject::getStyleProperty(gchar const *key, gchar const *def) const +{ + //g_return_val_if_fail(object != NULL, NULL); + //g_return_val_if_fail(SP_IS_OBJECT(object), NULL); + g_return_val_if_fail(key != NULL, NULL); + + //XML Tree being used here. + gchar const *style = getRepr()->attribute("style"); + if (style) { + size_t const len = strlen(key); + char const *p; + while ( (p = strstr(style, key)) + != NULL ) + { + p += len; + while ((*p <= ' ') && *p) { + p++; + } + if (*p++ != ':') { + break; + } + while ((*p <= ' ') && *p) { + p++; + } + size_t const inherit_len = sizeof("inherit") - 1; + if (*p + && !(strneq(p, "inherit", inherit_len) + && (p[inherit_len] == '\0' + || p[inherit_len] == ';' + || g_ascii_isspace(p[inherit_len])))) { + return p; + } + } + } + + //XML Tree being used here. + gchar const *val = getRepr()->attribute(key); + if (val && !streq(val, "inherit")) { + return val; + } + if (this->parent) { + return (this->parent)->getStyleProperty(key, def); + } + + return def; +} + +void SPObject::_requireSVGVersion(Inkscape::Version version) { + for ( SPObject::ParentIterator iter=this ; iter ; ++iter ) { + SPObject *object = iter; + if (SP_IS_ROOT(object)) { + SPRoot *root = SP_ROOT(object); + if ( root->version.svg < version ) { + root->version.svg = version; + } + } + } +} + +// Titles and descriptions + +/* Note: + Titles and descriptions are stored in 'title' and 'desc' child elements + (see section 5.4 of the SVG 1.0 and 1.1 specifications). The spec allows + an element to have more than one 'title' child element, but strongly + recommends against this and requires using the first one if a choice must + be made. The same applies to 'desc' elements. Therefore, these functions + ignore all but the first 'title' child element and first 'desc' child + element, except when deleting a title or description. + + This will change in SVG 2, where multiple 'title' and 'desc' elements will + be allowed with different localized strings. +*/ + +gchar * SPObject::title() const +{ + return getTitleOrDesc("svg:title"); +} + +bool SPObject::setTitle(gchar const *title, bool verbatim) +{ + return setTitleOrDesc(title, "svg:title", verbatim); +} + +gchar * SPObject::desc() const +{ + return getTitleOrDesc("svg:desc"); +} + +bool SPObject::setDesc(gchar const *desc, bool verbatim) +{ + return setTitleOrDesc(desc, "svg:desc", verbatim); +} + +char * SPObject::getTitleOrDesc(gchar const *svg_tagname) const +{ + char *result = NULL; + SPObject *elem = findFirstChild(svg_tagname); + if ( elem ) { + //This string copy could be avoided by changing + //the return type of SPObject::getTitleOrDesc + //to std::unique_ptr + result = g_strdup(elem->textualContent().c_str()); + } + return result; +} + +bool SPObject::setTitleOrDesc(gchar const *value, gchar const *svg_tagname, bool verbatim) +{ + if (!verbatim) { + // If the new title/description is just whitespace, + // treat it as though it were NULL. + if (value) { + bool just_whitespace = true; + for (const gchar *cp = value; *cp; ++cp) { + if (!std::strchr("\r\n \t", *cp)) { + just_whitespace = false; + break; + } + } + if (just_whitespace) { + value = NULL; + } + } + // Don't stomp on mark-up if there is no real change. + if (value) { + gchar *current_value = getTitleOrDesc(svg_tagname); + if (current_value) { + bool different = std::strcmp(current_value, value); + g_free(current_value); + if (!different) { + return false; + } + } + } + } + + SPObject *elem = findFirstChild(svg_tagname); + + if (value == NULL) { + if (elem == NULL) { + return false; + } + // delete the title/description(s) + while (elem) { + elem->deleteObject(); + elem = findFirstChild(svg_tagname); + } + return true; + } + + Inkscape::XML::Document *xml_doc = document->getReprDoc(); + + if (elem == NULL) { + // create a new 'title' or 'desc' element, putting it at the + // beginning (in accordance with the spec's recommendations) + Inkscape::XML::Node *xml_elem = xml_doc->createElement(svg_tagname); + repr->addChild(xml_elem, NULL); + elem = document->getObjectByRepr(xml_elem); + Inkscape::GC::release(xml_elem); + } + else { + // remove the current content of the 'text' or 'desc' element + auto tmp = elem->children | boost::adaptors::transformed([](SPObject& obj) { return &obj; }); + std::vector vec(tmp.begin(), tmp.end()); + for (auto &child: vec) { + child->deleteObject(); + } + } + + // add the new content + elem->appendChildRepr(xml_doc->createTextNode(value)); + return true; +} + +SPObject* SPObject::findFirstChild(gchar const *tagname) const +{ + for (auto& child: const_cast(this)->children) + { + if (child.repr->type() == Inkscape::XML::ELEMENT_NODE && + !strcmp(child.repr->name(), tagname)) { + return &child; + } + } + return nullptr; +} + +Glib::ustring SPObject::textualContent() const +{ + Glib::ustring text; + + for (auto& child: children) + { + Inkscape::XML::NodeType child_type = child.repr->type(); + + if (child_type == Inkscape::XML::ELEMENT_NODE) { + text += child.textualContent(); + } + else if (child_type == Inkscape::XML::TEXT_NODE) { + text += child.repr->content(); + } + } + return text; +} + +// For debugging: Print SP tree structure. +void SPObject::recursivePrintTree( unsigned level ) +{ + if (level == 0) { + std::cout << "SP Object Tree" << std::endl; + } + std::cout << "SP: "; + for (unsigned i = 0; i < level; ++i) { + std::cout << " "; + } + std::cout << (getId()?getId():"No object id") << std::endl; + for (auto& child: children) { + child.recursivePrintTree(level + 1); + } +} + +// Function to allow tracing of program flow through SPObject and derived classes. +// To trace function, add at entrance ('in' = true) and exit of function ('in' = false). +void SPObject::objectTrace( std::string text, bool in, unsigned flags ) { + if( in ) { + for (unsigned i = 0; i < indent_level; ++i) { + std::cout << " "; + } + std::cout << text << ":" + << " entrance: " + << (id?id:"null") + << " uflags: " << uflags + << " mflags: " << mflags + << " flags: " << flags << std::endl; + ++indent_level; + } else { + --indent_level; + for (unsigned i = 0; i < indent_level; ++i) { + std::cout << " "; + } + std::cout << text << ":" + << " exit: " + << (id?id:"null") + << " uflags: " << uflags + << " mflags: " << mflags + << " flags: " << flags << std::endl; + } +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-object.h b/src/object/sp-object.h new file mode 100644 index 000000000..ee092aa7b --- /dev/null +++ b/src/object/sp-object.h @@ -0,0 +1,901 @@ +#ifndef SP_OBJECT_H_SEEN +#define SP_OBJECT_H_SEEN + + +/* + * Authors: + * Lauris Kaplinski + * Jon A. Cruz + * Abhishek Sharma + * Adrian Boguszewski + * + * Copyright (C) 1999-2016 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +/* SPObject flags */ + +class SPObject; + +#define SP_OBJECT(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_OBJECT(obj) (dynamic_cast((SPObject*)obj) != NULL) + +/* Async modification flags */ +#define SP_OBJECT_MODIFIED_FLAG (1 << 0) +#define SP_OBJECT_CHILD_MODIFIED_FLAG (1 << 1) +#define SP_OBJECT_PARENT_MODIFIED_FLAG (1 << 2) +#define SP_OBJECT_STYLE_MODIFIED_FLAG (1 << 3) +#define SP_OBJECT_VIEWPORT_MODIFIED_FLAG (1 << 4) +#define SP_OBJECT_USER_MODIFIED_FLAG_A (1 << 5) +#define SP_OBJECT_USER_MODIFIED_FLAG_B (1 << 6) +#define SP_OBJECT_USER_MODIFIED_FLAG_C (1 << 7) + +/* Conveneience */ +#define SP_OBJECT_FLAGS_ALL 0xff + +/* Flags that mark object as modified */ +/* Object, Child, Style, Viewport, User */ +#define SP_OBJECT_MODIFIED_STATE (SP_OBJECT_FLAGS_ALL & ~(SP_OBJECT_PARENT_MODIFIED_FLAG)) + +/* Flags that will propagate downstreams */ +/* Parent, Style, Viewport, User */ +#define SP_OBJECT_MODIFIED_CASCADE (SP_OBJECT_FLAGS_ALL & ~(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)) + +/* Write flags */ +#define SP_OBJECT_WRITE_BUILD (1 << 0) +#define SP_OBJECT_WRITE_EXT (1 << 1) +#define SP_OBJECT_WRITE_ALL (1 << 2) +#define SP_OBJECT_WRITE_NO_CHILDREN (1 << 3) + +#include +#include +#include +#include +#include +#include +#include + +#include "version.h" +#include "util/forward-pointer-iterator.h" + +class SPCSSAttr; +class SPStyle; + +namespace Inkscape { +namespace XML { +class Node; +struct Document; +} +} + +namespace Glib { + class ustring; +} + +typedef enum { + SP_NO_EXCEPTION, + SP_INDEX_SIZE_ERR, + SP_DOMSTRING_SIZE_ERR, + SP_HIERARCHY_REQUEST_ERR, + SP_WRONG_DOCUMENT_ERR, + SP_INVALID_CHARACTER_ERR, + SP_NO_DATA_ALLOWED_ERR, + SP_NO_MODIFICATION_ALLOWED_ERR, + SP_NOT_FOUND_ERR, + SP_NOT_SUPPORTED_ERR, + SP_INUSE_ATTRIBUTE_ERR, + SP_INVALID_STATE_ERR, + SP_SYNTAX_ERR, + SP_INVALID_MODIFICATION_ERR, + SP_NAMESPACE_ERR, + SP_INVALID_ACCESS_ERR +} SPExceptionType; + +/// An attempt to implement exceptions, unused? +struct SPException { + SPExceptionType code; +}; + +#define SP_EXCEPTION_INIT(ex) {(ex)->code = SP_NO_EXCEPTION;} +#define SP_EXCEPTION_IS_OK(ex) (!(ex) || ((ex)->code == SP_NO_EXCEPTION)) + +/// Unused +struct SPCtx { + unsigned int flags; +}; + +enum { + SP_XML_SPACE_DEFAULT, + SP_XML_SPACE_PRESERVE +}; + +class SPDocument; + +/// Internal class consisting of two bits. +class SPIXmlSpace { +public: + SPIXmlSpace(): set(0), value(SP_XML_SPACE_DEFAULT) {}; + unsigned int set : 1; + unsigned int value : 1; +}; + +/* + * Refcounting + * + * Owner is here for debug reasons, you can set it to NULL safely + * Ref should return object, NULL is error, unref return always NULL + */ + +/** + * Increase reference count of object, with possible debugging. + * + * @param owner If non-NULL, make debug log entry. + * @return object, NULL is error. + * \pre object points to real object + * @todo need to move this to be a member of SPObject. + */ +SPObject *sp_object_ref(SPObject *object, SPObject *owner=NULL); + +/** + * Decrease reference count of object, with possible debugging and + * finalization. + * + * @param owner If non-NULL, make debug log entry. + * @return always NULL + * \pre object points to real object + * @todo need to move this to be a member of SPObject. + */ +SPObject *sp_object_unref(SPObject *object, SPObject *owner=NULL); + +/** + * Increase weak refcount. + * + * Hrefcount is used for weak references, for example, to + * determine whether any graphical element references a certain gradient + * node. + * @param owner Ignored. + * @return object, NULL is error + * \pre object points to real object + * @todo need to move this to be a member of SPObject. + */ +SPObject *sp_object_href(SPObject *object, SPObject* owner); + +/** + * Decrease weak refcount. + * + * Hrefcount is used for weak references, for example, to determine whether + * any graphical element references a certain gradient node. + * @param owner Ignored. + * @return always NULL + * \pre object points to real object and hrefcount>0 + * @todo need to move this to be a member of SPObject. + */ +SPObject *sp_object_hunref(SPObject *object, SPObject* owner); + +/** + * SPObject is an abstract base class of all of the document nodes at the + * SVG document level. Each SPObject subclass implements a certain SVG + * element node type, or is an abstract base class for different node + * types. The SPObject layer is bound to the SPRepr layer, closely + * following the SPRepr mutations via callbacks. During creation, + * SPObject parses and interprets all textual attributes and CSS style + * strings of the SPRepr, and later updates the internal state whenever + * it receives a signal about a change. The opposite is not true - there + * are methods manipulating SPObjects directly and such changes do not + * propagate to the SPRepr layer. This is important for implementation of + * the undo stack, animations and other features. + * + * SPObjects are bound to the higher-level container SPDocument, which + * provides document level functionality such as the undo stack, + * dictionary and so on. Source: doc/architecture.txt + */ +class SPObject { +public: + enum CollectionPolicy { + COLLECT_WITH_PARENT, + ALWAYS_COLLECT + }; + + SPObject(); + virtual ~SPObject(); + + unsigned int cloned : 1; + SPObject *clone_original; + unsigned int uflags : 8; + unsigned int mflags : 8; + SPIXmlSpace xml_space; + unsigned int hrefcount; /* number of xlink:href references */ + unsigned int _total_hrefcount; /* our hrefcount + total descendants */ + SPDocument *document; /* Document we are part of */ + SPObject *parent; /* Our parent (only one allowed) */ + +private: + SPObject(const SPObject&); + SPObject& operator=(const SPObject&); + + char *id; /* Our very own unique id */ + Inkscape::XML::Node *repr; /* Our xml representation */ + +public: + int refCount; + std::list hrefList; + + /** + * Returns the objects current ID string. + */ + char const* getId() const; + + /** + * Returns the XML representation of tree + */ +//protected: + Inkscape::XML::Node * getRepr(); + + /** + * Returns the XML representation of tree + */ + Inkscape::XML::Node const* getRepr() const; + +public: + + /** + * Cleans up an SPObject, releasing its references and + * requesting that references to it be released + */ + void releaseReferences(); + + /** + * Connects to the release request signal + * + * @param slot the slot to connect + * + * @return the sigc::connection formed + */ + sigc::connection connectRelease(sigc::slot slot) { + return _release_signal.connect(slot); + } + + /** + * Represents the style properties, whether from presentation attributes, the style + * attribute, or inherited. + * + * private_set() doesn't handle SP_ATTR_STYLE or any presentation attributes at the + * time of writing, so this is probably NULL for all SPObject's that aren't an SPItem. + * + * However, this gives rise to the bugs mentioned in sp_object_get_style_property. + * Note that some non-SPItem SPObject's, such as SPStop, do need styling information, + * and need to inherit properties even through other non-SPItem parents like \. + */ + SPStyle *style; + + /** + * Represents the style that should be used to resolve 'context-fill' and 'context-stroke' + */ + SPStyle *context_style; + + /// Switch containing next() method. + struct ParentIteratorStrategy { + static SPObject const *next(SPObject const *object) { + return object->parent; + } + }; + + typedef Inkscape::Util::ForwardPointerIterator ParentIterator; + typedef Inkscape::Util::ForwardPointerIterator ConstParentIterator; + + bool isSiblingOf(SPObject const *object) const { + if (object == NULL) return false; + return this->parent && this->parent == object->parent; + } + + /** + * True if object is non-NULL and this is some in/direct parent of object. + */ + bool isAncestorOf(SPObject const *object) const; + + /** + * Returns youngest object being parent to this and object. + */ + SPObject const *nearestCommonAncestor(SPObject const *object) const; + + /* Returns next object in sibling list or NULL. */ + SPObject *getNext(); + + /** + * Returns previous object in sibling list or NULL. + */ + SPObject *getPrev(); + + bool hasChildren() const { return ( children.size() > 0 ); } + + SPObject *firstChild() { return children.empty() ? nullptr : &children.front(); } + SPObject const *firstChild() const { return children.empty() ? nullptr : &children.front(); } + + SPObject *lastChild() { return children.empty() ? nullptr : &children.back(); } + SPObject const *lastChild() const { return children.empty() ? nullptr : &children.back(); } + + SPObject *nthChild(unsigned index); + SPObject const *nthChild(unsigned index) const; + + enum Action { ActionGeneral, ActionBBox, ActionUpdate, ActionShow }; + + /** + * Retrieves the children as a std vector object, optionally ref'ing the children + * in the process, if add_ref is specified. + */ + std::vector childList(bool add_ref, Action action = ActionGeneral); + + /** + * Append repr as child of this object. + * \pre this is not a cloned object + */ + SPObject *appendChildRepr(Inkscape::XML::Node *repr); + + /** + * Gets the author-visible label property for the object or a default if + * no label is defined. + */ + char const *label() const; + + /** + * Returns a default label property for this object. + */ + char const *defaultLabel() const; + + /** + * Sets the author-visible label for this object. + * + * @param label the new label. + */ + void setLabel(char const *label); + + /** + * Returns the title of this object, or NULL if there is none. + * The caller must free the returned string using g_free() - see comment + * for getTitleOrDesc() below. + */ + char *title() const; + + /** + * Sets the title of this object. + * A NULL first argument is interpreted as meaning that the existing title + * (if any) should be deleted. + * The second argument is optional - @see setTitleOrDesc() below for details. + */ + bool setTitle(char const *title, bool verbatim = false); + + /** + * Returns the description of this object, or NULL if there is none. + * The caller must free the returned string using g_free() - see comment + * for getTitleOrDesc() below. + */ + char *desc() const; + + /** + * Sets the description of this object. + * A NULL first argument is interpreted as meaning that the existing + * description (if any) should be deleted. + * The second argument is optional - @see setTitleOrDesc() below for details. + */ + bool setDesc(char const *desc, bool verbatim=false); + + /** + * Set the policy under which this object will be orphan-collected. + * + * Orphan-collection is the process of deleting all objects which no longer have + * hyper-references pointing to them. The policy determines when this happens. Many objects + * should not be deleted simply because they are no longer referred to; other objects (like + * "intermediate" gradients) are more or less throw-away and should always be collected when no + * longer in use. + * + * Along these lines, there are currently two orphan-collection policies: + * + * COLLECT_WITH_PARENT - don't worry about the object's hrefcount; + * if its parent is collected, this object + * will be too + * + * COLLECT_ALWAYS - always collect the object as soon as its + * hrefcount reaches zero + * + * @return the current collection policy in effect for this object + */ + CollectionPolicy collectionPolicy() const { return _collection_policy; } + + /** + * Sets the orphan-collection policy in effect for this object. + * + * @param policy the new policy to adopt + * + * @see SPObject::collectionPolicy + */ + void setCollectionPolicy(CollectionPolicy policy) { + _collection_policy = policy; + } + + /** + * Requests a later automatic call to collectOrphan(). + * + * This method requests that collectOrphan() be called during the document update cycle, + * deleting the object if it is no longer used. + * + * If the current collection policy is COLLECT_WITH_PARENT, this function has no effect. + * + * @see SPObject::collectOrphan + */ + void requestOrphanCollection(); + + /** + * Unconditionally delete the object if it is not referenced. + * + * Unconditionally delete the object if there are no outstanding hyper-references to it. + * Observers are not notified of the object's deletion (at the SPObject level; XML tree + * notifications still fire). + * + * @see SPObject::deleteObject + */ + void collectOrphan() { + if ( _total_hrefcount == 0 ) { + deleteObject(false); + } + } + + /** + * Check if object is referenced by any other object. + */ + bool isReferenced() { return ( _total_hrefcount > 0 ); } + + /** + * Deletes an object, unparenting it from its parent. + * + * Detaches the object's repr, and optionally sends notification that the object has been + * deleted. + * + * @param propagate If it is set to true, it emits a delete signal. + * + * @param propagate_descendants If it is true, it recursively sends the delete signal to children. + */ + void deleteObject(bool propagate, bool propagate_descendants); + + /** + * Deletes on object. + * + * @param propagate Notify observers of this object and its children that they have been + * deleted? + */ + void deleteObject(bool propagate = true) + { + deleteObject(propagate, propagate); + } + + /** + * Removes all children except for the given object, it's children and it's ancesstors. + */ + void cropToObject(SPObject *except); + + /** + * Connects a slot to be called when an object is deleted. + * + * This connects a slot to an object's internal delete signal, which is invoked when the object + * is deleted + * + * The signal is mainly useful for e.g. knowing when to break hrefs or dissociate clones. + * + * @param slot the slot to connect + * + * @see SPObject::deleteObject + */ + sigc::connection connectDelete(sigc::slot slot) { + return _delete_signal.connect(slot); + } + + sigc::connection connectPositionChanged(sigc::slot slot) { + return _position_changed_signal.connect(slot); + } + + /** + * Returns the object which supercedes this one (if any). + * + * This is mainly useful for ensuring we can correctly perform a series of moves or deletes, + * even if the objects in question have been replaced in the middle of the sequence. + */ + SPObject *successor() { return _successor; } + + /** + * Indicates that another object supercedes this one. + */ + void setSuccessor(SPObject *successor) { + assert(successor != NULL); + assert(_successor == NULL); + assert(successor->_successor == NULL); + sp_object_ref(successor, NULL); + _successor = successor; + } + + /* modifications; all three sets of methods should probably ultimately be protected, as they + * are not really part of its public interface. However, other parts of the code to + * occasionally use them at present. */ + + /* the no-argument version of updateRepr() is intended to be a bit more public, however -- it + * essentially just flushes any changes back to the backing store (the repr layer); maybe it + * should be called something else and made public at that point. */ + + /** + * Updates the object's repr based on the object's state. + * + * This method updates the repr attached to the object to reflect the object's current + * state; see the three-argument version for details. + * + * @param flags object write flags that apply to this update + * + * @return the updated repr + */ + Inkscape::XML::Node *updateRepr(unsigned int flags = SP_OBJECT_WRITE_EXT); + + /** + * Updates the given repr based on the object's state. + * + * Used both to create reprs in the original document, and to create reprs + * in another document (e.g. a temporary document used when saving as "Plain SVG". + * + * This method updates the given repr to reflect the object's current state. There are + * several flags that affect this: + * + * SP_OBJECT_WRITE_BUILD - create new reprs + * + * SP_OBJECT_WRITE_EXT - write elements and attributes + * which are not part of pure SVG + * (i.e. the Inkscape and Sodipodi + * namespaces) + * + * SP_OBJECT_WRITE_ALL - create all nodes and attributes, + * even those which might be redundant + * + * @param repr the repr to update + * @param flags object write flags that apply to this update + * + * @return the updated repr + */ + Inkscape::XML::Node *updateRepr(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, unsigned int flags); + + /** + * Queues an deferred update of this object's display. + * + * This method sets flags to indicate updates to be performed later, during the idle loop. + * + * There are several flags permitted here: + * + * SP_OBJECT_MODIFIED_FLAG - the object has been modified + * + * SP_OBJECT_CHILD_MODIFIED_FLAG - a child of the object has been + * modified + * + * SP_OBJECT_STYLE_MODIFIED_FLAG - the object's style has been + * modified + * + * There are also some subclass-specific modified flags which are hardly ever used. + * + * One of either MODIFIED or CHILD_MODIFIED is required. + * + * @param flags flags indicating what to update + */ + void requestDisplayUpdate(unsigned int flags); + + /** + * Updates the object's display immediately + * + * This method is called during the idle loop by SPDocument in order to update the object's + * display. + * + * One additional flag is legal here: + * + * SP_OBJECT_PARENT_MODIFIED_FLAG - the parent has been + * modified + * + * @param ctx an SPCtx which accumulates various state + * during the recursive update -- beware! some + * subclasses try to cast this to an SPItemCtx * + * + * @param flags flags indicating what to update (in addition + * to any already set flags) + */ + void updateDisplay(SPCtx *ctx, unsigned int flags); + + /** + * Requests that a modification notification signal + * be emitted later (e.g. during the idle loop) + * + * Request modified always bubbles *up* the tree, as opposed to + * request display update, which trickles down and relies on the + * flags set during this pass... + * + * @param flags flags indicating what has been modified + */ + void requestModified(unsigned int flags); + + /** + * Emits the MODIFIED signal with the object's flags. + * The object's mflags are the original set aside during the update pass for + * later delivery here. Once emitModified() is called, those flags don't + * need to be stored any longer. + * + * @param flags indicating what has been modified. + */ + void emitModified(unsigned int flags); + + /** + * Connects to the modification notification signal + * + * @param slot the slot to connect + * + * @return the connection formed thereby + */ + sigc::connection connectModified( + sigc::slot slot + ) { + return _modified_signal.connect(slot); + } + + /** Sends the delete signal to all children of this object recursively */ + void _sendDeleteSignalRecursive(); + + /** + * Adds increment to _total_hrefcount of object and its parents. + */ + void _updateTotalHRefCount(int increment); + + void _requireSVGVersion(unsigned major, unsigned minor) { + _requireSVGVersion(Inkscape::Version(major, minor)); + } + + /** + * Lifts SVG version of all root objects to version. + */ + void _requireSVGVersion(Inkscape::Version version); + + sigc::signal _release_signal; + sigc::signal _delete_signal; + sigc::signal _position_changed_signal; + sigc::signal _modified_signal; + SPObject *_successor; + CollectionPolicy _collection_policy; + char *_label; + mutable char *_default_label; + + // WARNING: + // Methods below should not be used outside of the SP tree, + // as they operate directly on the XML representation. + // In future, they will be made protected. + + /** + * Put object into object tree, under parent, and behind prev; + * also update object's XML space. + */ + void attach(SPObject *object, SPObject *prev); + + /** + * In list of object's children, move object behind prev. + */ + void reorder(SPObject* obj, SPObject *prev); + + /** + * Remove object from parent's children, release and unref it. + */ + void detach(SPObject *object); + + /** + * Return object's child whose node pointer equals repr. + */ + SPObject *get_child_by_repr(Inkscape::XML::Node *repr); + + void invoke_build(SPDocument *document, Inkscape::XML::Node *repr, unsigned int cloned); + + int getIntAttribute(char const *key, int def); + + unsigned getPosition(); + + char const * getAttribute(char const *name,SPException *ex=NULL) const; + + void appendChild(Inkscape::XML::Node *child); + + void addChild(Inkscape::XML::Node *child,Inkscape::XML::Node *prev=NULL); + + /** + * Call virtual set() function of object. + */ + void setKeyValue(unsigned int key, char const *value); + + void setAttribute( char const *key, char const *value, SPException *ex=NULL); + void setAttribute( char const *key, Glib::ustring const &value, SPException *ex=NULL); + void setAttribute(Glib::ustring const &key, Glib::ustring const &value, SPException *ex=NULL); + + /** + * Read value of key attribute from XML node into object. + */ + void readAttr(char const *key); + + char const *getTagName(SPException *ex) const; + + void removeAttribute(char const *key, SPException *ex=NULL); + + /** + * Returns an object style property. + * + * \todo + * fixme: Use proper CSS parsing. The current version is buggy + * in a number of situations where key is a substring of the + * style string other than as a property name (including + * where key is a substring of a property name), and is also + * buggy in its handling of inheritance for properties that + * aren't inherited by default. It also doesn't allow for + * the case where the property is specified but with an invalid + * value (in which case I believe the CSS2 error-handling + * behaviour applies, viz. behave as if the property hadn't + * been specified). Also, the current code doesn't use CRSelEng + * stuff to take a value from stylesheets. Also, we aren't + * setting any hooks to force an update for changes in any of + * the inputs (i.e., in any of the elements that this function + * queries). + * + * \par + * Given that the default value for a property depends on what + * property it is (e.g., whether to inherit or not), and given + * the above comment about ignoring invalid values, and that the + * repr parent isn't necessarily the right element to inherit + * from (e.g., maybe we need to inherit from the referencing + * element instead), we should probably make the caller + * responsible for ascending the repr tree as necessary. + */ + char const *getStyleProperty(char const *key, char const *def) const; + + void setCSS(SPCSSAttr *css, char const *attr); + + void changeCSS(SPCSSAttr *css, char const *attr); + + bool storeAsDouble( char const *key, double *val ) const; + +private: + // Private member functions used in the definitions of setTitle(), + // setDesc(), title() and desc(). + + /** + * Sets or deletes the title or description of this object. + * A NULL 'value' argument causes the title or description to be deleted. + * + * 'verbatim' parameter: + * If verbatim==true, then the title or description is set to exactly the + * specified value. If verbatim==false then two exceptions are made: + * (1) If the specified value is just whitespace, then the title/description + * is deleted. + * (2) If the specified value is the same as the current value except for + * mark-up, then the current value is left unchanged. + * This is usually the desired behaviour, so 'verbatim' defaults to false for + * setTitle() and setDesc(). + * + * The return value is true if a change was made to the title/description, + * and usually false otherwise. + */ + bool setTitleOrDesc(char const *value, char const *svg_tagname, bool verbatim); + + /** + * Returns the title or description of this object, or NULL if there is none. + * + * The SVG spec allows 'title' and 'desc' elements to contain text marked up + * using elements from other namespaces. Therefore, this function cannot + * in general just return a pointer to an existing string - it must instead + * construct a string containing the title or description without the mark-up. + * Consequently, the return value is a newly allocated string (or NULL), and + * must be freed (using g_free()) by the caller. + */ + char * getTitleOrDesc(char const *svg_tagname) const; + + /** + * Find the first child of this object with a given tag name, + * and return it. Returns NULL if there is no matching child. + */ + SPObject * findFirstChild(char const *tagname) const; + + /** + * Return the full textual content of an element (typically all the + * content except the tags). + * Must not be used on anything except elements. + */ + Glib::ustring textualContent() const; + + /* Real handlers of repr signals */ + +public: + /** + * Callback for attr_changed node event. + */ + static void repr_attr_changed(Inkscape::XML::Node *repr, char const *key, char const *oldval, char const *newval, bool is_interactive, void* data); + + /** + * Callback for content_changed node event. + */ + static void repr_content_changed(Inkscape::XML::Node *repr, char const *oldcontent, char const *newcontent, void* data); + + /** + * Callback for child_added node event. + */ + static void repr_child_added(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, void* data); + + /** + * Callback for remove_child node event. + */ + static void repr_child_removed(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, void *data); + + /** + * Callback for order_changed node event. + * + * \todo fixme: + */ + static void repr_order_changed(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *old, Inkscape::XML::Node *newer, void* data); + + + friend class SPObjectImpl; + +protected: + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + + virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref); + virtual void remove_child(Inkscape::XML::Node* child); + + virtual void order_changed(Inkscape::XML::Node* child, Inkscape::XML::Node* old_repr, Inkscape::XML::Node* new_repr); + + virtual void set(unsigned int key, const char* value); + + virtual void update(SPCtx* ctx, unsigned int flags); + virtual void modified(unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); + + typedef boost::intrusive::list_member_hook<> ListHook; + ListHook _child_hook; +public: + typedef boost::intrusive::list< + SPObject, + boost::intrusive::member_hook< + SPObject, + ListHook, + &SPObject::_child_hook + >> ChildrenList; + ChildrenList children; + virtual void read_content(); + + void recursivePrintTree(unsigned level = 0); // For debugging + static unsigned indent_level; + void objectTrace( std::string, bool in=true, unsigned flags=0 ); +}; + + +/** + * Compares height of objects in tree. + * + * Works for different-parent objects, so long as they have a common ancestor. + * \return \verbatim + * 0 positions are equivalent + * 1 first object's position is greater than the second + * -1 first object's position is less than the second \endverbatim + */ +int sp_object_compare_position(SPObject const *first, SPObject const *second); +bool sp_object_compare_position_bool(SPObject const *first, SPObject const *second); +gchar * sp_object_get_unique_id(SPObject *object, gchar const *defid); + +#endif // SP_OBJECT_H_SEEN + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-offset.cpp b/src/object/sp-offset.cpp new file mode 100644 index 000000000..82e0b4947 --- /dev/null +++ b/src/object/sp-offset.cpp @@ -0,0 +1,1224 @@ +/** \file + * Implementation of . + */ + +/* + * Authors: (of the sp-spiral.c upon which this file was constructed): + * Mitsuru Oka + * Lauris Kaplinski + * Abhishek Sharma + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "sp-offset.h" + +#include +#include + +#include + +#include "bad-uri-exception.h" +#include "svg/svg.h" +#include "attributes.h" +#include "display/curve.h" + +#include "livarot/Path.h" +#include "livarot/Shape.h" + +#include "enums.h" +#include "preferences.h" +#include "sp-text.h" +#include "sp-use-reference.h" +#include "uri.h" + +class SPDocument; + +#define noOFFSET_VERBOSE + +/** \note + * SPOffset is a derivative of SPShape, much like the SPSpiral or SPRect. + * The goal is to have a source shape (= originalPath), an offset (= radius) + * and compute the offset of the source by the radius. To get it to work, + * one needs to know what the source is and what the radius is, and how it's + * stored in the xml representation. The object itself is a "path" element, + * to get lots of shape functionality for free. The source is the easy part: + * it's stored in a "inkscape:original" attribute in the path. In case of + * "linked" offset, as they've been dubbed, there is an additional + * "inkscape:href" that contains the id of an element of the svg. + * When built, the object will attach a listener vector to that object and + * rebuild the "inkscape:original" whenever the href'd object changes. This + * is of course grossly inefficient, and also does not react to changes + * to the href'd during context stuff (like changing the shape of a star by + * dragging control points) unless the path of that object is changed during + * the context (seems to be the case for SPEllipse). The computation of the + * offset is done in sp_offset_set_shape(), a function that is called whenever + * a change occurs to the offset (change of source or change of radius). + * just like the sp-star and other, this path derivative can make control + * points, or more precisely one control point, that's enough to define the + * radius (look in shape-editor-knotholders). + */ + +static void refresh_offset_source(SPOffset* offset); + +static void sp_offset_start_listening(SPOffset *offset,SPObject* to); +static void sp_offset_quit_listening(SPOffset *offset); +static void sp_offset_href_changed(SPObject *old_ref, SPObject *ref, SPOffset *offset); +static void sp_offset_move_compensate(Geom::Affine const *mp, SPItem *original, SPOffset *self); +static void sp_offset_delete_self(SPObject *deleted, SPOffset *self); +static void sp_offset_source_modified (SPObject *iSource, guint flags, SPItem *item); + + +// slow= source path->polygon->offset of polygon->polygon->path +// fast= source path->offset of source path->polygon->path +// fast is not mathematically correct, because computing the offset of a single +// cubic bezier patch is not trivial; in particular, there are problems with holes +// reappearing in offset when the radius becomes too large +//TODO: need fix for bug: #384688 with fix released in r.14156 +//but reverted because bug #1507049 seems has more priority. +static bool use_slow_but_correct_offset_method = false; + +SPOffset::SPOffset() : SPShape() { + this->rad = 1.0; + this->original = NULL; + this->originalPath = NULL; + this->knotSet = false; + this->sourceDirty=false; + this->isUpdating=false; + // init various connections + this->sourceHref = NULL; + this->sourceRepr = NULL; + this->sourceObject = NULL; + + // set up the uri reference + this->sourceRef = new SPUseReference(this); + this->_changed_connection = this->sourceRef->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_offset_href_changed), this)); +} + +SPOffset::~SPOffset() { + delete this->sourceRef; + + this->_modified_connection.disconnect(); + this->_delete_connection.disconnect(); + this->_changed_connection.disconnect(); + this->_transformed_connection.disconnect(); +} + +void SPOffset::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPShape::build(document, repr); + + //XML Tree being used directly here while it shouldn't be. + if (this->getRepr()->attribute("inkscape:radius")) { + this->readAttr( "inkscape:radius" ); + } else { + //XML Tree being used directly here (as object->getRepr) + //in all the below lines in the block while it shouldn't be. + gchar const *oldA = this->getRepr()->attribute("sodipodi:radius"); + this->getRepr()->setAttribute("inkscape:radius",oldA); + this->getRepr()->setAttribute("sodipodi:radius",NULL); + + this->readAttr( "inkscape:radius" ); + } + + if (this->getRepr()->attribute("inkscape:original")) { + this->readAttr( "inkscape:original" ); + } else { + gchar const *oldA = this->getRepr()->attribute("sodipodi:original"); + this->getRepr()->setAttribute("inkscape:original",oldA); + this->getRepr()->setAttribute("sodipodi:original",NULL); + + this->readAttr( "inkscape:original" ); + } + + if (this->getRepr()->attribute("xlink:href")) { + this->readAttr( "xlink:href" ); + } else { + gchar const *oldA = this->getRepr()->attribute("inkscape:href"); + + if (oldA) { + size_t lA = strlen(oldA); + char *nA=(char*)malloc((1+lA+1)*sizeof(char)); + + memcpy(nA+1,oldA,lA*sizeof(char)); + + nA[0]='#'; + nA[lA+1]=0; + + this->getRepr()->setAttribute("xlink:href",nA); + + free(nA); + + this->getRepr()->setAttribute("inkscape:href",NULL); + } + + this->readAttr( "xlink:href" ); + } +} + +Inkscape::XML::Node* SPOffset::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:path"); + } + + if (flags & SP_OBJECT_WRITE_EXT) { + /** \todo + * Fixme: we may replace these attributes by + * inkscape:offset="cx cy exp revo rad arg t0" + */ + repr->setAttribute("sodipodi:type", "inkscape:offset"); + sp_repr_set_svg_double(repr, "inkscape:radius", this->rad); + repr->setAttribute("inkscape:original", this->original); + repr->setAttribute("inkscape:href", this->sourceHref); + } + + + // Make sure the offset has curve + SPCurve *curve = SP_SHAPE (this)->getCurve(); + + if (curve == NULL) { + this->set_shape(); + } + + // write that curve to "d" + char *d = sp_svg_write_path (this->_curve->get_pathvector()); + repr->setAttribute("d", d); + g_free (d); + + SPShape::write(xml_doc, repr, flags | SP_SHAPE_WRITE_PATH); + + return repr; +} + +void SPOffset::release() { + if (this->original) { + free (this->original); + } + + if (this->originalPath) { + delete ((Path *) this->originalPath); + } + + this->original = NULL; + this->originalPath = NULL; + + sp_offset_quit_listening(this); + + this->_changed_connection.disconnect(); + + g_free(this->sourceHref); + + this->sourceHref = NULL; + this->sourceRef->detach(); + + SPShape::release(); +} + +void SPOffset::set(unsigned int key, const gchar* value) { + if ( this->sourceDirty ) { + refresh_offset_source(this); + } + + /* fixme: we should really collect updates */ + switch (key) + { + case SP_ATTR_INKSCAPE_ORIGINAL: + case SP_ATTR_SODIPODI_ORIGINAL: + if (value == NULL) { + } else { + if (this->original) { + free (this->original); + delete ((Path *) this->originalPath); + + this->original = NULL; + this->originalPath = NULL; + } + + this->original = strdup (value); + + Geom::PathVector pv = sp_svg_read_pathv(this->original); + + this->originalPath = new Path; + reinterpret_cast(this->originalPath)->LoadPathVector(pv); + + this->knotSet = false; + + if ( this->isUpdating == false ) { + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } + } + break; + + case SP_ATTR_INKSCAPE_RADIUS: + case SP_ATTR_SODIPODI_RADIUS: + if (!sp_svg_length_read_computed_absolute (value, &this->rad)) { + if (fabs (this->rad) < 0.01) { + this->rad = (this->rad < 0) ? -0.01 : 0.01; + } + + this->knotSet = false; // knotset=false because it's not set from the context + } + + if ( this->isUpdating == false ) { + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } + break; + + case SP_ATTR_INKSCAPE_HREF: + case SP_ATTR_XLINK_HREF: + if ( value == NULL ) { + sp_offset_quit_listening(this); + if ( this->sourceHref ) { + g_free(this->sourceHref); + } + + this->sourceHref = NULL; + this->sourceRef->detach(); + } else { + if ( this->sourceHref && ( strcmp(value, this->sourceHref) == 0 ) ) { + } else { + if ( this->sourceHref ) { + g_free(this->sourceHref); + } + + this->sourceHref = g_strdup(value); + + try { + this->sourceRef->attach(Inkscape::URI(value)); + } catch (Inkscape::BadURIException &e) { + g_warning("%s", e.what()); + this->sourceRef->detach(); + } + } + } + break; + + default: + SPShape::set(key, value); + break; + } +} + +void SPOffset::update(SPCtx *ctx, guint flags) { + this->isUpdating=true; // prevent sp_offset_set from requesting updates + + if ( this->sourceDirty ) { + refresh_offset_source(this); + } + + if (flags & + (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + + this->set_shape(); + } + + this->isUpdating=false; + + SPShape::update(ctx, flags); +} + +const char* SPOffset::displayName() const { + if ( this->sourceHref ) { + return _("Linked Offset"); + } else { + return _("Dynamic Offset"); + } +} + +gchar* SPOffset::description() const { + // TRANSLATORS COMMENT: %s is either "outset" or "inset" depending on sign + return g_strdup_printf(_("%s by %f pt"), (this->rad >= 0) ? + _("outset") : _("inset"), fabs (this->rad)); +} + +void SPOffset::set_shape() { + if ( this->originalPath == NULL ) { + // oops : no path?! (the offset object should do harakiri) + return; + } +#ifdef OFFSET_VERBOSE + g_print ("rad=%g\n", offset->rad); +#endif + // au boulot + + if ( fabs(this->rad) < 0.01 ) { + // grosso modo: 0 + // just put the source this as the offseted one, no one will notice + // it's also useless to compute the offset with a 0 radius + + //XML Tree being used directly here while it shouldn't be. + const char *res_d = this->getRepr()->attribute("inkscape:original"); + + if ( res_d ) { + Geom::PathVector pv = sp_svg_read_pathv(res_d); + SPCurve *c = new SPCurve(pv); + g_assert(c != NULL); + + this->setCurveInsync (c, TRUE); + this->setCurveBeforeLPE(c); + + c->unref(); + } + + return; + } + + // extra paraniac careful check. the preceding if () should take care of this case + if (fabs (this->rad) < 0.01) { + this->rad = (this->rad < 0) ? -0.01 : 0.01; + } + + Path *orig = new Path; + orig->Copy ((Path *)this->originalPath); + + if ( use_slow_but_correct_offset_method == false ) { + // version par outline + Shape *theShape = new Shape; + Shape *theRes = new Shape; + Path *originaux[1]; + Path *res = new Path; + res->SetBackData (false); + + // and now: offset + float o_width; + if (this->rad >= 0) + { + o_width = this->rad; + orig->OutsideOutline (res, o_width, join_round, butt_straight, 20.0); + } + else + { + o_width = -this->rad; + orig->OutsideOutline (res, -o_width, join_round, butt_straight, 20.0); + } + + if (o_width >= 1.0) + { + // res->ConvertForOffset (1.0, orig, offset->rad); + res->ConvertWithBackData (1.0); + } + else + { + // res->ConvertForOffset (o_width, orig, offset->rad); + res->ConvertWithBackData (o_width); + } + res->Fill (theShape, 0); + theRes->ConvertToShape (theShape, fill_positive); + originaux[0] = res; + + theRes->ConvertToForme (orig, 1, originaux); + + Geom::OptRect bbox = this->desktopVisualBounds(); + + if ( bbox ) { + gdouble size = L2(bbox->dimensions()); + gdouble const exp = this->transform.descrim(); + + if (exp != 0) { + size /= exp; + } + + orig->Coalesce (size * 0.001); + //g_print ("coa %g exp %g item %p\n", size * 0.001, exp, item); + } + + + // if (o_width >= 1.0) + // { + // orig->Coalesce (0.1); // small treshhold, since we only want to get rid of small segments + // the curve should already be computed by the Outline() function + // orig->ConvertEvenLines (1.0); + // orig->Simplify (0.5); + // } + // else + // { + // orig->Coalesce (0.1*o_width); + // orig->ConvertEvenLines (o_width); + // orig->Simplify (0.5 * o_width); + // } + + delete theShape; + delete theRes; + delete res; + } else { + // version par makeoffset + Shape *theShape = new Shape; + Shape *theRes = new Shape; + + + // and now: offset + float o_width; + if (this->rad >= 0) + { + o_width = this->rad; + } + else + { + o_width = -this->rad; + } + + // one has to have a measure of the details + if (o_width >= 1.0) + { + orig->ConvertWithBackData (0.5); + } + else + { + orig->ConvertWithBackData (0.5*o_width); + } + + orig->Fill (theShape, 0); + theRes->ConvertToShape (theShape, fill_positive); + + Path *originaux[1]; + originaux[0]=orig; + + Path *res = new Path; + theRes->ConvertToForme (res, 1, originaux); + + int nbPart=0; + Path** parts=res->SubPaths(nbPart,true); + char *holes=(char*)malloc(nbPart*sizeof(char)); + + // we offset contours separately, because we can. + // this way, we avoid doing a unique big ConvertToShape when dealing with big shapes with lots of holes + { + Shape* onePart=new Shape; + Shape* oneCleanPart=new Shape; + + theShape->Reset(); + + for (int i=0;iSurface(); + parts[i]->Convert(1.0); + + { + // raffiner si besoin + double bL,bT,bR,bB; + parts[i]->PolylineBoundingBox(bL,bT,bR,bB); + double mesure=((bR-bL)+(bB-bT))*0.5; + if ( mesure < 10.0 ) { + parts[i]->Convert(0.02*mesure); + } + } + + if ( partSurf < 0 ) { // inverse par rapport a la realite + // plein + holes[i]=0; + parts[i]->Fill(oneCleanPart,0); + onePart->ConvertToShape(oneCleanPart,fill_positive); // there aren't intersections in that one, but maybe duplicate points and null edges + oneCleanPart->MakeOffset(onePart,this->rad,join_round,20.0); + onePart->ConvertToShape(oneCleanPart,fill_positive); + + onePart->CalcBBox(); + double typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY)); + + if ( typicalSize < 0.05 ) { + typicalSize=0.05; + } + + typicalSize*=0.01; + + if ( typicalSize > 1.0 ) { + typicalSize=1.0; + } + + onePart->ConvertToForme (parts[i]); + parts[i]->ConvertEvenLines (typicalSize); + parts[i]->Simplify (typicalSize); + + double nPartSurf=parts[i]->Surface(); + + if ( nPartSurf >= 0 ) { + // inversion de la surface -> disparait + delete parts[i]; + parts[i]=NULL; + } else { + } + +/* int firstP=theShape->nbPt; + for (int j=0;jnbPt;j++) theShape->AddPoint(onePart->pts[j].x); + for (int j=0;jnbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].st,firstP+onePart->aretes[j].en);*/ + } else { + // trou + holes[i]=1; + parts[i]->Fill(oneCleanPart,0,false,true,true); + onePart->ConvertToShape(oneCleanPart,fill_positive); + oneCleanPart->MakeOffset(onePart,-this->rad,join_round,20.0); + onePart->ConvertToShape(oneCleanPart,fill_positive); +// for (int j=0;jnbAr;j++) onePart->Inverse(j); // pas oublier de reinverser + + onePart->CalcBBox(); + double typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY)); + + if ( typicalSize < 0.05 ) { + typicalSize=0.05; + } + + typicalSize*=0.01; + + if ( typicalSize > 1.0 ) { + typicalSize=1.0; + } + + onePart->ConvertToForme (parts[i]); + parts[i]->ConvertEvenLines (typicalSize); + parts[i]->Simplify (typicalSize); + double nPartSurf=parts[i]->Surface(); + + if ( nPartSurf >= 0 ) { + // inversion de la surface -> disparait + delete parts[i]; + parts[i]=NULL; + } else { + } + + /* int firstP=theShape->nbPt; + for (int j=0;jnbPt;j++) theShape->AddPoint(onePart->pts[j].x); + for (int j=0;jnbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].en,firstP+onePart->aretes[j].st);*/ + } +// delete parts[i]; + } +// theShape->MakeOffset(theRes,offset->rad,join_round,20.0); + delete onePart; + delete oneCleanPart; + } + + if ( nbPart > 1 ) { + theShape->Reset(); + + for (int i=0;iConvertWithBackData(1.0); + + if ( holes[i] ) { + parts[i]->Fill(theShape,i,true,true,true); + } else { + parts[i]->Fill(theShape,i,true,true,false); + } + } + } + + theRes->ConvertToShape (theShape, fill_positive); + theRes->ConvertToForme (orig,nbPart,parts); + + for (int i=0;iCopy(parts[0]); + + for (int i=0;iReset(); + } +// theRes->ConvertToShape (theShape, fill_positive); +// theRes->ConvertToForme (orig); + +/* if (o_width >= 1.0) { + orig->ConvertEvenLines (1.0); + orig->Simplify (1.0); + } else { + orig->ConvertEvenLines (1.0*o_width); + orig->Simplify (1.0 * o_width); + }*/ + + if ( parts ) { + free(parts); + } + + if ( holes ) { + free(holes); + } + + delete res; + delete theShape; + delete theRes; + } + { + char *res_d = NULL; + + if (orig->descr_cmd.size() <= 1) + { + // Aie.... nothing left. + res_d = strdup ("M 0 0 L 0 0 z"); + //printf("%s\n",res_d); + } + else + { + + res_d = orig->svg_dump_path (); + } + + delete orig; + + Geom::PathVector pv = sp_svg_read_pathv(res_d); + SPCurve *c = new SPCurve(pv); + g_assert(c != NULL); + + this->setCurveInsync (c, TRUE); + this->setCurveBeforeLPE(c); + c->unref(); + + free (res_d); + } +} + +void SPOffset::snappoints(std::vector &p, Inkscape::SnapPreferences const *snapprefs) const { + SPShape::snappoints(p, snapprefs); +} + + +// utilitaires pour les poignees +// used to get the distance to the shape: distance to polygon give the fabs(radius), we still need +// the sign. for edges, it's easy to determine which side the point is on, for points of the polygon +// it's trickier: we need to identify which angle the point is in; to that effect, we take each +// successive clockwise angle (A,C) and check if the vector B given by the point is in the angle or +// outside. +// another method would be to use the Winding() function to test whether the point is inside or outside +// the polygon (it would be wiser to do so, in fact, but i like being stupid) + +/** + * + * \todo + * FIXME: This can be done using linear operations, more stably and + * faster. method: transform A and C into B's space, A should be + * negative and B should be positive in the orthogonal component. I + * think this is equivalent to + * dot(A, rot90(B))*dot(C, rot90(B)) == -1. + * -- njh + */ +static bool +vectors_are_clockwise (Geom::Point A, Geom::Point B, Geom::Point C) +{ + using Geom::rot90; + double ab_s = dot(A, rot90(B)); + double ab_c = dot(A, B); + double bc_s = dot(B, rot90(C)); + double bc_c = dot(B, C); + double ca_s = dot(C, rot90(A)); + double ca_c = dot(C, A); + + double ab_a = acos (ab_c); + + if (ab_c <= -1.0) { + ab_a = M_PI; + } + + if (ab_c >= 1.0) { + ab_a = 0; + } + + if (ab_s < 0) { + ab_a = 2 * M_PI - ab_a; + } + + double bc_a = acos (bc_c); + + if (bc_c <= -1.0) { + bc_a = M_PI; + } + + if (bc_c >= 1.0) { + bc_a = 0; + } + + if (bc_s < 0) { + bc_a = 2 * M_PI - bc_a; + } + + double ca_a = acos (ca_c); + + if (ca_c <= -1.0) { + ca_a = M_PI; + } + + if (ca_c >= 1.0) { + ca_a = 0; + } + + if (ca_s < 0) { + ca_a = 2 * M_PI - ca_a; + } + + double lim = 2 * M_PI - ca_a; + + if (ab_a < lim) { + return true; + } + + return false; +} + +/** + * Distance to the original path; that function is called from shape-editor-knotholders + * to set the radius when the control knot moves. + * + * The sign of the result is the radius we're going to offset the shape with, + * so result > 0 ==outset and result < 0 ==inset. thus result<0 means + * 'px inside source'. + */ +double +sp_offset_distance_to_original (SPOffset * offset, Geom::Point px) +{ + if (offset == NULL || offset->originalPath == NULL || ((Path *) offset->originalPath)->descr_cmd.size() <= 1) { + return 1.0; + } + + double dist = 1.0; + Shape *theShape = new Shape; + Shape *theRes = new Shape; + + /** \todo + * Awfully damn stupid method: uncross the source path EACH TIME you + * need to compute the distance. The good way to do this would be to + * store the uncrossed source path somewhere, and delete it when the + * context is finished. Hopefully this part is much faster than actually + * computing the offset (which happen just after), so the time spent in + * this function should end up being negligible with respect to the + * delay of one context. + */ + // move + ((Path *) offset->originalPath)->Convert (1.0); + ((Path *) offset->originalPath)->Fill (theShape, 0); + theRes->ConvertToShape (theShape, fill_oddEven); + + if (theRes->numberOfEdges() <= 1) + { + + } + else + { + double ptDist = -1.0; + bool ptSet = false; + double arDist = -1.0; + bool arSet = false; + + // first get the minimum distance to the points + for (int i = 0; i < theRes->numberOfPoints(); i++) + { + if (theRes->getPoint(i).totalDegree() > 0) + { + Geom::Point nx = theRes->getPoint(i).x; + Geom::Point nxpx = px-nx; + double ndist = sqrt (dot(nxpx,nxpx)); + + if (ptSet == false || fabs (ndist) < fabs (ptDist)) + { + // we have a new minimum distance + // now we need to wheck if px is inside or outside (for the sign) + nx = px - theRes->getPoint(i).x; + double nlen = sqrt (dot(nx , nx)); + nx /= nlen; + int pb, cb, fb; + fb = theRes->getPoint(i).incidentEdge[LAST]; + pb = theRes->getPoint(i).incidentEdge[LAST]; + cb = theRes->getPoint(i).incidentEdge[FIRST]; + + do + { + // one angle + Geom::Point prx, nex; + prx = theRes->getEdge(pb).dx; + nlen = sqrt (dot(prx, prx)); + prx /= nlen; + nex = theRes->getEdge(cb).dx; + nlen = sqrt (dot(nex , nex)); + nex /= nlen; + + if (theRes->getEdge(pb).en == i) + { + prx = -prx; + } + + if (theRes->getEdge(cb).en == i) + { + nex = -nex; + } + + if (vectors_are_clockwise (nex, nx, prx)) + { + // we're in that angle. set the sign, and exit that loop + if (theRes->getEdge(cb).st == i) + { + ptDist = -ndist; + ptSet = true; + } + else + { + ptDist = ndist; + ptSet = true; + } + break; + } + + pb = cb; + cb = theRes->NextAt (i, cb); + } + + while (cb >= 0 && pb >= 0 && pb != fb); + } + } + } + + // loop over the edges to try to improve the distance + for (int i = 0; i < theRes->numberOfEdges(); i++) + { + Geom::Point sx = theRes->getPoint(theRes->getEdge(i).st).x; + Geom::Point ex = theRes->getPoint(theRes->getEdge(i).en).x; + Geom::Point nx = ex - sx; + double len = sqrt (dot(nx,nx)); + + if (len > 0.0001) + { + Geom::Point pxsx=px-sx; + double ab = dot(nx,pxsx); + + if (ab > 0 && ab < len * len) + { + // we're in the zone of influence of the segment + double ndist = (cross(nx, pxsx)) / len; + + if (arSet == false || fabs (ndist) < fabs (arDist)) + { + arDist = ndist; + arSet = true; + } + } + } + } + + if (arSet || ptSet) + { + if (arSet == false) { + arDist = ptDist; + } + + if (ptSet == false) { + ptDist = arDist; + } + + if (fabs (ptDist) < fabs (arDist)) { + dist = ptDist; + } else { + dist = arDist; + } + } + } + + delete theShape; + delete theRes; + + return dist; +} + +/** + * Computes a point on the offset; used to set a "seed" position for + * the control knot. + * + * \return the topmost point on the offset. + */ +void +sp_offset_top_point (SPOffset const * offset, Geom::Point *px) +{ + (*px) = Geom::Point(0, 0); + + if (offset == NULL) { + return; + } + + if (offset->knotSet) + { + (*px) = offset->knot; + return; + } + + SPCurve *curve = SP_SHAPE (offset)->getCurve(); + + if (curve == NULL) + { + // CPPIFY + //offset->set_shape(); + const_cast(offset)->set_shape(); + + curve = SP_SHAPE (offset)->getCurve(); + + if (curve == NULL) + return; + } + + if (curve->is_empty()) + { + curve->unref(); + return; + } + + Path *finalPath = new Path; + finalPath->LoadPathVector(curve->get_pathvector()); + + Shape *theShape = new Shape; + + finalPath->Convert (1.0); + finalPath->Fill (theShape, 0); + + if (theShape->hasPoints()) + { + theShape->SortPoints (); + *px = theShape->getPoint(0).x; + } + + delete theShape; + delete finalPath; + curve->unref(); +} + +// the listening functions +static void sp_offset_start_listening(SPOffset *offset,SPObject* to) +{ + if ( to == NULL ) { + return; + } + + offset->sourceObject = to; + offset->sourceRepr = to->getRepr(); + + offset->_delete_connection = to->connectDelete(sigc::bind(sigc::ptr_fun(&sp_offset_delete_self), offset)); + offset->_transformed_connection = SP_ITEM(to)->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_offset_move_compensate), offset)); + offset->_modified_connection = to->connectModified(sigc::bind<2>(sigc::ptr_fun(&sp_offset_source_modified), offset)); +} + +static void sp_offset_quit_listening(SPOffset *offset) +{ + if ( offset->sourceObject == NULL ) { + return; + } + + offset->_modified_connection.disconnect(); + offset->_delete_connection.disconnect(); + offset->_transformed_connection.disconnect(); + + offset->sourceRepr = NULL; + offset->sourceObject = NULL; +} + +static void +sp_offset_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, SPOffset *offset) +{ + sp_offset_quit_listening(offset); + + if (offset->sourceRef) { + SPItem *refobj = offset->sourceRef->getObject(); + + if (refobj) { + sp_offset_start_listening(offset,refobj); + } + + offset->sourceDirty=true; + offset->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } +} + +static void sp_offset_move_compensate(Geom::Affine const *mp, SPItem */*original*/, SPOffset *self) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + guint mode = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_PARALLEL); + + Geom::Affine m(*mp); + + if (!(m.isTranslation()) || mode == SP_CLONE_COMPENSATION_NONE) { + self->sourceDirty=true; + self->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + return; + } + + // calculate the compensation matrix and the advertized movement matrix + self->readAttr("transform"); + + Geom::Affine t = self->transform; + Geom::Affine offset_move = t.inverse() * m * t; + + Geom::Affine advertized_move; + if (mode == SP_CLONE_COMPENSATION_PARALLEL) { + offset_move = offset_move.inverse() * m; + advertized_move = m; + } else if (mode == SP_CLONE_COMPENSATION_UNMOVED) { + offset_move = offset_move.inverse(); + advertized_move.setIdentity(); + } else { + g_assert_not_reached(); + } + + self->sourceDirty=true; + + // commit the compensation + self->transform *= offset_move; + self->doWriteTransform(self->transform, &advertized_move); + self->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +static void +sp_offset_delete_self(SPObject */*deleted*/, SPOffset *offset) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + guint const mode = prefs->getInt("/options/cloneorphans/value", SP_CLONE_ORPHANS_UNLINK); + + if (mode == SP_CLONE_ORPHANS_UNLINK) { + // leave it be. just forget about the source + sp_offset_quit_listening(offset); + + if ( offset->sourceHref ) { + g_free(offset->sourceHref); + } + + offset->sourceHref = NULL; + offset->sourceRef->detach(); + } else if (mode == SP_CLONE_ORPHANS_DELETE) { + offset->deleteObject(); + } +} + +static void +sp_offset_source_modified (SPObject */*iSource*/, guint flags, SPItem *item) +{ + SPOffset *offset = SP_OFFSET(item); + offset->sourceDirty=true; + + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)) { + offset->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } +} + +static void +refresh_offset_source(SPOffset* offset) +{ + if ( offset == NULL ) { + return; + } + + offset->sourceDirty=false; + + // le mauvais cas: pas d'attribut d => il faut verifier que c'est une SPShape puis prendre le contour + // The bad case: no d attribute. Must check that it's an SPShape and then take the outline. + SPObject *refobj=offset->sourceObject; + + if ( refobj == NULL ) { + return; + } + + SPItem *item = SP_ITEM (refobj); + SPCurve *curve = NULL; + + if (SP_IS_SHAPE (item)) { + curve = SP_SHAPE (item)->getCurve (); + } + else if (SP_IS_TEXT (item)) { + curve = SP_TEXT (item)->getNormalizedBpath (); + } + else { + return; + } + + if (curve == NULL) { + return; + } + + Path *orig = new Path; + orig->LoadPathVector(curve->get_pathvector()); + curve->unref(); + + if (!item->transform.isIdentity()) { + gchar const *t_attr = item->getRepr()->attribute("transform"); + + if (t_attr) { + Geom::Affine t; + + if (sp_svg_transform_read(t_attr, &t)) { + orig->Transform(t); + } + } + } + + // Finish up. + { + SPCSSAttr *css; + const gchar *val; + Shape *theShape = new Shape; + Shape *theRes = new Shape; + + orig->ConvertWithBackData (1.0); + orig->Fill (theShape, 0); + + css = sp_repr_css_attr (offset->sourceRepr , "style"); + val = sp_repr_css_property (css, "fill-rule", NULL); + + if (val && strcmp (val, "nonzero") == 0) + { + theRes->ConvertToShape (theShape, fill_nonZero); + } + else if (val && strcmp (val, "evenodd") == 0) + { + theRes->ConvertToShape (theShape, fill_oddEven); + } + else + { + theRes->ConvertToShape (theShape, fill_nonZero); + } + + Path *originaux[1]; + originaux[0] = orig; + Path *res = new Path; + theRes->ConvertToForme (res, 1, originaux); + + delete theShape; + delete theRes; + + char *res_d = res->svg_dump_path (); + delete res; + delete orig; + + // TODO fix: + //XML Tree being used diectly here while it shouldn't be. + offset->getRepr()->setAttribute("inkscape:original", res_d); + + free (res_d); + } +} + +SPItem * +sp_offset_get_source (SPOffset *offset) +{ + if (offset && offset->sourceRef) { + SPItem *refobj = offset->sourceRef->getObject(); + + if (SP_IS_ITEM (refobj)) { + return (SPItem *) refobj; + } + } + + return NULL; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-offset.h b/src/object/sp-offset.h new file mode 100644 index 000000000..117a3308e --- /dev/null +++ b/src/object/sp-offset.h @@ -0,0 +1,107 @@ +#ifndef SEEN_SP_OFFSET_H +#define SEEN_SP_OFFSET_H +/* + * Authors: + * Mitsuru Oka + * Lauris Kaplinski + * (of the sp-spiral.h upon which this file was created) + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include + +#include "sp-shape.h" + +#define SP_OFFSET(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_OFFSET(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPUseReference; + +/** + * SPOffset class. + * + * An offset is defined by curve and radius. The original curve is kept as + * a path in a sodipodi:original attribute. It's not possible to change + * the original curve. + * + * SPOffset is a derivative of SPShape, much like the SPSpiral or SPRect. + * The goal is to have a source shape (= originalPath), an offset (= radius) + * and compute the offset of the source by the radius. To get it to work, + * one needs to know what the source is and what the radius is, and how it's + * stored in the xml representation. The object itself is a "path" element, + * to get lots of shape functionality for free. The source is the easy part: + * it's stored in a "inkscape:original" attribute in the path. In case of + * "linked" offset, as they've been dubbed, there is an additional + * "inkscape:href" that contains the id of an element of the svg. + * When built, the object will attach a listener vector to that object and + * rebuild the "inkscape:original" whenever the href'd object changes. This + * is of course grossly inefficient, and also does not react to changes + * to the href'd during context stuff (like changing the shape of a star by + * dragging control points) unless the path of that object is changed during + * the context (seems to be the case for SPEllipse). The computation of the + * offset is done in sp_offset_set_shape(), a function that is called whenever + * a change occurs to the offset (change of source or change of radius). + * just like the sp-star and other, this path derivative can make control + * points, or more precisely one control point, that's enough to define the + * radius (look in shape-editor-knotholders). + */ +class SPOffset : public SPShape { +public: + SPOffset(); + virtual ~SPOffset(); + + void *originalPath; ///< will be a livarot Path, just don't declare it here to please the gcc linker FIXME what? + char *original; ///< SVG description of the source path + float rad; ///< offset radius + + /// for interactive setting of the radius + bool knotSet; + Geom::Point knot; + + bool sourceDirty; + bool isUpdating; + + char *sourceHref; + SPUseReference *sourceRef; + Inkscape::XML::Node *sourceRepr; ///< the repr associated with that id + SPObject *sourceObject; + + sigc::connection _modified_connection; + sigc::connection _delete_connection; + sigc::connection _changed_connection; + sigc::connection _transformed_connection; + + virtual void build(SPDocument *document, Inkscape::XML::Node *repr); + virtual void set(unsigned int key, char const* value); + virtual void update(SPCtx *ctx, unsigned int flags); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned flags); + virtual void release(); + + virtual void snappoints(std::vector &p, Inkscape::SnapPreferences const *snapprefs) const; + virtual const char* displayName() const; + virtual char* description() const; + + virtual void set_shape(); +}; + +double sp_offset_distance_to_original (SPOffset * offset, Geom::Point px); +void sp_offset_top_point (SPOffset const *offset, Geom::Point *px); + +SPItem *sp_offset_get_source (SPOffset *offset); + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-paint-server-reference.h b/src/object/sp-paint-server-reference.h new file mode 100644 index 000000000..bbd9c25fa --- /dev/null +++ b/src/object/sp-paint-server-reference.h @@ -0,0 +1,44 @@ +#ifndef SEEN_SP_PAINT_SERVER_REFERENCE_H +#define SEEN_SP_PAINT_SERVER_REFERENCE_H + +/* + * Reference class for gradients and patterns. + * + * Author: + * Lauris Kaplinski + * Jon A. Cruz + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2010 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "uri-references.h" + +class SPDocument; +class SPObject; +class SPPaintServer; + +class SPPaintServerReference : public Inkscape::URIReference { +public: + SPPaintServerReference (SPObject *obj) : URIReference(obj) {} + SPPaintServerReference (SPDocument *doc) : URIReference(doc) {} + SPPaintServer *getObject() const; + +protected: + virtual bool _acceptObject(SPObject *obj) const; +}; + +#endif // SEEN_SP_PAINT_SERVER_REFERENCE_H +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-paint-server.cpp b/src/object/sp-paint-server.cpp new file mode 100644 index 000000000..958078012 --- /dev/null +++ b/src/object/sp-paint-server.cpp @@ -0,0 +1,94 @@ +/* + * Base class for gradients and patterns + * + * Author: + * Lauris Kaplinski + * Jon A. Cruz + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2010 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-paint-server-reference.h" +#include "sp-paint-server.h" + +#include "sp-gradient.h" +#include "xml/node.h" + +SPPaintServer *SPPaintServerReference::getObject() const +{ + return static_cast(URIReference::getObject()); +} + +bool SPPaintServerReference::_acceptObject(SPObject *obj) const +{ + return SP_IS_PAINT_SERVER(obj) && URIReference::_acceptObject(obj); +} + +SPPaintServer::SPPaintServer() : SPObject() { + this->swatch = 0; +} + +SPPaintServer::~SPPaintServer() { +} + +bool SPPaintServer::isSwatch() const +{ + if( this ) // Protect against assumption that "vector" always exists. + return swatch; + return( false ); +} + + +// TODO: So a solid brush is a gradient with a swatch and zero stops? +// Should we derive a new class for that? Or at least make this method +// virtual and move it out of the way? +bool SPPaintServer::isSolid() const +{ + bool solid = false; + if (swatch && SP_IS_GRADIENT(this)) { + SPGradient *grad = SP_GRADIENT(this); + if ( grad->hasStops() && (grad->getStopCount() == 0) ) { + solid = true; + } + } + return solid; +} + +bool SPPaintServer::isValid() const +{ + return true; +} + +Inkscape::DrawingPattern *SPPaintServer::show(Inkscape::Drawing &/*drawing*/, unsigned int /*key*/, Geom::OptRect /*bbox*/) +{ + return NULL; +} + +void SPPaintServer::hide(unsigned int /*key*/) +{ +} + +void SPPaintServer::setBBox(unsigned int /*key*/, Geom::OptRect const &/*bbox*/) +{ +} + +cairo_pattern_t* SPPaintServer::pattern_new(cairo_t * /*ct*/, Geom::OptRect const &/*bbox*/, double /*opacity*/) +{ + return NULL; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-paint-server.h b/src/object/sp-paint-server.h new file mode 100644 index 000000000..7f3bfcba0 --- /dev/null +++ b/src/object/sp-paint-server.h @@ -0,0 +1,105 @@ +#ifndef SEEN_SP_PAINT_SERVER_H +#define SEEN_SP_PAINT_SERVER_H + +/* + * Base class for gradients and patterns + * + * Author: + * Lauris Kaplinski + * Jon A. Cruz + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2010 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include <2geom/rect.h> +#include +#include "sp-object.h" + +namespace Inkscape { + +class Drawing; +class DrawingPattern; + +} + +#define SP_PAINT_SERVER(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_PAINT_SERVER(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPPaintServer : public SPObject { +public: + SPPaintServer(); + virtual ~SPPaintServer(); + + bool isSwatch() const; + bool isSolid() const; + virtual bool isValid() const; + + //There are two ways to render a paint. The simple one is to create cairo_pattern_t structure + //on demand by pattern_new method. It is used for gradients. The other one is to add elements + //representing PaintServer in NR tree. It is used by hatches and patterns. + //Either pattern new or all three methods show, hide, setBBox need to be implemented + virtual Inkscape::DrawingPattern *show(Inkscape::Drawing &drawing, unsigned int key, Geom::OptRect bbox); // TODO check passing bbox by value. Looks suspicious. + virtual void hide(unsigned int key); + virtual void setBBox(unsigned int key, Geom::OptRect const &bbox); + + virtual cairo_pattern_t* pattern_new(cairo_t *ct, Geom::OptRect const &bbox, double opacity); + +protected: + bool swatch; +}; + +/** + * Returns the first of {src, src-\>ref-\>getObject(), + * src-\>ref-\>getObject()-\>ref-\>getObject(),...} + * for which \a match is true, or NULL if none found. + * + * The raison d'être of this routine is that it correctly handles cycles in the href chain (e.g., if + * a gradient gives itself as its href, or if each of two gradients gives the other as its href). + * + * \pre SP_IS_GRADIENT(src). + */ +template +PaintServer *chase_hrefs(PaintServer *src, sigc::slot match) { + /* Use a pair of pointers for detecting loops: p1 advances half as fast as p2. If there is a + loop, then once p1 has entered the loop, we'll detect it the next time the distance between + p1 and p2 is a multiple of the loop size. */ + PaintServer *p1 = src, *p2 = src; + bool do1 = false; + for (;;) { + if (match(p2)) { + return p2; + } + + p2 = p2->ref->getObject(); + if (!p2) { + return p2; + } + if (do1) { + p1 = p1->ref->getObject(); + } + do1 = !do1; + + if ( p2 == p1 ) { + /* We've been here before, so return NULL to indicate that no matching gradient found + * in the chain. */ + return NULL; + } + } +} + +#endif // SEEN_SP_PAINT_SERVER_H +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-path.cpp b/src/object/sp-path.cpp new file mode 100644 index 000000000..513e67810 --- /dev/null +++ b/src/object/sp-path.cpp @@ -0,0 +1,462 @@ +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * David Turner + * Abhishek Sharma + * Johan Engelen + * + * Copyright (C) 2004 David Turner + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 1999-2012 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "live_effects/effect.h" +#include "live_effects/lpeobject.h" +#include "live_effects/lpeobject-reference.h" +#include "sp-lpe-item.h" + +#include "display/curve.h" +#include <2geom/curves.h> +#include "helper/geom-curves.h" + +#include "svg/svg.h" +#include "xml/repr.h" +#include "attributes.h" + +#include "sp-path.h" +#include "sp-guide.h" + +#include "document.h" +#include "desktop.h" + +#include "desktop-style.h" +#include "ui/tools/tool-base.h" +#include "inkscape.h" +#include "style.h" +#include "message-stack.h" + +#define noPATH_VERBOSE + +gint SPPath::nodesInPath() const +{ + return _curve ? _curve->nodes_in_path() : 0; +} + +const char* SPPath::displayName() const { + return _("Path"); +} + +gchar* SPPath::description() const { + int count = this->nodesInPath(); + char *lpe_desc = g_strdup(""); + + if (hasPathEffect()) { + Glib::ustring s; + PathEffectList effect_list = this->getEffectList(); + + for (PathEffectList::iterator it = effect_list.begin(); it != effect_list.end(); ++it) + { + LivePathEffectObject *lpeobj = (*it)->lpeobject; + + if (!lpeobj || !lpeobj->get_lpe()) { + break; + } + + if (s.empty()) { + s = lpeobj->get_lpe()->getName(); + } else { + s = s + ", " + lpeobj->get_lpe()->getName(); + } + } + lpe_desc = g_strdup_printf(_(", path effect: %s"), s.c_str()); + } + char *ret = g_strdup_printf(ngettext( + _("%i node%s"), _("%i nodes%s"), count), count, lpe_desc); + g_free(lpe_desc); + return ret; +} + +void SPPath::convert_to_guides() const { + if (!this->_curve) { + return; + } + + std::list > pts; + + Geom::Affine const i2dt(this->i2dt_affine()); + Geom::PathVector const & pv = this->_curve->get_pathvector(); + + for(Geom::PathVector::const_iterator pit = pv.begin(); pit != pv.end(); ++pit) { + for(Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_default(); ++cit) { + // only add curves for straight line segments + if( is_straight_curve(*cit) ) + { + pts.push_back(std::make_pair(cit->initialPoint() * i2dt, cit->finalPoint() * i2dt)); + } + } + } + + sp_guide_pt_pairs_to_guides(this->document, pts); +} + +SPPath::SPPath() : SPShape(), connEndPair(this) { +} + +SPPath::~SPPath() { +} + +void SPPath::build(SPDocument *document, Inkscape::XML::Node *repr) { + /* Are these calls actually necessary? */ + this->readAttr( "marker" ); + this->readAttr( "marker-start" ); + this->readAttr( "marker-mid" ); + this->readAttr( "marker-end" ); + + sp_conn_end_pair_build(this); + + SPShape::build(document, repr); + + // this->readAttr( "inkscape:original-d" ); // bug #1299948 + // Why we take the long way of doing this probably needs some explaining: + // + // Normally upon being built, reading the inkscape:original-d attribute + // will cause the path to actually _write to its repr_ in response to this. + // This is bad, bad news if the attached effect refers to a path which + // hasn't been constructed yet. + // + // What will happen is the effect parameter will cause the effect to + // recalculate with a completely different value due to the parameter being + // "empty" -- even worse, an undo event might be created with the bad value, + // and undoing the current action could cause it to revert to the "bad" + // state. (After that, the referred object will be constructed and the + // reference will trigger the path effect to update and commit the right + // value to "d".) + // + // This mild nastiness here (don't recalculate effects on build) prevents a + // plethora of issues with effects with linked parameters doing wild and + // stupid things on new documents upon a mere undo. + + if (gchar const* s = this->getRepr()->attribute("inkscape:original-d")) + { + // Write the value to _curve_before_lpe, do not recalculate effects + Geom::PathVector pv = sp_svg_read_pathv(s); + SPCurve *curve = new SPCurve(pv); + + if (_curve_before_lpe) { + _curve_before_lpe = _curve_before_lpe->unref(); + } + + if (curve) { + _curve_before_lpe = curve->ref(); + } + } + this->readAttr( "d" ); + + /* d is a required attribute */ + char const *d = this->getAttribute("d", NULL); + + if (d == NULL) { + // First see if calculating the path effect will generate "d": + this->update_patheffect(true); + d = this->getAttribute("d", NULL); + + // I guess that didn't work, now we have nothing useful to write ("") + if (d == NULL) { + this->setKeyValue( sp_attribute_lookup("d"), ""); + } + } +} + +void SPPath::release() { + this->connEndPair.release(); + + SPShape::release(); +} + +void SPPath::set(unsigned int key, const gchar* value) { + switch (key) { + case SP_ATTR_INKSCAPE_ORIGINAL_D: + if (value) { + Geom::PathVector pv = sp_svg_read_pathv(value); + SPCurve *curve = new SPCurve(pv); + + if (curve) { + this->set_original_curve(curve, TRUE, true); + curve->unref(); + } + } else { + this->set_original_curve(NULL, TRUE, true); + } + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_D: + if (value) { + Geom::PathVector pv = sp_svg_read_pathv(value); + SPCurve *curve = new SPCurve(pv); + + if (curve) { + this->setCurve(curve, TRUE); + curve->unref(); + } + } else { + this->setCurve(NULL, TRUE); + } + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_PROP_MARKER: + case SP_PROP_MARKER_START: + case SP_PROP_MARKER_MID: + case SP_PROP_MARKER_END: + sp_shape_set_marker(this, key, value); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_CONNECTOR_TYPE: + case SP_ATTR_CONNECTOR_CURVATURE: + case SP_ATTR_CONNECTION_START: + case SP_ATTR_CONNECTION_END: + case SP_ATTR_CONNECTION_START_POINT: + case SP_ATTR_CONNECTION_END_POINT: + this->connEndPair.setAttr(key, value); + break; + + default: + SPShape::set(key, value); + break; + } +} + +Inkscape::XML::Node* SPPath::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:path"); + } + +#ifdef PATH_VERBOSE +g_message("sp_path_write writes 'd' attribute"); +#endif + + if ( this->_curve != NULL ) { + gchar *str = sp_svg_write_path(this->_curve->get_pathvector()); + repr->setAttribute("d", str); + g_free(str); + } else { + repr->setAttribute("d", NULL); + } + + if (flags & SP_OBJECT_WRITE_EXT) { + if ( this->_curve_before_lpe != NULL ) { + gchar *str = sp_svg_write_path(this->_curve_before_lpe->get_pathvector()); + repr->setAttribute("inkscape:original-d", str); + g_free(str); + } else { + repr->setAttribute("inkscape:original-d", NULL); + } + } + + this->connEndPair.writeRepr(repr); + + SPShape::write(xml_doc, repr, flags); + + return repr; +} + +void SPPath::update(SPCtx *ctx, guint flags) { + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore + } + + SPShape::update(ctx, flags); + + this->connEndPair.update(); +} + +Geom::Affine SPPath::set_transform(Geom::Affine const &transform) { + if (!_curve) { // 0 nodes, nothing to transform + return Geom::identity(); + } + // Transform the original-d path if this is a valid LPE this, other else the (ordinary) path + if (_curve_before_lpe && hasPathEffectRecursive()) { + if (this->hasPathEffectOfType(Inkscape::LivePathEffect::CLONE_ORIGINAL) || + this->hasPathEffectOfType(Inkscape::LivePathEffect::BEND_PATH) || + this->hasPathEffectOfType(Inkscape::LivePathEffect::FILL_BETWEEN_MANY) || + this->hasPathEffectOfType(Inkscape::LivePathEffect::FILL_BETWEEN_STROKES) ) + { + // if path has this LPE applied, don't write the transform to the pathdata, but write it 'unoptimized' + // also if the effect is type BEND PATH to fix bug #179842 + this->adjust_livepatheffect(transform); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + return transform; + } else { + _curve_before_lpe->transform(transform); + } + } else { + _curve->transform(transform); + } + + // Adjust stroke + this->adjust_stroke(transform.descrim()); + + // Adjust pattern fill + this->adjust_pattern(transform); + + // Adjust gradient fill + this->adjust_gradient(transform); + + // Adjust LPE + this->adjust_livepatheffect(transform); + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + + // nothing remains - we've written all of the transform, so return identity + return Geom::identity(); +} + + +void SPPath::update_patheffect(bool write) { + Inkscape::XML::Node *repr = this->getRepr(); + +#ifdef PATH_VERBOSE +g_message("sp_path_update_patheffect"); +#endif + + if (_curve_before_lpe && hasPathEffectRecursive()) { + SPCurve *curve = _curve_before_lpe->copy(); + /* if a path has an lpeitem applied, then reset the curve to the _curve_before_lpe. + * This is very important for LPEs to work properly! (the bbox might be recalculated depending on the curve in shape)*/ + this->setCurveInsync(curve, TRUE); + + bool success = this->performPathEffect(curve); + + if (success && write) { + // could also do this->getRepr()->updateRepr(); but only the d attribute needs updating. +#ifdef PATH_VERBOSE +g_message("sp_path_update_patheffect writes 'd' attribute"); +#endif + if (_curve) { + gchar *str = sp_svg_write_path(this->_curve->get_pathvector()); + repr->setAttribute("d", str); + g_free(str); + } else { + repr->setAttribute("d", NULL); + } + } else if (!success) { + // LPE was unsuccessful. Read the old 'd'-attribute. + if (gchar const * value = repr->attribute("d")) { + Geom::PathVector pv = sp_svg_read_pathv(value); + SPCurve *oldcurve = new SPCurve(pv); + + if (oldcurve) { + this->setCurve(oldcurve, TRUE); + oldcurve->unref(); + } + } + } + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + curve->unref(); + } +} + + +/** + * Adds a original_curve to the path. If owner is specified, a reference + * will be made, otherwise the curve will be copied into the path. + * Any existing curve in the path will be unreferenced first. + * This routine triggers reapplication of an effect if present + * and also triggers a request to update the display. Does not write + * result to XML when write=false. + */ +void SPPath::set_original_curve (SPCurve *new_curve, unsigned int owner, bool write) +{ + if (_curve_before_lpe) { + _curve_before_lpe = _curve_before_lpe->unref(); + } + + if (new_curve) { + if (owner) { + _curve_before_lpe = new_curve->ref(); + } else { + _curve_before_lpe = new_curve->copy(); + } + } + + sp_lpe_item_update_patheffect(this, true, write); + requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +/** + * Return duplicate of _curve_before_lpe (if any exists) or NULL if there is no curve + */ +SPCurve * SPPath::get_original_curve () const +{ + if (_curve_before_lpe) { + return _curve_before_lpe->copy(); + } + + return NULL; +} + +/** + * Return duplicate of edittable curve which is _curve_before_lpe if it exists or + * shape->curve if not. + */ +SPCurve* SPPath::get_curve_for_edit () const +{ + if (_curve_before_lpe && hasPathEffectRecursive()) { + return get_original_curve(); + } else { + return getCurve(); + } +} + +/** + * Returns \c _curve_before_lpe if it is not NULL and a valid LPE is applied or + * \c curve if not. + */ +const SPCurve* SPPath::get_curve_reference () const +{ + if (_curve_before_lpe && hasPathEffectRecursive()) { + return _curve_before_lpe; + } else { + return _curve; + } +} + +/** + * Returns \c _curve_before_lpe if it is not NULL and a valid LPE is applied or \c curve if not. + * \todo should only be available to class friends! + */ +SPCurve* SPPath::get_curve () +{ + if (_curve_before_lpe && hasPathEffectRecursive()) { + return _curve_before_lpe; + } else { + return _curve; + } +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-path.h b/src/object/sp-path.h new file mode 100644 index 000000000..572fd648d --- /dev/null +++ b/src/object/sp-path.h @@ -0,0 +1,76 @@ +#ifndef SEEN_SP_PATH_H +#define SEEN_SP_PATH_H + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * Ximian, Inc. + * Johan Engelen + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 1999-2012 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-shape.h" +#include "sp-conn-end-pair.h" + +class SPCurve; + +#define SP_PATH(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_PATH(obj) (dynamic_cast((SPObject*)obj) != NULL) + +/** + * SVG implementation + */ +class SPPath : public SPShape { +public: + SPPath(); + virtual ~SPPath(); + + int nodesInPath() const; + + // still in lowercase because the names should be clearer on whether curve, curve->copy or curve-ref is returned. + void set_original_curve (SPCurve *curve, unsigned int owner, bool write); + SPCurve* get_original_curve () const; + SPCurve* get_curve_for_edit () const; + const SPCurve* get_curve_reference() const; + +public: // should be made protected + SPCurve* get_curve(); + friend class SPConnEndPair; + +public: + SPConnEndPair connEndPair; + + virtual void build(SPDocument *document, Inkscape::XML::Node *repr); + virtual void release(); + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual void set(unsigned int key, char const* value); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); + + virtual const char* displayName() const; + virtual char* description() const; + virtual Geom::Affine set_transform(Geom::Affine const &transform); + virtual void convert_to_guides() const; + + virtual void update_patheffect(bool write); +}; + +#endif // SEEN_SP_PATH_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-pattern.cpp b/src/object/sp-pattern.cpp new file mode 100644 index 000000000..ebe78d63c --- /dev/null +++ b/src/object/sp-pattern.cpp @@ -0,0 +1,708 @@ +/* + * SVG implementation + * + * Author: + * Lauris Kaplinski + * bulia byak + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "sp-pattern.h" + +#include +#include +#include +#include <2geom/transforms.h> + +#include "bad-uri-exception.h" +#include "svg/svg.h" +#include "display/cairo-utils.h" +#include "display/drawing-context.h" +#include "display/drawing-surface.h" +#include "display/drawing.h" +#include "display/drawing-group.h" +#include "attributes.h" +#include "document-private.h" + +#include "sp-factory.h" + +#include "sp-defs.h" +#include "sp-item.h" + +SPPattern::SPPattern() + : SPPaintServer() + , SPViewBox() +{ + this->ref = new SPPatternReference(this); + this->ref->changedSignal().connect(sigc::mem_fun(this, &SPPattern::_onRefChanged)); + + this->_pattern_units = UNITS_OBJECTBOUNDINGBOX; + this->_pattern_units_set = false; + + this->_pattern_content_units = UNITS_USERSPACEONUSE; + this->_pattern_content_units_set = false; + + this->_pattern_transform = Geom::identity(); + this->_pattern_transform_set = false; + + this->_x.unset(); + this->_y.unset(); + this->_width.unset(); + this->_height.unset(); +} + +SPPattern::~SPPattern() {} + +void SPPattern::build(SPDocument *doc, Inkscape::XML::Node *repr) +{ + SPPaintServer::build(doc, repr); + + this->readAttr("patternUnits"); + this->readAttr("patternContentUnits"); + this->readAttr("patternTransform"); + this->readAttr("x"); + this->readAttr("y"); + this->readAttr("width"); + this->readAttr("height"); + this->readAttr("viewBox"); + this->readAttr("preserveAspectRatio"); + this->readAttr("xlink:href"); + + /* Register ourselves */ + doc->addResource("pattern", this); +} + +void SPPattern::release() +{ + if (this->document) { + // Unregister ourselves + this->document->removeResource("pattern", this); + } + + if (this->ref) { + this->_modified_connection.disconnect(); + this->ref->detach(); + delete this->ref; + this->ref = NULL; + } + + SPPaintServer::release(); +} + +void SPPattern::set(unsigned int key, const gchar *value) +{ + switch (key) { + case SP_ATTR_PATTERNUNITS: + if (value) { + if (!strcmp(value, "userSpaceOnUse")) { + this->_pattern_units = UNITS_USERSPACEONUSE; + } + else { + this->_pattern_units = UNITS_OBJECTBOUNDINGBOX; + } + + this->_pattern_units_set = true; + } + else { + this->_pattern_units_set = false; + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_PATTERNCONTENTUNITS: + if (value) { + if (!strcmp(value, "userSpaceOnUse")) { + this->_pattern_content_units = UNITS_USERSPACEONUSE; + } + else { + this->_pattern_content_units = UNITS_OBJECTBOUNDINGBOX; + } + + this->_pattern_content_units_set = true; + } + else { + this->_pattern_content_units_set = false; + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_PATTERNTRANSFORM: { + Geom::Affine t; + + if (value && sp_svg_transform_read(value, &t)) { + this->_pattern_transform = t; + this->_pattern_transform_set = true; + } + else { + this->_pattern_transform = Geom::identity(); + this->_pattern_transform_set = false; + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + case SP_ATTR_X: + this->_x.readOrUnset(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_Y: + this->_y.readOrUnset(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_WIDTH: + this->_width.readOrUnset(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_HEIGHT: + this->_height.readOrUnset(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_VIEWBOX: + set_viewBox(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + break; + + case SP_ATTR_PRESERVEASPECTRATIO: + set_preserveAspectRatio(value); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + break; + + case SP_ATTR_XLINK_HREF: + if (value && this->href == value) { + /* Href unchanged, do nothing. */ + } + else { + this->href.clear(); + + if (value) { + // First, set the href field; it's only used in the "unchanged" check above. + this->href = value; + // Now do the attaching, which emits the changed signal. + if (value) { + try { + this->ref->attach(Inkscape::URI(value)); + } + catch (Inkscape::BadURIException &e) { + g_warning("%s", e.what()); + this->ref->detach(); + } + } + else { + this->ref->detach(); + } + } + } + break; + + default: + SPPaintServer::set(key, value); + break; + } +} + + +/* TODO: do we need a ::remove_child handler? */ + +/* fixme: We need ::order_changed handler too (Lauris) */ + +void SPPattern::_getChildren(std::list &l) +{ + for (SPPattern *pat_i = this; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + if (pat_i->firstChild()) { // find the first one with children + for (auto& child: pat_i->children) { + l.push_back(&child); + } + break; // do not go further up the chain if children are found + } + } +} + +void SPPattern::update(SPCtx *ctx, unsigned int flags) +{ + typedef std::list::iterator SPObjectIterator; + + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + std::list l; + _getChildren(l); + + for (SPObjectIterator it = l.begin(); it != l.end(); ++it) { + SPObject *child = *it; + + sp_object_ref(child, NULL); + + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->updateDisplay(ctx, flags); + } + + sp_object_unref(child, NULL); + } +} + +void SPPattern::modified(unsigned int flags) +{ + typedef std::list::iterator SPObjectIterator; + + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + std::list l; + _getChildren(l); + + for (SPObjectIterator it = l.begin(); it != l.end(); ++it) { + SPObject *child = *it; + + sp_object_ref(child, NULL); + + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + + sp_object_unref(child, NULL); + } +} + +void SPPattern::_onRefChanged(SPObject *old_ref, SPObject *ref) +{ + if (old_ref) { + _modified_connection.disconnect(); + } + + if (SP_IS_PATTERN(ref)) { + _modified_connection = ref->connectModified(sigc::mem_fun(this, &SPPattern::_onRefModified)); + } + + _onRefModified(ref, 0); +} + +void SPPattern::_onRefModified(SPObject * /*ref*/, guint /*flags*/) +{ + requestModified(SP_OBJECT_MODIFIED_FLAG); + // Conditional to avoid causing infinite loop if there's a cycle in the href chain. +} + +guint SPPattern::_countHrefs(SPObject *o) const +{ + if (!o) + return 1; + + guint i = 0; + + SPStyle *style = o->style; + if (style && style->fill.isPaintserver() && SP_IS_PATTERN(SP_STYLE_FILL_SERVER(style)) && + SP_PATTERN(SP_STYLE_FILL_SERVER(style)) == this) { + i++; + } + if (style && style->stroke.isPaintserver() && SP_IS_PATTERN(SP_STYLE_STROKE_SERVER(style)) && + SP_PATTERN(SP_STYLE_STROKE_SERVER(style)) == this) { + i++; + } + + for (auto& child: o->children) { + i += _countHrefs(&child); + } + + return i; +} + +SPPattern *SPPattern::_chain() const +{ + Inkscape::XML::Document *xml_doc = document->getReprDoc(); + Inkscape::XML::Node *defsrepr = document->getDefs()->getRepr(); + + Inkscape::XML::Node *repr = xml_doc->createElement("svg:pattern"); + repr->setAttribute("inkscape:collect", "always"); + Glib::ustring parent_ref = Glib::ustring::compose("#%1", getRepr()->attribute("id")); + repr->setAttribute("xlink:href", parent_ref); + + defsrepr->addChild(repr, NULL); + const gchar *child_id = repr->attribute("id"); + SPObject *child = document->getObjectById(child_id); + g_assert(SP_IS_PATTERN(child)); + + return SP_PATTERN(child); +} + +SPPattern *SPPattern::clone_if_necessary(SPItem *item, const gchar *property) +{ + SPPattern *pattern = this; + if (pattern->href.empty() || pattern->hrefcount > _countHrefs(item)) { + pattern = _chain(); + Glib::ustring href = Glib::ustring::compose("url(#%1)", pattern->getRepr()->attribute("id")); + + SPCSSAttr *css = sp_repr_css_attr_new(); + sp_repr_css_set_property(css, property, href.c_str()); + sp_repr_css_change_recursive(item->getRepr(), css, "style"); + } + return pattern; +} + +void SPPattern::transform_multiply(Geom::Affine postmul, bool set) +{ + // this formula is for a different interpretation of pattern transforms as described in (*) in sp-pattern.cpp + // for it to work, we also need sp_object_read_attr( item, "transform"); + // pattern->patternTransform = premul * item->transform * pattern->patternTransform * item->transform.inverse() * + // postmul; + + // otherwise the formula is much simpler + if (set) { + _pattern_transform = postmul; + } + else { + _pattern_transform = getTransform() * postmul; + } + _pattern_transform_set = true; + + gchar *c = sp_svg_transform_write(_pattern_transform); + getRepr()->setAttribute("patternTransform", c); + g_free(c); +} + +const gchar *SPPattern::produce(const std::vector &reprs, Geom::Rect bounds, + SPDocument *document, Geom::Affine transform, Geom::Affine move) +{ + typedef std::vector::const_iterator NodePtrIterator; + + Inkscape::XML::Document *xml_doc = document->getReprDoc(); + Inkscape::XML::Node *defsrepr = document->getDefs()->getRepr(); + + Inkscape::XML::Node *repr = xml_doc->createElement("svg:pattern"); + repr->setAttribute("patternUnits", "userSpaceOnUse"); + sp_repr_set_svg_double(repr, "width", bounds.dimensions()[Geom::X]); + sp_repr_set_svg_double(repr, "height", bounds.dimensions()[Geom::Y]); + //TODO: Maybe is better handle it in sp_svg_transform_write + if(transform != Geom::Affine()){ + gchar *t = sp_svg_transform_write(transform); + repr->setAttribute("patternTransform", t); + g_free(t); + } + defsrepr->appendChild(repr); + const gchar *pat_id = repr->attribute("id"); + SPObject *pat_object = document->getObjectById(pat_id); + + for (NodePtrIterator i = reprs.begin(); i != reprs.end(); ++i) { + Inkscape::XML::Node *node = *i; + SPItem *copy = SP_ITEM(pat_object->appendChildRepr(node)); + + Geom::Affine dup_transform; + if (!sp_svg_transform_read(node->attribute("transform"), &dup_transform)) + dup_transform = Geom::identity(); + dup_transform *= move; + + copy->doWriteTransform(dup_transform, NULL, false); + } + + Inkscape::GC::release(repr); + return pat_id; +} + +SPPattern *SPPattern::rootPattern() +{ + for (SPPattern *pat_i = this; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + if (pat_i->firstChild()) { // find the first one with children + return pat_i; + } + } + return this; // document is broken, we can't get to root; but at least we can return pat which is supposedly a valid + // pattern +} + + + +// Access functions that look up fields up the chain of referenced patterns and return the first one which is set +// FIXME: all of them must use chase_hrefs the same as in SPGradient, to avoid lockup on circular refs + +SPPattern::PatternUnits SPPattern::patternUnits() const +{ + for (SPPattern const *pat_i = this; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + if (pat_i->_pattern_units_set) + return pat_i->_pattern_units; + } + return _pattern_units; +} + +SPPattern::PatternUnits SPPattern::patternContentUnits() const +{ + for (SPPattern const *pat_i = this; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + if (pat_i->_pattern_content_units_set) + return pat_i->_pattern_content_units; + } + return _pattern_content_units; +} + +Geom::Affine const &SPPattern::getTransform() const +{ + for (SPPattern const *pat_i = this; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + if (pat_i->_pattern_transform_set) + return pat_i->_pattern_transform; + } + return _pattern_transform; +} + +gdouble SPPattern::x() const +{ + for (SPPattern const *pat_i = this; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + if (pat_i->_x._set) + return pat_i->_x.computed; + } + return 0; +} + +gdouble SPPattern::y() const +{ + for (SPPattern const *pat_i = this; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + if (pat_i->_y._set) + return pat_i->_y.computed; + } + return 0; +} + +gdouble SPPattern::width() const +{ + for (SPPattern const *pat_i = this; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + if (pat_i->_width._set) + return pat_i->_width.computed; + } + return 0; +} + +gdouble SPPattern::height() const +{ + for (SPPattern const *pat_i = this; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + if (pat_i->_height._set) + return pat_i->_height.computed; + } + return 0; +} + +Geom::OptRect SPPattern::viewbox() const +{ + Geom::OptRect viewbox; + for (SPPattern const *pat_i = this; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + if (pat_i->viewBox_set) { + viewbox = pat_i->viewBox; + break; + } + } + return viewbox; +} + +bool SPPattern::_hasItemChildren() const +{ + for (auto& child: children) { + if (SP_IS_ITEM(&child)) { + return true; + } + } + + return false; +} + +bool SPPattern::isValid() const +{ + double tile_width = width(); + double tile_height = height(); + + if (tile_width <= 0 || tile_height <= 0) + return false; + return true; +} + +cairo_pattern_t *SPPattern::pattern_new(cairo_t *base_ct, Geom::OptRect const &bbox, double opacity) +{ + + bool needs_opacity = (1.0 - opacity) >= 1e-3; + bool visible = opacity >= 1e-3; + + if (!visible) { + return NULL; + } + + /* Show items */ + SPPattern *shown = NULL; + + for (SPPattern *pat_i = this; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + // find the first one with item children + if (pat_i && SP_IS_OBJECT(pat_i) && pat_i->_hasItemChildren()) { + shown = pat_i; + break; // do not go further up the chain if children are found + } + } + + if (!shown) { + return cairo_pattern_create_rgba(0, 0, 0, 0); + } + + /* Create drawing for rendering */ + Inkscape::Drawing drawing; + unsigned int dkey = SPItem::display_key_new(1); + Inkscape::DrawingGroup *root = new Inkscape::DrawingGroup(drawing); + drawing.setRoot(root); + + for (auto& child: shown->children) { + if (SP_IS_ITEM(&child)) { + // for each item in pattern, show it on our drawing, add to the group, + // and connect to the release signal in case the item gets deleted + Inkscape::DrawingItem *cai; + cai = SP_ITEM(&child)->invoke_show(drawing, dkey, SP_ITEM_SHOW_DISPLAY); + root->appendChild(cai); + } + } + + // ****** Geometry ****** + // + // * "width" and "height" determine tile size. + // * "viewBox" (if defined) or "patternContentUnits" determines placement of content inside + // tile. + // * "x", "y", and "patternTransform" transform tile to user space after tile is generated. + + // These functions recursively search up the tree to find the values. + double tile_x = x(); + double tile_y = y(); + double tile_width = width(); + double tile_height = height(); + if (bbox && (patternUnits() == UNITS_OBJECTBOUNDINGBOX)) { + tile_x *= bbox->width(); + tile_y *= bbox->height(); + tile_width *= bbox->width(); + tile_height *= bbox->height(); + } + + // Pattern size in pattern space + Geom::Rect pattern_tile = Geom::Rect::from_xywh(0, 0, tile_width, tile_height); + + // Content to tile (pattern space) + Geom::Affine content2ps; + Geom::OptRect effective_view_box = viewbox(); + if (effective_view_box) { + // viewBox to pattern server (using SPViewBox) + viewBox = *effective_view_box; + c2p.setIdentity(); + apply_viewbox(pattern_tile); + content2ps = c2p; + } + else { + + // Content to bbox + if (bbox && (patternContentUnits() == UNITS_OBJECTBOUNDINGBOX)) { + content2ps = Geom::Affine(bbox->width(), 0.0, 0.0, bbox->height(), 0, 0); + } + } + + + // Tile (pattern space) to user. + Geom::Affine ps2user = Geom::Translate(tile_x, tile_y) * getTransform(); + + + // Transform of object with pattern (includes screen scaling) + cairo_matrix_t cm; + cairo_get_matrix(base_ct, &cm); + Geom::Affine full(cm.xx, cm.yx, cm.xy, cm.yy, 0, 0); + + // The DrawingSurface class handles the mapping from "logical space" + // (coordinates in the rendering) to "physical space" (surface pixels). + // An oversampling is done as the pattern may not pixel align with the final surface. + // The cairo surface is created when the DrawingContext is declared. + + // Oversample the pattern + // TODO: find optimum value + // TODO: this is lame. instead of using descrim(), we should extract + // the scaling component from the complete matrix and use it + // to find the optimum tile size for rendering + // c is number of pixels in buffer x and y. + // Scale factor of 1.1 is too small... see bug #1251039 + Geom::Point c(pattern_tile.dimensions() * ps2user.descrim() * full.descrim() * 2.0); + + // Create drawing surface with size of pattern tile (in pattern space) but with number of pixels + // based on required resolution (c). + Inkscape::DrawingSurface pattern_surface(pattern_tile, c.ceil()); + Inkscape::DrawingContext dc(pattern_surface); + + pattern_tile *= pattern_surface.drawingTransform(); + Geom::IntRect one_tile = pattern_tile.roundOutwards(); + + // Render pattern. + if (needs_opacity) { + dc.pushGroup(); // this group is for pattern + opacity + } + + // TODO: make sure there are no leaks. + Inkscape::UpdateContext ctx; // UpdateContext is structure with only ctm! + ctx.ctm = content2ps * pattern_surface.drawingTransform(); + dc.transform(pattern_surface.drawingTransform().inverse()); + drawing.update(Geom::IntRect::infinite(), ctx); + + // Render drawing to pattern_surface via drawing context, this calls root->render + // which is really DrawingItem->render(). + drawing.render(dc, one_tile); + for (auto& child: shown->children) { + if (SP_IS_ITEM(&child)) { + SP_ITEM(&child)->invoke_hide(dkey); + } + } + + // Uncomment to debug + // cairo_surface_t* raw = pattern_surface.raw(); + // std::cout << " cairo_surface (sp-pattern): " + // << " width: " << cairo_image_surface_get_width( raw ) + // << " height: " << cairo_image_surface_get_height( raw ) + // << std::endl; + // std::string filename = "sp-pattern-" + (std::string)getId() + ".png"; + // cairo_surface_write_to_png( pattern_surface.raw(), filename.c_str() ); + + if (needs_opacity) { + dc.popGroupToSource(); // pop raw pattern + dc.paint(opacity); // apply opacity + } + + // Apply transformation to user space. Also compensate for oversampling. + Geom::Affine raw_transform = ps2user.inverse() * pattern_surface.drawingTransform(); + + // Cairo doesn't like large values of x0 and y0. We can replace x0 and y0 by equivalent + // values close to zero (since one tile on a grid is the same as another it doesn't + // matter which tile is used as the base tile). + int w = one_tile[Geom::X].extent(); + int h = one_tile[Geom::Y].extent(); + int m = raw_transform[4] / w; + int n = raw_transform[5] / h; + raw_transform *= Geom::Translate( -m*w, -n*h ); + + cairo_pattern_t *cp = cairo_pattern_create_for_surface(pattern_surface.raw()); + ink_cairo_pattern_set_matrix(cp, raw_transform); + cairo_pattern_set_extend(cp, CAIRO_EXTEND_REPEAT); + + return cp; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-pattern.h b/src/object/sp-pattern.h new file mode 100644 index 000000000..a5e7be1d4 --- /dev/null +++ b/src/object/sp-pattern.h @@ -0,0 +1,148 @@ +/** @file + * SVG implementation + *//* + * Author: + * Lauris Kaplinski + * Abhishek Sharma + * + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_SP_PATTERN_H +#define SEEN_SP_PATTERN_H + +#include +#include +#include +#include + +#include "svg/svg-length.h" +#include "sp-paint-server.h" +#include "uri-references.h" +#include "viewbox.h" + +class SPPatternReference; +class SPItem; + +namespace Inkscape { +namespace XML { + +class Node; +} +} + +#define SP_PATTERN(obj) (dynamic_cast((SPObject *)obj)) +#define SP_IS_PATTERN(obj) (dynamic_cast((SPObject *)obj) != NULL) + +class SPPattern : public SPPaintServer, public SPViewBox { +public: + enum PatternUnits { UNITS_USERSPACEONUSE, UNITS_OBJECTBOUNDINGBOX }; + + SPPattern(); + virtual ~SPPattern(); + + /* Reference (href) */ + Glib::ustring href; + SPPatternReference *ref; + + gdouble x() const; + gdouble y() const; + gdouble width() const; + gdouble height() const; + Geom::OptRect viewbox() const; + SPPattern::PatternUnits patternUnits() const; + SPPattern::PatternUnits patternContentUnits() const; + Geom::Affine const &getTransform() const; + SPPattern *rootPattern(); // TODO: const + + SPPattern *clone_if_necessary(SPItem *item, const gchar *property); + void transform_multiply(Geom::Affine postmul, bool set); + + /** + * @brief create a new pattern in XML tree + * @return created pattern id + */ + static const gchar *produce(const std::vector &reprs, Geom::Rect bounds, + SPDocument *document, Geom::Affine transform, Geom::Affine move); + + bool isValid() const; + + virtual cairo_pattern_t *pattern_new(cairo_t *ct, Geom::OptRect const &bbox, double opacity); + +protected: + virtual void build(SPDocument *doc, Inkscape::XML::Node *repr); + virtual void release(); + virtual void set(unsigned int key, const gchar *value); + virtual void update(SPCtx *ctx, unsigned int flags); + virtual void modified(unsigned int flags); + +private: + bool _hasItemChildren() const; + void _getChildren(std::list &l); + SPPattern *_chain() const; + + /** + Count how many times pattern is used by the styles of o and its descendants + */ + guint _countHrefs(SPObject *o) const; + + /** + Gets called when the pattern is reattached to another + */ + void _onRefChanged(SPObject *old_ref, SPObject *ref); + + /** + Gets called when the referenced is changed + */ + void _onRefModified(SPObject *ref, guint flags); + + /* patternUnits and patternContentUnits attribute */ + PatternUnits _pattern_units : 1; + bool _pattern_units_set : 1; + PatternUnits _pattern_content_units : 1; + bool _pattern_content_units_set : 1; + /* patternTransform attribute */ + Geom::Affine _pattern_transform; + bool _pattern_transform_set : 1; + /* Tile rectangle */ + SVGLength _x; + SVGLength _y; + SVGLength _width; + SVGLength _height; + + sigc::connection _modified_connection; +}; + + +class SPPatternReference : public Inkscape::URIReference { +public: + SPPatternReference(SPObject *obj) + : URIReference(obj) + { + } + + SPPattern *getObject() const + { + return reinterpret_cast(URIReference::getObject()); + } + +protected: + virtual bool _acceptObject(SPObject *obj) const { + return SP_IS_PATTERN (obj)&& URIReference::_acceptObject(obj); + } +}; + +#endif // SEEN_SP_PATTERN_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-polygon.cpp b/src/object/sp-polygon.cpp new file mode 100644 index 000000000..14fd104b3 --- /dev/null +++ b/src/object/sp-polygon.cpp @@ -0,0 +1,183 @@ +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * Abhishek Sharma + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "attributes.h" +#include "sp-polygon.h" +#include "display/curve.h" +#include +#include <2geom/curves.h> +#include "helper/geom-curves.h" +#include "svg/stringstream.h" +#include "xml/repr.h" +#include "document.h" + +SPPolygon::SPPolygon() : SPShape() { +} + +SPPolygon::~SPPolygon() { +} + +void SPPolygon::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPPolygon* object = this; + + SPShape::build(document, repr); + + object->readAttr( "points" ); +} + +/* + * sp_svg_write_polygon: Write points attribute for polygon tag. + * pathv may only contain paths with only straight line segments + * Return value: points attribute string. + */ +static gchar *sp_svg_write_polygon(Geom::PathVector const & pathv) +{ + Inkscape::SVGOStringStream os; + + for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) { + for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_default(); ++cit) { + if ( is_straight_curve(*cit) ) + { + os << cit->finalPoint()[0] << "," << cit->finalPoint()[1] << " "; + } else { + g_error("sp_svg_write_polygon: polygon path contains non-straight line segments"); + } + } + } + + return g_strdup(os.str().c_str()); +} + +Inkscape::XML::Node* SPPolygon::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + // Tolerable workaround: we need to update the object's curve before we set points= + // because it's out of sync when e.g. some extension attrs of the polygon or star are changed in XML editor + this->set_shape(); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:polygon"); + } + + /* We can safely write points here, because all subclasses require it too (Lauris) */ + /* While saving polygon element without points attribute _curve is NULL (see bug 1202753) */ + if (this->_curve != NULL) { + gchar *str = sp_svg_write_polygon(this->_curve->get_pathvector()); + repr->setAttribute("points", str); + g_free(str); + } + + SPShape::write(xml_doc, repr, flags); + + return repr; +} + + +static gboolean polygon_get_value(gchar const **p, gdouble *v) +{ + while (**p != '\0' && (**p == ',' || **p == '\x20' || **p == '\x9' || **p == '\xD' || **p == '\xA')) { + (*p)++; + } + + if (**p == '\0') { + return false; + } + + gchar *e = NULL; + *v = g_ascii_strtod(*p, &e); + + if (e == *p) { + return false; + } + + *p = e; + + return true; +} + +void SPPolygon::set(unsigned int key, const gchar* value) { + switch (key) { + case SP_ATTR_POINTS: { + if (!value) { + /* fixme: The points attribute is required. We should handle its absence as per + * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing. */ + break; + } + + SPCurve *curve = new SPCurve(); + gboolean hascpt = FALSE; + + gchar const *cptr = value; + bool has_error = false; + + while (TRUE) { + gdouble x; + + if (!polygon_get_value(&cptr, &x)) { + break; + } + + gdouble y; + + if (!polygon_get_value(&cptr, &y)) { + /* fixme: It is an error for an odd number of points to be specified. We + * should display the points up to now (as we currently do, though perhaps + * without the closepath: the spec isn't quite clear on whether to do a + * closepath or not, though I'd guess it's best not to do a closepath), but + * then flag the document as in error, as per + * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing. + * + * (Ref: http://www.w3.org/TR/SVG11/shapes.html#PolygonElement.) */ + has_error = true; + break; + } + + if (hascpt) { + curve->lineto(x, y); + } else { + curve->moveto(x, y); + hascpt = TRUE; + } + } + + if (has_error || *cptr != '\0') { + /* TODO: Flag the document as in error, as per + * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing. */ + } else if (hascpt) { + /* We might have done a moveto but no lineto. I'm not sure how we're supposed to represent + * a single-point polygon in SPCurve. TODO: add a testcase with only one coordinate pair */ + curve->closepath(); + } + + this->setCurve(curve, TRUE); + curve->unref(); + break; + } + default: + SPShape::set(key, value); + break; + } +} + +gchar* SPPolygon::description() const { + return g_strdup(_("Polygon")); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-polygon.h b/src/object/sp-polygon.h new file mode 100644 index 000000000..438fdf794 --- /dev/null +++ b/src/object/sp-polygon.h @@ -0,0 +1,35 @@ +#ifndef SEEN_SP_POLYGON_H +#define SEEN_SP_POLYGON_H + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-shape.h" + +#define SP_POLYGON(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_POLYGON(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPPolygon : public SPShape { +public: + SPPolygon(); + virtual ~SPPolygon(); + + virtual void build(SPDocument *document, Inkscape::XML::Node *repr); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); + virtual void set(unsigned int key, char const* value); + virtual char* description() const; +}; + +// made 'public' so that SPCurve can set it as friend: +void sp_polygon_set(SPObject *object, unsigned int key, char const*value); + +#endif diff --git a/src/object/sp-polyline.cpp b/src/object/sp-polyline.cpp new file mode 100644 index 000000000..29054f934 --- /dev/null +++ b/src/object/sp-polyline.cpp @@ -0,0 +1,132 @@ +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * Abhishek Sharma + * Jon A. Cruz + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "attributes.h" +#include "sp-polyline.h" +#include "display/curve.h" +#include +#include "xml/repr.h" +#include "document.h" + +SPPolyLine::SPPolyLine() : SPShape() { +} + +SPPolyLine::~SPPolyLine() { +} + +void SPPolyLine::build(SPDocument * document, Inkscape::XML::Node * repr) { + SPShape::build(document, repr); + + this->readAttr("points"); +} + +void SPPolyLine::set(unsigned int key, const gchar* value) { + switch (key) { + case SP_ATTR_POINTS: { + SPCurve * curve; + const gchar * cptr; + char * eptr; + gboolean hascpt; + + if (!value) { + break; + } + + curve = new SPCurve (); + hascpt = FALSE; + + cptr = value; + eptr = NULL; + + while (TRUE) { + gdouble x, y; + + while (*cptr != '\0' && (*cptr == ',' || *cptr == '\x20' || *cptr == '\x9' || *cptr == '\xD' || *cptr == '\xA')) { + cptr++; + } + + if (!*cptr) { + break; + } + + x = g_ascii_strtod (cptr, &eptr); + + if (eptr == cptr) { + break; + } + + cptr = eptr; + + while (*cptr != '\0' && (*cptr == ',' || *cptr == '\x20' || *cptr == '\x9' || *cptr == '\xD' || *cptr == '\xA')) { + cptr++; + } + + if (!*cptr) { + break; + } + + y = g_ascii_strtod (cptr, &eptr); + + if (eptr == cptr) { + break; + } + + cptr = eptr; + + if (hascpt) { + curve->lineto(x, y); + } else { + curve->moveto(x, y); + hascpt = TRUE; + } + } + + this->setCurve(curve, TRUE); + curve->unref(); + break; + } + default: + SPShape::set(key, value); + break; + } +} + +Inkscape::XML::Node* SPPolyLine::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:polyline"); + } + + if (repr != this->getRepr()) { + repr->mergeFrom(this->getRepr(), "id"); + } + + SPShape::write(xml_doc, repr, flags); + + return repr; +} + +gchar* SPPolyLine::description() const { + return g_strdup(_("Polyline")); +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-polyline.h b/src/object/sp-polyline.h new file mode 100644 index 000000000..1ca102a9e --- /dev/null +++ b/src/object/sp-polyline.h @@ -0,0 +1,32 @@ +#ifndef SEEN_SP_POLYLINE_H +#define SEEN_SP_POLYLINE_H + +#include "sp-shape.h" + +#define SP_POLYLINE(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_POLYLINE(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPPolyLine : public SPShape { +public: + SPPolyLine(); + virtual ~SPPolyLine(); + + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void set(unsigned int key, char const* value); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); + + virtual char* description() const; +}; + +#endif // SEEN_SP_POLYLINE_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-radial-gradient.cpp b/src/object/sp-radial-gradient.cpp new file mode 100644 index 000000000..fa6355478 --- /dev/null +++ b/src/object/sp-radial-gradient.cpp @@ -0,0 +1,206 @@ +#include + +#include "sp-radial-gradient.h" + +#include "attributes.h" +#include "xml/repr.h" + +#include <2geom/transforms.h> + +/* + * Radial Gradient + */ +SPRadialGradient::SPRadialGradient() : SPGradient() { + this->cx.unset(SVGLength::PERCENT, 0.5, 0.5); + this->cy.unset(SVGLength::PERCENT, 0.5, 0.5); + this->r.unset(SVGLength::PERCENT, 0.5, 0.5); + this->fx.unset(SVGLength::PERCENT, 0.5, 0.5); + this->fy.unset(SVGLength::PERCENT, 0.5, 0.5); + this->fr.unset(SVGLength::PERCENT, 0.5, 0.5); +} + +SPRadialGradient::~SPRadialGradient() { +} + +/** + * Set radial gradient attributes from associated repr. + */ +void SPRadialGradient::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPGradient::build(document, repr); + + this->readAttr( "cx" ); + this->readAttr( "cy" ); + this->readAttr( "r" ); + this->readAttr( "fx" ); + this->readAttr( "fy" ); + this->readAttr( "fr" ); +} + +/** + * Set radial gradient attribute. + */ +void SPRadialGradient::set(unsigned key, gchar const *value) { + switch (key) { + case SP_ATTR_CX: + if (!this->cx.read(value)) { + this->cx.unset(SVGLength::PERCENT, 0.5, 0.5); + } + + if (!this->fx._set) { + this->fx.value = this->cx.value; + this->fx.computed = this->cx.computed; + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_CY: + if (!this->cy.read(value)) { + this->cy.unset(SVGLength::PERCENT, 0.5, 0.5); + } + + if (!this->fy._set) { + this->fy.value = this->cy.value; + this->fy.computed = this->cy.computed; + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_R: + if (!this->r.read(value)) { + this->r.unset(SVGLength::PERCENT, 0.5, 0.5); + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_FX: + if (!this->fx.read(value)) { + this->fx.unset(this->cx.unit, this->cx.value, this->cx.computed); + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_FY: + if (!this->fy.read(value)) { + this->fy.unset(this->cy.unit, this->cy.value, this->cy.computed); + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_FR: + if (!this->fr.read(value)) { + this->fr.unset(SVGLength::PERCENT, 0.0, 0.0); + } + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + + default: + SPGradient::set(key, value); + break; + } +} + +/** + * Write radial gradient attributes to associated repr. + */ +Inkscape::XML::Node* SPRadialGradient::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:radialGradient"); + } + + if ((flags & SP_OBJECT_WRITE_ALL) || this->cx._set) { + sp_repr_set_svg_double(repr, "cx", this->cx.computed); + } + + if ((flags & SP_OBJECT_WRITE_ALL) || this->cy._set) { + sp_repr_set_svg_double(repr, "cy", this->cy.computed); + } + + if ((flags & SP_OBJECT_WRITE_ALL) || this->r._set) { + sp_repr_set_svg_double(repr, "r", this->r.computed); + } + + if ((flags & SP_OBJECT_WRITE_ALL) || this->fx._set) { + sp_repr_set_svg_double(repr, "fx", this->fx.computed); + } + + if ((flags & SP_OBJECT_WRITE_ALL) || this->fy._set) { + sp_repr_set_svg_double(repr, "fy", this->fy.computed); + } + + if ((flags & SP_OBJECT_WRITE_ALL) || this->fr._set) { + sp_repr_set_svg_double(repr, "fr", this->fr.computed); + } + + SPGradient::write(xml_doc, repr, flags); + + return repr; +} + +cairo_pattern_t* SPRadialGradient::pattern_new(cairo_t *ct, Geom::OptRect const &bbox, double opacity) { + this->ensureVector(); + + Geom::Point focus(this->fx.computed, this->fy.computed); + Geom::Point center(this->cx.computed, this->cy.computed); + + double radius = this->r.computed; + double focusr = this->fr.computed; + double scale = 1.0; + double tolerance = cairo_get_tolerance(ct); + + // NOTE: SVG2 will allow the use of a focus circle which can + // have its center outside the first circle. + + // code below suggested by Cairo devs to overcome tolerance problems + // more: https://bugs.freedesktop.org/show_bug.cgi?id=40918 + + // Corrected for + // https://bugs.launchpad.net/inkscape/+bug/970355 + + Geom::Affine gs2user = this->gradientTransform; + + if (this->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX && bbox) { + Geom::Affine bbox2user(bbox->width(), 0, 0, bbox->height(), bbox->left(), bbox->top()); + gs2user *= bbox2user; + } + + // we need to use vectors with the same direction to represent the transformed + // radius and the focus-center delta, because gs2user might contain non-uniform scaling + Geom::Point d(focus - center); + Geom::Point d_user(d.length(), 0); + Geom::Point r_user(radius, 0); + Geom::Point fr_user(focusr, 0); + d_user *= gs2user.withoutTranslation(); + r_user *= gs2user.withoutTranslation(); + fr_user *= gs2user.withoutTranslation(); + + double dx = d_user.x(), dy = d_user.y(); + cairo_user_to_device_distance(ct, &dx, &dy); + + // compute the tolerance distance in user space + // create a vector with the same direction as the transformed d, + // with the length equal to tolerance + double dl = hypot(dx, dy); + double tx = tolerance * dx / dl, ty = tolerance * dy / dl; + cairo_device_to_user_distance(ct, &tx, &ty); + double tolerance_user = hypot(tx, ty); + + if (d_user.length() + tolerance_user > r_user.length()) { + scale = r_user.length() / d_user.length(); + + // nudge the focus slightly inside + scale *= 1.0 - 2.0 * tolerance / dl; + } + + cairo_pattern_t *cp = cairo_pattern_create_radial( + scale * d.x() + center.x(), scale * d.y() + center.y(), focusr, + center.x(), center.y(), radius); + + sp_gradient_pattern_common_setup(cp, this, bbox, opacity); + + return cp; +} diff --git a/src/object/sp-radial-gradient.h b/src/object/sp-radial-gradient.h new file mode 100644 index 000000000..f90c8c7a9 --- /dev/null +++ b/src/object/sp-radial-gradient.h @@ -0,0 +1,49 @@ +#ifndef SP_RADIAL_GRADIENT_H +#define SP_RADIAL_GRADIENT_H + +/** \file + * SPRadialGradient: SVG implementtion. + */ + +#include "sp-gradient.h" +#include "svg/svg-length.h" + +typedef struct _cairo cairo_t; +typedef struct _cairo_pattern cairo_pattern_t; + +#define SP_RADIALGRADIENT(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_RADIALGRADIENT(obj) (dynamic_cast((SPObject*)obj) != NULL) + +/** Radial gradient. */ +class SPRadialGradient : public SPGradient { +public: + SPRadialGradient(); + virtual ~SPRadialGradient(); + + SVGLength cx; + SVGLength cy; + SVGLength r; + SVGLength fx; + SVGLength fy; + SVGLength fr; // Focus radius. Added in SVG 2 + + virtual cairo_pattern_t* pattern_new(cairo_t *ct, Geom::OptRect const &bbox, double opacity); + +protected: + virtual void build(SPDocument *document, Inkscape::XML::Node *repr); + virtual void set(unsigned key, char const *value); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); +}; + +#endif /* !SP_RADIAL_GRADIENT_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-rect.cpp b/src/object/sp-rect.cpp new file mode 100644 index 000000000..88dad5354 --- /dev/null +++ b/src/object/sp-rect.cpp @@ -0,0 +1,577 @@ +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "display/curve.h" + +#include "inkscape.h" +#include "document.h" +#include "attributes.h" +#include "style.h" +#include "sp-rect.h" +#include +#include "sp-guide.h" +#include "preferences.h" + +#define noRECT_VERBOSE + +//#define OBJECT_TRACE + +SPRect::SPRect() : SPShape() { +} + +SPRect::~SPRect() { +} + +void SPRect::build(SPDocument* doc, Inkscape::XML::Node* repr) { +#ifdef OBJECT_TRACE + objectTrace( "SPRect::build" ); +#endif + + SPShape::build(doc, repr); + + this->readAttr("x"); + this->readAttr("y"); + this->readAttr("width"); + this->readAttr("height"); + this->readAttr("rx"); + this->readAttr("ry"); + +#ifdef OBJECT_TRACE + objectTrace( "SPRect::build", false ); +#endif +} + +void SPRect::set(unsigned key, gchar const *value) { + +#ifdef OBJECT_TRACE + std::stringstream temp; + temp << "SPRect::set: " << key << " " << (value?value:"null"); + objectTrace( temp.str() ); +#endif + + /* fixme: We need real error processing some time */ + + // We must update the SVGLengths immediately or nodes may be misplaced after they are moved. + double const w = viewport.width(); + double const h = viewport.height(); + double const em = style->font_size.computed; + double const ex = em * 0.5; + + switch (key) { + case SP_ATTR_X: + this->x.readOrUnset(value); + this->x.update( em, ex, w ); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_Y: + this->y.readOrUnset(value); + this->y.update( em, ex, h ); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_WIDTH: + if (!this->width.read(value) || this->width.value < 0.0) { + this->width.unset(); + } + this->width.update( em, ex, w ); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_HEIGHT: + if (!this->height.read(value) || this->height.value < 0.0) { + this->height.unset(); + } + this->height.update( em, ex, h ); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_RX: + if (!this->rx.read(value) || this->rx.value <= 0.0) { + this->rx.unset(); + } + this->rx.update( em, ex, w ); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_RY: + if (!this->ry.read(value) || this->ry.value <= 0.0) { + this->ry.unset(); + } + this->ry.update( em, ex, h ); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + default: + SPShape::set(key, value); + break; + } +#ifdef OBJECT_TRACE + objectTrace( "SPRect::set", false ); +#endif +} + +void SPRect::update(SPCtx* ctx, unsigned int flags) { + +#ifdef OBJECT_TRACE + objectTrace( "SPRect::update", true, flags ); +#endif + + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + SPItemCtx const *ictx = reinterpret_cast(ctx); + + double const w = ictx->viewport.width(); + double const h = ictx->viewport.height(); + double const em = style->font_size.computed; + double const ex = 0.5 * em; // fixme: get x height from pango or libnrtype. + + this->x.update(em, ex, w); + this->y.update(em, ex, h); + this->width.update(em, ex, w); + this->height.update(em, ex, h); + this->rx.update(em, ex, w); + this->ry.update(em, ex, h); + this->set_shape(); + + flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore + } + + SPShape::update(ctx, flags); +#ifdef OBJECT_TRACE + objectTrace( "SPRect::update", false, flags ); +#endif +} + +Inkscape::XML::Node * SPRect::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + +#ifdef OBJECT_TRACE + objectTrace( "SPRect::write", true, flags ); +#endif + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:rect"); + } + + sp_repr_set_svg_length(repr, "width", this->width); + sp_repr_set_svg_length(repr, "height", this->height); + + if (this->rx._set) { + sp_repr_set_svg_length(repr, "rx", this->rx); + } + + if (this->ry._set) { + sp_repr_set_svg_length(repr, "ry", this->ry); + } + + sp_repr_set_svg_length(repr, "x", this->x); + sp_repr_set_svg_length(repr, "y", this->y); + + this->set_shape(); // evaluate SPCurve + SPShape::write(xml_doc, repr, flags); + +#ifdef OBJECT_TRACE + objectTrace( "SPRect::write", false, flags ); +#endif + + return repr; +} + +const char* SPRect::displayName() const { + return _("Rectangle"); +} + +#define C1 0.554 + +void SPRect::set_shape() { + if ((this->height.computed < 1e-18) || (this->width.computed < 1e-18)) { + this->setCurveInsync( NULL, TRUE); + this->setCurveBeforeLPE( NULL ); + return; + } + + SPCurve *c = new SPCurve(); + + double const x = this->x.computed; + double const y = this->y.computed; + double const w = this->width.computed; + double const h = this->height.computed; + double const w2 = w / 2; + double const h2 = h / 2; + double const rx = std::min(( this->rx._set + ? this->rx.computed + : ( this->ry._set + ? this->ry.computed + : 0.0 ) ), + .5 * this->width.computed); + double const ry = std::min(( this->ry._set + ? this->ry.computed + : ( this->rx._set + ? this->rx.computed + : 0.0 ) ), + .5 * this->height.computed); + /* TODO: Handle negative rx or ry as per + * http://www.w3.org/TR/SVG11/shapes.html#RectElementRXAttribute once Inkscape has proper error + * handling (see http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing). + */ + + /* We don't use proper circular/elliptical arcs, but bezier curves can approximate a 90-degree + * arc fairly well. + */ + if ((rx > 1e-18) && (ry > 1e-18)) { + c->moveto(x + rx, y); + + if (rx < w2) { + c->lineto(x + w - rx, y); + } + + c->curveto(x + w - rx * (1 - C1), y, x + w, y + ry * (1 - C1), x + w, y + ry); + + if (ry < h2) { + c->lineto(x + w, y + h - ry); + } + + c->curveto(x + w, y + h - ry * (1 - C1), x + w - rx * (1 - C1), y + h, x + w - rx, y + h); + + if (rx < w2) { + c->lineto(x + rx, y + h); + } + + c->curveto(x + rx * (1 - C1), y + h, x, y + h - ry * (1 - C1), x, y + h - ry); + + if (ry < h2) { + c->lineto(x, y + ry); + } + + c->curveto(x, y + ry * (1 - C1), x + rx * (1 - C1), y, x + rx, y); + } else { + c->moveto(x + 0.0, y + 0.0); + c->lineto(x + w, y + 0.0); + c->lineto(x + w, y + h); + c->lineto(x + 0.0, y + h); + } + + c->closepath(); + this->setCurveInsync(c, true); + this->setCurveBeforeLPE(c); + + // LPE is not applied because result can generally not be represented as SPRect + + c->unref(); +} + +/* fixme: Think (Lauris) */ + +void SPRect::setPosition(gdouble x, gdouble y, gdouble width, gdouble height) { + this->x = x; + this->y = y; + this->width = width; + this->height = height; + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +void SPRect::setRx(bool set, gdouble value) { + this->rx._set = set; + + if (set) { + this->rx = value; + } + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +void SPRect::setRy(bool set, gdouble value) { + this->ry._set = set; + + if (set) { + this->ry = value; + } + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +Geom::Affine SPRect::set_transform(Geom::Affine const& xform) { + /* Calculate rect start in parent coords. */ + Geom::Point pos(Geom::Point(this->x.computed, this->y.computed) * xform); + + /* This function takes care of translation and scaling, we return whatever parts we can't + handle. */ + Geom::Affine ret(Geom::Affine(xform).withoutTranslation()); + gdouble const sw = hypot(ret[0], ret[1]); + gdouble const sh = hypot(ret[2], ret[3]); + + if (sw > 1e-9) { + ret[0] /= sw; + ret[1] /= sw; + } else { + ret[0] = 1.0; + ret[1] = 0.0; + } + + if (sh > 1e-9) { + ret[2] /= sh; + ret[3] /= sh; + } else { + ret[2] = 0.0; + ret[3] = 1.0; + } + + /* Preserve units */ + this->width.scale( sw ); + this->height.scale( sh ); + + if (this->rx._set) { + this->rx.scale( sw ); + } + + if (this->ry._set) { + this->ry.scale( sh ); + } + + /* Find start in item coords */ + pos = pos * ret.inverse(); + this->x = pos[Geom::X]; + this->y = pos[Geom::Y]; + + this->set_shape(); + + // Adjust stroke width + this->adjust_stroke(sqrt(fabs(sw * sh))); + + // Adjust pattern fill + this->adjust_pattern(xform * ret.inverse()); + + // Adjust gradient fill + this->adjust_gradient(xform * ret.inverse()); + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + + return ret; +} + + +/** +Returns the ratio in which the vector from p0 to p1 is stretched by transform + */ +gdouble SPRect::vectorStretch(Geom::Point p0, Geom::Point p1, Geom::Affine xform) { + if (p0 == p1) { + return 0; + } + + return (Geom::distance(p0 * xform, p1 * xform) / Geom::distance(p0, p1)); +} + +void SPRect::setVisibleRx(gdouble rx) { + if (rx == 0) { + this->rx.unset(); + } else { + this->rx = rx / SPRect::vectorStretch( + Geom::Point(this->x.computed + 1, this->y.computed), + Geom::Point(this->x.computed, this->y.computed), + this->i2doc_affine()); + } + + this->updateRepr(); +} + +void SPRect::setVisibleRy(gdouble ry) { + if (ry == 0) { + this->ry.unset(); + } else { + this->ry = ry / SPRect::vectorStretch( + Geom::Point(this->x.computed, this->y.computed + 1), + Geom::Point(this->x.computed, this->y.computed), + this->i2doc_affine()); + } + + this->updateRepr(); +} + +gdouble SPRect::getVisibleRx() const { + if (!this->rx._set) { + return 0; + } + + return this->rx.computed * SPRect::vectorStretch( + Geom::Point(this->x.computed + 1, this->y.computed), + Geom::Point(this->x.computed, this->y.computed), + this->i2doc_affine()); +} + +gdouble SPRect::getVisibleRy() const { + if (!this->ry._set) { + return 0; + } + + return this->ry.computed * SPRect::vectorStretch( + Geom::Point(this->x.computed, this->y.computed + 1), + Geom::Point(this->x.computed, this->y.computed), + this->i2doc_affine()); +} + +Geom::Rect SPRect::getRect() const { + Geom::Point p0 = Geom::Point(this->x.computed, this->y.computed); + Geom::Point p2 = Geom::Point(this->x.computed + this->width.computed, this->y.computed + this->height.computed); + + return Geom::Rect(p0, p2); +} + +void SPRect::compensateRxRy(Geom::Affine xform) { + if (this->rx.computed == 0 && this->ry.computed == 0) { + return; // nothing to compensate + } + + // test unit vectors to find out compensation: + Geom::Point c(this->x.computed, this->y.computed); + Geom::Point cx = c + Geom::Point(1, 0); + Geom::Point cy = c + Geom::Point(0, 1); + + // apply previous transform if any + c *= this->transform; + cx *= this->transform; + cy *= this->transform; + + // find out stretches that we need to compensate + gdouble eX = SPRect::vectorStretch(cx, c, xform); + gdouble eY = SPRect::vectorStretch(cy, c, xform); + + // If only one of the radii is set, set both radii so they have the same visible length + // This is needed because if we just set them the same length in SVG, they might end up unequal because of transform + if ((this->rx._set && !this->ry._set) || (this->ry._set && !this->rx._set)) { + gdouble r = MAX(this->rx.computed, this->ry.computed); + this->rx = r / eX; + this->ry = r / eY; + } else { + this->rx = this->rx.computed / eX; + this->ry = this->ry.computed / eY; + } + + // Note that a radius may end up larger than half-side if the rect is scaled down; + // that's ok because this preserves the intended radii in case the rect is enlarged again, + // and set_shape will take care of trimming too large radii when generating d= +} + +void SPRect::setVisibleWidth(gdouble width) { + this->width = width / SPRect::vectorStretch( + Geom::Point(this->x.computed + 1, this->y.computed), + Geom::Point(this->x.computed, this->y.computed), + this->i2doc_affine()); + + this->updateRepr(); +} + +void SPRect::setVisibleHeight(gdouble height) { + this->height = height / SPRect::vectorStretch( + Geom::Point(this->x.computed, this->y.computed + 1), + Geom::Point(this->x.computed, this->y.computed), + this->i2doc_affine()); + + this->updateRepr(); +} + +gdouble SPRect::getVisibleWidth() const { + if (!this->width._set) { + return 0; + } + + return this->width.computed * SPRect::vectorStretch( + Geom::Point(this->x.computed + 1, this->y.computed), + Geom::Point(this->x.computed, this->y.computed), + this->i2doc_affine()); +} + +gdouble SPRect::getVisibleHeight() const { + if (!this->height._set) { + return 0; + } + + return this->height.computed * SPRect::vectorStretch( + Geom::Point(this->x.computed, this->y.computed + 1), + Geom::Point(this->x.computed, this->y.computed), + this->i2doc_affine()); +} + +void SPRect::snappoints(std::vector &p, Inkscape::SnapPreferences const *snapprefs) const { + /* This method overrides sp_shape_snappoints, which is the default for any shape. The default method + returns all eight points along the path of a rounded rectangle, but not the real corners. Snapping + the startpoint and endpoint of each rounded corner is not very useful and really confusing. Instead + we could snap either the real corners, or not snap at all. Bulia Byak opted to snap the real corners, + but it should be noted that this might be confusing in some cases with relatively large radii. With + small radii though the user will easily understand which point is snapping. */ + + Geom::Affine const i2dt (this->i2dt_affine ()); + + Geom::Point p0 = Geom::Point(this->x.computed, this->y.computed) * i2dt; + Geom::Point p1 = Geom::Point(this->x.computed, this->y.computed + this->height.computed) * i2dt; + Geom::Point p2 = Geom::Point(this->x.computed + this->width.computed, this->y.computed + this->height.computed) * i2dt; + Geom::Point p3 = Geom::Point(this->x.computed + this->width.computed, this->y.computed) * i2dt; + + if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_RECT_CORNER)) { + p.push_back(Inkscape::SnapCandidatePoint(p0, Inkscape::SNAPSOURCE_RECT_CORNER, Inkscape::SNAPTARGET_RECT_CORNER)); + p.push_back(Inkscape::SnapCandidatePoint(p1, Inkscape::SNAPSOURCE_RECT_CORNER, Inkscape::SNAPTARGET_RECT_CORNER)); + p.push_back(Inkscape::SnapCandidatePoint(p2, Inkscape::SNAPSOURCE_RECT_CORNER, Inkscape::SNAPTARGET_RECT_CORNER)); + p.push_back(Inkscape::SnapCandidatePoint(p3, Inkscape::SNAPSOURCE_RECT_CORNER, Inkscape::SNAPTARGET_RECT_CORNER)); + } + + if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_LINE_MIDPOINT)) { + p.push_back(Inkscape::SnapCandidatePoint((p0 + p1)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT)); + p.push_back(Inkscape::SnapCandidatePoint((p1 + p2)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT)); + p.push_back(Inkscape::SnapCandidatePoint((p2 + p3)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT)); + p.push_back(Inkscape::SnapCandidatePoint((p3 + p0)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT)); + } + + if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_OBJECT_MIDPOINT)) { + p.push_back(Inkscape::SnapCandidatePoint((p0 + p2)/2, Inkscape::SNAPSOURCE_OBJECT_MIDPOINT, Inkscape::SNAPTARGET_OBJECT_MIDPOINT)); + } +} + +void SPRect::convert_to_guides() const { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + if (!prefs->getBool("/tools/shapes/rect/convertguides", true)) { + // Use bounding box instead of edges + SPShape::convert_to_guides(); + return; + } + + std::list > pts; + + Geom::Affine const i2dt(this->i2dt_affine()); + + Geom::Point A1(Geom::Point(this->x.computed, this->y.computed) * i2dt); + Geom::Point A2(Geom::Point(this->x.computed, this->y.computed + this->height.computed) * i2dt); + Geom::Point A3(Geom::Point(this->x.computed + this->width.computed, this->y.computed + this->height.computed) * i2dt); + Geom::Point A4(Geom::Point(this->x.computed + this->width.computed, this->y.computed) * i2dt); + + pts.push_back(std::make_pair(A1, A2)); + pts.push_back(std::make_pair(A2, A3)); + pts.push_back(std::make_pair(A3, A4)); + pts.push_back(std::make_pair(A4, A1)); + + sp_guide_pt_pairs_to_guides(this->document, pts); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-rect.h b/src/object/sp-rect.h new file mode 100644 index 000000000..757229724 --- /dev/null +++ b/src/object/sp-rect.h @@ -0,0 +1,88 @@ +#ifndef SEEN_SP_RECT_H +#define SEEN_SP_RECT_H + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * Abhishek Sharma + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <2geom/forward.h> + +#include "svg/svg-length.h" +#include "sp-shape.h" + +#define SP_RECT(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_RECT(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPRect : public SPShape { +public: + SPRect(); + virtual ~SPRect(); + + void setPosition(double x, double y, double width, double height); + + /* If SET if FALSE, VALUE is just ignored */ + void setRx(bool set, double value); + void setRy(bool set, double value); + + double getVisibleRx() const; + void setVisibleRx(double rx); + + double getVisibleRy() const; + void setVisibleRy(double ry); + + Geom::Rect getRect() const; + + double getVisibleWidth() const; + void setVisibleWidth(double rx); + + double getVisibleHeight() const; + void setVisibleHeight(double ry); + + void compensateRxRy(Geom::Affine xform); + + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + + virtual void set(unsigned key, char const *value); + virtual void update(SPCtx* ctx, unsigned int flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); + virtual const char* displayName() const; + + virtual void set_shape(); + virtual Geom::Affine set_transform(Geom::Affine const& xform); + + virtual void snappoints(std::vector &p, Inkscape::SnapPreferences const *snapprefs) const; + virtual void convert_to_guides() const; + + SVGLength x; + SVGLength y; + SVGLength width; + SVGLength height; + SVGLength rx; + SVGLength ry; + +private: + static double vectorStretch(Geom::Point p0, Geom::Point p1, Geom::Affine xform); +}; + +#endif // SEEN_SP_RECT_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-root.cpp b/src/object/sp-root.cpp new file mode 100644 index 000000000..3f31588cc --- /dev/null +++ b/src/object/sp-root.cpp @@ -0,0 +1,393 @@ +/** \file + * SVG \ implementation. + */ +/* + * Authors: + * Lauris Kaplinski + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include <2geom/transforms.h> + +#include "attributes.h" +#include "print.h" +#include "document.h" +#include "inkscape-version.h" +#include "sp-defs.h" +#include "sp-root.h" +#include "display/drawing-group.h" +#include "svg/stringstream.h" +#include "svg/svg.h" +#include "xml/repr.h" +#include "util/units.h" + +SPRoot::SPRoot() : SPGroup(), SPViewBox() +{ + this->onload = NULL; + + static Inkscape::Version const zero_version(0, 0); + + sp_version_from_string(SVG_VERSION, &this->original.svg); + this->version.svg = zero_version; + this->original.svg = zero_version; + this->version.inkscape = zero_version; + this->original.inkscape = zero_version; + + this->unset_x_and_y(); + this->width.unset(SVGLength::PERCENT, 1.0, 1.0); + this->height.unset(SVGLength::PERCENT, 1.0, 1.0); + + this->defs = NULL; +} + +SPRoot::~SPRoot() +{ +} + +void SPRoot::unset_x_and_y() +{ + this->x.unset(SVGLength::PERCENT, 0.0, 0.0); // Ignored for root SVG element + this->y.unset(SVGLength::PERCENT, 0.0, 0.0); +} + +void SPRoot::build(SPDocument *document, Inkscape::XML::Node *repr) +{ + //XML Tree being used directly here while it shouldn't be. + if (!this->getRepr()->attribute("version")) { + repr->setAttribute("version", SVG_VERSION); + } + + this->readAttr("version"); + this->readAttr("inkscape:version"); + /* It is important to parse these here, so objects will have viewport build-time */ + this->readAttr("x"); + this->readAttr("y"); + this->readAttr("width"); + this->readAttr("height"); + this->readAttr("viewBox"); + this->readAttr("preserveAspectRatio"); + this->readAttr("onload"); + + SPGroup::build(document, repr); + + // Search for first node + for (auto& o: children) { + if (SP_IS_DEFS(&o)) { + this->defs = SP_DEFS(&o); + break; + } + } + + // clear transform, if any was read in - SVG does not allow transform= on + SP_ITEM(this)->transform = Geom::identity(); +} + +void SPRoot::release() +{ + this->defs = NULL; + + SPGroup::release(); +} + + +void SPRoot::set(unsigned int key, const gchar *value) +{ + switch (key) { + case SP_ATTR_VERSION: + if (!sp_version_from_string(value, &this->version.svg)) { + this->version.svg = this->original.svg; + } + break; + + case SP_ATTR_INKSCAPE_VERSION: + if (!sp_version_from_string(value, &this->version.inkscape)) { + this->version.inkscape = this->original.inkscape; + } + break; + + case SP_ATTR_X: + /* Valid for non-root SVG elements; ex, em not handled correctly. */ + if (!this->x.read(value)) { + this->x.unset(SVGLength::PERCENT, 0.0, 0.0); + } + + /* fixme: I am almost sure these do not require viewport flag (Lauris) */ + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + break; + + case SP_ATTR_Y: + /* Valid for non-root SVG elements; ex, em not handled correctly. */ + if (!this->y.read(value)) { + this->y.unset(SVGLength::PERCENT, 0.0, 0.0); + } + + /* fixme: I am almost sure these do not require viewport flag (Lauris) */ + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + break; + + case SP_ATTR_WIDTH: + if (!this->width.read(value) || !(this->width.computed > 0.0)) { + this->width.unset(SVGLength::PERCENT, 1.0, 1.0); + } + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + break; + + case SP_ATTR_HEIGHT: + if (!this->height.read(value) || !(this->height.computed > 0.0)) { + this->height.unset(SVGLength::PERCENT, 1.0, 1.0); + } + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + break; + + case SP_ATTR_VIEWBOX: + set_viewBox( value ); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + break; + + case SP_ATTR_PRESERVEASPECTRATIO: + set_preserveAspectRatio( value ); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + break; + + case SP_ATTR_ONLOAD: + this->onload = (char *) value; + break; + + default: + /* Pass the set event to the parent */ + SPGroup::set(key, value); + break; + } +} + +void SPRoot::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) +{ + SPGroup::child_added(child, ref); + + SPObject *co = this->document->getObjectByRepr(child); + // NOTE: some XML nodes do not have corresponding SP objects, + // for instance inkscape:clipboard used in the clipboard code. + // See LP bug #1227827 + //g_assert (co != NULL || !strcmp("comment", child->name())); // comment repr node has no object + + if (co && SP_IS_DEFS(co)) { + // We search for first node - it is not beautiful, but works + for (auto& c: children) { + if (SP_IS_DEFS(&c)) { + this->defs = SP_DEFS(&c); + break; + } + } + } +} + +void SPRoot::remove_child(Inkscape::XML::Node *child) +{ + if (this->defs && (this->defs->getRepr() == child)) { + SPObject *iter = 0; + + // We search for first remaining node - it is not beautiful, but works + for (auto& child: children) { + iter = &child; + if (SP_IS_DEFS(iter) && (SPDefs *)iter != this->defs) { + this->defs = (SPDefs *)iter; + break; + } + } + + if (!iter) { + /* we should probably create a new here? */ + this->defs = NULL; + } + } + + SPGroup::remove_child(child); +} + +void SPRoot::setRootDimensions() +{ + /* + * This is the root SVG element: + * + * x, y, width, and height apply to positioning the SVG element inside a parent. + * For the root SVG in Inkscape there is no parent, thus special rules apply: + * If width, height not set, width = 100%, height = 100% (as always). + * If width and height are in percent, they are percent of viewBox width/height. + * If width, height, and viewBox are not set... pick "random" width/height. + * x, y are ignored. + * initial viewport = (0 0 width height) + */ + if( this->viewBox_set ) { + + if( this->width._set ) { + // Check if this is necessary + if (this->width.unit == SVGLength::PERCENT) { + this->width.computed = this->width.value * this->viewBox.width(); + } + } else { + this->width.set( SVGLength::PX, this->viewBox.width(), this->viewBox.width() ); + } + + if( this->height._set ) { + if (this->height.unit == SVGLength::PERCENT) { + this->height.computed = this->height.value * this->viewBox.height(); + } + } else { + this->height.set(SVGLength::PX, this->viewBox.height(), this->viewBox.height() ); + } + + } else { + + if( !this->width._set || this->width.unit == SVGLength::PERCENT) { + this->width.set( SVGLength::PX, 300, 300 ); // CSS/SVG default + } + + if( !this->height._set || this->height.unit == SVGLength::PERCENT) { + this->height.set( SVGLength::PX, 150, 150 ); // CSS/SVG default + } + } + + // Ignore x, y values for root element + this->unset_x_and_y(); +} + +void SPRoot::update(SPCtx *ctx, guint flags) +{ + SPItemCtx const *ictx = (SPItemCtx const *) ctx; + + if( !this->parent ) { + this->setRootDimensions(); + } + + // Calculate x, y, width, height from parent/initial viewport + this->calcDimsFromParentViewport(ictx); + + // std::cout << "SPRoot::update: final:" + // << " x: " << x.computed + // << " y: " << y.computed + // << " width: " << width.computed + // << " height: " << height.computed << std::endl; + + // Calculate new viewport + SPItemCtx rctx = *ictx; + rctx.viewport = Geom::Rect::from_xywh( this->x.computed, this->y.computed, + this->width.computed, this->height.computed ); + rctx = get_rctx( &rctx, Inkscape::Util::Quantity::convert(1, this->document->getDisplayUnit(), "px") ); + + /* And invoke parent method */ + SPGroup::update((SPCtx *) &rctx, flags); + + /* As last step set additional transform of drawing group */ + for (SPItemView *v = this->display; v != NULL; v = v->next) { + Inkscape::DrawingGroup *g = dynamic_cast(v->arenaitem); + g->setChildTransform(this->c2p); + } +} + +void SPRoot::modified(unsigned int flags) +{ + SPGroup::modified(flags); + + /* fixme: (Lauris) */ + if (!this->parent && (flags & SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + this->document->emitResizedSignal(this->width.computed, this->height.computed); + } +} + + +Inkscape::XML::Node *SPRoot::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) +{ + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:svg"); + } + + /* Only update version string on successful write to file. This is handled by 'file_save()'. + * if (flags & SP_OBJECT_WRITE_EXT) { + * repr->setAttribute("inkscape:version", Inkscape::version_string); + * } + */ + + if (!repr->attribute("version")) { + gchar *myversion = sp_version_to_string(this->version.svg); + repr->setAttribute("version", myversion); + g_free(myversion); + } + + if (fabs(this->x.computed) > 1e-9) { + sp_repr_set_svg_double(repr, "x", this->x.computed); + } + + if (fabs(this->y.computed) > 1e-9) { + sp_repr_set_svg_double(repr, "y", this->y.computed); + } + + /* Unlike all other SPObject, here we want to preserve absolute units too (and only here, + * according to the recommendation in http://www.w3.org/TR/SVG11/coords.html#Units). + */ + repr->setAttribute("width", sp_svg_length_write_with_units(this->width).c_str()); + repr->setAttribute("height", sp_svg_length_write_with_units(this->height).c_str()); + + if (this->viewBox_set) { + Inkscape::SVGOStringStream os; + os << this->viewBox.left() << " " << this->viewBox.top() << " " + << this->viewBox.width() << " " << this->viewBox.height(); + + repr->setAttribute("viewBox", os.str().c_str()); + } + + SPGroup::write(xml_doc, repr, flags); + + return repr; +} + +Inkscape::DrawingItem *SPRoot::show(Inkscape::Drawing &drawing, unsigned int key, unsigned int flags) +{ + Inkscape::DrawingItem *ai = SPGroup::show(drawing, key, flags); + + if (ai) { + Inkscape::DrawingGroup *g = dynamic_cast(ai); + g->setChildTransform(this->c2p); + } + + // Uncomment to print out XML tree + // getRepr()->recursivePrintTree(0); + + // Uncomment to print out SP Object tree + // recursivePrintTree(0); + + // Uncomment to print out Display Item tree + // ai->recursivePrintTree(0); + + return ai; +} + +void SPRoot::print(SPPrintContext *ctx) +{ + sp_print_bind(ctx, this->c2p, 1.0); + + SPGroup::print(ctx); + + sp_print_release(ctx); +} + +const char *SPRoot::displayName() const { + return "SVG"; // Do not translate +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-root.h b/src/object/sp-root.h new file mode 100644 index 000000000..4a37840d9 --- /dev/null +++ b/src/object/sp-root.h @@ -0,0 +1,78 @@ +/** \file + * SPRoot: SVG \ implementation. + */ +/* + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SP_ROOT_H_SEEN +#define SP_ROOT_H_SEEN + +#include "version.h" +#include "svg/svg-length.h" +#include "sp-item-group.h" +#include "viewbox.h" +#include "sp-dimensions.h" + +#define SP_ROOT(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_ROOT(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPDefs; + +/** \ element */ +class SPRoot : public SPGroup, public SPViewBox, public SPDimensions { +public: + SPRoot(); + virtual ~SPRoot(); + + struct { + Inkscape::Version svg; + Inkscape::Version inkscape; + } version, original; + + char *onload; + + /** + * Primary \ element where we put new defs (patterns, gradients etc.). + * + * At the time of writing, this is chosen as the first \ child of + * this \ element: see writers of this member in sp-root.cpp. + */ + SPDefs *defs; + + virtual void build(SPDocument *document, Inkscape::XML::Node *repr); + virtual void release(); + virtual void set(unsigned int key, char const* value); + virtual void update(SPCtx *ctx, unsigned int flags); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); + + virtual void modified(unsigned int flags); + virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref); + virtual void remove_child(Inkscape::XML::Node* child); + + virtual Inkscape::DrawingItem* show(Inkscape::Drawing &drawing, unsigned int key, unsigned int flags); + virtual void print(SPPrintContext *ctx); + virtual const char* displayName() const; +private: + void unset_x_and_y(); + void setRootDimensions(); +}; + +#endif /* !SP_ROOT_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-script.cpp b/src/object/sp-script.cpp new file mode 100644 index 000000000..144c8d76a --- /dev/null +++ b/src/object/sp-script.cpp @@ -0,0 +1,85 @@ +/* + * SVG %d object"), _("of %d objects"), len), len); +} + +void SPSwitch::child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref) { + SPGroup::child_added(child, ref); + + this->_reevaluate(true); +} + +void SPSwitch::remove_child(Inkscape::XML::Node *child) { + SPGroup::remove_child(child); + + this->_reevaluate(); +} + +void SPSwitch::order_changed (Inkscape::XML::Node *child, Inkscape::XML::Node *old_ref, Inkscape::XML::Node *new_ref) +{ + SPGroup::order_changed(child, old_ref, new_ref); + + this->_reevaluate(); +} + +void SPSwitch::_reevaluate(bool /*add_to_drawing*/) { + SPObject *evaluated_child = _evaluateFirst(); + if (!evaluated_child || _cached_item == evaluated_child) { + return; + } + + _releaseLastItem(_cached_item); + + std::vector item_list = _childList(false, SPObject::ActionShow); + for ( std::vector::const_reverse_iterator iter=item_list.rbegin();iter!=item_list.rend();++iter) { + SPObject *o = *iter; + if ( !SP_IS_ITEM (o) ) { + continue; + } + + SPItem * child = SP_ITEM(o); + child->setEvaluated(o == evaluated_child); + } + + _cached_item = evaluated_child; + _release_connection = evaluated_child->connectRelease(sigc::bind(sigc::ptr_fun(&SPSwitch::_releaseItem), this)); + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); +} + +void SPSwitch::_releaseItem(SPObject *obj, SPSwitch *selection) +{ + selection->_releaseLastItem(obj); +} + +void SPSwitch::_releaseLastItem(SPObject *obj) +{ + if (NULL == this->_cached_item || this->_cached_item != obj) + return; + + this->_release_connection.disconnect(); + this->_cached_item = NULL; +} + +void SPSwitch::_showChildren (Inkscape::Drawing &drawing, Inkscape::DrawingItem *ai, unsigned int key, unsigned int flags) { + SPObject *evaluated_child = this->_evaluateFirst(); + + std::vector l = this->_childList(false, SPObject::ActionShow); + + for ( std::vector::const_reverse_iterator iter=l.rbegin();iter!=l.rend();++iter) { + SPObject *o = *iter; + + if (SP_IS_ITEM (o)) { + SPItem * child = SP_ITEM(o); + child->setEvaluated(o == evaluated_child); + Inkscape::DrawingItem *ac = child->invoke_show (drawing, key, flags); + + if (ac) { + ai->appendChild(ac); + } + } + } +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-switch.h b/src/object/sp-switch.h new file mode 100644 index 000000000..57ce8b236 --- /dev/null +++ b/src/object/sp-switch.h @@ -0,0 +1,49 @@ +#ifndef SEEN_SP_SWITCH_H +#define SEEN_SP_SWITCH_H + +/* + * SVG implementation + * + * Authors: + * Andrius R. + * + * Copyright (C) 2006 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include + +#include "sp-item-group.h" + + +#define SP_SWITCH(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_SWITCH(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPSwitch : public SPGroup { +public: + SPSwitch(); + virtual ~SPSwitch(); + + void resetChildEvaluated() { _reevaluate(); } + + std::vector _childList(bool add_ref, SPObject::Action action); + virtual void _showChildren (Inkscape::Drawing &drawing, Inkscape::DrawingItem *ai, unsigned int key, unsigned int flags); + + SPObject *_evaluateFirst(); + void _reevaluate(bool add_to_arena = false); + static void _releaseItem(SPObject *obj, SPSwitch *selection); + void _releaseLastItem(SPObject *obj); + + SPObject *_cached_item; + sigc::connection _release_connection; + + virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref); + virtual void remove_child(Inkscape::XML::Node *child); + virtual void order_changed(Inkscape::XML::Node *child, Inkscape::XML::Node *old_ref, Inkscape::XML::Node *new_ref); + virtual const char* displayName() const; + virtual gchar *description() const; +}; + +#endif diff --git a/src/object/sp-symbol.cpp b/src/object/sp-symbol.cpp new file mode 100644 index 000000000..55b5101af --- /dev/null +++ b/src/object/sp-symbol.cpp @@ -0,0 +1,169 @@ +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * Abhishek Sharma + * Jon A. Cruz + * + * Copyright (C) 1999-2003 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include <2geom/transforms.h> +#include "display/drawing-group.h" +#include "xml/repr.h" +#include "attributes.h" +#include "print.h" +#include "sp-symbol.h" +#include "document.h" + +SPSymbol::SPSymbol() : SPGroup(), SPViewBox() { +} + +SPSymbol::~SPSymbol() { +} + +void SPSymbol::build(SPDocument *document, Inkscape::XML::Node *repr) { + this->readAttr( "viewBox" ); + this->readAttr( "preserveAspectRatio" ); + + SPGroup::build(document, repr); +} + +void SPSymbol::release() { + SPGroup::release(); +} + +void SPSymbol::set(unsigned int key, const gchar* value) { + switch (key) { + case SP_ATTR_VIEWBOX: + set_viewBox( value ); + // std::cout << "Symbol: ViewBox: " << viewBox << std::endl; + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + break; + + case SP_ATTR_PRESERVEASPECTRATIO: + set_preserveAspectRatio( value ); + // std::cout << "Symbol: Preserve aspect ratio: " << aspect_align << ", " << aspect_clip << std::endl; + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + break; + + default: + SPGroup::set(key, value); + break; + } +} + +void SPSymbol::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) { + SPGroup::child_added(child, ref); +} + + +void SPSymbol::update(SPCtx *ctx, guint flags) { + if (this->cloned) { + + SPItemCtx *ictx = (SPItemCtx *) ctx; + SPItemCtx rctx = get_rctx( ictx ); + + // And invoke parent method + SPGroup::update((SPCtx *) &rctx, flags); + + // As last step set additional transform of drawing group + for (SPItemView *v = this->display; v != NULL; v = v->next) { + Inkscape::DrawingGroup *g = dynamic_cast(v->arenaitem); + g->setChildTransform(this->c2p); + } + } else { + // No-op + SPGroup::update(ctx, flags); + } +} + +void SPSymbol::modified(unsigned int flags) { + SPGroup::modified(flags); +} + + +Inkscape::XML::Node* SPSymbol::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:symbol"); + } + + //XML Tree being used directly here while it shouldn't be. + repr->setAttribute("viewBox", this->getRepr()->attribute("viewBox")); + + //XML Tree being used directly here while it shouldn't be. + repr->setAttribute("preserveAspectRatio", this->getRepr()->attribute("preserveAspectRatio")); + + SPGroup::write(xml_doc, repr, flags); + + return repr; +} + +Inkscape::DrawingItem* SPSymbol::show(Inkscape::Drawing &drawing, unsigned int key, unsigned int flags) { + Inkscape::DrawingItem *ai = 0; + + if (this->cloned) { + // Cloned is actually renderable + ai = SPGroup::show(drawing, key, flags); + Inkscape::DrawingGroup *g = dynamic_cast(ai); + + if (g) { + g->setChildTransform(this->c2p); + } + } + + return ai; +} + +void SPSymbol::hide(unsigned int key) { + if (this->cloned) { + /* Cloned is actually renderable */ + SPGroup::hide(key); + } +} + + +Geom::OptRect SPSymbol::bbox(Geom::Affine const &transform, SPItem::BBoxType type) const { + Geom::OptRect bbox; + + // We don't need a bounding box for Symbols dialog when selecting + // symbols. They have no canvas location. But cloned symbols are. + if (this->cloned) { + Geom::Affine const a( this->c2p * transform ); + bbox = SPGroup::bbox(a, type); + } + + return bbox; +} + +void SPSymbol::print(SPPrintContext* ctx) { + if (this->cloned) { + // Cloned is actually renderable + + sp_print_bind(ctx, this->c2p, 1.0); + + SPGroup::print(ctx); + + sp_print_release (ctx); + } +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-symbol.h b/src/object/sp-symbol.h new file mode 100644 index 000000000..6b46a8c1a --- /dev/null +++ b/src/object/sp-symbol.h @@ -0,0 +1,48 @@ +#ifndef SEEN_SP_SYMBOL_H +#define SEEN_SP_SYMBOL_H + +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2003 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +/* + * This is quite similar in logic to + * Maybe we should merge them somehow (Lauris) + */ + +#include <2geom/affine.h> +#include "sp-item-group.h" +#include "viewbox.h" + +#define SP_TYPE_SYMBOL (sp_symbol_get_type ()) +#define SP_SYMBOL(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_SYMBOL(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPSymbol : public SPGroup, public SPViewBox { +public: + SPSymbol(); + virtual ~SPSymbol(); + + virtual void build(SPDocument *document, Inkscape::XML::Node *repr); + virtual void release(); + virtual void set(unsigned int key, char const* value); + virtual void update(SPCtx *ctx, unsigned int flags); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); + + virtual void modified(unsigned int flags); + virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref); + + virtual Inkscape::DrawingItem* show(Inkscape::Drawing &drawing, unsigned int key, unsigned int flags); + virtual void print(SPPrintContext *ctx); + virtual Geom::OptRect bbox(Geom::Affine const &transform, SPItem::BBoxType type) const; + virtual void hide (unsigned int key); +}; + +#endif diff --git a/src/object/sp-tag-use-reference.cpp b/src/object/sp-tag-use-reference.cpp new file mode 100644 index 000000000..bb03c120a --- /dev/null +++ b/src/object/sp-tag-use-reference.cpp @@ -0,0 +1,137 @@ +/* + * The reference corresponding to href of element. + * + * Copyright (C) Theodore Janeczko 2012-2014 + * + * Released under GNU GPL, read the file 'COPYING' for more information. + */ + +#include "sp-tag-use-reference.h" + +#include +#include + +#include "bad-uri-exception.h" +#include "livarot/Path.h" +#include "preferences.h" +#include "sp-shape.h" +#include "sp-text.h" +#include "uri.h" + + +bool SPTagUseReference::_acceptObject(SPObject * const obj) const +{ + if (SP_IS_ITEM(obj)) { + return URIReference::_acceptObject(obj); + } else { + return false; + } +} + + +static void sp_usepath_href_changed(SPObject *old_ref, SPObject *ref, SPTagUsePath *offset); +static void sp_usepath_delete_self(SPObject *deleted, SPTagUsePath *offset); + +SPTagUsePath::SPTagUsePath(SPObject* i_owner):SPTagUseReference(i_owner) +{ + owner=i_owner; + originalPath = NULL; + sourceDirty=false; + sourceHref = NULL; + sourceRepr = NULL; + sourceObject = NULL; + _changed_connection = changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_usepath_href_changed), this)); // listening to myself, this should be virtual instead + + user_unlink = NULL; +} + +SPTagUsePath::~SPTagUsePath(void) +{ + delete originalPath; + originalPath = NULL; + + _changed_connection.disconnect(); // to do before unlinking + + quit_listening(); + unlink(); +} + +void +SPTagUsePath::link(char *to) +{ + if ( to == NULL ) { + quit_listening(); + unlink(); + } else { + if ( !sourceHref || ( strcmp(to, sourceHref) != 0 ) ) { + g_free(sourceHref); + sourceHref = g_strdup(to); + try { + attach(Inkscape::URI(to)); + } catch (Inkscape::BadURIException &e) { + /* TODO: Proper error handling as per + * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing. + */ + g_warning("%s", e.what()); + detach(); + } + } + } +} + +void +SPTagUsePath::unlink(void) +{ + g_free(sourceHref); + sourceHref = NULL; + detach(); +} + +void +SPTagUsePath::start_listening(SPObject* to) +{ + if ( to == NULL ) { + return; + } + sourceObject = to; + sourceRepr = to->getRepr(); + _delete_connection = to->connectDelete(sigc::bind(sigc::ptr_fun(&sp_usepath_delete_self), this)); +} + +void +SPTagUsePath::quit_listening(void) +{ + if ( sourceObject == NULL ) { + return; + } + _delete_connection.disconnect(); + sourceRepr = NULL; + sourceObject = NULL; +} + +static void +sp_usepath_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, SPTagUsePath *offset) +{ + offset->quit_listening(); + SPItem *refobj = offset->getObject(); + if ( refobj ) { + offset->start_listening(refobj); + } +} + +static void +sp_usepath_delete_self(SPObject */*deleted*/, SPTagUsePath *offset) +{ + offset->owner->deleteObject(); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-tag-use-reference.h b/src/object/sp-tag-use-reference.h new file mode 100644 index 000000000..0895be010 --- /dev/null +++ b/src/object/sp-tag-use-reference.h @@ -0,0 +1,79 @@ +#ifndef SEEN_SP_TAG_USE_REFERENCE_H +#define SEEN_SP_TAG_USE_REFERENCE_H + +/* + * The reference corresponding to href of element. + * + * Copyright (C) Theodore Janeczko 2012-2014 + * + * Released under GNU GPL, read the file 'COPYING' for more information. + */ + +#include +#include +#include + +#include "sp-object.h" +#include "sp-item.h" +#include "uri-references.h" + +class Path; + +namespace Inkscape { +namespace XML { + class Node; +} +} + + +class SPTagUseReference : public Inkscape::URIReference { +public: + SPTagUseReference(SPObject *owner) : URIReference(owner) {} + + SPItem *getObject() const { + return static_cast(URIReference::getObject()); + } + +protected: + virtual bool _acceptObject(SPObject * const obj) const; + +}; + + +class SPTagUsePath : public SPTagUseReference { +public: + Path *originalPath; + bool sourceDirty; + + SPObject *owner; + gchar *sourceHref; + Inkscape::XML::Node *sourceRepr; + SPObject *sourceObject; + + sigc::connection _delete_connection; + sigc::connection _changed_connection; + + SPTagUsePath(SPObject* i_owner); + ~SPTagUsePath(void); + + void link(char* to); + void unlink(void); + void start_listening(SPObject* to); + void quit_listening(void); + void refresh_source(void); + + void (*user_unlink) (SPObject *user); +}; + +#endif /* !SEEN_SP_USE_REFERENCE_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-tag-use.cpp b/src/object/sp-tag-use.cpp new file mode 100644 index 000000000..1312b923f --- /dev/null +++ b/src/object/sp-tag-use.cpp @@ -0,0 +1,198 @@ +/* + * SVG implementation + * + * Authors: + * Theodore Janeczko + * Liam P White + * + * Copyright (C) Theodore Janeczko 2012-2014 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-tag-use.h" + +#include +#include + +#include + +#include "bad-uri-exception.h" +#include "display/drawing-group.h" +#include "attributes.h" +#include "document.h" +#include "uri.h" +#include "xml/repr.h" +#include "preferences.h" +#include "style.h" +#include "sp-factory.h" +#include "sp-symbol.h" +#include "sp-tag-use-reference.h" + +SPTagUse::SPTagUse() +{ + href = NULL; + //new (_changed_connection) sigc::connection; + ref = new SPTagUseReference(this); + + _changed_connection = ref->changedSignal().connect(sigc::mem_fun(*this, &SPTagUse::href_changed)); +} + +SPTagUse::~SPTagUse() +{ + + if (child) { + detach(child); + child = NULL; + } + + ref->detach(); + delete ref; + ref = 0; + + _changed_connection.~connection(); //FIXME why? +} + +void +SPTagUse::build(SPDocument *document, Inkscape::XML::Node *repr) +{ + SPObject::build(document, repr); + readAttr( "xlink:href" ); + + // We don't need to create child here: + // reading xlink:href will attach ref, and that will cause the changed signal to be emitted, + // which will call sp_tag_use_href_changed, and that will take care of the child +} + +void +SPTagUse::release() +{ + + if (child) { + detach(child); + child = NULL; + } + + _changed_connection.disconnect(); + + g_free(href); + href = NULL; + + ref->detach(); + + SPObject::release(); +} + +void +SPTagUse::set(unsigned key, gchar const *value) +{ + + switch (key) { + case SP_ATTR_XLINK_HREF: { + if ( value && href && ( strcmp(value, href) == 0 ) ) { + /* No change, do nothing. */ + } else { + g_free(href); + href = NULL; + if (value) { + // First, set the href field, because sp_tag_use_href_changed will need it. + href = g_strdup(value); + + // Now do the attaching, which emits the changed signal. + try { + ref->attach(Inkscape::URI(value)); + } catch (Inkscape::BadURIException &e) { + g_warning("%s", e.what()); + ref->detach(); + } + } else { + ref->detach(); + } + } + break; + } + + default: + SPObject::set(key, value); + break; + } +} + +Inkscape::XML::Node * +SPTagUse::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) +{ + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("inkscape:tagref"); + } + + SPObject::write(xml_doc, repr, flags); + + if (ref->getURI()) { + gchar *uri_string = ref->getURI()->toString(); + repr->setAttribute("xlink:href", uri_string); + g_free(uri_string); + } + + return repr; +} + +/** + * Returns the ultimate original of a SPTagUse (i.e. the first object in the chain of its originals + * which is not an SPTagUse). If no original is found, NULL is returned (it is the responsibility + * of the caller to make sure that this is handled correctly). + * + * Note that the returned is the clone object, i.e. the child of an SPTagUse (of the argument one for + * the trivial case) and not the "true original". + */ + +SPItem * SPTagUse::root() +{ + SPObject *orig = child; + while (orig && SP_IS_TAG_USE(orig)) { + orig = SP_TAG_USE(orig)->child; + } + if (!orig || !SP_IS_ITEM(orig)) + return NULL; + return SP_ITEM(orig); +} + +void +SPTagUse::href_changed(SPObject */*old_ref*/, SPObject */*ref*/) +{ + if (href) { + SPItem *refobj = ref->getObject(); + if (refobj) { + Inkscape::XML::Node *childrepr = refobj->getRepr(); + const std::string typeString = NodeTraits::get_type_string(*childrepr); + + SPObject* child_ = SPFactory::createObject(typeString); + if (child_) { + child = child_; + attach(child_, lastChild()); + sp_object_unref(child_, 0); + child_->invoke_build(this->document, childrepr, TRUE); + + } + } + } +} + +SPItem * SPTagUse::get_original() +{ + SPItem *ref_ = NULL; + if (ref) { + ref_ = ref->getObject(); + } + return ref_; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-tag-use.h b/src/object/sp-tag-use.h new file mode 100644 index 000000000..651c8f045 --- /dev/null +++ b/src/object/sp-tag-use.h @@ -0,0 +1,56 @@ +#ifndef __SP_TAG_USE_H__ +#define __SP_TAG_USE_H__ + +/* + * SVG implementation + * + * Authors: + * Theodore Janeczko + * + * Copyright (C) Theodore Janeczko 2012 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include +#include "svg/svg-length.h" +#include "sp-object.h" + + +#define SP_TAG_USE(obj) (dynamic_cast (obj)) +#define SP_IS_TAG_USE(obj) (dynamic_cast (obj) != NULL) + +class SPItem; +class SPTagUse; +class SPTagUseReference; + +class SPTagUse : public SPObject { + +public: + // item built from the original's repr (the visible clone) + // relative to the SPUse itself, it is treated as a child, similar to a grouped item relative to its group + SPObject *child; + gchar *href; +public: + SPTagUse(); + virtual ~SPTagUse(); + + virtual void build(SPDocument *doc, Inkscape::XML::Node *repr); + virtual void set(unsigned key, gchar const *value); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags); + virtual void release(); + + virtual void href_changed(SPObject* old_ref, SPObject* ref); + + //virtual SPItem* unlink(); + virtual SPItem* get_original(); + virtual SPItem* root(); + + // the reference to the original object + SPTagUseReference *ref; + sigc::connection _changed_connection; +}; + +#endif diff --git a/src/object/sp-tag.cpp b/src/object/sp-tag.cpp new file mode 100644 index 000000000..d331e6b18 --- /dev/null +++ b/src/object/sp-tag.cpp @@ -0,0 +1,142 @@ +/** \file + * SVG implementation + * + * Authors: + * Theodore Janeczko + * Liam P. White + * + * Copyright (C) Theodore Janeczko 2012-2014 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "attributes.h" +#include "sp-tag.h" +#include "xml/repr.h" +#include + +/* + * Move this SPItem into or after another SPItem in the doc + * \param target - the SPItem to move into or after + * \param intoafter - move to after the target (false), move inside (sublayer) of the target (true) + */ +void SPTag::moveTo(SPObject *target, gboolean intoafter) { + + Inkscape::XML::Node *target_ref = ( target ? target->getRepr() : NULL ); + Inkscape::XML::Node *our_ref = getRepr(); + gboolean first = FALSE; + + if (target_ref == our_ref) { + // Move to ourself ignore + return; + } + + if (!target_ref) { + // Assume move to the "first" in the top node, find the top node + target_ref = our_ref; + while (target_ref->parent() != target_ref->root()) { + target_ref = target_ref->parent(); + } + first = TRUE; + } + + if (intoafter) { + // Move this inside of the target at the end + our_ref->parent()->removeChild(our_ref); + target_ref->addChild(our_ref, NULL); + } else if (target_ref->parent() != our_ref->parent()) { + // Change in parent, need to remove and add + our_ref->parent()->removeChild(our_ref); + target_ref->parent()->addChild(our_ref, target_ref); + } else if (!first) { + // Same parent, just move + our_ref->parent()->changeOrder(our_ref, target_ref); + } +} + +/** + * Reads the Inkscape::XML::Node, and initializes SPTag variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void +SPTag::build(SPDocument *document, Inkscape::XML::Node *repr) +{ + readAttr( "inkscape:expanded" ); + SPObject::build(document, repr); +} + +/** + * Sets a specific value in the SPTag. + */ +void +SPTag::set(unsigned int key, gchar const *value) +{ + + switch (key) + { + case SP_ATTR_INKSCAPE_EXPANDED: + if ( value && !strcmp(value, "true") ) { + setExpanded(true); + } + break; + default: + SPObject::set(key, value); + break; + } +} + +void SPTag::setExpanded(bool isexpanded) { + //if ( _expanded != isexpanded ){ + _expanded = isexpanded; + //} +} + +/** + * Receives update notifications. + */ +void +SPTag::update(SPCtx *ctx, guint flags) +{ + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + + /* do something to trigger redisplay, updates? */ + + } + SPObject::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node * +SPTag::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) +{ + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = doc->createElement("inkscape:tag"); + } + + // Inkscape-only object, not copied during an "plain SVG" dump: + if (flags & SP_OBJECT_WRITE_EXT) { + if (_expanded) { + repr->setAttribute("inkscape:expanded", "true"); + } else { + repr->setAttribute("inkscape:expanded", NULL); + } + } + SPObject::write(doc, repr, flags); + return repr; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-tag.h b/src/object/sp-tag.h new file mode 100644 index 000000000..36459a04b --- /dev/null +++ b/src/object/sp-tag.h @@ -0,0 +1,57 @@ +#ifndef SP_TAG_H_SEEN +#define SP_TAG_H_SEEN + +/** \file + * SVG implementation + * + * Authors: + * Theodore Janeczko + * + * Copyright (C) Theodore Janeczko 2012 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-object.h" + +/* Skeleton base class */ + +#define SP_TAG(o) (dynamic_cast(o)) +#define SP_IS_TAG(o) (dynamic_cast(o) != NULL) + +class SPTag; + +class SPTag : public SPObject { +public: + SPTag() {} + virtual ~SPTag() {} + + virtual void build(SPDocument * doc, Inkscape::XML::Node *repr); + //virtual void release(); + virtual void set(unsigned key, const gchar* value); + virtual void update(SPCtx * ctx, unsigned flags); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags); + + bool expanded() const { return _expanded; } + void setExpanded(bool isexpanded); + + void moveTo(SPObject *target, gboolean intoafter); + +private: + bool _expanded; +}; + + +#endif /* !SP_SKELETON_H_SEEN */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-text.cpp b/src/object/sp-text.cpp new file mode 100644 index 000000000..17723a5c9 --- /dev/null +++ b/src/object/sp-text.cpp @@ -0,0 +1,1279 @@ +/* + * SVG and implementation + * + * Author: + * Lauris Kaplinski + * bulia byak + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +/* + * fixme: + * + * These subcomponents should not be items, or alternately + * we have to invent set of flags to mark, whether standard + * attributes are applicable to given item (I even like this + * idea somewhat - Lauris) + * + */ + +#include <2geom/affine.h> +#include +#include + +#include +#include +#include "svg/svg.h" +#include "display/drawing-text.h" +#include "attributes.h" +#include "document.h" +#include "preferences.h" +#include "desktop.h" +#include "sp-namedview.h" +#include "inkscape.h" +#include "xml/quote.h" +#include "mod360.h" + +#include "sp-title.h" +#include "sp-desc.h" +#include "sp-text.h" + +#include "sp-shape.h" +#include "sp-textpath.h" +#include "sp-tref.h" +#include "sp-tspan.h" + +#include "text-editing.h" + +// For SVG 2 text flow +#include "livarot/Shape.h" +#include "display/curve.h" + +/*##################################################### +# SPTEXT +#####################################################*/ +SPText::SPText() : SPItem() { +} + +SPText::~SPText() { +} + +void SPText::build(SPDocument *doc, Inkscape::XML::Node *repr) { + this->readAttr( "x" ); + this->readAttr( "y" ); + this->readAttr( "dx" ); + this->readAttr( "dy" ); + this->readAttr( "rotate" ); + + // textLength and friends + this->readAttr( "textLength" ); + this->readAttr( "lengthAdjust" ); + + SPItem::build(doc, repr); + + this->readAttr( "sodipodi:linespacing" ); // has to happen after the styles are read +} + +void SPText::release() { + SPItem::release(); +} + +void SPText::set(unsigned int key, const gchar* value) { + //std::cout << "SPText::set: " << sp_attribute_name( key ) << ": " << (value?value:"Null") << std::endl; + + if (this->attributes.readSingleAttribute(key, value, style, &viewport)) { + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } else { + switch (key) { + case SP_ATTR_SODIPODI_LINESPACING: + // convert deprecated tag to css... but only if 'line-height' missing. + if (value && !this->style->line_height.set) { + this->style->line_height.set = TRUE; + this->style->line_height.inherit = FALSE; + this->style->line_height.normal = FALSE; + this->style->line_height.unit = SP_CSS_UNIT_PERCENT; + this->style->line_height.value = this->style->line_height.computed = sp_svg_read_percentage (value, 1.0); + } + // Remove deprecated attribute + this->getRepr()->setAttribute("sodipodi:linespacing", NULL); + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG); + break; + + default: + SPItem::set(key, value); + break; + } + } +} + +void SPText::child_added(Inkscape::XML::Node *rch, Inkscape::XML::Node *ref) { + SPItem::child_added(rch, ref); + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_CONTENT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG); +} + +void SPText::remove_child(Inkscape::XML::Node *rch) { + SPItem::remove_child(rch); + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_CONTENT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG); +} + + +void SPText::update(SPCtx *ctx, guint flags) { + + unsigned childflags = (flags & SP_OBJECT_MODIFIED_CASCADE); + if (flags & SP_OBJECT_MODIFIED_FLAG) { + childflags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + // Create temporary list of children + std::vector l; + for (auto& child: children) { + sp_object_ref(&child, this); + l.push_back(&child); + } + + for (auto child:l) { + if (childflags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + /* fixme: Do we need transform? */ + child->updateDisplay(ctx, childflags); + } + sp_object_unref(child, this); + } + + // update ourselves after updating children + SPItem::update(ctx, flags); + + if (flags & ( SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_CHILD_MODIFIED_FLAG | + SP_TEXT_LAYOUT_MODIFIED_FLAG ) ) + { + + SPItemCtx const *ictx = reinterpret_cast(ctx); + + double const w = ictx->viewport.width(); + double const h = ictx->viewport.height(); + double const em = style->font_size.computed; + double const ex = 0.5 * em; // fixme: get x height from pango or libnrtype. + + attributes.update( em, ex, w, h ); + + // Set inline_size computed value if necessary (i.e. if unit is %). + if (style->inline_size.set) { + if (style->inline_size.unit == SP_CSS_UNIT_PERCENT) { + if (style->writing_mode.computed == SP_CSS_WRITING_MODE_LR_TB || + style->writing_mode.computed == SP_CSS_WRITING_MODE_RL_TB) { + style->inline_size.computed = style->inline_size.value * ictx->viewport.width(); + } else { + style->inline_size.computed = style->inline_size.value * ictx->viewport.height(); + } + } + } + + /* fixme: It is not nice to have it here, but otherwise children content changes does not work */ + /* fixme: Even now it may not work, as we are delayed */ + /* fixme: So check modification flag everywhere immediate state is used */ + this->rebuildLayout(); + + Geom::OptRect paintbox = this->geometricBounds(); + + for (SPItemView* v = this->display; v != NULL; v = v->next) { + Inkscape::DrawingGroup *g = dynamic_cast(v->arenaitem); + this->_clearFlow(g); + g->setStyle(this->style, this->parent->style); + // pass the bbox of the this this as paintbox (used for paintserver fills) + this->layout.show(g, paintbox); + } + } +} + +void SPText::modified(guint flags) { +// SPItem::onModified(flags); + + guint cflags = (flags & SP_OBJECT_MODIFIED_CASCADE); + + if (flags & SP_OBJECT_MODIFIED_FLAG) { + cflags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + // FIXME: all that we need to do here is to call setStyle, to set the changed + // style, but there's no easy way to access the drawing glyphs or texts corresponding to a + // text this. Therefore we do here the same as in _update, that is, destroy all items + // and create new ones. This is probably quite wasteful. + if (flags & ( SP_OBJECT_STYLE_MODIFIED_FLAG )) { + Geom::OptRect paintbox = this->geometricBounds(); + + for (SPItemView* v = this->display; v != NULL; v = v->next) { + Inkscape::DrawingGroup *g = dynamic_cast(v->arenaitem); + this->_clearFlow(g); + g->setStyle(this->style, this->parent->style); + this->layout.show(g, paintbox); + } + } + + // Create temporary list of children + std::vector l; + for (auto& child: children) { + sp_object_ref(&child, this); + l.push_back(&child); + } + + for (auto child:l) { + if (cflags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(cflags); + } + sp_object_unref(child, this); + } +} + +Inkscape::XML::Node *SPText::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if (flags & SP_OBJECT_WRITE_BUILD) { + if (!repr) { + repr = xml_doc->createElement("svg:text"); + } + + std::vector l; + + for (auto& child: children) { + if (SP_IS_TITLE(&child) || SP_IS_DESC(&child)) { + continue; + } + + Inkscape::XML::Node *crepr = NULL; + + if (SP_IS_STRING(&child)) { + crepr = xml_doc->createTextNode(SP_STRING(&child)->string.c_str()); + } else { + crepr = child.updateRepr(xml_doc, NULL, flags); + } + + if (crepr) { + l.push_back(crepr); + } + } + + for (auto i=l.rbegin();i!=l.rend();++i) { + repr->addChild(*i, NULL); + Inkscape::GC::release(*i); + } + } else { + for (auto& child: children) { + if (SP_IS_TITLE(&child) || SP_IS_DESC(&child)) { + continue; + } + + if (SP_IS_STRING(&child)) { + child.getRepr()->setContent(SP_STRING(&child)->string.c_str()); + } else { + child.updateRepr(flags); + } + } + } + + this->attributes.writeTo(repr); + this->rebuildLayout(); // copied from update(), see LP Bug 1339305 + + SPItem::write(xml_doc, repr, flags); + + return repr; +} + +Geom::OptRect SPText::bbox(Geom::Affine const &transform, SPItem::BBoxType type) const { + Geom::OptRect bbox = SP_TEXT(this)->layout.bounds(transform); + + // FIXME this code is incorrect + if (bbox && type == SPItem::VISUAL_BBOX && !this->style->stroke.isNone()) { + double scale = transform.descrim(); + bbox->expandBy(0.5 * this->style->stroke_width.computed * scale); + } + + return bbox; +} + +Inkscape::DrawingItem* SPText::show(Inkscape::Drawing &drawing, unsigned /*key*/, unsigned /*flags*/) { + Inkscape::DrawingGroup *flowed = new Inkscape::DrawingGroup(drawing); + flowed->setPickChildren(false); + flowed->setStyle(this->style, this->parent->style); + + // pass the bbox of the text object as paintbox (used for paintserver fills) + this->layout.show(flowed, this->geometricBounds()); + + return flowed; +} + + +void SPText::hide(unsigned int key) { + for (SPItemView* v = this->display; v != NULL; v = v->next) { + if (v->key == key) { + Inkscape::DrawingGroup *g = dynamic_cast(v->arenaitem); + this->_clearFlow(g); + } + } +} + +const char* SPText::displayName() const { + return _("Text"); +} + +gchar* SPText::description() const { + + SPStyle *style = this->style; + + char *n = xml_quote_strdup( style->font_family.value ); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT); + Inkscape::Util::Quantity q = Inkscape::Util::Quantity(style->font_size.computed, "px"); + q.quantity *= this->i2doc_affine().descrim(); + Glib::ustring xs = q.string(sp_style_get_css_unit_string(unit)); + + char const *trunc = ""; + Inkscape::Text::Layout const *layout = te_get_layout((SPItem *) this); + + if (layout && layout->inputTruncated()) { + trunc = _(" [truncated]"); + } + + char *ret = ( SP_IS_TEXT_TEXTPATH(this) + ? g_strdup_printf(_("on path%s (%s, %s)"), trunc, n, xs.c_str()) + : g_strdup_printf(_("%s (%s, %s)"), trunc, n, xs.c_str()) ); + return ret; +} + +void SPText::snappoints(std::vector &p, Inkscape::SnapPreferences const *snapprefs) const { + if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_TEXT_BASELINE)) { + // Choose a point on the baseline for snapping from or to, with the horizontal position + // of this point depending on the text alignment (left vs. right) + Inkscape::Text::Layout const *layout = te_get_layout(this); + + if (layout != NULL && layout->outputExists()) { + boost::optional pt = layout->baselineAnchorPoint(); + + if (pt) { + p.push_back(Inkscape::SnapCandidatePoint((*pt) * this->i2dt_affine(), Inkscape::SNAPSOURCE_TEXT_ANCHOR, Inkscape::SNAPTARGET_TEXT_ANCHOR)); + } + } + } +} + +Geom::Affine SPText::set_transform(Geom::Affine const &xform) { + // we cannot optimize textpath because changing its fontsize will break its match to the path + if (SP_IS_TEXT_TEXTPATH (this)) { + if (!this->_optimizeTextpathText) { + return xform; + } else { + this->_optimizeTextpathText = false; + } + } + + // we cannot optimize text with textLength because it may show different size than specified + if (this->attributes.getTextLength()->_set) + return xform; + + /* This function takes care of scaling & translation only, we return whatever parts we can't + handle. */ + +// TODO: pjrm tried to use fontsize_expansion(xform) here and it works for text in that font size +// is scaled more intuitively when scaling non-uniformly; however this necessitated using +// fontsize_expansion instead of expansion in other places too, where it was not appropriate +// (e.g. it broke stroke width on copy/pasting of style from horizontally stretched to vertically +// stretched shape). Using fontsize_expansion only here broke setting the style via font +// dialog. This needs to be investigated further. + double const ex = xform.descrim(); + if (ex == 0) { + return xform; + } + + Geom::Affine ret(Geom::Affine(xform).withoutTranslation()); + ret[0] /= ex; + ret[1] /= ex; + ret[2] /= ex; + ret[3] /= ex; + + // Adjust x/y, dx/dy + this->_adjustCoordsRecursive (this, xform * ret.inverse(), ex); + + // Adjust font size + this->_adjustFontsizeRecursive (this, ex); + + // Adjust stroke width + this->adjust_stroke_width_recursive (ex); + + // Adjust pattern fill + this->adjust_pattern(xform * ret.inverse()); + + // Adjust gradient fill + this->adjust_gradient(xform * ret.inverse()); + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG); + + return ret; +} + +void SPText::print(SPPrintContext *ctx) { + Geom::OptRect pbox, bbox, dbox; + pbox = this->geometricBounds(); + bbox = this->desktopVisualBounds(); + dbox = Geom::Rect::from_xywh(Geom::Point(0,0), this->document->getDimensions()); + + Geom::Affine const ctm (this->i2dt_affine()); + + this->layout.print(ctx,pbox,dbox,bbox,ctm); +} + +/* + * Member functions + */ + +unsigned SPText::_buildLayoutInput(SPObject *root, Inkscape::Text::Layout::OptionalTextTagAttrs const &parent_optional_attrs, unsigned parent_attrs_offset, bool in_textpath) +{ + unsigned length = 0; + int child_attrs_offset = 0; + Inkscape::Text::Layout::OptionalTextTagAttrs optional_attrs; + + // To do: follow SPItem clip_ref/mask_ref code + if (style->shape_inside.set ) { + + // Find union of all exclusion shapes + Shape *exclusion_shape; + if(style->shape_subtract.set) { + exclusion_shape = _buildExclusionShape(); + } + + // Extract out shapes (a comma separated list of urls) + Glib::ustring shapeInside_value = style->shape_inside.value; + std::vector shapes_url = Glib::Regex::split_simple(" ", shapeInside_value); + for (int i=0; i(document->getObjectById( shape_url )); + if ( shape ) { + + // This code adapted from sp-flowregion.cpp: GetDest() + if (!(shape->_curve)) { + shape->set_shape(); + } + SPCurve *curve = shape->getCurve(); + + if ( curve ) { + Path *temp = new Path; + Path *padded = new Path; + temp->LoadPathVector( curve->get_pathvector(), shape->transform, true ); + if( style->shape_padding.set ) { + // std::cout << " padding: " << style->shape_padding.computed << std::endl; + temp->OutsideOutline ( padded, style->shape_padding.computed, join_round, butt_straight, 20.0 ); + } else { + // std::cout << " no padding" << std::endl; + padded->Copy( temp ); + } + padded->Convert( 0.25 ); // Convert to polyline + Shape* sh = new Shape; + padded->Fill( sh, 0 ); + // for( unsigned i = 0; i < temp->pts.size(); ++i ) { + // std::cout << " ........ " << temp->pts[i].p << std::endl; + // } + // std::cout << " ...... shape: " << sh->numberOfPoints() << std::endl; + Shape *uncross = new Shape; + uncross->ConvertToShape( sh ); + + // Subtract exclusion shape + if(style->shape_subtract.set) { + Shape *copy = new Shape; + if (exclusion_shape && exclusion_shape->hasEdges()) { + copy->Booleen(uncross, const_cast(exclusion_shape), bool_op_diff); + } else { + copy->Copy(uncross); + } + layout.appendWrapShape( copy ); + //delete exclusion_shape; + continue; + } + + layout.appendWrapShape( uncross ); + + delete temp; + delete padded; + delete sh; + // delete uncross; + } else { + std::cerr << "SPText::_buildLayoutInput(): Failed to get curve." << std::endl; + } + } else { + std::cerr << "SPText::_buildLayoutInput(): Failed to find shape." << std::endl; + } + } + } + } else if (style->inline_size.set) { + // If both shape_inside and inline_size are set, shape_inside wins out. + + // We construct a rectangle with one dimension set by the computed value of 'inline-size' + // and the other dimension set to infinity. Text is laid out starting at the 'x' and 'y' + // attribute values. This is handled elsewhere. + + double inline_size = style->inline_size.computed; + unsigned mode = style->writing_mode.computed; + unsigned anchor = style->text_anchor.computed; + unsigned direction = style->direction.computed; + + Geom::Rect frame; + if (mode == SP_CSS_WRITING_MODE_LR_TB || + mode == SP_CSS_WRITING_MODE_RL_TB) { + // horizontal + frame = Geom::Rect::from_xywh(attributes.firstXY()[Geom::X], -100000, inline_size, 200000); + if (anchor == SP_CSS_TEXT_ANCHOR_MIDDLE) { + frame *= Geom::Translate (-inline_size/2.0, 0 ); + } else if ( (direction == SP_CSS_DIRECTION_LTR && anchor == SP_CSS_TEXT_ANCHOR_END ) || + (direction == SP_CSS_DIRECTION_RTL && anchor == SP_CSS_TEXT_ANCHOR_START) ) { + frame *= Geom::Translate (-inline_size, 0); + } + } else { + // vertical + frame = Geom::Rect::from_xywh(-100000, attributes.firstXY()[Geom::Y], 200000, inline_size); + if (anchor == SP_CSS_TEXT_ANCHOR_MIDDLE) { + frame *= Geom::Translate (0, -inline_size/2.0); + } else if (anchor == SP_CSS_TEXT_ANCHOR_END) { + frame *= Geom::Translate (0, -inline_size); + } + } + // std::cout << " inline_size frame: " << frame << std::endl; + + Shape *shape = new Shape; + shape->Reset(); + int v0 = shape->AddPoint(frame.corner(0)); + int v1 = shape->AddPoint(frame.corner(1)); + int v2 = shape->AddPoint(frame.corner(2)); + int v3 = shape->AddPoint(frame.corner(3)); + shape->AddEdge(v0, v1); + shape->AddEdge(v1, v2); + shape->AddEdge(v2, v3); + shape->AddEdge(v3, v0); + Shape *uncross = new Shape; + uncross->ConvertToShape( shape ); + + layout.appendWrapShape( uncross ); + + delete shape; + } + + if (SP_IS_TEXT(root)) { + SP_TEXT(root)->attributes.mergeInto(&optional_attrs, parent_optional_attrs, parent_attrs_offset, true, true); + + layout.strut.reset(); + if (style) { + + // Strut + font_instance *font = font_factory::Default()->FaceFromStyle( style ); + if (font) { + font->FontMetrics(layout.strut.ascent, layout.strut.descent, layout.strut.xheight); + font->Unref(); + } + layout.strut *= style->font_size.computed; + if (style->line_height.normal ) { + layout.strut.computeEffective( Inkscape::Text::Layout::LINE_HEIGHT_NORMAL ); + } else if (style->line_height.unit == SP_CSS_UNIT_NONE) { + layout.strut.computeEffective( style->line_height.computed ); + } else { + if( style->font_size.computed > 0.0 ) { + layout.strut.computeEffective( style->line_height.computed/style->font_size.computed ); + } + } + + // SVG 2 Text wrapping + if (style->shape_inside.set) { + // 'x' and 'y' attributes are always ignored. + optional_attrs.x.clear(); + optional_attrs.y.clear(); + } + else if (style->inline_size.set) { + // For horizontal text: + // 'x' is used to calculate the left/right edges of the rectangle but is not + // needed later. If not deleted here, it will cause an incorrect positioning + // of the first line. + // 'y' is used to determine where the first line box is located and is needed + // during the output stage. + // For vertical text: + // Follow above but exchange 'x' and 'y'. + if (style->writing_mode.computed == SP_CSS_WRITING_MODE_LR_TB || + style->writing_mode.computed == SP_CSS_WRITING_MODE_RL_TB) { + // Horizontal text + optional_attrs.x.clear(); + } else { + // Vertical text + optional_attrs.y.clear(); + } + } + + } + + // set textLength on the entire layout, see note in TNG-Layout.h + if (SP_TEXT(root)->attributes.getTextLength()->_set) { + layout.textLength._set = true; + layout.textLength.value = SP_TEXT(root)->attributes.getTextLength()->value; + layout.textLength.computed = SP_TEXT(root)->attributes.getTextLength()->computed; + layout.textLength.unit = SP_TEXT(root)->attributes.getTextLength()->unit; + layout.lengthAdjust = (Inkscape::Text::Layout::LengthAdjust) SP_TEXT(root)->attributes.getLengthAdjust(); + } + } + else if (SP_IS_TSPAN(root)) { + SPTSpan *tspan = SP_TSPAN(root); + // x, y attributes are stripped from some tspans marked with role="line" as we do our own line layout. + // This should be checked carefully, as it can undo line layout in imported SVG files. + bool use_xy = !in_textpath && (tspan->role == SP_TSPAN_ROLE_UNSPECIFIED || !tspan->attributes.singleXYCoordinates()); + tspan->attributes.mergeInto(&optional_attrs, parent_optional_attrs, parent_attrs_offset, use_xy, true); + } + else if (SP_IS_TREF(root)) { + SP_TREF(root)->attributes.mergeInto(&optional_attrs, parent_optional_attrs, parent_attrs_offset, true, true); + } + else if (SP_IS_TEXTPATH(root)) { + in_textpath = true; + SP_TEXTPATH(root)->attributes.mergeInto(&optional_attrs, parent_optional_attrs, parent_attrs_offset, false, true); + optional_attrs.x.clear(); + optional_attrs.y.clear(); + } + else { + optional_attrs = parent_optional_attrs; + child_attrs_offset = parent_attrs_offset; + } + + if (SP_IS_TSPAN(root)) { + if (SP_TSPAN(root)->role != SP_TSPAN_ROLE_UNSPECIFIED) { + // we need to allow the first line not to have role=line, but still set the source_cookie to the right value + SPObject *prev_object = root->getPrev(); + if (prev_object && SP_IS_TSPAN(prev_object)) { + if (!layout.inputExists()) { + layout.appendText("", prev_object->style, prev_object, &optional_attrs); + } + layout.appendControlCode(Inkscape::Text::Layout::PARAGRAPH_BREAK, prev_object); + } + if (!root->hasChildren()) { + layout.appendText("", root->style, root, &optional_attrs); + } + length++; // interpreting line breaks as a character for the purposes of x/y/etc attributes + // is a liberal interpretation of the svg spec, but a strict reading would mean + // that if the first line is empty the second line would take its place at the + // start position. Very confusing. + child_attrs_offset--; + } + } + + for (auto& child: root->children) { + SPString *str = dynamic_cast(&child); + if (str) { + Glib::ustring const &string = str->string; + // std::cout << " Appending: >" << string << "<" << std::endl; + layout.appendText(string, root->style, &child, &optional_attrs, child_attrs_offset + length); + length += string.length(); + } else if (!sp_repr_is_meta_element(child.getRepr())) { + /* ^^^^ XML Tree being directly used here while it shouldn't be.*/ + length += _buildLayoutInput(&child, optional_attrs, child_attrs_offset + length, in_textpath); + } + } + + return length; +} + +Shape* SPText::_buildExclusionShape() const +{ + Shape *result = new Shape(); // Union of all exlusion shapes + Shape *shape_temp = new Shape(); + + Glib::ustring shapeSubtract_value = style->shape_subtract.value; + + // Extract out shapes (a comma separated list of urls) + std::vector shapes_url = Glib::Regex::split_simple(" ", shapeSubtract_value); + + for(int i=0; i(document->getObjectById( shape_url )); + if ( shape ) { + // This code adapted from sp-flowregion.cpp: GetDest() + if (!(shape->_curve)) { + shape->set_shape(); + } + SPCurve *curve = shape->getCurve(); + + if ( curve ) { + Path *temp = new Path; + Path *margin = new Path; + temp->LoadPathVector( curve->get_pathvector(), shape->transform, true ); + + if( shape->style->shape_margin.set ) { + temp->OutsideOutline ( margin, -shape->style->shape_margin.computed, join_round, butt_straight, 20.0 ); + } else { + margin->Copy( temp ); + } + + margin->Convert( 0.25 ); // Convert to polyline + Shape* sh = new Shape; + margin->Fill( sh, 0 ); + + Shape *uncross = new Shape; + uncross->ConvertToShape( sh ); + + if (result->hasEdges()) { + shape_temp->Booleen(result, uncross, bool_op_union); + std::swap(result, shape_temp); + } else { + result->Copy(uncross); + } + } + } + } + } + return result; +} + +void SPText::rebuildLayout() +{ + layout.clear(); + Inkscape::Text::Layout::OptionalTextTagAttrs optional_attrs; + _buildLayoutInput(this, optional_attrs, 0, false); + layout.calculateFlow(); + for (auto& child: children) { + if (SP_IS_TEXTPATH(&child)) { + SPTextPath const *textpath = SP_TEXTPATH(&child); + if (textpath->originalPath != NULL) { +#if DEBUG_TEXTLAYOUT_DUMPASTEXT + g_print("%s", layout.dumpAsText().c_str()); +#endif + layout.fitToPathAlign(textpath->startOffset, *textpath->originalPath); + } + } + } +#if DEBUG_TEXTLAYOUT_DUMPASTEXT + g_print("%s", layout.dumpAsText().c_str()); +#endif + + // set the x,y attributes on role:line spans + for (auto& child: children) { + if (SP_IS_TSPAN(&child)) { + SPTSpan *tspan = SP_TSPAN(&child); + if ( (tspan->role != SP_TSPAN_ROLE_UNSPECIFIED) + && tspan->attributes.singleXYCoordinates() ) { + Inkscape::Text::Layout::iterator iter = layout.sourceToIterator(tspan); + Geom::Point anchor_point = layout.chunkAnchorPoint(iter); + tspan->attributes.setFirstXY(anchor_point); + } + } + } +} + + +void SPText::_adjustFontsizeRecursive(SPItem *item, double ex, bool is_root) +{ + SPStyle *style = item->style; + + if (style && !Geom::are_near(ex, 1.0)) { + if (!style->font_size.set && is_root) { + style->font_size.set = 1; + } + style->font_size.type = SP_FONT_SIZE_LENGTH; + style->font_size.computed *= ex; + style->letter_spacing.computed *= ex; + style->word_spacing.computed *= ex; + if (style->line_height.unit != SP_CSS_UNIT_NONE && + style->line_height.unit != SP_CSS_UNIT_PERCENT && + style->line_height.unit != SP_CSS_UNIT_EM && + style->line_height.unit != SP_CSS_UNIT_EX) { + // No unit on 'line-height' property has special behavior. + style->line_height.computed *= ex; + } + item->updateRepr(); + } + + for(auto& o: item->children) { + if (SP_IS_ITEM(&o)) + _adjustFontsizeRecursive(SP_ITEM(&o), ex, false); + } +} + +void SPText::_adjustCoordsRecursive(SPItem *item, Geom::Affine const &m, double ex, bool is_root) +{ + if (SP_IS_TSPAN(item)) + SP_TSPAN(item)->attributes.transform(m, ex, ex, is_root); + // it doesn't matter if we change the x,y for role=line spans because we'll just overwrite them anyway + else if (SP_IS_TEXT(item)) + SP_TEXT(item)->attributes.transform(m, ex, ex, is_root); + else if (SP_IS_TEXTPATH(item)) + SP_TEXTPATH(item)->attributes.transform(m, ex, ex, is_root); + else if (SP_IS_TREF(item)) { + SP_TREF(item)->attributes.transform(m, ex, ex, is_root); + } + + for(auto& o: item->children) { + if (SP_IS_ITEM(&o)) + _adjustCoordsRecursive(SP_ITEM(&o), m, ex, false); + } +} + + +void SPText::_clearFlow(Inkscape::DrawingGroup *in_arena) +{ + in_arena->clearChildren(); +} + + +/* + * TextTagAttributes implementation + */ + +// Not used. +// void TextTagAttributes::readFrom(Inkscape::XML::Node const *node) +// { +// readSingleAttribute(SP_ATTR_X, node->attribute("x")); +// readSingleAttribute(SP_ATTR_Y, node->attribute("y")); +// readSingleAttribute(SP_ATTR_DX, node->attribute("dx")); +// readSingleAttribute(SP_ATTR_DY, node->attribute("dy")); +// readSingleAttribute(SP_ATTR_ROTATE, node->attribute("rotate")); +// readSingleAttribute(SP_ATTR_TEXTLENGTH, node->attribute("textLength")); +// readSingleAttribute(SP_ATTR_LENGTHADJUST, node->attribute("lengthAdjust")); +// } + +bool TextTagAttributes::readSingleAttribute(unsigned key, gchar const *value, SPStyle const *style, Geom::Rect const *viewport) +{ + // std::cout << "TextTagAttributes::readSingleAttribute: key: " << key + // << " value: " << (value?value:"Null") << std::endl; + std::vector *attr_vector; + bool update_x = false; + bool update_y = false; + switch (key) { + case SP_ATTR_X: attr_vector = &attributes.x; update_x = true; break; + case SP_ATTR_Y: attr_vector = &attributes.y; update_y = true; break; + case SP_ATTR_DX: attr_vector = &attributes.dx; update_x = true; break; + case SP_ATTR_DY: attr_vector = &attributes.dy; update_y = true; break; + case SP_ATTR_ROTATE: attr_vector = &attributes.rotate; break; + case SP_ATTR_TEXTLENGTH: + attributes.textLength.readOrUnset(value); + return true; + break; + case SP_ATTR_LENGTHADJUST: + attributes.lengthAdjust = (value && !strcmp(value, "spacingAndGlyphs")? + Inkscape::Text::Layout::LENGTHADJUST_SPACINGANDGLYPHS : + Inkscape::Text::Layout::LENGTHADJUST_SPACING); // default is "spacing" + return true; + break; + default: return false; + } + + // FIXME: sp_svg_length_list_read() amalgamates repeated separators. This prevents unset values. + *attr_vector = sp_svg_length_list_read(value); + + if( (update_x || update_y) && style != NULL && viewport != NULL ) { + double const w = viewport->width(); + double const h = viewport->height(); + double const em = style->font_size.computed; + double const ex = em * 0.5; + for(std::vector::iterator it = attr_vector->begin(); it != attr_vector->end(); ++it) { + if( update_x ) + it->update( em, ex, w ); + if( update_y ) + it->update( em, ex, h ); + } + } + return true; +} + +void TextTagAttributes::writeTo(Inkscape::XML::Node *node) const +{ + writeSingleAttributeVector(node, "x", attributes.x); + writeSingleAttributeVector(node, "y", attributes.y); + writeSingleAttributeVector(node, "dx", attributes.dx); + writeSingleAttributeVector(node, "dy", attributes.dy); + writeSingleAttributeVector(node, "rotate", attributes.rotate); + + writeSingleAttributeLength(node, "textLength", attributes.textLength); + + if (attributes.textLength._set) { + if (attributes.lengthAdjust == Inkscape::Text::Layout::LENGTHADJUST_SPACING) { + node->setAttribute("lengthAdjust", "spacing"); + } else if (attributes.lengthAdjust == Inkscape::Text::Layout::LENGTHADJUST_SPACINGANDGLYPHS) { + node->setAttribute("lengthAdjust", "spacingAndGlyphs"); + } + } +} + +void TextTagAttributes::update( double em, double ex, double w, double h ) +{ + for(std::vector::iterator it = attributes.x.begin(); it != attributes.x.end(); ++it) { + it->update( em, ex, w ); + } + for(std::vector::iterator it = attributes.y.begin(); it != attributes.y.end(); ++it) { + it->update( em, ex, h ); + } + for(std::vector::iterator it = attributes.dx.begin(); it != attributes.dx.end(); ++it) { + it->update( em, ex, w ); + } + for(std::vector::iterator it = attributes.dy.begin(); it != attributes.dy.end(); ++it) { + it->update( em, ex, h ); + } +} + +void TextTagAttributes::writeSingleAttributeLength(Inkscape::XML::Node *node, gchar const *key, const SVGLength &length) +{ + if (length._set) { + node->setAttribute(key, length.write().c_str()); + } else + node->setAttribute(key, NULL); +} + +void TextTagAttributes::writeSingleAttributeVector(Inkscape::XML::Node *node, gchar const *key, std::vector const &attr_vector) +{ + if (attr_vector.empty()) + node->setAttribute(key, NULL); + else { + Glib::ustring string; + + // FIXME: this has no concept of unset values because sp_svg_length_list_read() can't read them back in + for (std::vector::const_iterator it = attr_vector.begin() ; it != attr_vector.end() ; ++it) { + if (!string.empty()) string += ' '; + string += it->write(); + } + node->setAttribute(key, string.c_str()); + } +} + +bool TextTagAttributes::singleXYCoordinates() const +{ + return attributes.x.size() <= 1 && attributes.y.size() <= 1; +} + +bool TextTagAttributes::anyAttributesSet() const +{ + return !attributes.x.empty() || !attributes.y.empty() || !attributes.dx.empty() || !attributes.dy.empty() || !attributes.rotate.empty(); +} + +Geom::Point TextTagAttributes::firstXY() const +{ + Geom::Point point; + if (attributes.x.empty()) point[Geom::X] = 0.0; + else point[Geom::X] = attributes.x[0].computed; + if (attributes.y.empty()) point[Geom::Y] = 0.0; + else point[Geom::Y] = attributes.y[0].computed; + return point; +} + +void TextTagAttributes::setFirstXY(Geom::Point &point) +{ + SVGLength zero_length; + zero_length = 0.0; + + if (attributes.x.empty()) + attributes.x.resize(1, zero_length); + if (attributes.y.empty()) + attributes.y.resize(1, zero_length); + attributes.x[0] = point[Geom::X]; + attributes.y[0] = point[Geom::Y]; +} + +void TextTagAttributes::mergeInto(Inkscape::Text::Layout::OptionalTextTagAttrs *output, Inkscape::Text::Layout::OptionalTextTagAttrs const &parent_attrs, unsigned parent_attrs_offset, bool copy_xy, bool copy_dxdyrotate) const +{ + mergeSingleAttribute(&output->x, parent_attrs.x, parent_attrs_offset, copy_xy ? &attributes.x : NULL); + mergeSingleAttribute(&output->y, parent_attrs.y, parent_attrs_offset, copy_xy ? &attributes.y : NULL); + mergeSingleAttribute(&output->dx, parent_attrs.dx, parent_attrs_offset, copy_dxdyrotate ? &attributes.dx : NULL); + mergeSingleAttribute(&output->dy, parent_attrs.dy, parent_attrs_offset, copy_dxdyrotate ? &attributes.dy : NULL); + mergeSingleAttribute(&output->rotate, parent_attrs.rotate, parent_attrs_offset, copy_dxdyrotate ? &attributes.rotate : NULL); + if (attributes.textLength._set) { // only from current node, this is not inherited from parent + output->textLength.value = attributes.textLength.value; + output->textLength.computed = attributes.textLength.computed; + output->textLength.unit = attributes.textLength.unit; + output->textLength._set = attributes.textLength._set; + output->lengthAdjust = attributes.lengthAdjust; + } +} + +void TextTagAttributes::mergeSingleAttribute(std::vector *output_list, std::vector const &parent_list, unsigned parent_offset, std::vector const *overlay_list) +{ + output_list->clear(); + if (overlay_list == NULL) { + if (parent_list.size() > parent_offset) + { + output_list->reserve(parent_list.size() - parent_offset); + std::copy(parent_list.begin() + parent_offset, parent_list.end(), std::back_inserter(*output_list)); + } + } else { + output_list->reserve(std::max((int)parent_list.size() - (int)parent_offset, (int)overlay_list->size())); + unsigned overlay_offset = 0; + while (parent_offset < parent_list.size() || overlay_offset < overlay_list->size()) { + SVGLength const *this_item; + if (overlay_offset < overlay_list->size()) { + this_item = &(*overlay_list)[overlay_offset]; + overlay_offset++; + parent_offset++; + } else { + this_item = &parent_list[parent_offset]; + parent_offset++; + } + output_list->push_back(*this_item); + } + } +} + +void TextTagAttributes::erase(unsigned start_index, unsigned n) +{ + if (n == 0) return; + if (!singleXYCoordinates()) { + eraseSingleAttribute(&attributes.x, start_index, n); + eraseSingleAttribute(&attributes.y, start_index, n); + } + eraseSingleAttribute(&attributes.dx, start_index, n); + eraseSingleAttribute(&attributes.dy, start_index, n); + eraseSingleAttribute(&attributes.rotate, start_index, n); +} + +void TextTagAttributes::eraseSingleAttribute(std::vector *attr_vector, unsigned start_index, unsigned n) +{ + if (attr_vector->size() <= start_index) return; + if (attr_vector->size() <= start_index + n) + attr_vector->erase(attr_vector->begin() + start_index, attr_vector->end()); + else + attr_vector->erase(attr_vector->begin() + start_index, attr_vector->begin() + start_index + n); +} + +void TextTagAttributes::insert(unsigned start_index, unsigned n) +{ + if (n == 0) return; + if (!singleXYCoordinates()) { + insertSingleAttribute(&attributes.x, start_index, n, true); + insertSingleAttribute(&attributes.y, start_index, n, true); + } + insertSingleAttribute(&attributes.dx, start_index, n, false); + insertSingleAttribute(&attributes.dy, start_index, n, false); + insertSingleAttribute(&attributes.rotate, start_index, n, false); +} + +void TextTagAttributes::insertSingleAttribute(std::vector *attr_vector, unsigned start_index, unsigned n, bool is_xy) +{ + if (attr_vector->size() <= start_index) return; + SVGLength zero_length; + zero_length = 0.0; + attr_vector->insert(attr_vector->begin() + start_index, n, zero_length); + if (is_xy) { + double begin = start_index == 0 ? (*attr_vector)[start_index + n].computed : (*attr_vector)[start_index - 1].computed; + double diff = ((*attr_vector)[start_index + n].computed - begin) / n; // n tested for nonzero in insert() + for (unsigned i = 0 ; i < n ; i++) + (*attr_vector)[start_index + i] = begin + diff * i; + } +} + +void TextTagAttributes::split(unsigned index, TextTagAttributes *second) +{ + if (!singleXYCoordinates()) { + splitSingleAttribute(&attributes.x, index, &second->attributes.x, false); + splitSingleAttribute(&attributes.y, index, &second->attributes.y, false); + } + splitSingleAttribute(&attributes.dx, index, &second->attributes.dx, true); + splitSingleAttribute(&attributes.dy, index, &second->attributes.dy, true); + splitSingleAttribute(&attributes.rotate, index, &second->attributes.rotate, true); +} + +void TextTagAttributes::splitSingleAttribute(std::vector *first_vector, unsigned index, std::vector *second_vector, bool trimZeros) +{ + second_vector->clear(); + if (first_vector->size() <= index) return; + second_vector->resize(first_vector->size() - index); + std::copy(first_vector->begin() + index, first_vector->end(), second_vector->begin()); + first_vector->resize(index); + if (trimZeros) + while (!first_vector->empty() && (!first_vector->back()._set || first_vector->back().value == 0.0)) + first_vector->resize(first_vector->size() - 1); +} + +void TextTagAttributes::join(TextTagAttributes const &first, TextTagAttributes const &second, unsigned second_index) +{ + if (second.singleXYCoordinates()) { + attributes.x = first.attributes.x; + attributes.y = first.attributes.y; + } else { + joinSingleAttribute(&attributes.x, first.attributes.x, second.attributes.x, second_index); + joinSingleAttribute(&attributes.y, first.attributes.y, second.attributes.y, second_index); + } + joinSingleAttribute(&attributes.dx, first.attributes.dx, second.attributes.dx, second_index); + joinSingleAttribute(&attributes.dy, first.attributes.dy, second.attributes.dy, second_index); + joinSingleAttribute(&attributes.rotate, first.attributes.rotate, second.attributes.rotate, second_index); +} + +void TextTagAttributes::joinSingleAttribute(std::vector *dest_vector, std::vector const &first_vector, std::vector const &second_vector, unsigned second_index) +{ + if (second_vector.empty()) + *dest_vector = first_vector; + else { + dest_vector->resize(second_index + second_vector.size()); + if (first_vector.size() < second_index) { + std::copy(first_vector.begin(), first_vector.end(), dest_vector->begin()); + SVGLength zero_length; + zero_length = 0.0; + std::fill(dest_vector->begin() + first_vector.size(), dest_vector->begin() + second_index, zero_length); + } else + std::copy(first_vector.begin(), first_vector.begin() + second_index, dest_vector->begin()); + std::copy(second_vector.begin(), second_vector.end(), dest_vector->begin() + second_index); + } +} + +void TextTagAttributes::transform(Geom::Affine const &matrix, double scale_x, double scale_y, bool extend_zero_length) +{ + SVGLength zero_length; + zero_length = 0.0; + + /* edge testcases for this code: + 1) moving text elements whose position is done entirely with transform="...", no x,y attributes + 2) unflowing multi-line flowtext then moving it (it has x but not y) + */ + unsigned points_count = std::max(attributes.x.size(), attributes.y.size()); + if (extend_zero_length && points_count < 1) + points_count = 1; + for (unsigned i = 0 ; i < points_count ; i++) { + Geom::Point point; + if (i < attributes.x.size()) point[Geom::X] = attributes.x[i].computed; + else point[Geom::X] = 0.0; + if (i < attributes.y.size()) point[Geom::Y] = attributes.y[i].computed; + else point[Geom::Y] = 0.0; + point *= matrix; + if (i < attributes.x.size()) + attributes.x[i] = point[Geom::X]; + else if (point[Geom::X] != 0.0 && extend_zero_length) { + attributes.x.resize(i + 1, zero_length); + attributes.x[i] = point[Geom::X]; + } + if (i < attributes.y.size()) + attributes.y[i] = point[Geom::Y]; + else if (point[Geom::Y] != 0.0 && extend_zero_length) { + attributes.y.resize(i + 1, zero_length); + attributes.y[i] = point[Geom::Y]; + } + } + for (std::vector::iterator it = attributes.dx.begin() ; it != attributes.dx.end() ; ++it) + *it = it->computed * scale_x; + for (std::vector::iterator it = attributes.dy.begin() ; it != attributes.dy.end() ; ++it) + *it = it->computed * scale_y; +} + +double TextTagAttributes::getDx(unsigned index) +{ + if( attributes.dx.empty()) { + return 0.0; + } + if( index < attributes.dx.size() ) { + return attributes.dx[index].computed; + } else { + return 0.0; // attributes.dx.back().computed; + } +} + + +double TextTagAttributes::getDy(unsigned index) +{ + if( attributes.dy.empty() ) { + return 0.0; + } + if( index < attributes.dy.size() ) { + return attributes.dy[index].computed; + } else { + return 0.0; // attributes.dy.back().computed; + } +} + + +void TextTagAttributes::addToDx(unsigned index, double delta) +{ + SVGLength zero_length; + zero_length = 0.0; + + if (attributes.dx.size() < index + 1) attributes.dx.resize(index + 1, zero_length); + attributes.dx[index] = attributes.dx[index].computed + delta; +} + +void TextTagAttributes::addToDy(unsigned index, double delta) +{ + SVGLength zero_length; + zero_length = 0.0; + + if (attributes.dy.size() < index + 1) attributes.dy.resize(index + 1, zero_length); + attributes.dy[index] = attributes.dy[index].computed + delta; +} + +void TextTagAttributes::addToDxDy(unsigned index, Geom::Point const &adjust) +{ + SVGLength zero_length; + zero_length = 0.0; + + if (adjust[Geom::X] != 0.0) { + if (attributes.dx.size() < index + 1) attributes.dx.resize(index + 1, zero_length); + attributes.dx[index] = attributes.dx[index].computed + adjust[Geom::X]; + } + if (adjust[Geom::Y] != 0.0) { + if (attributes.dy.size() < index + 1) attributes.dy.resize(index + 1, zero_length); + attributes.dy[index] = attributes.dy[index].computed + adjust[Geom::Y]; + } +} + +double TextTagAttributes::getRotate(unsigned index) +{ + if( attributes.rotate.empty() ) { + return 0.0; + } + if( index < attributes.rotate.size() ) { + return attributes.rotate[index].computed; + } else { + return attributes.rotate.back().computed; + } +} + + +void TextTagAttributes::addToRotate(unsigned index, double delta) +{ + SVGLength zero_length; + zero_length = 0.0; + + if (attributes.rotate.size() < index + 2) { + if (attributes.rotate.empty()) + attributes.rotate.resize(index + 2, zero_length); + else + attributes.rotate.resize(index + 2, attributes.rotate.back()); + } + attributes.rotate[index] = mod360(attributes.rotate[index].computed + delta); +} + + +void TextTagAttributes::setRotate(unsigned index, double angle) +{ + SVGLength zero_length; + zero_length = 0.0; + + if (attributes.rotate.size() < index + 2) { + if (attributes.rotate.empty()) + attributes.rotate.resize(index + 2, zero_length); + else + attributes.rotate.resize(index + 2, attributes.rotate.back()); + } + attributes.rotate[index] = mod360(angle); +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-text.h b/src/object/sp-text.h new file mode 100644 index 000000000..67613d043 --- /dev/null +++ b/src/object/sp-text.h @@ -0,0 +1,109 @@ +#ifndef SEEN_SP_TEXT_H +#define SEEN_SP_TEXT_H + +/* + * SVG and implementation + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include + +#include "libnrtype/Layout-TNG.h" +#include "sp-item.h" +#include "sp-string.h" // Provides many other headers with SP_IS_STRING +#include "text-tag-attributes.h" + +#define SP_TEXT(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_TEXT(obj) (dynamic_cast((SPObject*)obj) != NULL) + +/* Text specific flags */ +#define SP_TEXT_CONTENT_MODIFIED_FLAG SP_OBJECT_USER_MODIFIED_FLAG_A +#define SP_TEXT_LAYOUT_MODIFIED_FLAG SP_OBJECT_USER_MODIFIED_FLAG_A + + +/* SPText */ +class SPText : public SPItem { +public: + SPText(); + virtual ~SPText(); + + /** Converts the text object to its component curves */ + SPCurve *getNormalizedBpath() const + {return layout.convertToCurves();} + + /** Completely recalculates the layout. */ + void rebuildLayout(); + + //semiprivate: (need to be accessed by the C-style functions still) + TextTagAttributes attributes; + Inkscape::Text::Layout layout; + + /** when the object is transformed it's nicer to change the font size + and coordinates when we can, rather than just applying a matrix + transform. is_root is used to indicate to the function that it should + extend zero-length position vectors to length 1 in order to record the + new position. This is necessary to convert from objects whose position is + completely specified by transformations. */ + static void _adjustCoordsRecursive(SPItem *item, Geom::Affine const &m, double ex, bool is_root = true); + static void _adjustFontsizeRecursive(SPItem *item, double ex, bool is_root = true); + + /** discards the drawing objects representing this text. */ + void _clearFlow(Inkscape::DrawingGroup *in_arena); + + bool _optimizeTextpathText; + +private: + /** Recursively walks the xml tree adding tags and their contents. The + non-trivial code does two things: firstly, it manages the positioning + attributes and their inheritance rules, and secondly it keeps track of line + breaks and makes sure both that they are assigned the correct SPObject and + that we don't get a spurious extra one at the end of the flow. */ + unsigned _buildLayoutInput(SPObject *root, Inkscape::Text::Layout::OptionalTextTagAttrs const &parent_optional_attrs, unsigned parent_attrs_offset, bool in_textpath); + + /** Union all exlusion shapes. */ + Shape* _buildExclusionShape() const; + +public: + /** Optimize textpath text on next set_transform. */ + void optimizeTextpathText() + {_optimizeTextpathText = true;} + + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref); + virtual void remove_child(Inkscape::XML::Node* child); + virtual void set(unsigned int key, const char* value); + virtual void update(SPCtx* ctx, unsigned int flags); + virtual void modified(unsigned int flags); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); + + virtual Geom::OptRect bbox(Geom::Affine const &transform, SPItem::BBoxType type) const; + virtual void print(SPPrintContext *ctx); + virtual const char* displayName() const; + virtual char* description() const; + virtual Inkscape::DrawingItem* show(Inkscape::Drawing &drawing, unsigned int key, unsigned int flags); + virtual void hide(unsigned int key); + virtual void snappoints(std::vector &p, Inkscape::SnapPreferences const *snapprefs) const; + virtual Geom::Affine set_transform(Geom::Affine const &transform); +}; + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-textpath.h b/src/object/sp-textpath.h new file mode 100644 index 000000000..4457cb8cf --- /dev/null +++ b/src/object/sp-textpath.h @@ -0,0 +1,51 @@ +#ifndef INKSCAPE_SP_TEXTPATH_H +#define INKSCAPE_SP_TEXTPATH_H + +#include "svg/svg-length.h" +#include "sp-item.h" +#include "sp-text.h" + +class SPUsePath; +class Path; + +#define SP_TEXTPATH(obj) (dynamic_cast((SPObject*)obj)) +#define SP_IS_TEXTPATH(obj) (dynamic_cast((SPObject*)obj) != NULL) + +class SPTextPath : public SPItem { +public: + SPTextPath(); + virtual ~SPTextPath(); + + TextTagAttributes attributes; + SVGLength startOffset; + + Path *originalPath; + bool isUpdating; + SPUsePath *sourcePath; + + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + virtual void set(unsigned int key, const char* value); + virtual void update(SPCtx* ctx, unsigned int flags); + virtual void modified(unsigned int flags); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); +}; + +#define SP_IS_TEXT_TEXTPATH(obj) (SP_IS_TEXT(obj) && obj->firstChild() && SP_IS_TEXTPATH(obj->firstChild())) + +SPItem *sp_textpath_get_path_item(SPTextPath *tp); +void sp_textpath_to_text(SPObject *tp); + + +#endif /* !INKSCAPE_SP_TEXTPATH_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-title.cpp b/src/object/sp-title.cpp new file mode 100644 index 000000000..ba5ae754a --- /dev/null +++ b/src/object/sp-title.cpp @@ -0,0 +1,32 @@ +/* + * SVG implementation + * + * Authors: + * Jeff Schiller + * + * Copyright (C) 2008 Jeff Schiller + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-title.h" +#include "xml/repr.h" + +SPTitle::SPTitle() : SPObject() { +} + +SPTitle::~SPTitle() { +} + +Inkscape::XML::Node* SPTitle::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + SPTitle* object = this; + + if (!repr) { + repr = object->getRepr()->duplicate(xml_doc); + } + + SPObject::write(xml_doc, repr, flags); + + return repr; +} + diff --git a/src/object/sp-title.h b/src/object/sp-title.h new file mode 100644 index 000000000..04f3829c6 --- /dev/null +++ b/src/object/sp-title.h @@ -0,0 +1,28 @@ +#ifndef SEEN_SP_TITLE_H +#define SEEN_SP_TITLE_H + +/* + * SVG implementation + * + * Authors: + * Jeff Schiller <codedread@gmail.com> + * + * Copyright (C) 2008 Jeff Schiller + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-object.h" + +#define SP_TITLE(obj) (dynamic_cast<SPTitle*>((SPObject*)obj)) +#define SP_IS_TITLE(obj) (dynamic_cast<const SPTitle*>((SPObject*)obj) != NULL) + +class SPTitle : public SPObject { +public: + SPTitle(); + virtual ~SPTitle(); + + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); +}; + +#endif diff --git a/src/object/sp-tref-reference.cpp b/src/object/sp-tref-reference.cpp new file mode 100644 index 000000000..d683e34ed --- /dev/null +++ b/src/object/sp-tref-reference.cpp @@ -0,0 +1,106 @@ +/* + * The reference corresponding to href of <tref> element. + * + * Copyright (C) 2007 Gail Banaszkiewicz + * + * This file was created based on sp-use-reference.cpp + * + * Released under GNU GPL, read the file 'COPYING' for more information. + */ + +//#include "enums.h" +#include "sp-tref-reference.h" + +#include "sp-text.h" +#include "sp-tref.h" + + +bool SPTRefReference::_acceptObject(SPObject * const obj) const +{ + SPObject *owner = getOwner(); + if (SP_IS_TREF(owner)) + return URIReference::_acceptObject(obj); + else + return false; +} + + +void SPTRefReference::updateObserver() +{ + SPObject *referred = getObject(); + + if (referred) { + if (subtreeObserved) { + subtreeObserved->removeObserver(*this); + delete subtreeObserved; + } + + subtreeObserved = new Inkscape::XML::Subtree(*referred->getRepr()); + subtreeObserved->addObserver(*this); + } +} + + +void SPTRefReference::notifyChildAdded(Inkscape::XML::Node &/*node*/, Inkscape::XML::Node &/*child*/, + Inkscape::XML::Node */*prev*/) +{ + SPObject *owner = getOwner(); + + if (owner && SP_IS_TREF(owner)) { + sp_tref_update_text(SP_TREF(owner)); + } +} + + +void SPTRefReference::notifyChildRemoved(Inkscape::XML::Node &/*node*/, Inkscape::XML::Node &/*child*/, + Inkscape::XML::Node */*prev*/) +{ + SPObject *owner = getOwner(); + + if (owner && SP_IS_TREF(owner)) { + sp_tref_update_text(SP_TREF(owner)); + } +} + + +void SPTRefReference::notifyChildOrderChanged(Inkscape::XML::Node &/*node*/, Inkscape::XML::Node &/*child*/, + Inkscape::XML::Node */*old_prev*/, Inkscape::XML::Node */*new_prev*/) +{ + SPObject *owner = getOwner(); + + if (owner && SP_IS_TREF(owner)) { + sp_tref_update_text(SP_TREF(owner)); + } +} + + +void SPTRefReference::notifyContentChanged(Inkscape::XML::Node &/*node*/, + Inkscape::Util::ptr_shared /*old_content*/, + Inkscape::Util::ptr_shared /*new_content*/) +{ + SPObject *owner = getOwner(); + + if (owner && SP_IS_TREF(owner)) { + sp_tref_update_text(SP_TREF(owner)); + } +} + + +void SPTRefReference::notifyAttributeChanged(Inkscape::XML::Node &/*node*/, GQuark /*name*/, + Inkscape::Util::ptr_shared /*old_value*/, + Inkscape::Util::ptr_shared /*new_value*/) +{ + // Do nothing - tref only cares about textual content +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-tref-reference.h b/src/object/sp-tref-reference.h new file mode 100644 index 000000000..516e125c1 --- /dev/null +++ b/src/object/sp-tref-reference.h @@ -0,0 +1,79 @@ +#ifndef SEEN_SP_TREF_REFERENCE_H +#define SEEN_SP_TREF_REFERENCE_H + +/* + * The reference corresponding to href of <tref> element. + * + * This file was created based on sp-use-reference.h + * + * Copyright (C) 2007 Gail Banaszkiewicz + * Abhishek Sharma + * + * Released under GNU GPL, read the file 'COPYING' for more information. + */ + +#include <cstddef> +#include <sigc++/sigc++.h> + +#include "sp-item.h" +#include "uri-references.h" + +#include "util/share.h" +#include "xml/node-observer.h" +#include "xml/subtree.h" + +typedef unsigned int GQuark; + +class SPTRefReference : public Inkscape::URIReference, + public Inkscape::XML::NodeObserver { +public: + SPTRefReference(SPObject *owner) : URIReference(owner), subtreeObserved(NULL) { + updateObserver(); + } + + virtual ~SPTRefReference() { + if (subtreeObserved) { + subtreeObserved->removeObserver(*this); + delete subtreeObserved; + } + } + + SPItem *getObject() const { + return static_cast<SPItem *>(URIReference::getObject()); + } + + void updateObserver(); + + ///////////////////////////////////////////////////////////////////// + // Node Observer Functions + // ----------------------- + virtual void notifyChildAdded(Inkscape::XML::Node &node, Inkscape::XML::Node &child, Inkscape::XML::Node *prev); + virtual void notifyChildRemoved(Inkscape::XML::Node &node, Inkscape::XML::Node &child, Inkscape::XML::Node *prev); + virtual void notifyChildOrderChanged(Inkscape::XML::Node &node, Inkscape::XML::Node &child, + Inkscape::XML::Node *old_prev, Inkscape::XML::Node *new_prev); + virtual void notifyContentChanged(Inkscape::XML::Node &node, + Inkscape::Util::ptr_shared old_content, + Inkscape::Util::ptr_shared new_content); + virtual void notifyAttributeChanged(Inkscape::XML::Node &node, GQuark name, + Inkscape::Util::ptr_shared old_value, + Inkscape::Util::ptr_shared new_value); + ///////////////////////////////////////////////////////////////////// + +protected: + virtual bool _acceptObject(SPObject * obj) const; + + Inkscape::XML::Subtree *subtreeObserved; +}; + +#endif /* !SEEN_SP_TREF_REFERENCE_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-tref.cpp b/src/object/sp-tref.cpp new file mode 100644 index 000000000..dd44c5855 --- /dev/null +++ b/src/object/sp-tref.cpp @@ -0,0 +1,532 @@ +/** \file + * SVG <tref> implementation - All character data within the referenced + * element, including character data enclosed within additional markup, + * will be rendered. + * + * This file was created based on skeleton.cpp + */ +/* + * Authors: + * Gail Banaszkiewicz <Gail.Banaszkiewicz@gmail.com> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * + * Copyright (C) 2007 Gail Banaszkiewicz + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-tref.h" + +#include <glibmm/i18n.h> + +#include "bad-uri-exception.h" +#include "attributes.h" +#include "document.h" +#include "sp-factory.h" +#include "sp-text.h" +#include "style.h" +#include "text-editing.h" + +//#define DEBUG_TREF +#ifdef DEBUG_TREF +# define debug(f, a...) { g_message("%s(%d) %s:", \ + __FILE__,__LINE__,__FUNCTION__); \ + g_message(f, ## a); \ + g_message("\n"); \ + } +#else +# define debug(f, a...) /**/ +#endif + + +static void build_string_from_root(Inkscape::XML::Node *root, Glib::ustring *retString); + +/* TRef base class */ +static void sp_tref_href_changed(SPObject *old_ref, SPObject *ref, SPTRef *tref); +static void sp_tref_delete_self(SPObject *deleted, SPTRef *self); + +SPTRef::SPTRef() : SPItem() { + this->stringChild = NULL; + + this->href = NULL; + this->uriOriginalRef = new SPTRefReference(this); + + this->_changed_connection = + this->uriOriginalRef->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_tref_href_changed), this)); +} + +SPTRef::~SPTRef() { + delete this->uriOriginalRef; +} + +void SPTRef::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPItem::build(document, repr); + + this->readAttr( "xlink:href" ); + this->readAttr( "x" ); + this->readAttr( "y" ); + this->readAttr( "dx" ); + this->readAttr( "dy" ); + this->readAttr( "rotate" ); +} + +void SPTRef::release() { + //this->attributes.~TextTagAttributes(); + + this->_delete_connection.disconnect(); + this->_changed_connection.disconnect(); + + g_free(this->href); + this->href = NULL; + + this->uriOriginalRef->detach(); + + SPItem::release(); +} + +void SPTRef::set(unsigned int key, const gchar* value) { + debug("0x%p %s(%u): '%s'",this, + sp_attribute_name(key),key,value ? value : "<no value>"); + + if (this->attributes.readSingleAttribute(key, value, style, &viewport)) { // x, y, dx, dy, rotate + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } else if (key == SP_ATTR_XLINK_HREF) { // xlink:href + if ( !value ) { + // No value + g_free(this->href); + this->href = NULL; + this->uriOriginalRef->detach(); + } else if ((this->href && strcmp(value, this->href) != 0) || (!this->href)) { + // Value has changed + + if ( this->href ) { + g_free(this->href); + this->href = NULL; + } + + this->href = g_strdup(value); + + try { + this->uriOriginalRef->attach(Inkscape::URI(value)); + this->uriOriginalRef->updateObserver(); + } catch ( Inkscape::BadURIException &e ) { + g_warning("%s", e.what()); + this->uriOriginalRef->detach(); + } + + // No matter what happened, an update should be in order + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } + } else { // default + SPItem::set(key, value); + } +} + +void SPTRef::update(SPCtx *ctx, guint flags) { + debug("0x%p",this); + + unsigned childflags = flags; + if (flags & SP_OBJECT_MODIFIED_FLAG) { + childflags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + childflags &= SP_OBJECT_MODIFIED_CASCADE; + + SPObject *child = this->stringChild; + + if (child) { + if ( childflags || ( child->uflags & SP_OBJECT_MODIFIED_FLAG )) { + child->updateDisplay(ctx, childflags); + } + } + + SPItem::update(ctx, flags); +} + +void SPTRef::modified(unsigned int flags) { + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + SPObject *child = this->stringChild; + + if (child) { + sp_object_ref(child); + + if (flags || (child->mflags & SP_OBJECT_MODIFIED_FLAG)) { + child->emitModified(flags); + } + + sp_object_unref(child); + } +} + +Inkscape::XML::Node* SPTRef::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + debug("0x%p",this); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:tref"); + } + + this->attributes.writeTo(repr); + + if (this->uriOriginalRef->getURI()) { + gchar *uri_string = this->uriOriginalRef->getURI()->toString(); + debug("uri_string=%s", uri_string); + repr->setAttribute("xlink:href", uri_string); + g_free(uri_string); + } + + SPItem::write(xml_doc, repr, flags); + + return repr; +} + +Geom::OptRect SPTRef::bbox(Geom::Affine const &transform, SPItem::BBoxType type) const { + Geom::OptRect bbox; + // find out the ancestor text which holds our layout + SPObject const *parent_text = this; + + while ( parent_text && !SP_IS_TEXT(parent_text) ) { + parent_text = parent_text->parent; + } + + if (parent_text == NULL) { + return bbox; + } + + // get the bbox of our portion of the layout + bbox = SP_TEXT(parent_text)->layout.bounds(transform, + sp_text_get_length_upto(parent_text, this), sp_text_get_length_upto(this, NULL) - 1); + + // Add stroke width + // FIXME this code is incorrect + if (bbox && type == SPItem::VISUAL_BBOX && !this->style->stroke.isNone()) { + double scale = transform.descrim(); + bbox->expandBy(0.5 * this->style->stroke_width.computed * scale); + } + + return bbox; +} + +const char* SPTRef::displayName() const { + return _("Cloned Character Data"); +} + +gchar* SPTRef::description() const { + SPObject const *referred = this->getObjectReferredTo(); + + if (referred) { + char *child_desc; + + if (SP_IS_ITEM(referred)) { + child_desc = SP_ITEM(referred)->detailedDescription(); + } else { + child_desc = g_strdup(""); + } + + char *ret = g_strdup_printf("%s%s", + (SP_IS_ITEM(referred) ? _(" from ") : ""), child_desc); + g_free(child_desc); + + return ret; + } + + return g_strdup(_("[orphaned]")); +} + + +/* For the sigc::connection changes (i.e. when the object being referred to changes) */ +static void +sp_tref_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, SPTRef *tref) +{ + if (tref) + { + // Save a pointer to the original object being referred to + SPObject *refRoot = tref->getObjectReferredTo(); + + tref->_delete_connection.disconnect(); + + if (tref->stringChild) { + tref->detach(tref->stringChild); + tref->stringChild = NULL; + } + + // Ensure that we are referring to a legitimate object + if (tref->href && refRoot && sp_tref_reference_allowed(tref, refRoot)) { + + // Update the text being referred to (will create a new string child) + sp_tref_update_text(tref); + + // Restore the delete connection now that we're done messing with stuff + tref->_delete_connection = refRoot->connectDelete(sigc::bind(sigc::ptr_fun(&sp_tref_delete_self), tref)); + } + + } +} + + +/** + * Delete the tref object + */ +static void +sp_tref_delete_self(SPObject */*deleted*/, SPTRef *self) +{ + self->deleteObject(); +} + +/** + * Return the object referred to via the URI reference + */ +SPObject * SPTRef::getObjectReferredTo(void) +{ + SPObject *referredObject = NULL; + + if (uriOriginalRef) { + referredObject = uriOriginalRef->getObject(); + } + + return referredObject; +} + +/** + * Return the object referred to via the URI reference + */ +SPObject const *SPTRef::getObjectReferredTo() const { + SPObject *referredObject = NULL; + + if (uriOriginalRef) { + referredObject = uriOriginalRef->getObject(); + } + + return referredObject; +} + + +/** + * Returns true when the given tref is allowed to refer to a particular object + */ +bool +sp_tref_reference_allowed(SPTRef *tref, SPObject *possible_ref) +{ + bool allowed = false; + + if (tref && possible_ref) { + if (tref != possible_ref) { + bool ancestor = false; + for (SPObject *obj = tref; obj; obj = obj->parent) { + if (possible_ref == obj) { + ancestor = true; + break; + } + } + allowed = !ancestor; + } + } + + return allowed; +} + + +/** + * Returns true if a tref is fully contained in the confines of the given + * iterators and layout (or if there is no tref). + */ +bool +sp_tref_fully_contained(SPObject *start_item, Glib::ustring::iterator &start, + SPObject *end_item, Glib::ustring::iterator &end) +{ + bool fully_contained = false; + + if (start_item && end_item) { + + // If neither the beginning or the end is a tref then we return true (whether there + // is a tref in the innards or not, because if there is one then it must be totally + // contained) + if (!(SP_IS_STRING(start_item) && SP_IS_TREF(start_item->parent)) + && !(SP_IS_STRING(end_item) && SP_IS_TREF(end_item->parent))) { + fully_contained = true; + } + + // Both the beginning and end are trefs; but in this case, the string iterators + // must be at the right places + else if ((SP_IS_STRING(start_item) && SP_IS_TREF(start_item->parent)) + && (SP_IS_STRING(end_item) && SP_IS_TREF(end_item->parent))) { + if (start == SP_STRING(start_item)->string.begin() + && end == SP_STRING(start_item)->string.end()) { + fully_contained = true; + } + } + + // If the beginning is a string that is a child of a tref, the iterator has to be + // at the beginning of the item + else if ((SP_IS_STRING(start_item) && SP_IS_TREF(start_item->parent)) + && !(SP_IS_STRING(end_item) && SP_IS_TREF(end_item->parent))) { + if (start == SP_STRING(start_item)->string.begin()) { + fully_contained = true; + } + } + + // Same, but the for the end + else if (!(SP_IS_STRING(start_item) && SP_IS_TREF(start_item->parent)) + && (SP_IS_STRING(end_item) && SP_IS_TREF(end_item->parent))) { + if (end == SP_STRING(start_item)->string.end()) { + fully_contained = true; + } + } + } + + return fully_contained; +} + + +void sp_tref_update_text(SPTRef *tref) +{ + if (tref) { + // Get the character data that will be used with this tref + Glib::ustring charData = ""; + build_string_from_root(tref->getObjectReferredTo()->getRepr(), &charData); + + if (tref->stringChild) { + tref->detach(tref->stringChild); + tref->stringChild = NULL; + } + + // Create the node and SPString to be the tref's child + Inkscape::XML::Document *xml_doc = tref->document->getReprDoc(); + + Inkscape::XML::Node *newStringRepr = xml_doc->createTextNode(charData.c_str()); + tref->stringChild = SPFactory::createObject(NodeTraits::get_type_string(*newStringRepr)); + + // Add this SPString as a child of the tref + tref->attach(tref->stringChild, tref->lastChild()); + sp_object_unref(tref->stringChild, NULL); + (tref->stringChild)->invoke_build(tref->document, newStringRepr, TRUE); + + Inkscape::GC::release(newStringRepr); + } +} + + + +/** + * Using depth-first search, build up a string by concatenating all SPStrings + * found in the tree starting at the root + */ +static void +build_string_from_root(Inkscape::XML::Node *root, Glib::ustring *retString) +{ + if (root && retString) { + + // Stop and concatenate when a SPString is found + if (root->type() == Inkscape::XML::TEXT_NODE) { + *retString += (root->content()); + + debug("%s", retString->c_str()); + + // Otherwise, continue searching down the tree (with the assumption that no children nodes + // of a SPString are actually legal) + } else { + Inkscape::XML::Node *childNode; + for (childNode = root->firstChild(); childNode; childNode = childNode->next()) { + build_string_from_root(childNode, retString); + } + } + } +} + +/** + * This function will create a new tspan element with the same attributes as + * the tref had and add the same text as a child. The tref is replaced in the + * tree with the new tspan. + * The code is based partially on sp_use_unlink + */ +SPObject * +sp_tref_convert_to_tspan(SPObject *obj) +{ + SPObject * new_tspan = NULL; + + //////////////////// + // BASE CASE + //////////////////// + if (SP_IS_TREF(obj)) { + + SPTRef *tref = SP_TREF(obj); + + if (tref && tref->stringChild) { + Inkscape::XML::Node *tref_repr = tref->getRepr(); + Inkscape::XML::Node *tref_parent = tref_repr->parent(); + + SPDocument *document = tref->document; + Inkscape::XML::Document *xml_doc = document->getReprDoc(); + + Inkscape::XML::Node *new_tspan_repr = xml_doc->createElement("svg:tspan"); + + // Add the new tspan element just after the current tref + tref_parent->addChild(new_tspan_repr, tref_repr); + Inkscape::GC::release(new_tspan_repr); + + new_tspan = document->getObjectByRepr(new_tspan_repr); + + // Create a new string child for the tspan + Inkscape::XML::Node *new_string_repr = tref->stringChild->getRepr()->duplicate(xml_doc); + new_tspan_repr->addChild(new_string_repr, NULL); + + //SPObject * new_string_child = document->getObjectByRepr(new_string_repr); + + // Merge style from the tref + new_tspan->style->merge( tref->style ); + new_tspan->style->cascade( new_tspan->parent->style ); + new_tspan->updateRepr(); + + // Hold onto our SPObject and repr for now. + sp_object_ref(tref, NULL); + Inkscape::GC::anchor(tref_repr); + + // Remove ourselves, not propagating delete events to avoid a + // chain-reaction with other elements that might reference us. + tref->deleteObject(false); + + // Give the copy our old id and let go of our old repr. + new_tspan_repr->setAttribute("id", tref_repr->attribute("id")); + Inkscape::GC::release(tref_repr); + + // Establish the succession and let go of our object. + tref->setSuccessor(new_tspan); + sp_object_unref(tref, NULL); + } + } + //////////////////// + // RECURSIVE CASE + //////////////////// + else { + std::vector<SPObject *> l; + for (auto& child: obj->children) { + sp_object_ref(&child, obj); + l.push_back(&child); + } + for(auto child:l) { + // Note that there may be more than one conversion happening here, so if it's not a + // tref being passed into this function, the returned value can't be specifically known + new_tspan = sp_tref_convert_to_tspan(child); + + sp_object_unref(child, obj); + } + } + + return new_tspan; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-tref.h b/src/object/sp-tref.h new file mode 100644 index 000000000..1727620cd --- /dev/null +++ b/src/object/sp-tref.h @@ -0,0 +1,84 @@ +#ifndef SP_TREF_H +#define SP_TREF_H + +/** \file + * SVG <tref> implementation, see sp-tref.cpp. + * + * This file was created based on skeleton.h + */ +/* + * Authors: + * Gail Banaszkiewicz <Gail.Banaszkiewicz@gmail.com> + * + * Copyright (C) 2007 Gail Banaszkiewicz + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-item.h" +#include "sp-tref-reference.h" +#include "text-tag-attributes.h" + + +/* tref base class */ + +#define SP_TREF(obj) (dynamic_cast<SPTRef*>((SPObject*)obj)) +#define SP_IS_TREF(obj) (dynamic_cast<const SPTRef*>((SPObject*)obj) != NULL) + +class SPTRef : public SPItem { +public: + SPTRef(); + virtual ~SPTRef(); + + // Attributes that are used in the same way they would be in a tspan + TextTagAttributes attributes; + + // Text stored in the xlink:href attribute + char *href; + + // URI reference to original object + SPTRefReference *uriOriginalRef; + + // Shortcut pointer to the child of the tref (which is a copy + // of the character data stored at and/or below the node + // referenced by uriOriginalRef) + SPObject *stringChild; + + // The sigc connections for various notifications + sigc::connection _delete_connection; + sigc::connection _changed_connection; + + SPObject * getObjectReferredTo(); + SPObject const *getObjectReferredTo() const; + + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + virtual void set(unsigned int key, char const* value); + virtual void update(SPCtx* ctx, unsigned int flags); + virtual void modified(unsigned int flags); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags); + + virtual Geom::OptRect bbox(Geom::Affine const &transform, SPItem::BBoxType type) const; + virtual const char* displayName() const; + virtual char* description() const; +}; + +void sp_tref_update_text(SPTRef *tref); +bool sp_tref_reference_allowed(SPTRef *tref, SPObject *possible_ref); +bool sp_tref_fully_contained(SPObject *start_item, Glib::ustring::iterator &start, + SPObject *end_item, Glib::ustring::iterator &end); +SPObject * sp_tref_convert_to_tspan(SPObject *item); + + +#endif /* !SP_TREF_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-tspan.cpp b/src/object/sp-tspan.cpp new file mode 100644 index 000000000..d8c655c06 --- /dev/null +++ b/src/object/sp-tspan.cpp @@ -0,0 +1,499 @@ +/* + * SVG <text> and <tspan> implementation + * + * Author: + * Lauris Kaplinski <lauris@kaplinski.com> + * bulia byak <buliabyak@users.sf.net> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +/* + * fixme: + * + * These subcomponents should not be items, or alternately + * we have to invent set of flags to mark, whether standard + * attributes are applicable to given item (I even like this + * idea somewhat - Lauris) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <cstring> +#include <string> +#include <glibmm/i18n.h> + +#include <livarot/Path.h> +#include "svg/stringstream.h" +#include "attributes.h" +#include "sp-use-reference.h" +#include "sp-tspan.h" +#include "sp-tref.h" +#include "sp-textpath.h" +#include "text-editing.h" +#include "style.h" +#include "document.h" + +/*##################################################### +# SPTSPAN +#####################################################*/ +SPTSpan::SPTSpan() : SPItem() { + this->role = SP_TSPAN_ROLE_UNSPECIFIED; +} + +SPTSpan::~SPTSpan() { +} + +void SPTSpan::build(SPDocument *doc, Inkscape::XML::Node *repr) { + this->readAttr( "x" ); + this->readAttr( "y" ); + this->readAttr( "dx" ); + this->readAttr( "dy" ); + this->readAttr( "rotate" ); + this->readAttr( "sodipodi:role" ); + + SPItem::build(doc, repr); +} + +void SPTSpan::release() { + SPItem::release(); +} + +void SPTSpan::set(unsigned int key, const gchar* value) { + if (this->attributes.readSingleAttribute(key, value, style, &viewport)) { + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } else { + switch (key) { + case SP_ATTR_SODIPODI_ROLE: + if (value && (!strcmp(value, "line") || !strcmp(value, "paragraph"))) { + this->role = SP_TSPAN_ROLE_LINE; + } else { + this->role = SP_TSPAN_ROLE_UNSPECIFIED; + } + break; + + default: + SPItem::set(key, value); + break; + } + } +} + +void SPTSpan::update(SPCtx *ctx, guint flags) { + unsigned childflags = flags; + if (flags & SP_OBJECT_MODIFIED_FLAG) { + childflags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + childflags &= SP_OBJECT_MODIFIED_CASCADE; + + for (auto& ochild: children) { + if ( flags || ( ochild.uflags & SP_OBJECT_MODIFIED_FLAG )) { + ochild.updateDisplay(ctx, childflags); + } + } + + SPItem::update(ctx, flags); + + if (flags & ( SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_CHILD_MODIFIED_FLAG | + SP_TEXT_LAYOUT_MODIFIED_FLAG ) ) + { + SPItemCtx const *ictx = reinterpret_cast<SPItemCtx const *>(ctx); + + double const w = ictx->viewport.width(); + double const h = ictx->viewport.height(); + double const em = style->font_size.computed; + double const ex = 0.5 * em; // fixme: get x height from pango or libnrtype. + + attributes.update( em, ex, w, h ); + } +} + +void SPTSpan::modified(unsigned int flags) { +// SPItem::onModified(flags); + + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + for (auto& ochild: children) { + if (flags || (ochild.mflags & SP_OBJECT_MODIFIED_FLAG)) { + ochild.emitModified(flags); + } + } +} + +Geom::OptRect SPTSpan::bbox(Geom::Affine const &transform, SPItem::BBoxType type) const { + Geom::OptRect bbox; + // find out the ancestor text which holds our layout + SPObject const *parent_text = this; + + while (parent_text && !SP_IS_TEXT(parent_text)) { + parent_text = parent_text->parent; + } + + if (parent_text == NULL) { + return bbox; + } + + // get the bbox of our portion of the layout + bbox = SP_TEXT(parent_text)->layout.bounds(transform, sp_text_get_length_upto(parent_text, this), sp_text_get_length_upto(this, NULL) - 1); + + if (!bbox) { + return bbox; + } + + // Add stroke width + // FIXME this code is incorrect + if (type == SPItem::VISUAL_BBOX && !this->style->stroke.isNone()) { + double scale = transform.descrim(); + bbox->expandBy(0.5 * this->style->stroke_width.computed * scale); + } + + return bbox; +} + +Inkscape::XML::Node* SPTSpan::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:tspan"); + } + + this->attributes.writeTo(repr); + + if ( flags&SP_OBJECT_WRITE_BUILD ) { + std::vector<Inkscape::XML::Node *> l; + + for (auto& child: children) { + Inkscape::XML::Node* c_repr=NULL; + + if ( SP_IS_TSPAN(&child) || SP_IS_TREF(&child) ) { + c_repr = child.updateRepr(xml_doc, NULL, flags); + } else if ( SP_IS_TEXTPATH(&child) ) { + //c_repr = child.updateRepr(xml_doc, NULL, flags); // shouldn't happen + } else if ( SP_IS_STRING(&child) ) { + c_repr = xml_doc->createTextNode(SP_STRING(&child)->string.c_str()); + } + + if ( c_repr ) { + l.push_back(c_repr); + } + } + + for (auto i = l.rbegin(); i!= l.rend(); ++i) { + repr->addChild((*i), NULL); + Inkscape::GC::release(*i); + } + } else { + for (auto& child: children) { + if ( SP_IS_TSPAN(&child) || SP_IS_TREF(&child) ) { + child.updateRepr(flags); + } else if ( SP_IS_TEXTPATH(&child) ) { + //c_repr = child->updateRepr(xml_doc, NULL, flags); // shouldn't happen + } else if ( SP_IS_STRING(&child) ) { + child.getRepr()->setContent(SP_STRING(&child)->string.c_str()); + } + } + } + + SPItem::write(xml_doc, repr, flags); + + return repr; +} + +const char* SPTSpan::displayName() const { + return _("Text Span"); +} + + +/*##################################################### +# SPTEXTPATH +#####################################################*/ +void refresh_textpath_source(SPTextPath* offset); + +SPTextPath::SPTextPath() : SPItem() { + this->startOffset._set = false; + this->originalPath = NULL; + this->isUpdating=false; + + // set up the uri reference + this->sourcePath = new SPUsePath(this); + this->sourcePath->user_unlink = sp_textpath_to_text; +} + +SPTextPath::~SPTextPath() { + delete this->sourcePath; +} + +void SPTextPath::build(SPDocument *doc, Inkscape::XML::Node *repr) { + this->readAttr( "x" ); + this->readAttr( "y" ); + this->readAttr( "dx" ); + this->readAttr( "dy" ); + this->readAttr( "rotate" ); + this->readAttr( "startOffset" ); + this->readAttr( "xlink:href" ); + + bool no_content = true; + + for (Inkscape::XML::Node* rch = repr->firstChild() ; rch != NULL; rch = rch->next()) { + if ( rch->type() == Inkscape::XML::TEXT_NODE ) + { + no_content = false; + break; + } + } + + if ( no_content ) { + Inkscape::XML::Document *xml_doc = doc->getReprDoc(); + Inkscape::XML::Node* rch = xml_doc->createTextNode(""); + repr->addChild(rch, NULL); + } + + SPItem::build(doc, repr); +} + +void SPTextPath::release() { + //this->attributes.~TextTagAttributes(); + + if (this->originalPath) { + delete this->originalPath; + } + + this->originalPath = NULL; + + SPItem::release(); +} + +void SPTextPath::set(unsigned int key, const gchar* value) { + if (this->attributes.readSingleAttribute(key, value, style, &viewport)) { + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } else { + switch (key) { + case SP_ATTR_XLINK_HREF: + this->sourcePath->link((char*)value); + break; + case SP_ATTR_STARTOFFSET: + this->startOffset.readOrUnset(value); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + default: + SPItem::set(key, value); + break; + } + } +} + +void SPTextPath::update(SPCtx *ctx, guint flags) { + this->isUpdating = true; + + if ( this->sourcePath->sourceDirty ) { + refresh_textpath_source(this); + } + + this->isUpdating = false; + + SPItem::update(ctx, flags); + + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + for (auto& ochild: children) { + if ( flags || ( ochild.uflags & SP_OBJECT_MODIFIED_FLAG )) { + ochild.updateDisplay(ctx, flags); + } + } + + if (flags & ( SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_CHILD_MODIFIED_FLAG | + SP_TEXT_LAYOUT_MODIFIED_FLAG ) ) + { + SPItemCtx const *ictx = reinterpret_cast<SPItemCtx const *>(ctx); + + double const w = ictx->viewport.width(); + double const h = ictx->viewport.height(); + double const em = style->font_size.computed; + double const ex = 0.5 * em; // fixme: get x height from pango or libnrtype. + + attributes.update( em, ex, w, h ); + } +} + + +void refresh_textpath_source(SPTextPath* tp) +{ + if ( tp == NULL ) { + return; + } + + tp->sourcePath->refresh_source(); + tp->sourcePath->sourceDirty=false; + + // finalisons + if ( tp->sourcePath->originalPath ) { + if (tp->originalPath) { + delete tp->originalPath; + } + + tp->originalPath = NULL; + + tp->originalPath = new Path; + tp->originalPath->Copy(tp->sourcePath->originalPath); + tp->originalPath->ConvertWithBackData(0.01); + } +} + +void SPTextPath::modified(unsigned int flags) { +// SPItem::onModified(flags); + + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + for (auto& ochild: children) { + if (flags || (ochild.mflags & SP_OBJECT_MODIFIED_FLAG)) { + ochild.emitModified(flags); + } + } +} + +Inkscape::XML::Node* SPTextPath::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:textPath"); + } + + this->attributes.writeTo(repr); + if (this->startOffset._set) { + if (this->startOffset.unit == SVGLength::PERCENT) { + Inkscape::SVGOStringStream os; + os << (this->startOffset.computed * 100.0) << "%"; + this->getRepr()->setAttribute("startOffset", os.str().c_str()); + } else { + /* FIXME: This logic looks rather undesirable if e.g. startOffset is to be + in ems. */ + sp_repr_set_svg_double(repr, "startOffset", this->startOffset.computed); + } + } + + if ( this->sourcePath->sourceHref ) { + repr->setAttribute("xlink:href", this->sourcePath->sourceHref); + } + + if ( flags & SP_OBJECT_WRITE_BUILD ) { + std::vector<Inkscape::XML::Node *> l; + + for (auto& child: children) { + Inkscape::XML::Node* c_repr=NULL; + + if ( SP_IS_TSPAN(&child) || SP_IS_TREF(&child) ) { + c_repr = child.updateRepr(xml_doc, NULL, flags); + } else if ( SP_IS_TEXTPATH(&child) ) { + //c_repr = child->updateRepr(xml_doc, NULL, flags); // shouldn't happen + } else if ( SP_IS_STRING(&child) ) { + c_repr = xml_doc->createTextNode(SP_STRING(&child)->string.c_str()); + } + + if ( c_repr ) { + l.push_back(c_repr); + } + } + + for( auto i = l.rbegin(); i != l.rend(); ++i ) { + repr->addChild(*i, NULL); + Inkscape::GC::release(*i); + } + } else { + for (auto& child: children) { + if ( SP_IS_TSPAN(&child) || SP_IS_TREF(&child) ) { + child.updateRepr(flags); + } else if ( SP_IS_TEXTPATH(&child) ) { + //c_repr = child.updateRepr(xml_doc, NULL, flags); // shouldn't happen + } else if ( SP_IS_STRING(&child) ) { + child.getRepr()->setContent(SP_STRING(&child)->string.c_str()); + } + } + } + + SPItem::write(xml_doc, repr, flags); + + return repr; +} + + +SPItem *sp_textpath_get_path_item(SPTextPath *tp) +{ + if (tp && tp->sourcePath) { + SPItem *refobj = tp->sourcePath->getObject(); + + if (SP_IS_ITEM(refobj)) { + return refobj; + } + } + return NULL; +} + +void sp_textpath_to_text(SPObject *tp) +{ + SPObject *text = tp->parent; + + Geom::OptRect bbox = SP_ITEM(text)->geometricBounds(SP_ITEM(text)->i2doc_affine()); + + if (!bbox) { + return; + } + + Geom::Point xy = bbox->min(); + xy *= tp->document->getDocumentScale().inverse(); // Convert to user-units. + + // make a list of textpath children + std::vector<Inkscape::XML::Node *> tp_reprs; + + for (auto& o: tp->children) { + tp_reprs.push_back(o.getRepr()); + } + + for (auto i = tp_reprs.rbegin(); i != tp_reprs.rend(); ++i) { + // make a copy of each textpath child + Inkscape::XML::Node *copy = (*i)->duplicate(text->getRepr()->document()); + // remove the old repr from under textpath + tp->getRepr()->removeChild(*i); + // put its copy under text + text->getRepr()->addChild(copy, NULL); // fixme: copy id + } + + //remove textpath + tp->deleteObject(); + + // set x/y on text (to be near where it was when on path) + /* fixme: Yuck, is this really the right test? */ + if (xy[Geom::X] != 1e18 && xy[Geom::Y] != 1e18) { + sp_repr_set_svg_double(text->getRepr(), "x", xy[Geom::X]); + sp_repr_set_svg_double(text->getRepr(), "y", xy[Geom::Y]); + } +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-tspan.h b/src/object/sp-tspan.h new file mode 100644 index 000000000..1aef4cd0c --- /dev/null +++ b/src/object/sp-tspan.h @@ -0,0 +1,50 @@ +#ifndef INKSCAPE_SP_TSPAN_H +#define INKSCAPE_SP_TSPAN_H + +/* + * tspan and textpath, based on the flowtext routines + */ + +#include "sp-item.h" +#include "text-tag-attributes.h" + +#define SP_TSPAN(obj) (dynamic_cast<SPTSpan*>((SPObject*)obj)) +#define SP_IS_TSPAN(obj) (dynamic_cast<const SPTSpan*>((SPObject*)obj) != NULL) + +enum { + SP_TSPAN_ROLE_UNSPECIFIED, + SP_TSPAN_ROLE_PARAGRAPH, + SP_TSPAN_ROLE_LINE +}; + +class SPTSpan : public SPItem { +public: + SPTSpan(); + virtual ~SPTSpan(); + + unsigned int role : 2; + TextTagAttributes attributes; + + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + virtual void set(unsigned int key, const char* value); + virtual void update(SPCtx* ctx, unsigned int flags); + virtual void modified(unsigned int flags); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); + + virtual Geom::OptRect bbox(Geom::Affine const &transform, SPItem::BBoxType type) const; + virtual const char* displayName() const; +}; + +#endif /* !INKSCAPE_SP_TSPAN_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/sp-use-reference.cpp b/src/object/sp-use-reference.cpp new file mode 100644 index 000000000..ea8bc5e86 --- /dev/null +++ b/src/object/sp-use-reference.cpp @@ -0,0 +1,241 @@ +/* + * The reference corresponding to href of <use> element. + * + * Copyright (C) 2004 Bulia Byak + * Copyright (C) 2004 Monash University + * + * Released under GNU GPL, read the file 'COPYING' for more information. + */ + +#include "sp-use-reference.h" + +#include <cstring> +#include <string> + +#include "bad-uri-exception.h" +#include "enums.h" + +#include "display/curve.h" +#include "livarot/Path.h" +#include "preferences.h" +#include "sp-shape.h" +#include "sp-text.h" +#include "uri.h" + +bool SPUseReference::_acceptObject(SPObject * const obj) const +{ + return URIReference::_acceptObject(obj); +} + + +static void sp_usepath_href_changed(SPObject *old_ref, SPObject *ref, SPUsePath *offset); +static void sp_usepath_move_compensate(Geom::Affine const *mp, SPItem *original, SPUsePath *self); +static void sp_usepath_delete_self(SPObject *deleted, SPUsePath *offset); +static void sp_usepath_source_modified(SPObject *iSource, guint flags, SPUsePath *offset); + +SPUsePath::SPUsePath(SPObject* i_owner):SPUseReference(i_owner) +{ + owner=i_owner; + originalPath = NULL; + sourceDirty=false; + sourceHref = NULL; + sourceRepr = NULL; + sourceObject = NULL; + _changed_connection = changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_usepath_href_changed), this)); // listening to myself, this should be virtual instead + + user_unlink = NULL; +} + +SPUsePath::~SPUsePath(void) +{ + delete originalPath; + originalPath = NULL; + + _changed_connection.disconnect(); // to do before unlinking + + quit_listening(); + unlink(); +} + +void +SPUsePath::link(char *to) +{ + if ( to == NULL ) { + quit_listening(); + unlink(); + } else { + if ( !sourceHref || ( strcmp(to, sourceHref) != 0 ) ) { + g_free(sourceHref); + sourceHref = g_strdup(to); + try { + attach(Inkscape::URI(to)); + } catch (Inkscape::BadURIException &e) { + /* TODO: Proper error handling as per + * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing. + */ + g_warning("%s", e.what()); + detach(); + } + } + } +} + +void +SPUsePath::unlink(void) +{ + g_free(sourceHref); + sourceHref = NULL; + detach(); +} + +void +SPUsePath::start_listening(SPObject* to) +{ + if ( to == NULL ) { + return; + } + sourceObject = to; + sourceRepr = to->getRepr(); + _delete_connection = to->connectDelete(sigc::bind(sigc::ptr_fun(&sp_usepath_delete_self), this)); + _transformed_connection = SP_ITEM(to)->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_usepath_move_compensate), this)); + _modified_connection = to->connectModified(sigc::bind<2>(sigc::ptr_fun(&sp_usepath_source_modified), this)); +} + +void +SPUsePath::quit_listening(void) +{ + if ( sourceObject == NULL ) { + return; + } + _modified_connection.disconnect(); + _delete_connection.disconnect(); + _transformed_connection.disconnect(); + sourceRepr = NULL; + sourceObject = NULL; +} + +static void +sp_usepath_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, SPUsePath *offset) +{ + offset->quit_listening(); + SPItem *refobj = offset->getObject(); + if ( refobj ) { + offset->start_listening(refobj); + } + offset->sourceDirty=true; + offset->owner->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +static void +sp_usepath_move_compensate(Geom::Affine const *mp, SPItem *original, SPUsePath *self) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + guint mode = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_PARALLEL); + if (mode == SP_CLONE_COMPENSATION_NONE) { + return; + } + SPItem *item = SP_ITEM(self->owner); + +// TODO kill naughty naughty #if 0 +#if 0 + Geom::Affine m(*mp); + if (!(m.is_translation())) { + return; + } + Geom::Affine const t(item->transform); + Geom::Affine clone_move = t.inverse() * m * t; + + // Calculate the compensation matrix and the advertized movement matrix. + Geom::Affine advertized_move; + if (mode == SP_CLONE_COMPENSATION_PARALLEL) { + //clone_move = clone_move.inverse(); + advertized_move.set_identity(); + } else if (mode == SP_CLONE_COMPENSATION_UNMOVED) { + clone_move = clone_move.inverse() * m; + advertized_move = m; + } else { + g_assert_not_reached(); + } + + // Commit the compensation. + item->transform *= clone_move; + sp_item_write_transform(item, item->getRepr(), item->transform, &advertized_move); +#else + (void)mp; + (void)original; +#endif + + self->sourceDirty = true; + item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +static void +sp_usepath_delete_self(SPObject */*deleted*/, SPUsePath *offset) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + guint const mode = prefs->getInt("/options/cloneorphans/value", SP_CLONE_ORPHANS_UNLINK); + + if (mode == SP_CLONE_ORPHANS_UNLINK) { + // leave it be. just forget about the source + offset->quit_listening(); + offset->unlink(); + if (offset->user_unlink) + offset->user_unlink(offset->owner); + } else if (mode == SP_CLONE_ORPHANS_DELETE) { + offset->owner->deleteObject(); + } +} + +static void +sp_usepath_source_modified(SPObject */*iSource*/, guint /*flags*/, SPUsePath *offset) +{ + offset->sourceDirty = true; + offset->owner->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +void SPUsePath::refresh_source() +{ + sourceDirty = false; + delete originalPath; + originalPath = NULL; + + // le mauvais cas: pas d'attribut d => il faut verifier que c'est une SPShape puis prendre le contour + // [tr: The bad case: no d attribute. Must check that it's a SPShape and then take the outline.] + SPObject *refobj = sourceObject; + if ( refobj == NULL ) return; + + SPItem *item = SP_ITEM(refobj); + SPCurve *curve = NULL; + + if (SP_IS_SHAPE(item)) + { + curve = SP_SHAPE(item)->getCurve(); + } + else if (SP_IS_TEXT(item)) + { + curve = SP_TEXT(item)->getNormalizedBpath(); + } + else + { + return; + } + + if (curve == NULL) + return; + + originalPath = new Path; + originalPath->LoadPathVector(curve->get_pathvector(), item->transform, true); + curve->unref(); +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-use-reference.h b/src/object/sp-use-reference.h new file mode 100644 index 000000000..297bdc26c --- /dev/null +++ b/src/object/sp-use-reference.h @@ -0,0 +1,78 @@ +#ifndef SEEN_SP_USE_REFERENCE_H +#define SEEN_SP_USE_REFERENCE_H + +/* + * The reference corresponding to href of <use> element. + * + * Copyright (C) 2004 Bulia Byak + * + * Released under GNU GPL, read the file 'COPYING' for more information. + */ + +#include <sigc++/sigc++.h> + +#include "sp-item.h" +#include "uri-references.h" + +class Path; + +namespace Inkscape { +namespace XML { +class Node; +} +} + + +class SPUseReference : public Inkscape::URIReference { +public: + SPUseReference(SPObject *owner) : URIReference(owner) {} + + SPItem *getObject() const { + return static_cast<SPItem *>(URIReference::getObject()); + } + +protected: + virtual bool _acceptObject(SPObject * const obj) const; + +}; + + +class SPUsePath : public SPUseReference { +public: + Path *originalPath; + bool sourceDirty; + + SPObject *owner; + char *sourceHref; + Inkscape::XML::Node *sourceRepr; + SPObject *sourceObject; + + sigc::connection _modified_connection; + sigc::connection _delete_connection; + sigc::connection _changed_connection; + sigc::connection _transformed_connection; + + SPUsePath(SPObject* i_owner); + ~SPUsePath(void); + + void link(char* to); + void unlink(void); + void start_listening(SPObject* to); + void quit_listening(void); + void refresh_source(void); + + void (*user_unlink) (SPObject *user); +}; + +#endif /* !SEEN_SP_USE_REFERENCE_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-use.cpp b/src/object/sp-use.cpp new file mode 100644 index 000000000..2837e56f1 --- /dev/null +++ b/src/object/sp-use.cpp @@ -0,0 +1,763 @@ +/* + * SVG <use> implementation + * + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * bulia byak <buliabyak@users.sf.net> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * + * Copyright (C) 1999-2005 authors + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <cstring> +#include <string> + +#include <2geom/transforms.h> +#include <glibmm/i18n.h> +#include <glibmm/markup.h> + +#include "bad-uri-exception.h" +#include "display/drawing-group.h" +#include "attributes.h" +#include "document.h" +#include "sp-clippath.h" +#include "sp-mask.h" +#include "sp-factory.h" +#include "sp-flowregion.h" +#include "uri.h" +#include "print.h" +#include "xml/repr.h" +#include "svg/svg.h" +#include "preferences.h" +#include "style.h" + +#include "sp-use.h" +#include "sp-symbol.h" +#include "sp-root.h" +#include "sp-use-reference.h" +#include "sp-shape.h" +#include "sp-text.h" +#include "sp-flowtext.h" + +SPUse::SPUse() + : SPItem(), + SPDimensions(), + child(NULL), + href(NULL), + ref(new SPUseReference(this)), + _delete_connection(), + _changed_connection(), + _transformed_connection() +{ + this->x.unset(); + this->y.unset(); + this->width.unset(SVGLength::PERCENT, 1.0, 1.0); + this->height.unset(SVGLength::PERCENT, 1.0, 1.0); + + this->_changed_connection = this->ref->changedSignal().connect( + sigc::hide(sigc::hide(sigc::mem_fun(this, &SPUse::href_changed))) + ); +} + +SPUse::~SPUse() { + if (this->child) { + this->detach(this->child); + this->child = NULL; + } + + this->ref->detach(); + delete this->ref; + this->ref = 0; +} + +void SPUse::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPItem::build(document, repr); + + this->readAttr( "x" ); + this->readAttr( "y" ); + this->readAttr( "width" ); + this->readAttr( "height" ); + this->readAttr( "xlink:href" ); + + // We don't need to create child here: + // reading xlink:href will attach ref, and that will cause the changed signal to be emitted, + // which will call SPUse::href_changed, and that will take care of the child +} + +void SPUse::release() { + if (this->child) { + this->detach(this->child); + this->child = NULL; + } + + this->_delete_connection.disconnect(); + this->_changed_connection.disconnect(); + this->_transformed_connection.disconnect(); + + g_free(this->href); + this->href = NULL; + + this->ref->detach(); + + SPItem::release(); +} + +void SPUse::set(unsigned int key, const gchar* value) { + switch (key) { + case SP_ATTR_X: + this->x.readOrUnset(value); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_Y: + this->y.readOrUnset(value); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_WIDTH: + this->width.readOrUnset(value, SVGLength::PERCENT, 1.0, 1.0); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_HEIGHT: + this->height.readOrUnset(value, SVGLength::PERCENT, 1.0, 1.0); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SP_ATTR_XLINK_HREF: { + if ( value && this->href && ( strcmp(value, this->href) == 0 ) ) { + /* No change, do nothing. */ + } else { + g_free(this->href); + this->href = NULL; + + if (value) { + // First, set the href field, because SPUse::href_changed will need it. + this->href = g_strdup(value); + + // Now do the attaching, which emits the changed signal. + try { + this->ref->attach(Inkscape::URI(value)); + } catch (Inkscape::BadURIException &e) { + g_warning("%s", e.what()); + this->ref->detach(); + } + } else { + this->ref->detach(); + } + } + break; + } + + default: + SPItem::set(key, value); + break; + } +} + +Inkscape::XML::Node* SPUse::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:use"); + } + + SPItem::write(xml_doc, repr, flags); + + sp_repr_set_svg_double(repr, "x", this->x.computed); + sp_repr_set_svg_double(repr, "y", this->y.computed); + repr->setAttribute("width", sp_svg_length_write_with_units(this->width).c_str()); + repr->setAttribute("height", sp_svg_length_write_with_units(this->height).c_str()); + + if (this->ref->getURI()) { + gchar *uri_string = this->ref->getURI()->toString(); + repr->setAttribute("xlink:href", uri_string); + g_free(uri_string); + } + + SPShape *shape = dynamic_cast<SPShape *>(child); + if (shape) { + shape->set_shape(); // evaluate SPCurve of child + } else { + SPText *text = dynamic_cast<SPText *>(child); + if (text) { + text->rebuildLayout(); // refresh Layout, LP Bug 1339305 + } else { + SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(child); + if (flowtext) { + SPFlowregion *flowregion = dynamic_cast<SPFlowregion *>(flowtext->firstChild()); + if (flowregion) { + flowregion->UpdateComputed(); + } + flowtext->rebuildLayout(); + } + } + } + + return repr; +} + +Geom::OptRect SPUse::bbox(Geom::Affine const &transform, SPItem::BBoxType bboxtype) const { + Geom::OptRect bbox; + + if (this->child) { + Geom::Affine const ct(child->transform * Geom::Translate(this->x.computed, this->y.computed) * transform ); + + bbox = child->bounds(bboxtype, ct); + } + + return bbox; +} + +void SPUse::print(SPPrintContext* ctx) { + bool translated = false; + + if ((this->x._set && this->x.computed != 0) || (this->y._set && this->y.computed != 0)) { + Geom::Affine tp(Geom::Translate(this->x.computed, this->y.computed)); + sp_print_bind(ctx, tp, 1.0); + translated = true; + } + + if (this->child) { + this->child->invoke_print(ctx); + } + + if (translated) { + sp_print_release(ctx); + } +} + +const char* SPUse::displayName() const { + if (dynamic_cast<SPSymbol *>(child)) { + return _("Symbol"); + } else { + return _("Clone"); + } +} + +gchar* SPUse::description() const { + if (child) { + if ( dynamic_cast<SPSymbol *>(child) ) { + if (child->title()) { + return g_strdup_printf(_("called %s"), Glib::Markup::escape_text(Glib::ustring( g_dpgettext2(NULL, "Symbol", child->title()))).c_str()); + } else if (child->getAttribute("id")) { + return g_strdup_printf(_("called %s"), Glib::Markup::escape_text(Glib::ustring( g_dpgettext2(NULL, "Symbol", child->getAttribute("id")))).c_str()); + } else { + return g_strdup_printf(_("called %s"), _("Unnamed Symbol")); + } + } + + static unsigned recursion_depth = 0; + + if (recursion_depth >= 4) { + /* TRANSLATORS: Used for statusbar description for long <use> chains: + * "Clone of: Clone of: ... in Layer 1". */ + return g_strdup(_("...")); + /* We could do better, e.g. chasing the href chain until we reach something other than + * a <use>, and giving its description. */ + } + + ++recursion_depth; + char *child_desc = this->child->detailedDescription(); + --recursion_depth; + + char *ret = g_strdup_printf(_("of: %s"), child_desc); + g_free(child_desc); + + return ret; + } else { + return g_strdup(_("[orphaned]")); + } +} + +Inkscape::DrawingItem* SPUse::show(Inkscape::Drawing &drawing, unsigned int key, unsigned int flags) { + + // std::cout << "SPUse::show: " << (getId()?getId():"null") << std::endl; + Inkscape::DrawingGroup *ai = new Inkscape::DrawingGroup(drawing); + ai->setPickChildren(false); + this->context_style = this->style; + ai->setStyle(this->style, this->context_style); + + if (this->child) { + Inkscape::DrawingItem *ac = this->child->invoke_show(drawing, key, flags); + + if (ac) { + ai->prependChild(ac); + } + + Geom::Translate t(this->x.computed, this->y.computed); + ai->setChildTransform(t); + } + + return ai; +} + +void SPUse::hide(unsigned int key) { + if (this->child) { + this->child->invoke_hide(key); + } + +// SPItem::onHide(key); +} + + +/** + * Returns the ultimate original of a SPUse (i.e. the first object in the chain of its originals + * which is not an SPUse). If no original is found, NULL is returned (it is the responsibility + * of the caller to make sure that this is handled correctly). + * + * Note that the returned is the clone object, i.e. the child of an SPUse (of the argument one for + * the trivial case) and not the "true original". + */ +SPItem *SPUse::root() { + SPItem *orig = this->child; + + SPUse *use = dynamic_cast<SPUse *>(orig); + while (orig && use) { + orig = use->child; + use = dynamic_cast<SPUse *>(orig); + } + + return orig; +} + +SPItem const *SPUse::root() const { + return const_cast<SPUse*>(this)->root(); +} + +/** + * Get the number of dereferences or calls to get_original() needed to get an object + * which is not an svg:use. Returns -1 if there is no original object. + */ +int SPUse::cloneDepth() const { + unsigned depth = 1; + SPItem *orig = this->child; + + while (orig && dynamic_cast<SPUse *>(orig)) { + ++depth; + orig = dynamic_cast<SPUse *>(orig)->child; + } + + if (!orig) { + return -1; + } else { + return depth; + } +} + +/** + * Returns the effective transform that goes from the ultimate original to given SPUse, both ends + * included. + */ +Geom::Affine SPUse::get_root_transform() { + //track the ultimate source of a chain of uses + SPObject *orig = this->child; + + std::vector<SPItem*> chain; + chain.push_back(this); + + while (dynamic_cast<SPUse *>(orig)) { + chain.push_back(dynamic_cast<SPItem *>(orig)); + orig = dynamic_cast<SPUse *>(orig)->child; + } + + chain.push_back(dynamic_cast<SPItem *>(orig)); + + // calculate the accummulated transform, starting from the original + Geom::Affine t(Geom::identity()); + + for (auto i=chain.rbegin(); i!=chain.rend(); ++i) { + SPItem *i_tem = *i; + + // "An additional transformation translate(x,y) is appended to the end (i.e., + // right-side) of the transform attribute on the generated 'g', where x and y + // represent the values of the x and y attributes on the 'use' element." - http://www.w3.org/TR/SVG11/struct.html#UseElement + SPUse *i_use = dynamic_cast<SPUse *>(i_tem); + if (i_use) { + if ((i_use->x._set && i_use->x.computed != 0) || (i_use->y._set && i_use->y.computed != 0)) { + t = t * Geom::Translate(i_use->x._set ? i_use->x.computed : 0, i_use->y._set ? i_use->y.computed : 0); + } + } + + t *= i_tem->transform; + } + return t; +} + +/** + * Returns the transform that leads to the use from its immediate original. + * Does not include the original's transform if any. + */ +Geom::Affine SPUse::get_parent_transform() { + Geom::Affine t(Geom::identity()); + + if ((this->x._set && this->x.computed != 0) || (this->y._set && this->y.computed != 0)) { + t *= Geom::Translate(this->x._set ? this->x.computed : 0, this->y._set ? this->y.computed : 0); + } + + t *= this->transform; + return t; +} + +/** + * Sensing a movement of the original, this function attempts to compensate for it in such a way + * that the clone stays unmoved or moves in parallel (depending on user setting) regardless of the + * clone's transform. + */ +void SPUse::move_compensate(Geom::Affine const *mp) { + // the clone is orphaned; or this is not a real use, but a clone of another use; + // we skip it, otherwise duplicate compensation will occur + if (this->cloned) { + return; + } + + // never compensate uses which are used in flowtext + if (parent && dynamic_cast<SPFlowregion *>(parent)) { + return; + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + guint mode = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_PARALLEL); + // user wants no compensation + if (mode == SP_CLONE_COMPENSATION_NONE) + return; + + Geom::Affine m(*mp); + Geom::Affine t = this->get_parent_transform(); + Geom::Affine clone_move = t.inverse() * m * t; + + // this is not a simple move, do not try to compensate + if (!(m.isTranslation())){ + //BUT move clippaths accordingly. + //if clone has a clippath, move it accordingly + if(clip_ref->getObject()){ + for(auto& clip: clip_ref->getObject()->children){ + SPItem *item = (SPItem*) &clip; + if(item){ + item->transform *= m; + Geom::Affine identity; + item->doWriteTransform(item->transform, &identity); + } + } + } + if(mask_ref->getObject()){ + for(auto& mask: mask_ref->getObject()->children){ + SPItem *item = (SPItem*) &mask; + if(item){ + item->transform *= m; + Geom::Affine identity; + item->doWriteTransform(item->transform, &identity); + } + } + } + return; + } + + // restore item->transform field from the repr, in case it was changed by seltrans + this->readAttr ("transform"); + + + // calculate the compensation matrix and the advertized movement matrix + Geom::Affine advertized_move; + if (mode == SP_CLONE_COMPENSATION_PARALLEL) { + clone_move = clone_move.inverse() * m; + advertized_move = m; + } else if (mode == SP_CLONE_COMPENSATION_UNMOVED) { + clone_move = clone_move.inverse(); + advertized_move.setIdentity(); + } else { + g_assert_not_reached(); + } + + //if clone has a clippath, move it accordingly + if(clip_ref->getObject()){ + for(auto& clip: clip_ref->getObject()->children){ + SPItem *item = (SPItem*) &clip; + if(item){ + item->transform *= clone_move.inverse(); + Geom::Affine identity; + item->doWriteTransform(item->transform, &identity); + } + } + } + if(mask_ref->getObject()){ + for(auto& mask: mask_ref->getObject()->children){ + SPItem *item = (SPItem*) &mask; + if(item){ + item->transform *= clone_move.inverse(); + Geom::Affine identity; + item->doWriteTransform(item->transform, &identity); + } + } + } + + + // commit the compensation + this->transform *= clone_move; + this->doWriteTransform(this->transform, &advertized_move); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +void SPUse::href_changed() { + this->_delete_connection.disconnect(); + this->_transformed_connection.disconnect(); + + if (this->child) { + this->detach(this->child); + this->child = NULL; + } + + if (this->href) { + SPItem *refobj = this->ref->getObject(); + + if (refobj) { + Inkscape::XML::Node *childrepr = refobj->getRepr(); + + SPObject* obj = SPFactory::createObject(NodeTraits::get_type_string(*childrepr)); + + SPItem *item = dynamic_cast<SPItem *>(obj); + if (item) { + child = item; + + this->attach(this->child, this->lastChild()); + sp_object_unref(this->child, this); + + this->child->invoke_build(this->document, childrepr, TRUE); + + for (SPItemView *v = this->display; v != NULL; v = v->next) { + Inkscape::DrawingItem *ai = this->child->invoke_show(v->arenaitem->drawing(), v->key, v->flags); + + if (ai) { + v->arenaitem->prependChild(ai); + } + } + } else { + delete obj; + g_warning("Tried to create svg:use from invalid object"); + } + + this->_delete_connection = refobj->connectDelete( + sigc::hide(sigc::mem_fun(this, &SPUse::delete_self)) + ); + + this->_transformed_connection = refobj->connectTransformed( + sigc::hide(sigc::mem_fun(this, &SPUse::move_compensate)) + ); + } + } +} + +void SPUse::delete_self() { + // always delete uses which are used in flowtext + if (parent && dynamic_cast<SPFlowregion *>(parent)) { + deleteObject(); + return; + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + guint const mode = prefs->getInt("/options/cloneorphans/value", + SP_CLONE_ORPHANS_UNLINK); + + if (mode == SP_CLONE_ORPHANS_UNLINK) { + this->unlink(); + } else if (mode == SP_CLONE_ORPHANS_DELETE) { + this->deleteObject(); + } +} + +void SPUse::update(SPCtx *ctx, unsigned flags) { + // std::cout << "SPUse::update: " << (getId()?getId():"null") << std::endl; + SPItemCtx *ictx = (SPItemCtx *) ctx; + SPItemCtx cctx = *ictx; + + unsigned childflags = flags; + if (flags & SP_OBJECT_MODIFIED_FLAG) { + childflags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + childflags &= SP_OBJECT_MODIFIED_CASCADE; + + /* Set up child viewport */ + this->calcDimsFromParentViewport(ictx); + + childflags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; + + if (this->child) { + sp_object_ref(this->child); + + // viewport is only changed if referencing a symbol or svg element + if( SP_IS_SYMBOL(this->child) || SP_IS_ROOT(this->child) ) { + cctx.viewport = Geom::Rect::from_xywh(0, 0, this->width.computed, this->height.computed); + cctx.i2vp = Geom::identity(); + } + + if (childflags || (this->child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + SPItem const *chi = dynamic_cast<SPItem const *>(child); + g_assert(chi != NULL); + cctx.i2doc = chi->transform * ictx->i2doc; + cctx.i2vp = chi->transform * ictx->i2vp; + this->child->updateDisplay((SPCtx *)&cctx, childflags); + } + + sp_object_unref(this->child); + } + + SPItem::update(ctx, flags); + + if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) { + for (SPItemView *v = this->display; v != NULL; v = v->next) { + Inkscape::DrawingGroup *g = dynamic_cast<Inkscape::DrawingGroup *>(v->arenaitem); + this->context_style = this->style; + g->setStyle(this->style, this->context_style); + } + } + + /* As last step set additional transform of arena group */ + for (SPItemView *v = this->display; v != NULL; v = v->next) { + Inkscape::DrawingGroup *g = dynamic_cast<Inkscape::DrawingGroup *>(v->arenaitem); + Geom::Affine t(Geom::Translate(this->x.computed, this->y.computed)); + g->setChildTransform(t); + } +} + +void SPUse::modified(unsigned int flags) { + // std::cout << "SPUse::modified: " << (getId()?getId():"null") << std::endl; + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) { + for (SPItemView *v = this->display; v != NULL; v = v->next) { + Inkscape::DrawingGroup *g = dynamic_cast<Inkscape::DrawingGroup *>(v->arenaitem); + this->context_style = this->style; + g->setStyle(this->style, this->context_style); + } + } + + if (child) { + sp_object_ref(child); + + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + + sp_object_unref(child); + } +} + +SPItem *SPUse::unlink() { + Inkscape::XML::Node *repr = this->getRepr(); + + if (!repr) { + return NULL; + } + + Inkscape::XML::Node *parent = repr->parent(); + SPDocument *document = this->document; + Inkscape::XML::Document *xml_doc = document->getReprDoc(); + + // Track the ultimate source of a chain of uses. + SPItem *orig = this->root(); + + if (!orig) { + return NULL; + } + + // Calculate the accumulated transform, starting from the original. + Geom::Affine t = this->get_root_transform(); + + Inkscape::XML::Node *copy = NULL; + + if (dynamic_cast<SPSymbol *>(orig)) { // make a group, copy children + copy = xml_doc->createElement("svg:g"); + + for (Inkscape::XML::Node *child = orig->getRepr()->firstChild() ; child != NULL; child = child->next()) { + Inkscape::XML::Node *newchild = child->duplicate(xml_doc); + copy->appendChild(newchild); + } + } else { // just copy + copy = orig->getRepr()->duplicate(xml_doc); + } + + // Add the duplicate repr just after the existing one. + parent->addChild(copy, repr); + + // Retrieve the SPItem of the resulting repr. + SPObject *unlinked = document->getObjectByRepr(copy); + + // Merge style from the use. + unlinked->style->merge( this->style ); + unlinked->style->cascade( unlinked->parent->style ); + unlinked->updateRepr(); + + // Hold onto our SPObject and repr for now. + sp_object_ref(this, NULL); + Inkscape::GC::anchor(repr); + + // Remove ourselves, not propagating delete events to avoid a + // chain-reaction with other elements that might reference us. + this->deleteObject(false); + + // Give the copy our old id and let go of our old repr. + copy->setAttribute("id", repr->attribute("id")); + Inkscape::GC::release(repr); + + // Remove tiled clone attrs. + copy->setAttribute("inkscape:tiled-clone-of", NULL); + copy->setAttribute("inkscape:tile-w", NULL); + copy->setAttribute("inkscape:tile-h", NULL); + copy->setAttribute("inkscape:tile-cx", NULL); + copy->setAttribute("inkscape:tile-cy", NULL); + + // Establish the succession and let go of our object. + this->setSuccessor(unlinked); + sp_object_unref(this, NULL); + + SPItem *item = dynamic_cast<SPItem *>(unlinked); + g_assert(item != NULL); + + // Set the accummulated transform. + { + Geom::Affine nomove(Geom::identity()); + // Advertise ourselves as not moving. + item->doWriteTransform(t, &nomove); + } + + return item; +} + +SPItem *SPUse::get_original() { + SPItem *ref = NULL; + + if (this->ref){ + ref = this->ref->getObject(); + } + + return ref; +} + +void SPUse::snappoints(std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) const { + SPItem const *root = this->root(); + + if (!root) { + return; + } + + root->snappoints(p, snapprefs); +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-use.h b/src/object/sp-use.h new file mode 100644 index 000000000..bcf0a8513 --- /dev/null +++ b/src/object/sp-use.h @@ -0,0 +1,91 @@ +#ifndef SEEN_SP_USE_H +#define SEEN_SP_USE_H + +/* + * SVG <use> implementation + * + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * Jon A. Cruz <jon@joncruz.org> + * + * Copyright (C) 1999-2014 Authors + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <cstddef> +#include <sigc++/sigc++.h> + +#include "svg/svg-length.h" +#include "sp-dimensions.h" +#include "sp-item.h" +#include "enums.h" + +class SPUseReference; + +class SPUse : public SPItem, public SPDimensions { +public: + SPUse(); + virtual ~SPUse(); + + // item built from the original's repr (the visible clone) + // relative to the SPUse itself, it is treated as a child, similar to a grouped item relative to its group + SPItem *child; + + // SVG attrs + char *href; + + // the reference to the original object + SPUseReference *ref; + + // a sigc connection for delete notifications + sigc::connection _delete_connection; + sigc::connection _changed_connection; + + // a sigc connection for transformed signal, used to do move compensation + sigc::connection _transformed_connection; + + virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); + virtual void release(); + virtual void set(unsigned key, char const *value); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags); + virtual void update(SPCtx* ctx, unsigned int flags); + virtual void modified(unsigned int flags); + + virtual Geom::OptRect bbox(Geom::Affine const &transform, SPItem::BBoxType bboxtype) const; + virtual const char* displayName() const; + virtual char* description() const; + virtual void print(SPPrintContext *ctx); + virtual Inkscape::DrawingItem* show(Inkscape::Drawing &drawing, unsigned int key, unsigned int flags); + virtual void hide(unsigned int key); + virtual void snappoints(std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) const; + + SPItem *root(); + SPItem const *root() const; + int cloneDepth() const; + + SPItem *unlink(); + SPItem *get_original(); + Geom::Affine get_parent_transform(); + Geom::Affine get_root_transform(); + +private: + void href_changed(); + void move_compensate(Geom::Affine const *mp); + void delete_self(); +}; + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/uri-references.cpp b/src/object/uri-references.cpp new file mode 100644 index 000000000..7d6abd10a --- /dev/null +++ b/src/object/uri-references.cpp @@ -0,0 +1,268 @@ +/** + * Helper methods for resolving URI References + * + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * Marc Jeanmougin + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "uri-references.h" + +#include <iostream> +#include <cstring> + +#include <glibmm/miscutils.h> + +#include "bad-uri-exception.h" +#include "document.h" +#include "sp-object.h" +#include "uri.h" +#include "extract-uri.h" +#include "sp-tag-use.h" + +namespace Inkscape { + +URIReference::URIReference(SPObject *owner) + : _owner(owner) + , _owner_document(NULL) + , _obj(NULL) + , _uri(NULL) +{ + g_assert(_owner != NULL); + /* FIXME !!! attach to owner's destroy signal to clean up in case */ +} + +URIReference::URIReference(SPDocument *owner_document) + : _owner(NULL) + , _owner_document(owner_document) + , _obj(NULL) + , _uri(NULL) +{ + g_assert(_owner_document != NULL); +} + +URIReference::~URIReference() { detach(); } + +/* + * The main ideas here are: + * (1) "If we are inside a clone, then we can accept if and only if our "original thing" can accept the reference" + * (this caused problems when there are clones because a change in ids triggers signals for the object hrefing this id, + * but also its cloned reprs(descendants of <use> referencing an ancestor of the href'ing object)). + * + * (2) Once we have an (potential owner) object, it can accept a href to obj, iff the graph of objects where directed + * edges are + * either parent->child relations , *** or href'ing to href'ed *** relations, stays acyclic. + * We can go either from owner and up in the tree, or from obj and down, in either case this will be in the worst case + *linear in the number of objects. + * There are no easy objects allowing to do the second proposition, while "hrefList" is a "list of objects href'ing us", + *so we'll take this. + * Then we keep a set of already visited elements, and do a DFS on this graph. if we find obj, then BOOM. + */ + +bool URIReference::_acceptObject(SPObject *obj) const +{ + // we go back following hrefList and parent to find if the object already references ourselves indirectly + std::set<SPObject *> done; + SPObject *owner = getOwner(); + if (!owner) + return true; + + while (owner->cloned) { + if(!owner->clone_original)//happens when the clone is existing and linking to something, even before the original objects exists. + //for instance, it can happen when you paste a filtered object in a already cloned group: The construction of the + //clone representation of the filtered object will finish before the original object, so the cloned repr will + //have to _accept the filter even though the original does not exist yet. In that case, we'll accept iff the parent of the + //original can accept it: loops caused by other relations than parent-child would be prevented when created on their base object. + //Fixes bug 1636533. + owner = owner->parent; + else + owner = owner->clone_original; + } + // once we have the "original" object (hopefully) we look at who is referencing it + if (obj == owner) + return false; + std::list<SPObject *> todo(owner->hrefList); + todo.push_front(owner->parent); + while (!todo.empty()) { + SPObject *e = todo.front(); + todo.pop_front(); + if (!dynamic_cast<SPObject *>(e)) + continue; + if (done.insert(e).second) { + if (e == obj) { + return false; + } + todo.push_front(e->parent); + todo.insert(todo.begin(), e->hrefList.begin(), e->hrefList.end()); + } + } + return true; +} + + + +void URIReference::attach(const URI &uri) +{ + SPDocument *document = NULL; + + // Attempt to get the document that contains the URI + if (_owner) { + document = _owner->document; + } else if (_owner_document) { + document = _owner_document; + } + + // createChildDoc() assumes that the referenced file is an SVG. + // PNG and JPG files are allowed (in the case of feImage). + gchar *filename = uri.toString(); + bool skip = false; + if (g_str_has_suffix(filename, ".jpg") || g_str_has_suffix(filename, ".JPG") || + g_str_has_suffix(filename, ".png") || g_str_has_suffix(filename, ".PNG")) { + skip = true; + } + + // The path contains references to separate document files to load. + if (document && uri.getPath() && !skip) { + std::string base = document->getBase() ? document->getBase() : ""; + std::string path = uri.getFullPath(base); + if (!path.empty()) { + document = document->createChildDoc(path); + } else { + document = NULL; + } + } + if (!document) { + g_warning("Can't get document for referenced URI: %s", filename); + g_free(filename); + return; + } + g_free(filename); + + gchar const *fragment = uri.getFragment(); + if (!uri.isRelative() || uri.getQuery() || !fragment) { + throw UnsupportedURIException(); + } + + /* FIXME !!! real xpointer support should be delegated to document */ + /* for now this handles the minimal xpointer form that SVG 1.0 + * requires of us + */ + gchar *id = NULL; + if (!strncmp(fragment, "xpointer(", 9)) { + /* FIXME !!! this is wasteful */ + /* FIXME: It looks as though this is including "))" in the id. I suggest moving + the strlen calculation and validity testing to before strdup, and copying just + the id without the "))". -- pjrm */ + if (!strncmp(fragment, "xpointer(id(", 12)) { + id = g_strdup(fragment + 12); + size_t const len = strlen(id); + if (len < 3 || strcmp(id + len - 2, "))")) { + g_free(id); + throw MalformedURIException(); + } + } else { + throw UnsupportedURIException(); + } + } else { + id = g_strdup(fragment); + } + + /* FIXME !!! validate id as an NCName somewhere */ + + _connection.disconnect(); + delete _uri; + _uri = new URI(uri); + + _setObject(document->getObjectById(id)); + _connection = document->connectIdChanged(id, sigc::mem_fun(*this, &URIReference::_setObject)); + + g_free(id); +} + +void URIReference::detach() +{ + _connection.disconnect(); + delete _uri; + _uri = NULL; + _setObject(NULL); +} + +void URIReference::_setObject(SPObject *obj) +{ + if (obj && !_acceptObject(obj)) { + obj = NULL; + } + + if (obj == _obj) + return; + + SPObject *old_obj = _obj; + _obj = obj; + + _release_connection.disconnect(); + if (_obj) { + sp_object_href(_obj, _owner); + _release_connection = _obj->connectRelease(sigc::mem_fun(*this, &URIReference::_release)); + } + _changed_signal.emit(old_obj, _obj); + if (old_obj) { + /* release the old object _after_ the signal emission */ + sp_object_hunref(old_obj, _owner); + } +} + +/* If an object is deleted, current semantics require that we release + * it on its "release" signal, rather than later, when its ID is actually + * unregistered from the document. + */ +void URIReference::_release(SPObject *obj) +{ + g_assert(_obj == obj); + _setObject(NULL); +} + +} /* namespace Inkscape */ + + + +SPObject *sp_css_uri_reference_resolve(SPDocument *document, const gchar *uri) +{ + SPObject *ref = NULL; + + if (document && uri && (strncmp(uri, "url(", 4) == 0)) { + gchar *trimmed = extract_uri(uri); + if (trimmed) { + ref = sp_uri_reference_resolve(document, trimmed); + g_free(trimmed); + } + } + + return ref; +} + +SPObject *sp_uri_reference_resolve(SPDocument *document, const gchar *uri) +{ + SPObject *ref = NULL; + + if (uri && (*uri == '#')) { + ref = document->getObjectById(uri + 1); + } + + return ref; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 : diff --git a/src/object/uri-references.h b/src/object/uri-references.h new file mode 100644 index 000000000..4c57709a0 --- /dev/null +++ b/src/object/uri-references.h @@ -0,0 +1,165 @@ +#ifndef SEEN_SP_URI_REFERENCES_H +#define SEEN_SP_URI_REFERENCES_H + +/* + * Helper methods for resolving URI References + * + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * Abhishek Sharma + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <cstddef> +#include <vector> +#include <set> +#include <sigc++/connection.h> +#include <sigc++/trackable.h> + +class SPObject; +class SPDocument; + +namespace Inkscape { + +class URI; + +/** + * A class encapsulating a reference to a particular URI; observers can + * be notified when the URI comes to reference a different SPObject. + * + * The URIReference increments and decrements the SPObject's hrefcount + * automatically. + * + * @see SPObject + * @see sp_object_href + * @see sp_object_hunref + */ +class URIReference : public sigc::trackable { +public: + /** + * Constructor. + * + * @param owner The object on whose behalf this URIReference + * is holding a reference to the target object. + */ + URIReference(SPObject *owner); + URIReference(SPDocument *owner_document); + + /** + * Destructor. Calls shutdown() if the reference has not been + * shut down yet. + */ + virtual ~URIReference(); + + /** + * Attaches to a URI, relative to the specified document. + * + * Throws a BadURIException if the URI is unsupported, + * or the fragment identifier is xpointer and malformed. + * + * @param rel_document document for relative URIs + * @param uri the URI to watch + */ + void attach(URI const& uri); + + /** + * Detaches from the currently attached URI target, if any; + * the current referrent is signaled as NULL. + */ + void detach(); + + /** + * @brief Returns a pointer to the current referrent of the + * attached URI, or NULL. + * + * @return a pointer to the referenced SPObject or NULL + */ + SPObject *getObject() const { return _obj; } + + /** + * @brief Returns a pointer to the URIReference's owner + * + * @return a pointer to the URIReference's owner + */ + SPObject *getOwner() const { return _owner; } + + /** + * Accessor for the referrent change notification signal; + * this signal is emitted whenever the URIReference's + * referrent changes. + * + * Signal handlers take two parameters: the old and new + * referrents. + * + * @returns a signal + */ + sigc::signal<void, SPObject *, SPObject *> changedSignal() { + return _changed_signal; + } + + /** + * Returns a pointer to a URI containing the currently attached + * URI, or NULL if no URI is currently attached. + * + * @returns the currently attached URI, or NULL + */ + URI const* getURI() const { + return _uri; + } + + /** + * Returns true if there is currently an attached URI + * + * @returns true if there is an attached URI + */ + bool isAttached() const { + return (bool)_uri; + } + + SPDocument *getOwnerDocument() { return _owner_document; } + SPObject *getOwnerObject() { return _owner; } + +protected: + virtual bool _acceptObject(SPObject *obj) const; +private: + SPObject *_owner; + SPDocument *_owner_document; + sigc::connection _connection; + sigc::connection _release_connection; + SPObject *_obj; + URI *_uri; + + sigc::signal<void, SPObject *, SPObject *> _changed_signal; + + void _setObject(SPObject *object); + void _release(SPObject *object); + + void operator=(URIReference const& ref); + /* Private and definition-less to prevent accidental use. */ +}; + +} + +/** + * Resolves an item referenced by a URI in CSS form contained in "url(...)" + */ +SPObject* sp_css_uri_reference_resolve( SPDocument *document, const char *uri ); + +SPObject *sp_uri_reference_resolve (SPDocument *document, const char *uri); + +#endif // SEEN_SP_URI_REFERENCES_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 : diff --git a/src/object/uri.cpp b/src/object/uri.cpp new file mode 100644 index 000000000..881b322b4 --- /dev/null +++ b/src/object/uri.cpp @@ -0,0 +1,249 @@ +/* + * Authors: + * MenTaLguY <mental@rydia.net> + * Jon A. Cruz <jon@joncruz.org> + * + * Copyright (C) 2003 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "uri.h" + +#include <glibmm/ustring.h> +#include <glibmm/miscutils.h> + +#include "bad-uri-exception.h" + +namespace Inkscape { + +URI::URI() { + const gchar *in = ""; + _impl = Impl::create(xmlParseURI(in)); +} + +URI::URI(const URI &uri) { + uri._impl->reference(); + _impl = uri._impl; +} + +URI::URI(gchar const *preformed) { + xmlURIPtr uri; + if (!preformed) { + throw MalformedURIException(); + } + uri = xmlParseURI(preformed); + if (!uri) { + throw MalformedURIException(); + } + _impl = Impl::create(uri); +} + +URI::~URI() { + _impl->unreference(); +} + +URI &URI::operator=(URI const &uri) { +// No check for self-assignment needed, as _impl refcounting increments first. + uri._impl->reference(); + _impl->unreference(); + _impl = uri._impl; + return *this; +} + +URI::Impl *URI::Impl::create(xmlURIPtr uri) { + return new Impl(uri); +} + +URI::Impl::Impl(xmlURIPtr uri) +: _refcount(1), _uri(uri) {} + +URI::Impl::~Impl() { + if (_uri) { + xmlFreeURI(_uri); + _uri = NULL; + } +} + +void URI::Impl::reference() { + _refcount++; +} + +void URI::Impl::unreference() { + if (!--_refcount) { + delete this; + } +} + +bool URI::Impl::isOpaque() const { + bool opq = !isRelative() && (getOpaque() != NULL); + return opq; +} + +bool URI::Impl::isRelative() const { + return !_uri->scheme; +} + +bool URI::Impl::isNetPath() const { + bool isNet = false; + if ( isRelative() ) + { + const gchar *path = getPath(); + isNet = path && path[0] == '\\' && path[1] == '\\'; + } + return isNet; +} + +bool URI::Impl::isRelativePath() const { + bool isRel = false; + if ( isRelative() ) + { + const gchar *path = getPath(); + isRel = !path || path[0] != '\\'; + } + return isRel; +} + +bool URI::Impl::isAbsolutePath() const { + bool isAbs = false; + if ( isRelative() ) + { + const gchar *path = getPath(); + isAbs = path && path[0] == '\\'&& path[1] != '\\'; + } + return isAbs; +} + +const gchar *URI::Impl::getScheme() const { + return (gchar *)_uri->scheme; +} + +const gchar *URI::Impl::getPath() const { + return (gchar *)_uri->path; +} + +const gchar *URI::Impl::getQuery() const { + return (gchar *)_uri->query; +} + +const gchar *URI::Impl::getFragment() const { + return (gchar *)_uri->fragment; +} + +const gchar *URI::Impl::getOpaque() const { + return (gchar *)_uri->opaque; +} + +gchar *URI::to_native_filename(gchar const* uri) +{ + gchar *filename = NULL; + URI tmp(uri); + filename = tmp.toNativeFilename(); + return filename; +} +/* + * Returns the absolute path to an existing file referenced in this URI, + * if the uri is data, the path is empty or the file doesn't exist, then + * an empty string is returned. + * + * Does not check if the returned path is the local document's path (local) + * and thus redundent. Caller is expected to check against the document's path. + */ +const std::string URI::getFullPath(std::string const &base) const { + if (!_impl->getPath()) { + return ""; + } + std::string path = std::string(_impl->getPath()); + // Calculate the absolute path from an available base + if(!base.empty() && !path.empty() && path[0] != '/') { + path = Glib::build_filename(base, path); + } + // Check the existence of the file + if(! g_file_test(path.c_str(), G_FILE_TEST_EXISTS) + || g_file_test(path.c_str(), G_FILE_TEST_IS_DIR) ) { + path.clear(); + } + return path; +} + + +/* TODO !!! proper error handling */ +gchar *URI::toNativeFilename() const { + gchar *uriString = toString(); + if (isRelativePath()) { + return uriString; + } else { + gchar *filename = g_filename_from_uri(uriString, NULL, NULL); + g_free(uriString); + if (filename) { + return filename; + } else { + throw MalformedURIException(); + } + } +} + +URI URI::fromUtf8( gchar const* path ) { + if ( !path ) { + throw MalformedURIException(); + } + Glib::ustring tmp; + for ( int i = 0; path[i]; i++ ) + { + gint one = 0x0ff & path[i]; + if ( ('a' <= one && one <= 'z') + || ('A' <= one && one <= 'Z') + || ('0' <= one && one <= '9') + || one == '_' + || one == '-' + || one == '!' + || one == '.' + || one == '~' + || one == '\'' + || one == '(' + || one == ')' + || one == '*' + ) { + tmp += (gunichar)one; + } else { + gchar scratch[4]; + g_snprintf( scratch, 4, "%%%02X", one ); + tmp.append( scratch ); + } + } + return URI( tmp.data() ); +} + +/* TODO !!! proper error handling */ +URI URI::from_native_filename(gchar const *path) { + gchar *uri = g_filename_to_uri(path, NULL, NULL); + URI result(uri); + g_free( uri ); + return result; +} + +gchar *URI::Impl::toString() const { + xmlChar *string = xmlSaveUri(_uri); + if (string) { + /* hand the string off to glib memory management */ + gchar *glib_string = g_strdup((gchar *)string); + xmlFree(string); + return glib_string; + } else { + return NULL; + } +} + +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/uri.h b/src/object/uri.h new file mode 100644 index 000000000..f0b59780e --- /dev/null +++ b/src/object/uri.h @@ -0,0 +1,164 @@ +/* + * Authors: + * MenTaLguY <mental@rydia.net> + * Jon A. Cruz <jon@joncruz.org> + * + * Copyright (C) 2003 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef INKSCAPE_URI_H +#define INKSCAPE_URI_H + +#include <exception> +#include <libxml/uri.h> +#include <string> + +namespace Inkscape { + +/** + * Represents an URI as per RFC 2396. + */ +class URI { +public: + + /* Blank constructor */ + URI(); + + /** + * Copy constructor. + */ + URI(URI const &uri); + + /** + * Constructor from a C-style ASCII string. + * + * @param preformed Properly quoted C-style string to be represented. + */ + explicit URI(char const *preformed); + + /** + * Destructor. + */ + ~URI(); + + /** + * Determines if the URI represented is an 'opaque' URI. + * + * @return \c true if the URI is opaque, \c false if hierarchial. + */ + bool isOpaque() const { return _impl->isOpaque(); } + + /** + * Determines if the URI represented is 'relative' as per RFC 2396. + * + * Relative URI references are distinguished by not beginning with a + * scheme name. + * + * @return \c true if the URI is relative, \c false if it is absolute. + */ + bool isRelative() const { return _impl->isRelative(); } + + /** + * Determines if the relative URI represented is a 'net-path' as per RFC 2396. + * + * A net-path is one that starts with "\\". + * + * @return \c true if the URI is relative and a net-path, \c false otherwise. + */ + bool isNetPath() const { return _impl->isNetPath(); } + + /** + * Determines if the relative URI represented is a 'relative-path' as per RFC 2396. + * + * A relative-path is one that starts with no slashes. + * + * @return \c true if the URI is relative and a relative-path, \c false otherwise. + */ + bool isRelativePath() const { return _impl->isRelativePath(); } + + /** + * Determines if the relative URI represented is a 'absolute-path' as per RFC 2396. + * + * An absolute-path is one that starts with a single "\". + * + * @return \c true if the URI is relative and an absolute-path, \c false otherwise. + */ + bool isAbsolutePath() const { return _impl->isAbsolutePath(); } + + const char *getScheme() const { return _impl->getScheme(); } + + const char *getPath() const { return _impl->getPath(); } + + const char *getQuery() const { return _impl->getQuery(); } + + const char *getFragment() const { return _impl->getFragment(); } + + const char *getOpaque() const { return _impl->getOpaque(); } + + static URI fromUtf8( char const* path ); + + static URI from_native_filename(char const *path); + + static char *to_native_filename(char const* uri); + + const std::string getFullPath(std::string const &base) const; + + char *toNativeFilename() const; + + /** + * Returns a glib string version of this URI. + * + * The returned string must be freed with \c g_free(). + * + * @return a glib string version of this URI. + */ + char *toString() const { return _impl->toString(); } + + /** + * Assignment operator. + */ + URI &operator=(URI const &uri); + +private: + class Impl { + public: + static Impl *create(xmlURIPtr uri); + void reference(); + void unreference(); + + bool isOpaque() const; + bool isRelative() const; + bool isNetPath() const; + bool isRelativePath() const; + bool isAbsolutePath() const; + const char *getScheme() const; + const char *getPath() const; + const char *getQuery() const; + const char *getFragment() const; + const char *getOpaque() const; + char *toString() const; + private: + Impl(xmlURIPtr uri); + ~Impl(); + int _refcount; + xmlURIPtr _uri; + }; + Impl *_impl; +}; + +} /* namespace Inkscape */ + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/viewbox.cpp b/src/object/viewbox.cpp new file mode 100644 index 000000000..1b50fe71c --- /dev/null +++ b/src/object/viewbox.cpp @@ -0,0 +1,277 @@ +/* + * viewBox helper class, common code used by root, symbol, marker, pattern, image, view + * + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> (code extracted from symbol.cpp) + * Tavmjong Bah <tavmjong@free.fr> + * Johan Engelen + * + * Copyright (C) 2013-2014 Tavmjong Bah, authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + * + */ + +#include <2geom/transforms.h> + +#include "viewbox.h" +#include "enums.h" +#include "sp-item.h" + +SPViewBox::SPViewBox() + : viewBox_set(false) + , viewBox() + , aspect_set(false) + , aspect_align(SP_ASPECT_XMID_YMID) // Default per spec + , aspect_clip(SP_ASPECT_MEET) + , c2p(Geom::identity()) +{ +} + +void SPViewBox::set_viewBox(const gchar* value) { + + if (value) { + gchar *eptr = const_cast<gchar*>(value); // const-cast necessary because of const-incorrect interface definition of g_ascii_strtod + + double x = g_ascii_strtod (eptr, &eptr); + + while (*eptr && ((*eptr == ',') || (*eptr == ' '))) { + eptr++; + } + + double y = g_ascii_strtod (eptr, &eptr); + + while (*eptr && ((*eptr == ',') || (*eptr == ' '))) { + eptr++; + } + + double width = g_ascii_strtod (eptr, &eptr); + + while (*eptr && ((*eptr == ',') || (*eptr == ' '))) { + eptr++; + } + + double height = g_ascii_strtod (eptr, &eptr); + + while (*eptr && ((*eptr == ',') || (*eptr == ' '))) { + eptr++; + } + + if ((width > 0) && (height > 0)) { + /* Set viewbox */ + this->viewBox = Geom::Rect::from_xywh(x, y, width, height); + this->viewBox_set = true; + } else { + this->viewBox_set = false; + } + } else { + this->viewBox_set = false; + } + + // The C++ way? -- not necessarily using iostreams + // std::string sv( value ); + // std::replace( sv.begin(), sv.end(), ',', ' '); + // std::stringstream ss( sv ); + // double x, y, width, height; + // ss >> x >> y >> width >> height; +} + +void SPViewBox::set_preserveAspectRatio(const gchar* value) { + + /* Do setup before, so we can use break to escape */ + this->aspect_set = false; + this->aspect_align = SP_ASPECT_XMID_YMID; // Default per spec + this->aspect_clip = SP_ASPECT_MEET; + + if (value) { + const gchar *p = value; + + while (*p && (*p == 32)) { + p += 1; + } + + if (!*p) { + return; + } + + const gchar *e = p; + + while (*e && (*e != 32)) { + e += 1; + } + + int len = e - p; + + if (len > 8) { // Can't have buffer overflow as 8 < 256 + return; + } + + gchar c[256]; + memcpy (c, value, len); + + c[len] = 0; + + /* Now the actual part */ + unsigned int align = SP_ASPECT_NONE; + if (!strcmp (c, "none")) { + align = SP_ASPECT_NONE; + } else if (!strcmp (c, "xMinYMin")) { + align = SP_ASPECT_XMIN_YMIN; + } else if (!strcmp (c, "xMidYMin")) { + align = SP_ASPECT_XMID_YMIN; + } else if (!strcmp (c, "xMaxYMin")) { + align = SP_ASPECT_XMAX_YMIN; + } else if (!strcmp (c, "xMinYMid")) { + align = SP_ASPECT_XMIN_YMID; + } else if (!strcmp (c, "xMidYMid")) { + align = SP_ASPECT_XMID_YMID; + } else if (!strcmp (c, "xMaxYMid")) { + align = SP_ASPECT_XMAX_YMID; + } else if (!strcmp (c, "xMinYMax")) { + align = SP_ASPECT_XMIN_YMAX; + } else if (!strcmp (c, "xMidYMax")) { + align = SP_ASPECT_XMID_YMAX; + } else if (!strcmp (c, "xMaxYMax")) { + align = SP_ASPECT_XMAX_YMAX; + } else { + return; + } + + unsigned int clip = SP_ASPECT_MEET; + + while (*e && (*e == 32)) { + e += 1; + } + + if (*e) { + if (!strcmp (e, "meet")) { + clip = SP_ASPECT_MEET; + } else if (!strcmp (e, "slice")) { + clip = SP_ASPECT_SLICE; + } else { + return; + } + } + + this->aspect_set = true; + this->aspect_align = align; + this->aspect_clip = clip; + } +} + +// Apply scaling from viewbox +void SPViewBox::apply_viewbox(const Geom::Rect& in, double scale_none) { + + /* Determine actual viewbox in viewport coordinates */ + // scale_none is the scale that would apply if the viewbox and page size are same size + // it is passed here because it is a double-precision variable, while 'in' is originally float + double x = 0.0; + double y = 0.0; + double scale_x = in.width() / this->viewBox.width(); + double scale_y = in.height() / this->viewBox.height(); + double scale_uniform = 1.0; // used only if scaling is uniform + + if (Geom::are_near(scale_x / scale_y, 1.0, Geom::EPSILON)) { + // scaling is already uniform, reduce numerical error + scale_uniform = (scale_x + scale_y)/2.0; + if (Geom::are_near(scale_uniform / scale_none, 1.0, Geom::EPSILON)) + scale_uniform = scale_none; // objects are same size, reduce numerical error + scale_x = scale_uniform; + scale_y = scale_uniform; + } else if (this->aspect_align != SP_ASPECT_NONE) { + // scaling is not uniform, but force it to be + scale_uniform = (this->aspect_clip == SP_ASPECT_MEET) ? MIN (scale_x, scale_y) : MAX (scale_x, scale_y); + scale_x = scale_uniform; + scale_y = scale_uniform; + double width = this->viewBox.width() * scale_uniform; + double height = this->viewBox.height() * scale_uniform; + + /* Now place viewbox to requested position */ + switch (this->aspect_align) { + case SP_ASPECT_XMIN_YMIN: + break; + case SP_ASPECT_XMID_YMIN: + x = 0.5 * (in.width() - width); + break; + case SP_ASPECT_XMAX_YMIN: + x = 1.0 * (in.width() - width); + break; + case SP_ASPECT_XMIN_YMID: + y = 0.5 * (in.height() - height); + break; + case SP_ASPECT_XMID_YMID: + x = 0.5 * (in.width() - width); + y = 0.5 * (in.height() - height); + break; + case SP_ASPECT_XMAX_YMID: + x = 1.0 * (in.width() - width); + y = 0.5 * (in.height() - height); + break; + case SP_ASPECT_XMIN_YMAX: + y = 1.0 * (in.height() - height); + break; + case SP_ASPECT_XMID_YMAX: + x = 0.5 * (in.width() - width); + y = 1.0 * (in.height() - height); + break; + case SP_ASPECT_XMAX_YMAX: + x = 1.0 * (in.width() - width); + y = 1.0 * (in.height() - height); + break; + default: + break; + } + } + + /* Viewbox transform from scale and position */ + Geom::Affine q; + q[0] = scale_x; + q[1] = 0.0; + q[2] = 0.0; + q[3] = scale_y; + q[4] = x - scale_x * this->viewBox.left(); + q[5] = y - scale_y * this->viewBox.top(); + + // std::cout << " q\n" << q << std::endl; + + /* Append viewbox transformation */ + this->c2p = q * this->c2p; +} + +SPItemCtx SPViewBox::get_rctx(const SPItemCtx* ictx, double scale_none) { + + /* Create copy of item context */ + SPItemCtx rctx = *ictx; + + /* Calculate child to parent transformation */ + /* Apply parent translation (set up as viewport) */ + this->c2p = Geom::Translate(rctx.viewport.min()); + + if (this->viewBox_set) { + // Adjusts c2p for viewbox + apply_viewbox( rctx.viewport, scale_none ); + } + + rctx.i2doc = this->c2p * rctx.i2doc; + + /* If viewBox is set initialize child viewport */ + /* Otherwise it is already correct */ + if (this->viewBox_set) { + rctx.viewport = this->viewBox; + rctx.i2vp = Geom::identity(); + } + + return rctx; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-basic-offset:2 + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=2:tabstop=8:softtabstop=2:fileencoding=utf-8:textwidth=99 : diff --git a/src/object/viewbox.h b/src/object/viewbox.h new file mode 100644 index 000000000..c71abb610 --- /dev/null +++ b/src/object/viewbox.h @@ -0,0 +1,62 @@ +#ifndef __SP_VIEWBOX_H__ +#define __SP_VIEWBOX_H__ + +/* + * viewBox helper class, common code used by root, symbol, marker, pattern, image, view + * + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> (code extracted from sp-symbol.h) + * Tavmjong Bah + * Johan Engelen + * + * Copyright (C) 2013-2014 Tavmjong Bah, authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + * + */ + +#include <2geom/rect.h> +#include <glib.h> + +class SPItemCtx; + +class SPViewBox { + +public: + SPViewBox(); + + /* viewBox; */ + bool viewBox_set; + Geom::Rect viewBox; // Could use optrect + + /* preserveAspectRatio */ + bool aspect_set; + unsigned int aspect_align; // enum + unsigned int aspect_clip; // enum + + /* Child to parent additional transform */ + Geom::Affine c2p; + + void set_viewBox(const gchar* value); + void set_preserveAspectRatio(const gchar* value); + + /* Adjusts c2p for viewbox */ + void apply_viewbox(const Geom::Rect& in, double scale_none = 1.0); + + SPItemCtx get_rctx( const SPItemCtx* ictx, double scale_none = 1.0); + +}; + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-basic-offset:2 + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=2:tabstop=8:softtabstop=2:fileencoding=utf-8:textwidth=99 : -- cgit v1.2.3