summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAlexander Valavanis <valavanisalex@gmail.com>2018-12-07 17:20:05 +0000
committerAlexander Valavanis <valavanisalex@gmail.com>2018-12-07 17:20:05 +0000
commitdbc26e64c62b757cb0c41658d37bee016367e1d4 (patch)
tree315e3251bded0cb3027ab625a617a0ea95fb2155 /src
parentdesktop-widget: Make canvas private (diff)
parentAdd some SVG 2 fallbacks: (diff)
downloadinkscape-dbc26e64c62b757cb0c41658d37bee016367e1d4.tar.gz
inkscape-dbc26e64c62b757cb0c41658d37bee016367e1d4.zip
Merge branch 'master' of gitlab.com:inkscape/inkscape
Diffstat (limited to 'src')
-rw-r--r--src/extension/internal/polyfill/README5
-rw-r--r--src/extension/internal/polyfill/mesh.js857
-rw-r--r--src/extension/internal/polyfill/mesh_compressed.include3
-rw-r--r--src/extension/internal/svg.cpp525
-rw-r--r--src/ui/dialog/filedialogimpl-gtkmm.cpp16
-rw-r--r--src/ui/dialog/filedialogimpl-gtkmm.h10
-rw-r--r--src/ui/dialog/inkscape-preferences.cpp22
-rw-r--r--src/ui/dialog/inkscape-preferences.h13
8 files changed, 1441 insertions, 10 deletions
diff --git a/src/extension/internal/polyfill/README b/src/extension/internal/polyfill/README
new file mode 100644
index 000000000..b08613df4
--- /dev/null
+++ b/src/extension/internal/polyfill/README
@@ -0,0 +1,5 @@
+
+This directory contains JavaScript "Polyfills" to support rendering of SVG 2 features that are not well supported by browsers.
+
+ mesh.js Renders mesh gradients. Currently does not support bi-cubic meshes and meshes on strokes.
+ mesh_compressed.include mesh.js minified and wrapped as a C++11 raw string literal.
diff --git a/src/extension/internal/polyfill/mesh.js b/src/extension/internal/polyfill/mesh.js
new file mode 100644
index 000000000..60acbfc1b
--- /dev/null
+++ b/src/extension/internal/polyfill/mesh.js
@@ -0,0 +1,857 @@
+
+// Use Canvas to render a mesh gradient, passing the rendering to an image via a data stream.
+// Copyright: Tavmjong Bah 2018
+// Distributed under GNU General Public License version 2 or later. See <http://fsf.org/>.
+
+(function() {
+
+ var counter = 0; // Temp, number of calls to Canvas
+
+ // Name spaces -----------------------------------
+ var svgNS = "http://www.w3.org/2000/svg";
+ var xlinkNS = "http://www.w3.org/1999/xlink"
+ var xhtmlNS = "http://www.w3.org/1999/xhtml";
+
+ // Test if mesh gradients are supported.
+ var m = document.createElementNS( svgNS, "meshgradient" );
+ if (m.x) {
+ return;
+ }
+
+ // Test above test using known good SVG element
+ // var l = document.createElementNS( svgNS, "linearGradient" );
+ // if (l.x1) {
+ // console.log( "linearGradient has x1" );
+ // return;
+ // } else {
+ // console.log( "linearGradient does not have x1" );
+ // }
+
+ // Point class -----------------------------------
+ function Point(x, y) {
+ this.x = x || 0;
+ this.y = y || 0;
+ };
+
+ Point.prototype.x = null;
+ Point.prototype.y = null;
+
+ Point.prototype.get_x = function() {
+ return this.x;
+ }
+
+ Point.prototype.get_y = function() {
+ return this.y;
+ }
+
+ Point.prototype.clone = function() {
+ return new Point(this.x, this.y);
+ }
+
+ Point.prototype.add = function(v) {
+ return new Point(this.x + v.x, this.y + v.y);
+ };
+
+ Point.prototype.scale = function(v) {
+ if( v instanceof Point ) {
+ return new Point(this.x * v.x, this.y * v.y);
+ }
+ return new Point(this.x * v, this.y * v);
+ };
+
+ // Transform by affine
+ Point.prototype.transform = function(a) {
+ var x = this.x * a.a + this.y * a.c + a.e;
+ var y = this.x * a.b + this.y * a.d + a.f;
+ return new Point(x, y);
+ };
+
+ Point.prototype.dist_sq = function(v) {
+ var x = this.x - v.x;
+ var y = this.y - v.y;
+ return (x*x + y*y);
+ };
+
+ Point.prototype.toString = function() {
+ return "(x=" + this.x + ", y=" + this.y + ")";
+ };
+
+ // Affine class -----------------------------------
+
+ // As defined in the SVG spec
+ // | a c e |
+ // | b d f |
+ // | 0 0 1 |
+ function Affine(a, b, c, d, e, f) {
+ if (a === undefined) {
+ this.a = 1;
+ this.b = 0;
+ this.c = 0;
+ this.d = 1;
+ this.e = 0;
+ this.f = 0;
+ } else {
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ this.d = d;
+ this.e = e;
+ this.f = f;
+ }
+ };
+
+ Affine.prototype.a = null;
+ Affine.prototype.b = null;
+ Affine.prototype.c = null;
+ Affine.prototype.d = null;
+ Affine.prototype.e = null;
+ Affine.prototype.f = null;
+
+ Affine.prototype.append = function(v) {
+ if ( !(v instanceof Affine) ) {
+ console.log ( "mesh.js: argument to Affine.append is not affine!");
+ }
+ var a = this.a * v.a + this.c * v.b;
+ var b = this.b * v.a + this.d * v.b;
+ var c = this.a * v.c + this.c * v.d;
+ var d = this.b * v.c + this.d * v.d;
+ var e = this.a * v.e + this.c * v.f + this.e;
+ var f = this.b * v.e + this.d * v.f + this.f;
+ return new Affine( a, b, c, d, e, f );
+ };
+
+ Affine.prototype.toString = function() {
+ return ("affine: " + this.a + " " + this.c + " " + this.e +
+ "\n " + this.b + " " + this.d + " " + this.f);
+ };
+
+ // Utility functions ---------------------------------
+
+ // Browsers return a string rather than a transform list for gradientTransform!
+ function parseTransform(t) {
+ // console.log( "parseTransform: " + t );
+ var affine = new Affine();
+ for (var i in t = t.match(/(\w+\(\s*(\-?\d+\.?\d*e?\-?\d*\s*,?\s*)+\))+/g)) {
+ var c = t[i].match(/[\w\.\-]+/g);
+ var type = c.shift();
+ switch ( type ) {
+
+ case "translate":
+ var trans;
+ if (c.length == 2) {
+ trans = new Affine( 1, 0, 0, 1, c[0], c[1] );
+ } else {
+ console.log( "mesh.js: translate does not have 2 arguments!" );
+ trans = new Affine( 1, 0, 0, 1, 0, 0 );
+ }
+ console.log( trans.toString() );
+ affine = affine.append( trans );
+ break;
+
+ case "scale":
+ var scale;
+ if (c.length == 1 ) {
+ scale = new Affine( c[0], 0, 0, c[0], 0, 0 );
+ } else if (c.length == 2) {
+ scale = new Affine( c[0], 0, 0, c[1], 0, 0 );
+ } else {
+ console.log( "mesh.js: scale does not have 1 or 2 arguments!" );
+ scale = new Affine( 1, 0, 0, 1, 0, 0 );
+ }
+ affine = affine.append( scale );
+ break;
+
+ case "rotate":
+ if (c.length == 3 ) {
+ var trans = new Affine( 1, 0, 0, 1, c[1], c[2]);
+ affine = affine.append( trans );
+ }
+ if (c[0]) {
+ var radian = c[0] * Math.PI/180.0;
+ var cos = Math.cos(radian);
+ var sin = Math.sin(radian);
+ if (Math.abs( cos ) < 1e-16) { // I hate rounding errors...
+ cos = 0;
+ }
+ if (Math.abs( sin ) < 1e-16) { // I hate rounding errors...
+ sin = 0;
+ }
+ var rotate = new Affine( cos, sin, -sin, cos, 0, 0 );
+ affine = affine.append( rotate );
+ } else {
+ console.log( "math.js: No argument to rotate transform!" );
+ }
+ if (c.length == 3 ) {
+ var trans = new Affine( 1, 0, 0, 1, -c[1], -c[2]);
+ affine = affine.append( trans );
+ }
+ break;
+
+ case "skewX":
+ if (c[0]) {
+ var radian = c[0] * Math.PI/180.0;
+ var tan = Math.tan(radian);
+ var skewx = new Affine( 1, 0, tan, 1, 0, 0 );
+ affine = affine.append( skewx );
+ } else {
+ console.log( "math.js: No argument to skewX transform!" );
+ }
+ break;
+
+ case "skewY":
+ if (c[0]) {
+ var radian = c[0] * Math.PI/180.0;
+ var tan = Math.tan(radian);
+ var skewy = new Affine( 1, tan, 0, 1, 0, 0 );
+ affine = affine.append( skewy );
+ } else {
+ console.log( "math.js: No argument to skewY transform!" );
+ }
+ break;
+
+ case "matrix":
+ if (c.length == 6) {
+ var matrix = new Affine( c[0], c[1], c[2], c[3], c[4], c[5] );
+ affine = affine.append( matrix );
+ } else {
+ console.log( "math.js: Incorrect number of arguments for matrix!" );
+ }
+ break;
+
+ default:
+ console.log( "mesh.js: Unhandled transform type: " + type);
+ break;
+ }
+ }
+ // console.log( " affine:\n" + affine.toString() );
+ return affine;
+ };
+
+
+ function colorToString(c) {
+ return ("rgb(" + Math.round(c[0]) + "," + Math.round(c[1]) + "," +Math.round(c[2]) + ")");
+ };
+
+ // Split Bezier using de Casteljau's method.
+ function split_bezier(p0, p1, p2, p3) {
+
+ // console.log( "split_bezier" );
+ var p00 = p0.clone();
+ var p13 = p3.clone();
+
+ var tmp = p1.add(p2).scale(0.5);
+ var p01 = p0.add(p1).scale(0.5);
+ var p12 = p2.add(p3).scale(0.5);
+
+ var p02 = p01.add(tmp).scale(0.5);
+ var p11 = tmp.add(p12).scale(0.5);
+
+ var p03 = p02.add(p11).scale(0.5);
+ var p10 = p03.clone();
+
+ return ([[p00, p01, p02, p03],
+ [p10, p11, p12, p13]]);
+ }
+
+ // See Cairo: cairo-mesh-pattern-rasterizer.c
+ function bezier_steps_sq(points) {
+ var tmp = [];
+ tmp[0] = points[0].dist_sq(points[1]);
+ tmp[1] = points[2].dist_sq(points[3]);
+ tmp[2] = points[0].dist_sq(points[2]) * 0.25;
+ tmp[3] = points[1].dist_sq(points[3]) * 0.25;
+ return Math.max.apply(null,tmp) * 18;
+ };
+
+
+ // Curve class --------------------------------------
+ function Curve(nodes, colors) {
+ this.nodes = nodes; // 4 Bezier points
+ this.colors = colors; // 2 x 4 colors (two ends x R+G+B+A)
+ };
+
+ // Paint a Bezier curve. w is width of Canvas window.
+ Curve.prototype.paint_curve = function(v, w) {
+
+ // console.log( "Curve.paint_curve" );
+ // If inside, see if we need to split
+ var max = bezier_steps_sq(this.nodes);
+
+ if (max > 2.0) { // Larger values leave holes, smaller take longer to render.
+ var beziers = split_bezier(this.nodes[0],this.nodes[1],this.nodes[2],this.nodes[3]);
+ var colors0 = [[],[]]; // ([start][end])
+ var colors1 = [[],[]];
+ for (var i = 0; i < 4; ++ i) {
+ colors0[0][i] = this.colors[0][i];
+ colors0[1][i] = (this.colors[0][i] + this.colors[1][i])/2;
+ colors1[0][i] = (this.colors[0][i] + this.colors[1][i])/2;
+ colors1[1][i] = this.colors[1][i];
+ }
+ var curve0 = new Curve( beziers[0], colors0 );
+ var curve1 = new Curve( beziers[1], colors1 );
+ curve0.paint_curve(v, w);
+ curve1.paint_curve(v, w);
+ } else {
+ counter++;
+
+ // Directly write data
+ var x = Math.round(this.nodes[0].x);
+ var y = Math.round(this.nodes[0].y);
+ if (x >= 0 && x < w ) {
+ var index = (y * w + x) * 4;
+ v[index ] = Math.round(this.colors[0][0]);
+ v[index + 1] = Math.round(this.colors[0][1]);
+ v[index + 2] = Math.round(this.colors[0][2]);
+ v[index + 3] = Math.round(this.colors[0][3]); // Alpha
+ }
+
+ // Draw curve, quick and dirty (via canvas context)
+ // v.beginPath();
+ // v.moveTo( this.nodes[0].x, this.nodes[0].y );
+ // v.bezierCurveTo( this.nodes[1].x, this.nodes[1].y,
+ // this.nodes[2].x, this.nodes[2].y,
+ // this.nodes[3].x, this.nodes[3].y );
+ // v.strokeStyle = colorToString( this.colors[0] );
+ // v.stroke();
+ }
+ }
+
+ // Patch class -------------------------------------
+ function Patch(nodes, colors) {
+ this.nodes = nodes; // 4x4 array of points
+ this.colors = colors; // 2x2x4 colors (four corners x R+G+B+A)
+ };
+
+ // Set path for future stroking or filling... useful for debugging.
+ Patch.prototype.setOutline = function(v) {
+
+ // Draw patch outline
+ v.beginPath();
+ v.moveTo( this.nodes[0][0].x, this.nodes[0][0].y );
+ v.bezierCurveTo( this.nodes[0][1].x, this.nodes[0][1].y,
+ this.nodes[0][2].x, this.nodes[0][2].y,
+ this.nodes[0][3].x, this.nodes[0][3].y );
+ v.bezierCurveTo( this.nodes[1][3].x, this.nodes[1][3].y,
+ this.nodes[2][3].x, this.nodes[2][3].y,
+ this.nodes[3][3].x, this.nodes[3][3].y );
+ v.bezierCurveTo( this.nodes[3][2].x, this.nodes[3][2].y,
+ this.nodes[3][1].x, this.nodes[3][1].y,
+ this.nodes[3][0].x, this.nodes[3][0].y );
+ v.bezierCurveTo( this.nodes[2][0].x, this.nodes[2][0].y,
+ this.nodes[1][0].x, this.nodes[1][0].y,
+ this.nodes[0][0].x, this.nodes[0][0].y );
+ v.closePath();
+ };
+
+ // Draw stroke patch... useful if debugging.
+ Patch.prototype.drawOutline = function(v) {
+
+ this.setOutline(v);
+ v.strokeStyle = "black";
+ v.stroke();
+ };
+
+ // Fill patch... useful if debugging.
+ Patch.prototype.fillOutline = function(v) {
+
+ this.setOutline(v);
+ v.fillStyle = colorToString( this.colors[0] );
+ v.fill();
+ };
+
+ // Split patch horizontally into two patches.
+ Patch.prototype.split = function() {
+
+ // console.log( "Patch.split" );
+
+ var nodes0 = [[],[],[],[]];
+ var nodes1 = [[],[],[],[]];
+ var colors0 = [[[],[]],[[],[]]];
+ var colors1 = [[[],[]],[[],[]]];
+
+ for (var i = 0; i < 4; ++i) {
+ var beziers = split_bezier( this.nodes[0][i], this.nodes[1][i], this.nodes[2][i], this.nodes[3][i] );
+ for (var j = 0; j < 4; ++j) {
+ nodes0[0][i] = beziers[0][0];
+ nodes0[1][i] = beziers[0][1];
+ nodes0[2][i] = beziers[0][2];
+ nodes0[3][i] = beziers[0][3];
+ nodes1[0][i] = beziers[1][0];
+ nodes1[1][i] = beziers[1][1];
+ nodes1[2][i] = beziers[1][2];
+ nodes1[3][i] = beziers[1][3];
+ }
+ }
+
+ for (var i = 0; i < 4; ++ i) {
+ colors0[0][0][i] = this.colors[0][0][i];
+ colors0[0][1][i] = this.colors[0][1][i];
+ colors0[1][0][i] = (this.colors[0][0][i] + this.colors[1][0][i])/2;
+ colors0[1][1][i] = (this.colors[0][1][i] + this.colors[1][1][i])/2;
+ colors1[0][0][i] = (this.colors[0][0][i] + this.colors[1][0][i])/2;
+ colors1[0][1][i] = (this.colors[0][1][i] + this.colors[1][1][i])/2;
+ colors1[1][0][i] = this.colors[1][0][i];
+ colors1[1][1][i] = this.colors[1][1][i];
+ }
+
+ var patch0 = new Patch( nodes0, colors0 );
+ var patch1 = new Patch( nodes1, colors1 );
+
+ return ([patch0, patch1]);
+ };
+
+ Patch.prototype.paint = function(v, w) {
+
+ // console.log( "Patch.paint" );
+ // console.log( this.nodes );
+
+ // Check if patch is inside canvas (need canvas dimensions)
+ // To be done.....
+
+ // If inside, see if we need to split
+ var tmp = [];
+ for (var i = 0; i < 4; ++i ) {
+ tmp[i] = bezier_steps_sq([this.nodes[0][i],this.nodes[1][i],
+ this.nodes[2][i],this.nodes[3][i]]);
+ }
+
+ var max = Math.max.apply(null,tmp);
+ // console.log( "Max: " + max );
+
+ if (max > 2.0) { // Larger values leave holes, smaller take longer to render.
+ // console.log( "Paint: Splitting" );
+ var patches = this.split();
+ // console.log( patches );
+ patches[0].paint(v, w);
+ patches[1].paint(v, w)
+ } else {
+ // console.log( "Paint: Filling" );
+ //this.fillOutline(v);
+ this.paint_curve(v, w);
+ }
+ };
+
+ Patch.prototype.paint_curve = function(v, w) {
+
+ // console.log( "Patch.paint_curve" );
+
+ // Paint a Bezier curve using just the top of the patch. If
+ // the patch is thin enough this should work. We leave this
+ // function here in case we want to do something more fancy.
+ var curve = new Curve(
+ [this.nodes[0][0],this.nodes[0][1],this.nodes[0][2],this.nodes[0][3]],
+ [this.colors[0][0],this.colors[0][1]]);
+ curve.paint_curve(v, w);
+ }
+
+
+ // Mesh class ---------------------------------------
+ function Mesh(id) {
+ // console.log( "Mesh: " + id );
+ this.id = id;
+ var raw = this.readMesh( id );
+ this.nodes = raw.nodes; // (m*3+1) x (n*3+1) points
+ this.colors = raw.colors; // (m+1) x (n+1) x 4 colors (R+G+B+A)
+ // console.log( this.nodes );
+ // console.log( this.colors );
+ };
+
+ // Weighted average to find Bezier points for linear sides.
+ function w_ave(p0, p1) {
+ var p = p0.scale(2.0/3.0).add(p1.scale(1.0/3.0));
+ return p;
+ }
+
+ // Function to parse an SVG mesh and return an array of nodes (points) and an array of colors.
+ Mesh.prototype.readMesh = function(id) {
+
+ var nodes = [];
+ var colors = [];
+
+ // First, find the mesh
+ var theMesh = document.getElementById(id);
+ if (theMesh == null) {
+ console.log( "mesh.js: Could not find mesh: " + id);
+ } else {
+ // console.log( "Reading mesh: " + id);
+
+ nodes[0] = []; // Top row
+ colors[0] = []; // Top row
+
+ var x = Number(theMesh.getAttribute("x"));
+ var y = Number(theMesh.getAttribute("y"));
+ // console.log( " x: " + x + " y: " + y );
+ nodes[0][0] = new Point(x, y);
+
+ var rows = theMesh.children
+ for (var i = 0; i < rows.length; ++i ) {
+ // Need to validate if meshrow...
+ nodes[ 3*i+1] = []; // Need three extra rows for each meshrow.
+ nodes[ 3*i+2] = [];
+ nodes[ 3*i+3] = [];
+ colors[i+1] = []; // Need one more row than number of meshrows.
+ // console.log( " row: " + i);
+ var patches = rows[i].children;
+ for (var j = 0; j < patches.length; ++j) {
+ // console.log( " patch: " + j);
+ var stops = patches[j].children;
+ for (var k = 0; k < stops.length; ++k) {
+ var l = k;
+ if (i != 0) {
+ ++l; // There is no top if row isn't first row.
+ }
+ // console.log( " stop: " + k);
+ var path = stops[k].getAttribute("path");
+
+ var type = "l"; // We need to still find mid-points even if no path.
+ if (path != null) {
+ var parts = path.match(/\s*([lLcC])\s*(.*)/);
+ type = parts[1];
+ }
+ var stop_nodes = parse_points( parts[2] );
+
+ switch (type) {
+ case "l":
+ if (l == 0) { // Top
+ nodes[3*i][3*j+3] = stop_nodes[0].add(nodes[3*i][3*j]);
+ nodes[3*i][3*j+1] = w_ave( nodes[3*i][3*j], nodes[3*i][3*j+3] );
+ nodes[3*i][3*j+2] = w_ave( nodes[3*i][3*j+3], nodes[3*i][3*j] );
+ } else if (l == 1) { // Right
+ nodes[3*i+3][3*j+3] = stop_nodes[0].add(nodes[3*i][3*j+3]);
+ nodes[3*i+1][3*j+3] = w_ave( nodes[3*i][3*j+3], nodes[3*i+3][3*j+3] );
+ nodes[3*i+2][3*j+3] = w_ave( nodes[3*i+3][3*j+3], nodes[3*i][3*j+3] );
+ } else if (l == 2) { // Bottom
+ if(j==0) {
+ nodes[3*i+3][3*j+0] = stop_nodes[0].add(nodes[3*i+3][3*j+3]);
+ }
+ nodes[3*i+3][3*j+1] = w_ave( nodes[3*i+3][3*j], nodes[3*i+3][3*j+3] );
+ nodes[3*i+3][3*j+2] = w_ave( nodes[3*i+3][3*j+3], nodes[3*i+3][3*j] );
+ } else { // Left
+ nodes[3*i+1][3*j] = w_ave( nodes[3*i][3*j], nodes[3*i+3][3*j] );
+ nodes[3*i+2][3*j] = w_ave( nodes[3*i+3][3*j], nodes[3*i][3*j] );
+ }
+ break;
+ case "L":
+ if (l == 0) { // Top
+ nodes[3*i][3*j+3] = stop_nodes[0];
+ nodes[3*i][3*j+1] = w_ave( nodes[3*i][3*j], nodes[3*i][3*j+3] );
+ nodes[3*i][3*j+2] = w_ave( nodes[3*i][3*j+3], nodes[3*i][3*j] );
+ } else if (l == 1) { // Right
+ nodes[3*i+3][3*j+3] = stop_nodes[0];
+ nodes[3*i+1][3*j+3] = w_ave( nodes[3*i][3*j+3], nodes[3*i+3][3*j+3] );
+ nodes[3*i+2][3*j+3] = w_ave( nodes[3*i+3][3*j+3], nodes[3*i][3*j+3] );
+ } else if (l == 2) { // Bottom
+ if(j==0) {
+ nodes[3*i+3][3*j+0] = stop_nodes[0];
+ }
+ nodes[3*i+3][3*j+1] = w_ave( nodes[3*i+3][3*j], nodes[3*i+3][3*j+3] );
+ nodes[3*i+3][3*j+2] = w_ave( nodes[3*i+3][3*j+3], nodes[3*i+3][3*j] );
+ } else { // Left
+ nodes[3*i+1][3*j] = w_ave( nodes[3*i][3*j], nodes[3*i+3][3*j] );
+ nodes[3*i+2][3*j] = w_ave( nodes[3*i+3][3*j], nodes[3*i][3*j] );
+ }
+ break;
+ case "c":
+ if (l == 0) { // Top
+ nodes[3*i][3*j+1] = stop_nodes[0].add(nodes[3*i][3*j]);
+ nodes[3*i][3*j+2] = stop_nodes[1].add(nodes[3*i][3*j]);
+ nodes[3*i][3*j+3] = stop_nodes[2].add(nodes[3*i][3*j]);
+ } else if (l == 1) { // Right
+ nodes[3*i+1][3*j+3] = stop_nodes[0].add(nodes[3*i][3*j+3]);
+ nodes[3*i+2][3*j+3] = stop_nodes[1].add(nodes[3*i][3*j+3]);
+ nodes[3*i+3][3*j+3] = stop_nodes[2].add(nodes[3*i][3*j+3]);
+ } else if (l == 2) { // Bottom
+ nodes[3*i+3][3*j+2] = stop_nodes[0].add(nodes[3*i+3][3*j+3]);
+ nodes[3*i+3][3*j+1] = stop_nodes[1].add(nodes[3*i+3][3*j+3]);
+ if(j==0) {
+ nodes[3*i+3][3*j+0] = stop_nodes[2].add(nodes[3*i+3][3*j+3]);
+ }
+ } else { // Left
+ nodes[3*i+2][3*j] = stop_nodes[0].add(nodes[3*i+3][3*j]);
+ nodes[3*i+1][3*j] = stop_nodes[1].add(nodes[3*i+3][3*j]);
+ }
+ break;
+ case "C":
+ if (l == 0) { // Top
+ nodes[3*i][3*j+1] = stop_nodes[0];
+ nodes[3*i][3*j+2] = stop_nodes[1];
+ nodes[3*i][3*j+3] = stop_nodes[2];
+ } else if (l == 1) { // Right
+ nodes[3*i+1][3*j+3] = stop_nodes[0];
+ nodes[3*i+2][3*j+3] = stop_nodes[1];
+ nodes[3*i+3][3*j+3] = stop_nodes[2];
+ } else if (l == 2) { // Bottom
+ nodes[3*i+3][3*j+2] = stop_nodes[0];
+ nodes[3*i+3][3*j+1] = stop_nodes[1];
+ if(j==0) {
+ nodes[3*i+3][3*j+0] = stop_nodes[2];
+ }
+ } else { // Left
+ nodes[3*i+2][3*j] = stop_nodes[0];
+ nodes[3*i+1][3*j] = stop_nodes[1];
+ }
+ break
+ default:
+ console.log("mesh.js: " + type + " invalid path type.");
+ }
+
+ if ( (i == 0 && j == 0) || k > 0 ) {
+ var color_raw = getComputedStyle(stops[k]).stopColor.match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i);
+ var alpha_raw = getComputedStyle(stops[k]).stopOpacity;
+ // console.log( " color_raw: " + color_raw + " alpha_raw: " + alpha_raw);
+ var alpha = 255;
+ if (alpha_raw) {
+ alpha = parseInt(alpha_raw * 255);
+ }
+ // console.log( " alpha: " + alpha );
+ if (color_raw) {
+ if (l == 0) { // upper left corner
+ colors[i][j] = [];
+ colors[i][j][0] = parseInt(color_raw[1]);
+ colors[i][j][1] = parseInt(color_raw[2]);
+ colors[i][j][2] = parseInt(color_raw[3]);
+ colors[i][j][3] = alpha; // Alpha
+ } else if (l == 1) { // upper right corner
+ colors[i][j+1] = [];
+ colors[i][j+1][0] = parseInt(color_raw[1]);
+ colors[i][j+1][1] = parseInt(color_raw[2]);
+ colors[i][j+1][2] = parseInt(color_raw[3]);
+ colors[i][j+1][3] = alpha; // Alpha
+ } else if (l == 2) { // lower right corner
+ colors[i+1][j+1] = [];
+ colors[i+1][j+1][0] = parseInt(color_raw[1]);
+ colors[i+1][j+1][1] = parseInt(color_raw[2]);
+ colors[i+1][j+1][2] = parseInt(color_raw[3]);
+ colors[i+1][j+1][3] = alpha; // Alpha
+ } else if (l == 3) { // lower left corner
+ colors[i+1][j] = [];
+ colors[i+1][j][0] = parseInt(color_raw[1]);
+ colors[i+1][j][1] = parseInt(color_raw[2]);
+ colors[i+1][j][2] = parseInt(color_raw[3]);
+ colors[i+1][j][3] = alpha; // Alpha
+ }
+ }
+ }
+ }
+
+ // SVG doesn't use tensor points but we need them for rendering.
+ nodes[3*i+1][3*j+1] = new Point;
+ nodes[3*i+1][3*j+2] = new Point;
+ nodes[3*i+2][3*j+1] = new Point;
+ nodes[3*i+2][3*j+2] = new Point;
+
+ nodes[3*i+1][3*j+1].x =
+ ( -4.0 * nodes[3*i ][3*j ].x +
+ 6.0 * ( nodes[3*i ][3*j+1].x + nodes[3*i+1][3*j ].x ) +
+ -2.0 * ( nodes[3*i ][3*j+3].x + nodes[3*i+3][3*j ].x ) +
+ 3.0 * ( nodes[3*i+3][3*j+1].x + nodes[3*i+1][3*j+3].x ) +
+ -1.0 * nodes[3*i+3][3*j+3].x ) / 9.0;
+ nodes[3*i+1][3*j+2].x =
+ ( -4.0 * nodes[3*i ][3*j+3].x +
+ 6.0 * ( nodes[3*i ][3*j+2].x + nodes[3*i+1][3*j+3].x ) +
+ -2.0 * ( nodes[3*i ][3*j ].x + nodes[3*i+3][3*j+3].x ) +
+ 3.0 * ( nodes[3*i+3][3*j+2].x + nodes[3*i+1][3*j ].x ) +
+ -1.0 * nodes[3*i+3][3*j ].x ) / 9.0;
+ nodes[3*i+2][3*j+1].x =
+ ( -4.0 * nodes[3*i+3][3*j ].x +
+ 6.0 * ( nodes[3*i+3][3*j+1].x + nodes[3*i+2][3*j ].x ) +
+ -2.0 * ( nodes[3*i+3][3*j+3].x + nodes[3*i ][3*j ].x ) +
+ 3.0 * ( nodes[3*i ][3*j+1].x + nodes[3*i+2][3*j+3].x ) +
+ -1.0 * nodes[3*i ][3*j+3].x ) / 9.0;
+ nodes[3*i+2][3*j+2].x =
+ ( -4.0 * nodes[3*i+3][3*j+3].x +
+ 6.0 * ( nodes[3*i+3][3*j+2].x + nodes[3*i+2][3*j+3].x ) +
+ -2.0 * ( nodes[3*i+3][3*j ].x + nodes[3*i ][3*j+3].x ) +
+ 3.0 * ( nodes[3*i ][3*j+2].x + nodes[3*i+2][3*j ].x ) +
+ -1.0 * nodes[3*i ][3*j ].x ) / 9.0;
+
+ nodes[3*i+1][3*j+1].y =
+ ( -4.0 * nodes[3*i ][3*j ].y +
+ 6.0 * ( nodes[3*i ][3*j+1].y + nodes[3*i+1][3*j ].y ) +
+ -2.0 * ( nodes[3*i ][3*j+3].y + nodes[3*i+3][3*j ].y ) +
+ 3.0 * ( nodes[3*i+3][3*j+1].y + nodes[3*i+1][3*j+3].y ) +
+ -1.0 * nodes[3*i+3][3*j+3].y ) / 9.0;
+ nodes[3*i+1][3*j+2].y =
+ ( -4.0 * nodes[3*i ][3*j+3].y +
+ 6.0 * ( nodes[3*i ][3*j+2].y + nodes[3*i+1][3*j+3].y ) +
+ -2.0 * ( nodes[3*i ][3*j ].y + nodes[3*i+3][3*j+3].y ) +
+ 3.0 * ( nodes[3*i+3][3*j+2].y + nodes[3*i+1][3*j ].y ) +
+ -1.0 * nodes[3*i+3][3*j ].y ) / 9.0;
+ nodes[3*i+2][3*j+1].y =
+ ( -4.0 * nodes[3*i+3][3*j ].y +
+ 6.0 * ( nodes[3*i+3][3*j+1].y + nodes[3*i+2][3*j ].y ) +
+ -2.0 * ( nodes[3*i+3][3*j+3].y + nodes[3*i ][3*j ].y ) +
+ 3.0 * ( nodes[3*i ][3*j+1].y + nodes[3*i+2][3*j+3].y ) +
+ -1.0 * nodes[3*i ][3*j+3].y ) / 9.0;
+ nodes[3*i+2][3*j+2].y =
+ ( -4.0 * nodes[3*i+3][3*j+3].y +
+ 6.0 * ( nodes[3*i+3][3*j+2].y + nodes[3*i+2][3*j+3].y ) +
+ -2.0 * ( nodes[3*i+3][3*j ].y + nodes[3*i ][3*j+3].y ) +
+ 3.0 * ( nodes[3*i ][3*j+2].y + nodes[3*i+2][3*j ].y ) +
+ -1.0 * nodes[3*i ][3*j ].y ) / 9.0;
+
+ }
+ }
+ // console.log( nodes );
+ }
+ return {
+ nodes: nodes,
+ colors: colors
+ };
+ }
+
+ // Extracts out each patch and then paints it
+ Mesh.prototype.paint = function(v, w) {
+
+ for (var i = 0; i < (this.nodes.length-1)/3; ++i) {
+ for (var j = 0; j < (this.nodes[0].length-1)/3; ++j) {
+
+ var slice_nodes = [];
+ for ( var k = i*3; k < (i*3)+4; ++k ) {
+ slice_nodes.push(this.nodes[k].slice(j*3,(j*3)+4));
+ }
+
+ var slice_colors = [];
+ slice_colors.push(this.colors[i ].slice(j,j+2));
+ slice_colors.push(this.colors[i+1].slice(j,j+2));
+
+ var patch = new Patch(slice_nodes, slice_colors);
+ patch.paint(v, w);
+ }
+ }
+ };
+
+ // Transforms mesh into coordinate space of canvas (t is either Point or Affine).
+ Mesh.prototype.transform = function(t) {
+ // console.log( "t: " + t );
+ if (t instanceof Point) {
+ for (var i = 0; i < this.nodes.length; ++i) {
+ for (var j = 0; j < this.nodes[0].length; ++j) {
+ this.nodes[i][j] = this.nodes[i][j].add(t);
+ }
+ }
+ }
+ if (t instanceof Affine) {
+ for (var i = 0; i < this.nodes.length; ++i) {
+ for (var j = 0; j < this.nodes[0].length; ++j) {
+ this.nodes[i][j] = this.nodes[i][j].transform(t);
+ }
+ }
+ }
+ };
+
+ // Scale mesh into coordinate space of canvas (t is a Point).
+ Mesh.prototype.scale = function(t) {
+ for (var i = 0; i < this.nodes.length; ++i) {
+ for (var j = 0; j < this.nodes[0].length; ++j) {
+ this.nodes[i][j] = this.nodes[i][j].scale(t);
+ }
+ }
+ };
+
+ function parse_points(s) {
+
+ var points = [];
+ var values = s.split(/[ ,]+/);
+ for (var i = 0; i < values.length-1; i += 2) {
+ points.push( new Point( parseFloat( values[i]), parseFloat( values[i+1] )));
+ }
+ return points;
+ }
+
+ // Start of document processing ---------------------
+
+ var shapes = document.querySelectorAll('rect,circle,ellipse,path,text');
+ // console.log("Shapes: " + shapes.length);
+
+ for (var i = 0; i < shapes.length; ++i) {
+ var shape = shapes[i];
+ // console.log( shape.nodeName );
+ // Get id. If no id, create one.
+ var shape_id = shape.getAttribute("id");
+ if (!shape_id) {
+ shape_id = "patchjs_shape" + i;
+ shape.setAttribute("id", shape_id);
+ }
+ // console.log( "id: " + shape_id );
+
+ var fill = shape.style.fill;
+ var url_value = fill.match(/^url\(\s*\"?\s*#([^\s\"]+)\"?\s*\)/);
+ if (url_value && url_value[1]) {
+ // console.log( "Got url! " + url_value[1]);
+ var mesh = document.getElementById(url_value[1]);
+ // console.log( mesh );
+ // console.log( mesh.nodeName );
+ if (mesh.nodeName === "meshgradient" ) {
+ // console.log( "Got mesh" );
+ var bbox = shape.getBBox();
+ // console.log( bbox );
+
+ // Create temporary canvas
+ var my_canvas = document.createElementNS( xhtmlNS, "canvas" );
+ //var my_canvas = document.createElement( "canvas" ); // Both work for HTML...
+ my_canvas.width = bbox.width;
+ my_canvas.height = bbox.height;
+
+ // console.log ( "Canvas: " + my_canvas.width + "x" + my_canvas.height );
+ var my_context = my_canvas.getContext("2d");
+
+ var my_canvas_image = my_context.getImageData( 0, 0, my_canvas.width, my_canvas.height);
+ var my_data = my_canvas_image.data;
+
+ // Draw a mesh
+ var my_mesh = new Mesh( url_value[1] );
+
+ // Adjust for bounding box if necessary.
+ if (mesh.getAttribute( "gradientUnits" ) === "objectBoundingBox") {
+ my_mesh.scale( new Point( bbox.width, bbox.height ) );
+ }
+
+ // Apply gradient transform.
+ var gradientTransform = mesh.getAttribute("gradientTransform");
+ // console.log( typeof gradientTransform );
+ if ( gradientTransform != null ) {
+ var affine = parseTransform( gradientTransform );
+ my_mesh.transform( affine );
+ }
+
+ // Position to Canvas coordinate.
+ var t = new Point( -bbox.x, -bbox.y );
+ if (mesh.getAttribute( "gradientUnits" ) === "userSpaceOnUse") {
+ my_mesh.transform(t);
+ }
+
+ // Paint
+ my_mesh.paint(my_data, my_canvas.width);
+
+ my_context.putImageData(my_canvas_image, 0, 0);
+
+ // Create image element of correct size
+ var my_image = document.createElementNS( svgNS, "image" );
+ my_image.setAttribute("width", my_canvas.width);
+ my_image.setAttribute("height",my_canvas.height);
+ my_image.setAttribute("x", bbox.x);
+ my_image.setAttribute("y", bbox.y);
+
+ // Set image to data url
+ var my_png = my_canvas.toDataURL();
+ my_image.setAttributeNS(xlinkNS, "xlink:href", my_png);
+
+ // Insert image into document
+ shape.parentNode.insertBefore( my_image, shape );
+ shape.style.fill = "none";
+
+ // Create clip referencing shape and insert into document
+ var clip = document.createElementNS( svgNS, "clipPath");
+ var clip_id = "patchjs_clip" + i;
+ clip.setAttribute("id", clip_id);
+ var use = document.createElementNS( svgNS, "use");
+ use.setAttributeNS( xlinkNS, "xlink:href", "#" + shape_id);
+ clip.appendChild(use);
+ shape.parentElement.insertBefore(clip, shape);
+ my_image.setAttribute("clip-path", "url(#" + clip_id + ")");
+ }
+ }
+ }
+})();
+
diff --git a/src/extension/internal/polyfill/mesh_compressed.include b/src/extension/internal/polyfill/mesh_compressed.include
new file mode 100644
index 000000000..f7b28f8ec
--- /dev/null
+++ b/src/extension/internal/polyfill/mesh_compressed.include
@@ -0,0 +1,3 @@
+R"=====(
+!function(){var t="http://www.w3.org/2000/svg",e="http://www.w3.org/1999/xlink";if(!document.createElementNS(t,"meshgradient").x){M.prototype.x=null,M.prototype.y=null,M.prototype.get_x=function(){return this.x},M.prototype.get_y=function(){return this.y},M.prototype.clone=function(){return new M(this.x,this.y)},M.prototype.add=function(t){return new M(this.x+t.x,this.y+t.y)},M.prototype.scale=function(t){return t instanceof M?new M(this.x*t.x,this.y*t.y):new M(this.x*t,this.y*t)},M.prototype.transform=function(t){return new M(this.x*t.a+this.y*t.c+t.e,this.x*t.b+this.y*t.d+t.f)},M.prototype.dist_sq=function(t){var e=this.x-t.x,s=this.y-t.y;return e*e+s*s},M.prototype.toString=function(){return"(x="+this.x+", y="+this.y+")"},I.prototype.a=null,I.prototype.b=null,I.prototype.c=null,I.prototype.d=null,I.prototype.e=null,I.prototype.f=null,I.prototype.append=function(t){return t instanceof I||console.log("mesh.js: argument to Affine.append is not affine!"),new I(this.a*t.a+this.c*t.b,this.b*t.a+this.d*t.b,this.a*t.c+this.c*t.d,this.b*t.c+this.d*t.d,this.a*t.e+this.c*t.f+this.e,this.b*t.e+this.d*t.f+this.f)},I.prototype.toString=function(){return"affine: "+this.a+" "+this.c+" "+this.e+"\n "+this.b+" "+this.d+" "+this.f},_.prototype.paint_curve=function(t,e){if(2<S(this.nodes)){for(var s=A(this.nodes[0],this.nodes[1],this.nodes[2],this.nodes[3]),n=[[],[]],o=[[],[]],i=0;i<4;++i)n[0][i]=this.colors[0][i],n[1][i]=(this.colors[0][i]+this.colors[1][i])/2,o[0][i]=(this.colors[0][i]+this.colors[1][i])/2,o[1][i]=this.colors[1][i];var r=new _(s[0],n),a=new _(s[1],o);r.paint_curve(t,e),a.paint_curve(t,e)}else{0;var h=Math.round(this.nodes[0].x),d=Math.round(this.nodes[0].y);if(0<=h&&h<e){var l=4*(d*e+h);t[l]=Math.round(this.colors[0][0]),t[l+1]=Math.round(this.colors[0][1]),t[l+2]=Math.round(this.colors[0][2]),t[l+3]=Math.round(this.colors[0][3])}}},N.prototype.setOutline=function(t){t.beginPath(),t.moveTo(this.nodes[0][0].x,this.nodes[0][0].y),t.bezierCurveTo(this.nodes[0][1].x,this.nodes[0][1].y,this.nodes[0][2].x,this.nodes[0][2].y,this.nodes[0][3].x,this.nodes[0][3].y),t.bezierCurveTo(this.nodes[1][3].x,this.nodes[1][3].y,this.nodes[2][3].x,this.nodes[2][3].y,this.nodes[3][3].x,this.nodes[3][3].y),t.bezierCurveTo(this.nodes[3][2].x,this.nodes[3][2].y,this.nodes[3][1].x,this.nodes[3][1].y,this.nodes[3][0].x,this.nodes[3][0].y),t.bezierCurveTo(this.nodes[2][0].x,this.nodes[2][0].y,this.nodes[1][0].x,this.nodes[1][0].y,this.nodes[0][0].x,this.nodes[0][0].y),t.closePath()},N.prototype.drawOutline=function(t){this.setOutline(t),t.strokeStyle="black",t.stroke()},N.prototype.fillOutline=function(t){var e;this.setOutline(t),t.fillStyle=(e=this.colors[0],"rgb("+Math.round(e[0])+","+Math.round(e[1])+","+Math.round(e[2])+")"),t.fill()},N.prototype.split=function(){for(var t=[[],[],[],[]],e=[[],[],[],[]],s=[[[],[]],[[],[]]],n=[[[],[]],[[],[]]],o=0;o<4;++o)for(var i=A(this.nodes[0][o],this.nodes[1][o],this.nodes[2][o],this.nodes[3][o]),r=0;r<4;++r)t[0][o]=i[0][0],t[1][o]=i[0][1],t[2][o]=i[0][2],t[3][o]=i[0][3],e[0][o]=i[1][0],e[1][o]=i[1][1],e[2][o]=i[1][2],e[3][o]=i[1][3];for(o=0;o<4;++o)s[0][0][o]=this.colors[0][0][o],s[0][1][o]=this.colors[0][1][o],s[1][0][o]=(this.colors[0][0][o]+this.colors[1][0][o])/2,s[1][1][o]=(this.colors[0][1][o]+this.colors[1][1][o])/2,n[0][0][o]=(this.colors[0][0][o]+this.colors[1][0][o])/2,n[0][1][o]=(this.colors[0][1][o]+this.colors[1][1][o])/2,n[1][0][o]=this.colors[1][0][o],n[1][1][o]=this.colors[1][1][o];return[new N(t,s),new N(e,n)]},N.prototype.paint=function(t,e){for(var s=[],n=0;n<4;++n)s[n]=S([this.nodes[0][n],this.nodes[1][n],this.nodes[2][n],this.nodes[3][n]]);if(2<Math.max.apply(null,s)){var o=this.split();o[0].paint(t,e),o[1].paint(t,e)}else this.paint_curve(t,e)},N.prototype.paint_curve=function(t,e){new _([this.nodes[0][0],this.nodes[0][1],this.nodes[0][2],this.nodes[0][3]],[this.colors[0][0],this.colors[0][1]]).paint_curve(t,e)},j.prototype.readMesh=function(t){var e=[],s=[],n=document.getElementById(t);if(null==n)console.log("mesh.js: Could not find mesh: "+t);else{e[0]=[],s[0]=[];var o=Number(n.getAttribute("x")),i=Number(n.getAttribute("y"));e[0][0]=new M(o,i);for(var r=n.children,a=0;a<r.length;++a){e[3*a+1]=[],e[3*a+2]=[],e[3*a+3]=[],s[a+1]=[];for(var h=r[a].children,d=0;d<h.length;++d){for(var l=h[d].children,c=0;c<l.length;++c){var p=c;0!=a&&++p;var u=l[c].getAttribute("path"),f="l";if(null!=u){var y=u.match(/\s*([lLcC])\s*(.*)/);f=y[1]}var x=B(y[2]);switch(f){case"l":0==p?(e[3*a][3*d+3]=x[0].add(e[3*a][3*d]),e[3*a][3*d+1]=C(e[3*a][3*d],e[3*a][3*d+3]),e[3*a][3*d+2]=C(e[3*a][3*d+3],e[3*a][3*d])):1==p?(e[3*a+3][3*d+3]=x[0].add(e[3*a][3*d+3]),e[3*a+1][3*d+3]=C(e[3*a][3*d+3],e[3*a+3][3*d+3]),e[3*a+2][3*d+3]=C(e[3*a+3][3*d+3],e[3*a][3*d+3])):2==p?(0==d&&(e[3*a+3][3*d+0]=x[0].add(e[3*a+3][3*d+3])),e[3*a+3][3*d+1]=C(e[3*a+3][3*d],e[3*a+3][3*d+3]),e[3*a+3][3*d+2]=C(e[3*a+3][3*d+3],e[3*a+3][3*d])):(e[3*a+1][3*d]=C(e[3*a][3*d],e[3*a+3][3*d]),e[3*a+2][3*d]=C(e[3*a+3][3*d],e[3*a][3*d]));break;case"L":0==p?(e[3*a][3*d+3]=x[0],e[3*a][3*d+1]=C(e[3*a][3*d],e[3*a][3*d+3]),e[3*a][3*d+2]=C(e[3*a][3*d+3],e[3*a][3*d])):1==p?(e[3*a+3][3*d+3]=x[0],e[3*a+1][3*d+3]=C(e[3*a][3*d+3],e[3*a+3][3*d+3]),e[3*a+2][3*d+3]=C(e[3*a+3][3*d+3],e[3*a][3*d+3])):2==p?(0==d&&(e[3*a+3][3*d+0]=x[0]),e[3*a+3][3*d+1]=C(e[3*a+3][3*d],e[3*a+3][3*d+3]),e[3*a+3][3*d+2]=C(e[3*a+3][3*d+3],e[3*a+3][3*d])):(e[3*a+1][3*d]=C(e[3*a][3*d],e[3*a+3][3*d]),e[3*a+2][3*d]=C(e[3*a+3][3*d],e[3*a][3*d]));break;case"c":0==p?(e[3*a][3*d+1]=x[0].add(e[3*a][3*d]),e[3*a][3*d+2]=x[1].add(e[3*a][3*d]),e[3*a][3*d+3]=x[2].add(e[3*a][3*d])):1==p?(e[3*a+1][3*d+3]=x[0].add(e[3*a][3*d+3]),e[3*a+2][3*d+3]=x[1].add(e[3*a][3*d+3]),e[3*a+3][3*d+3]=x[2].add(e[3*a][3*d+3])):2==p?(e[3*a+3][3*d+2]=x[0].add(e[3*a+3][3*d+3]),e[3*a+3][3*d+1]=x[1].add(e[3*a+3][3*d+3]),0==d&&(e[3*a+3][3*d+0]=x[2].add(e[3*a+3][3*d+3]))):(e[3*a+2][3*d]=x[0].add(e[3*a+3][3*d]),e[3*a+1][3*d]=x[1].add(e[3*a+3][3*d]));break;case"C":0==p?(e[3*a][3*d+1]=x[0],e[3*a][3*d+2]=x[1],e[3*a][3*d+3]=x[2]):1==p?(e[3*a+1][3*d+3]=x[0],e[3*a+2][3*d+3]=x[1],e[3*a+3][3*d+3]=x[2]):2==p?(e[3*a+3][3*d+2]=x[0],e[3*a+3][3*d+1]=x[1],0==d&&(e[3*a+3][3*d+0]=x[2])):(e[3*a+2][3*d]=x[0],e[3*a+1][3*d]=x[1]);break;default:console.log("mesh.js: "+f+" invalid path type.")}if(0==a&&0==d||0<c){var g=getComputedStyle(l[c]).stopColor.match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i),v=getComputedStyle(l[c]).stopOpacity,m=255;v&&(m=parseInt(255*v)),g&&(0==p?(s[a][d]=[],s[a][d][0]=parseInt(g[1]),s[a][d][1]=parseInt(g[2]),s[a][d][2]=parseInt(g[3]),s[a][d][3]=m):1==p?(s[a][d+1]=[],s[a][d+1][0]=parseInt(g[1]),s[a][d+1][1]=parseInt(g[2]),s[a][d+1][2]=parseInt(g[3]),s[a][d+1][3]=m):2==p?(s[a+1][d+1]=[],s[a+1][d+1][0]=parseInt(g[1]),s[a+1][d+1][1]=parseInt(g[2]),s[a+1][d+1][2]=parseInt(g[3]),s[a+1][d+1][3]=m):3==p&&(s[a+1][d]=[],s[a+1][d][0]=parseInt(g[1]),s[a+1][d][1]=parseInt(g[2]),s[a+1][d][2]=parseInt(g[3]),s[a+1][d][3]=m))}}e[3*a+1][3*d+1]=new M,e[3*a+1][3*d+2]=new M,e[3*a+2][3*d+1]=new M,e[3*a+2][3*d+2]=new M,e[3*a+1][3*d+1].x=(-4*e[3*a][3*d].x+6*(e[3*a][3*d+1].x+e[3*a+1][3*d].x)+-2*(e[3*a][3*d+3].x+e[3*a+3][3*d].x)+3*(e[3*a+3][3*d+1].x+e[3*a+1][3*d+3].x)+-1*e[3*a+3][3*d+3].x)/9,e[3*a+1][3*d+2].x=(-4*e[3*a][3*d+3].x+6*(e[3*a][3*d+2].x+e[3*a+1][3*d+3].x)+-2*(e[3*a][3*d].x+e[3*a+3][3*d+3].x)+3*(e[3*a+3][3*d+2].x+e[3*a+1][3*d].x)+-1*e[3*a+3][3*d].x)/9,e[3*a+2][3*d+1].x=(-4*e[3*a+3][3*d].x+6*(e[3*a+3][3*d+1].x+e[3*a+2][3*d].x)+-2*(e[3*a+3][3*d+3].x+e[3*a][3*d].x)+3*(e[3*a][3*d+1].x+e[3*a+2][3*d+3].x)+-1*e[3*a][3*d+3].x)/9,e[3*a+2][3*d+2].x=(-4*e[3*a+3][3*d+3].x+6*(e[3*a+3][3*d+2].x+e[3*a+2][3*d+3].x)+-2*(e[3*a+3][3*d].x+e[3*a][3*d+3].x)+3*(e[3*a][3*d+2].x+e[3*a+2][3*d].x)+-1*e[3*a][3*d].x)/9,e[3*a+1][3*d+1].y=(-4*e[3*a][3*d].y+6*(e[3*a][3*d+1].y+e[3*a+1][3*d].y)+-2*(e[3*a][3*d+3].y+e[3*a+3][3*d].y)+3*(e[3*a+3][3*d+1].y+e[3*a+1][3*d+3].y)+-1*e[3*a+3][3*d+3].y)/9,e[3*a+1][3*d+2].y=(-4*e[3*a][3*d+3].y+6*(e[3*a][3*d+2].y+e[3*a+1][3*d+3].y)+-2*(e[3*a][3*d].y+e[3*a+3][3*d+3].y)+3*(e[3*a+3][3*d+2].y+e[3*a+1][3*d].y)+-1*e[3*a+3][3*d].y)/9,e[3*a+2][3*d+1].y=(-4*e[3*a+3][3*d].y+6*(e[3*a+3][3*d+1].y+e[3*a+2][3*d].y)+-2*(e[3*a+3][3*d+3].y+e[3*a][3*d].y)+3*(e[3*a][3*d+1].y+e[3*a+2][3*d+3].y)+-1*e[3*a][3*d+3].y)/9,e[3*a+2][3*d+2].y=(-4*e[3*a+3][3*d+3].y+6*(e[3*a+3][3*d+2].y+e[3*a+2][3*d+3].y)+-2*(e[3*a+3][3*d].y+e[3*a][3*d+3].y)+3*(e[3*a][3*d+2].y+e[3*a+2][3*d].y)+-1*e[3*a][3*d].y)/9}}}return{nodes:e,colors:s}},j.prototype.paint=function(t,e){for(var s=0;s<(this.nodes.length-1)/3;++s)for(var n=0;n<(this.nodes[0].length-1)/3;++n){for(var o=[],i=3*s;i<3*s+4;++i)o.push(this.nodes[i].slice(3*n,3*n+4));var r=[];r.push(this.colors[s].slice(n,n+2)),r.push(this.colors[s+1].slice(n,n+2)),new N(o,r).paint(t,e)}},j.prototype.transform=function(t){if(t instanceof M)for(var e=0;e<this.nodes.length;++e)for(var s=0;s<this.nodes[0].length;++s)this.nodes[e][s]=this.nodes[e][s].add(t);if(t instanceof I)for(e=0;e<this.nodes.length;++e)for(s=0;s<this.nodes[0].length;++s)this.nodes[e][s]=this.nodes[e][s].transform(t)},j.prototype.scale=function(t){for(var e=0;e<this.nodes.length;++e)for(var s=0;s<this.nodes[0].length;++s)this.nodes[e][s]=this.nodes[e][s].scale(t)};for(var s=document.querySelectorAll("rect,circle,ellipse,path,text"),n=0;n<s.length;++n){var o=s[n],i=o.getAttribute("id");i||(i="patchjs_shape"+n,o.setAttribute("id",i));var r=o.style.fill.match(/^url\(\s*\"?\s*#([^\s\"]+)\"?\s*\)/);if(r&&r[1]){var a=document.getElementById(r[1]);if("meshgradient"===a.nodeName){var h=o.getBBox(),d=document.createElementNS("http://www.w3.org/1999/xhtml","canvas");d.width=h.width,d.height=h.height;var l=d.getContext("2d"),c=l.getImageData(0,0,d.width,d.height),p=c.data,u=new j(r[1]);"objectBoundingBox"===a.getAttribute("gradientUnits")&&u.scale(new M(h.width,h.height));var f=a.getAttribute("gradientTransform");if(null!=f){var y=k(f);u.transform(y)}var x=new M(-h.x,-h.y);"userSpaceOnUse"===a.getAttribute("gradientUnits")&&u.transform(x),u.paint(p,d.width),l.putImageData(c,0,0);var g=document.createElementNS(t,"image");g.setAttribute("width",d.width),g.setAttribute("height",d.height),g.setAttribute("x",h.x),g.setAttribute("y",h.y);var v=d.toDataURL();g.setAttributeNS(e,"xlink:href",v),o.parentNode.insertBefore(g,o),o.style.fill="none";var m=document.createElementNS(t,"clipPath"),w="patchjs_clip"+n;m.setAttribute("id",w);var b=document.createElementNS(t,"use");b.setAttributeNS(e,"xlink:href","#"+i),m.appendChild(b),o.parentElement.insertBefore(m,o),g.setAttribute("clip-path","url(#"+w+")")}}}}function M(t,e){this.x=t||0,this.y=e||0}function I(t,e,s,n,o,i){void 0===t?(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0):(this.a=t,this.b=e,this.c=s,this.d=n,this.e=o,this.f=i)}function k(t){var e=new I;for(var s in t=t.match(/(\w+\(\s*(\-?\d+\.?\d*e?\-?\d*\s*,?\s*)+\))+/g)){var n=t[s].match(/[\w\.\-]+/g),o=n.shift();switch(o){case"translate":2==n.length?r=new I(1,0,0,1,n[0],n[1]):(console.log("mesh.js: translate does not have 2 arguments!"),r=new I(1,0,0,1,0,0)),console.log(r.toString()),e=e.append(r);break;case"scale":var i;1==n.length?i=new I(n[0],0,0,n[0],0,0):2==n.length?i=new I(n[0],0,0,n[1],0,0):(console.log("mesh.js: scale does not have 1 or 2 arguments!"),i=new I(1,0,0,1,0,0)),e=e.append(i);break;case"rotate":if(3==n.length){var r=new I(1,0,0,1,n[1],n[2]);e=e.append(r)}if(n[0]){var a=n[0]*Math.PI/180,h=Math.cos(a),d=Math.sin(a);Math.abs(h)<1e-16&&(h=0),Math.abs(d)<1e-16&&(d=0);var l=new I(h,d,-d,h,0,0);e=e.append(l)}else console.log("math.js: No argument to rotate transform!");if(3==n.length){r=new I(1,0,0,1,-n[1],-n[2]);e=e.append(r)}break;case"skewX":if(n[0]){a=n[0]*Math.PI/180;var c=new I(1,0,Math.tan(a),1,0,0);e=e.append(c)}else console.log("math.js: No argument to skewX transform!");break;case"skewY":if(n[0]){a=n[0]*Math.PI/180;var p=new I(1,Math.tan(a),0,1,0,0);e=e.append(p)}else console.log("math.js: No argument to skewY transform!");break;case"matrix":if(6==n.length){var u=new I(n[0],n[1],n[2],n[3],n[4],n[5]);e=e.append(u)}else console.log("math.js: Incorrect number of arguments for matrix!");break;default:console.log("mesh.js: Unhandled transform type: "+o)}}return e}function A(t,e,s,n){var o=t.clone(),i=n.clone(),r=e.add(s).scale(.5),a=t.add(e).scale(.5),h=s.add(n).scale(.5),d=a.add(r).scale(.5),l=r.add(h).scale(.5),c=d.add(l).scale(.5),p=c.clone();return[[o,a,d,c],[p,l,h,i]]}function S(t){var e=[];return e[0]=t[0].dist_sq(t[1]),e[1]=t[2].dist_sq(t[3]),e[2]=.25*t[0].dist_sq(t[2]),e[3]=.25*t[1].dist_sq(t[3]),18*Math.max.apply(null,e)}function _(t,e){this.nodes=t,this.colors=e}function N(t,e){this.nodes=t,this.colors=e}function j(t){this.id=t;var e=this.readMesh(t);this.nodes=e.nodes,this.colors=e.colors}function C(t,e){return t.scale(2/3).add(e.scale(1/3))}function B(t){for(var e=[],s=t.split(/[ ,]+/),n=0;n<s.length-1;n+=2)e.push(new M(parseFloat(s[n]),parseFloat(s[n+1])));return e}}();
+)====="
diff --git a/src/extension/internal/svg.cpp b/src/extension/internal/svg.cpp
index e45966afa..8fe87ad4d 100644
--- a/src/extension/internal/svg.cpp
+++ b/src/extension/internal/svg.cpp
@@ -18,7 +18,6 @@
#include <gtkmm.h>
#include <giomm/file.h>
-#include <vector>
#include <giomm/file.h>
#include "document.h"
@@ -38,6 +37,8 @@
#include "object/sp-image.h"
#include "object/sp-root.h"
+#include "object/sp-text.h"
+
#include "util/units.h"
#include "selection-chemistry.h"
@@ -114,6 +115,488 @@ static void pruneProprietaryGarbage( Inkscape::XML::Node *repr )
}
/**
+ * \return None
+ *
+ * \brief Create new markers where necessary to simulate the SVG 2 marker attribute 'orient'
+ * value 'auto-start-reverse'.
+ *
+ * \param repr The current element to check.
+ * \param defs A pointer to the <defs> element.
+ * \param css The properties of the element to check.
+ * \param property Which property to check, either 'marker' or 'marker-start'.
+ *
+ */
+static void remove_marker_auto_start_reverse(Inkscape::XML::Node *repr,
+ Inkscape::XML::Node *defs,
+ SPCSSAttr *css,
+ Glib::ustring const &property)
+{
+ Glib::ustring value = sp_repr_css_property (css, property.c_str(), "");
+
+ if (!value.empty()) {
+
+ // Find reference <marker>
+ static Glib::RefPtr<Glib::Regex> regex = Glib::Regex::create("url\\(#([A-z0-9#]*)\\)");
+ Glib::MatchInfo matchInfo;
+ regex->match(value, matchInfo);
+
+ if (matchInfo.matches()) {
+
+ std::string marker_name = matchInfo.fetch(1);
+ Inkscape::XML::Node *marker = sp_repr_lookup_child (defs, "id", marker_name.c_str());
+ if (marker) {
+
+ // Does marker use "auto-start-reverse"?
+ if (strncmp(marker->attribute("orient"), "auto-start-reverse", 17)==0) {
+
+ // See if a reversed marker already exists.
+ Glib::ustring marker_name_reversed = marker_name + "_reversed";
+ Inkscape::XML::Node *marker_reversed =
+ sp_repr_lookup_child (defs, "id", marker_name_reversed.c_str());
+
+ if (!marker_reversed) {
+
+ // No reversed marker, need to create!
+ marker_reversed = repr->document()->createElement("svg:marker");
+
+ // Copy attributes
+ for (List<AttributeRecord const> iter = marker->attributeList();
+ iter ; ++iter) {
+ marker_reversed->setAttribute(g_quark_to_string(iter->key), iter->value);
+ }
+
+ // Override attributes
+ marker_reversed->setAttribute("id", marker_name_reversed.c_str());
+ marker_reversed->setAttribute("orient", "auto");
+
+ // Find transform
+ const char* refX = marker_reversed->attribute("refX");
+ const char* refY = marker_reversed->attribute("refY");
+ std::string transform = "rotate(180";
+ if (refX) {
+ transform += ",";
+ transform += refX;
+
+ if (refY) {
+ if (refX) {
+ transform += ",";
+ transform += refY;
+ } else {
+ transform += ",0,";
+ transform += refY;
+ }
+ }
+ }
+ transform += ")";
+
+ // We can't set a transform on a marker... must create group first.
+ Inkscape::XML::Node *group = repr->document()->createElement("svg:g");
+ group->setAttribute("transform", transform);
+ marker_reversed->addChild(group, nullptr);
+
+ // Copy all marker content to group.
+ for (auto child = marker->firstChild() ; child != nullptr ; child = child->next() ) {
+ auto new_child = child->duplicate(repr->document());
+ group->addChild(new_child, nullptr);
+ new_child->release();
+ }
+
+ // Add new marker to <defs>.
+ defs->addChild(marker_reversed, marker);
+ marker_reversed->release();
+ }
+
+ // Change url to reference reversed marker.
+ std::string marker_url("url(#" + marker_name_reversed + ")");
+ sp_repr_css_set_property(css, "marker-start", marker_url.c_str());
+
+ // Also fix up if property is marker shorthand.
+ if (property == "marker") {
+ std::string marker_old_url("url(#" + marker_name + ")");
+ sp_repr_css_unset_property(css, "marker");
+ sp_repr_css_set_property(css, "marker-mid", marker_old_url.c_str());
+ sp_repr_css_set_property(css, "marker-end", marker_old_url.c_str());
+ }
+
+ sp_repr_css_set(repr, css, "style");
+
+ } // Uses auto-start-reverse
+ }
+ }
+ }
+}
+
+// Called by remove_marker_context_paint() for each property value ("marker", "marker-start", ...).
+static void remove_marker_context_paint (Inkscape::XML::Node *repr,
+ Inkscape::XML::Node *defs,
+ Glib::ustring property)
+{
+ // Value of 'marker', 'marker-start', ... property.
+ std::string value("url(#");
+ value += repr->attribute("id");
+ value += ")";
+
+ // Generate a list of elements that reference this marker.
+ std::vector<Inkscape::XML::Node *> to_fix_fill_stroke =
+ sp_repr_lookup_property_many(repr->root(), property, value);
+
+ for (auto it: to_fix_fill_stroke) {
+
+ // Figure out value of fill... could be inherited.
+ SPCSSAttr* css = sp_repr_css_attr_inherited (it, "style");
+ Glib::ustring fill = sp_repr_css_property (css, "fill", "");
+ Glib::ustring stroke = sp_repr_css_property (css, "stroke", "");
+
+ // Name of new marker.
+ Glib::ustring marker_fixed_id = repr->attribute("id");
+ if (!fill.empty()) {
+ marker_fixed_id += "_F" + fill;
+ }
+ if (!stroke.empty()) {
+ marker_fixed_id += "_S" + stroke;
+ }
+
+ // See if a fixed marker already exists.
+ // Could be more robust, assumes markers are direct children of <defs>.
+ Inkscape::XML::Node* marker_fixed = sp_repr_lookup_child(defs, "id", marker_fixed_id.c_str());
+
+ if (!marker_fixed) {
+
+ // Need to create new marker.
+
+ marker_fixed = repr->duplicate(repr->document());
+ marker_fixed->setAttribute("id", marker_fixed_id);
+
+ // This needs to be turned into a function that fixes all descendents.
+ for (auto child = marker_fixed->firstChild() ; child != nullptr ; child = child->next()) {
+ // Find style.
+ SPCSSAttr* css = sp_repr_css_attr ( child, "style" );
+
+ Glib::ustring fill2 = sp_repr_css_property (css, "fill", "");
+ if (fill2 == "context-fill" ) {
+ sp_repr_css_set_property (css, "fill", fill.c_str());
+ }
+ if (fill2 == "context-stroke" ) {
+ sp_repr_css_set_property (css, "fill", stroke.c_str());
+ }
+
+ Glib::ustring stroke2 = sp_repr_css_property (css, "stroke", "");
+ if (stroke2 == "context-fill" ) {
+ sp_repr_css_set_property (css, "stroke", fill.c_str());
+ }
+ if (stroke2 == "context-stroke" ) {
+ sp_repr_css_set_property (css, "stroke", stroke.c_str());
+ }
+
+ sp_repr_css_set(child, css, "style");
+ }
+
+ defs->addChild(marker_fixed, repr);
+ marker_fixed->release();
+ }
+
+ Glib::ustring marker_value = "url(#" + marker_fixed_id + ")";
+ sp_repr_css_set_property (css, property.c_str(), marker_value.c_str());
+ sp_repr_css_set (it, css, "style");
+ }
+}
+
+static void remove_marker_context_paint (Inkscape::XML::Node *repr,
+ Inkscape::XML::Node *defs)
+{
+ if (strncmp("svg:marker", repr->name(), 10) == 0) {
+
+ if (!repr->attribute("id")) {
+
+ std::cerr << "remove_marker_context_paint: <marker> without 'id'!" << std::endl;
+
+ } else {
+
+ // First see if we need to do anything.
+ bool need_to_fix = false;
+
+ // This needs to be turned into a function that searches all descendents.
+ for (auto child = repr->firstChild() ; child != nullptr ; child = child->next()) {
+
+ // Find style.
+ SPCSSAttr* css = sp_repr_css_attr ( child, "style" );
+ Glib::ustring fill = sp_repr_css_property (css, "fill", "");
+ Glib::ustring stroke = sp_repr_css_property (css, "stroke", "");
+ if (fill == "context-fill" ||
+ fill == "context-stroke" ||
+ stroke == "context-fill" ||
+ stroke == "context-stroke" ) {
+ need_to_fix = true;
+ break;
+ }
+ }
+
+ if (need_to_fix) {
+
+ // Now we need to search document for all elements that use this marker.
+ remove_marker_context_paint (repr, defs, "marker");
+ remove_marker_context_paint (repr, defs, "marker-start");
+ remove_marker_context_paint (repr, defs, "marker-mid");
+ remove_marker_context_paint (repr, defs, "marker-end");
+ }
+ }
+ }
+}
+
+/*
+ * Recursively insert SVG 1.1 fallback for SVG 2 text (ignored by SVG 2 renderers).
+ * Notes:
+ * Text must have been layed out. Access via old document.
+ */
+static void insert_text_fallback( Inkscape::XML::Node *repr, SPDocument *doc, Inkscape::XML::Node *defs = nullptr )
+{
+ if (repr) {
+
+ if (strncmp("svg:text", repr->name(), 8) == 0) {
+
+ auto id = repr->attribute("id");
+ // std::cout << "insert_text_fallback: found text! id: " << (id?id:"null") << std::endl;
+
+ // See if we need to do anything (i.e. do we have SVG 2 text?).
+ SPCSSAttr* css = sp_repr_css_attr_inherited (repr, "style");
+ Glib::ustring inline_size = sp_repr_css_property (css, "inline-size", "");
+ Glib::ustring shape_inside = sp_repr_css_property (css, "shape-inside", "");
+
+ // No SVG 2 text, nothing to do.
+ if (inline_size.length() == 0 &&
+ shape_inside.length() == 0) {
+ return;
+ }
+
+ // We need to get SPText object to access layout.
+ SPText* text = static_cast<SPText *>(doc->getObjectById( id ));
+ if (text == nullptr) {
+ std::cerr << "insert_text_fallback: bad cast" << std::endl;
+ return;
+ }
+
+ // We will keep this text node but replace all children.
+
+ // Make a list of children to delete at end:
+ std::vector<Inkscape::XML::Node *> old_children;
+ for (auto child = repr->firstChild(); child; child = child->next()) {
+ old_children.push_back(child);
+ }
+
+ // For round-tripping, xml:space (or 'white-space:pre') must be set.
+ repr->setAttribute("xml:space", "preserve");
+
+ // Set 'x' and 'y' on <text>
+ Geom::Point anchor_point = text->layout.characterAnchorPoint(text->layout.begin());
+ sp_repr_set_svg_double(repr, "x", anchor_point[Geom::X]);
+ sp_repr_set_svg_double(repr, "y", anchor_point[Geom::Y]);
+
+ // Loop over all lines in layout.
+ for (auto it = text->layout.begin() ; it != text->layout.end() ; ) {
+
+ // Create a <tspan> with 'x' and 'y' for each line.
+ Inkscape::XML::Node *line_tspan = repr->document()->createElement("svg:tspan");
+
+ // This could be useful if one wants to edit in an old version of Inkscape but we need to check if it breaks anything:
+ // line_tspan->setAttribute("sodipodi:role", "line");
+
+ // Inside line <tspan>, create <tspan>s for each change of style or shift.
+ // For simple lines, this creates an unneeded <tspan> but so be it.
+ Inkscape::Text::Layout::iterator it_line_end = it;
+ it_line_end.nextStartOfLine();
+
+ while (it != it_line_end) {
+
+ Inkscape::XML::Node *span_tspan = repr->document()->createElement("svg:tspan");
+ Geom::Point anchor_point = text->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;
+ text->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 (!text->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 = nullptr;
+ Glib::ustring::iterator span_text_start_iter;
+ text->layout.getSourceOfCharacter(it, &rawptr, &span_text_start_iter);
+ SPObject *source_obj = reinterpret_cast<SPObject *>(rawptr);
+
+ Glib::ustring style_text = (dynamic_cast<SPString *>(source_obj) ? source_obj->parent : source_obj)->style->write( SP_STYLE_FLAG_IFDIFF, SP_STYLE_SRC_UNSET, text->style);
+ if (!style_text.empty()) {
+ span_tspan->setAttribute("style", style_text.c_str());
+ }
+
+ SPString *str = dynamic_cast<SPString *>(source_obj);
+ if (str) {
+ Glib::ustring *string = &(str->string); // TODO fixme: dangerous, unsafe premature-optimization
+ void *rawptr = nullptr;
+ Glib::ustring::iterator span_text_end_iter;
+ text->layout.getSourceOfCharacter(it_span_end, &rawptr, &span_text_end_iter);
+ SPObject *span_end_obj = reinterpret_cast<SPObject *>(rawptr);
+ if (span_end_obj != source_obj) {
+ if (it_span_end == text->layout.end()) {
+ span_text_end_iter = span_text_start_iter;
+ for (int i = text->layout.iteratorToCharIndex(it_span_end) - text->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 = repr->document()->createTextNode(new_string.c_str());
+ span_tspan->appendChild(new_text);
+ Inkscape::GC::release(new_text);
+ // std::cout << " new_string: |" << new_string << "|" << std::endl;
+ }
+ }
+ it = it_span_end;
+
+ line_tspan->appendChild(span_tspan);
+ Inkscape::GC::release(span_tspan);
+ }
+
+ repr->appendChild(line_tspan);
+ Inkscape::GC::release(line_tspan);
+ }
+
+ for (auto i: old_children) {
+ repr->removeChild (i);
+ }
+
+ return; // No need to look at children of <text>
+ }
+
+ for ( Node *child = repr->firstChild(); child; child = child->next() ) {
+ insert_text_fallback (child, doc, defs);
+ }
+ }
+}
+
+
+static void insert_mesh_polyfill( Inkscape::XML::Node *repr )
+{
+ if (repr) {
+
+ Inkscape::XML::Node *defs = sp_repr_lookup_name (repr, "svg:defs");
+
+ if (defs == nullptr) {
+ // We always put meshes in <defs>, no defs -> no mesh.
+ return;
+ }
+
+ bool has_mesh = false;
+ for ( Node *child = defs->firstChild(); child; child = child->next() ) {
+ if (strncmp("svg:meshgradient", child->name(), 16) == 0) {
+ has_mesh = true;
+ break;
+ }
+ }
+
+ Inkscape::XML::Node *script = sp_repr_lookup_child (repr, "id", "mesh_polyfill");
+
+ if (has_mesh && script == nullptr) {
+
+ script = repr->document()->createElement("svg:script");
+ script->setAttribute ("id", "mesh_polyfill");
+ script->setAttribute ("type", "text/javascript");
+ repr->root()->appendChild(script); // Must be last
+
+ // Insert JavaScript via raw string literal.
+ Glib::ustring js =
+#include "polyfill/mesh_compressed.include"
+;
+
+ Inkscape::XML::Node *script_text = repr->document()->createTextNode(js.c_str());
+ script->appendChild(script_text);
+ }
+ }
+}
+
+/*
+ * Recursively transform SVG 2 to SVG 1.1, if possible.
+ */
+static void transform_2_to_1( Inkscape::XML::Node *repr, Inkscape::XML::Node *defs = nullptr )
+{
+ if (repr) {
+
+ // std::cout << "transform_2_to_1: " << repr->name() << std::endl;
+
+ // Things we do once per node. -----------------------
+
+ // Find defs, if does not exist, create.
+ if (defs == nullptr) {
+ defs = sp_repr_lookup_name (repr, "svg:defs");
+ }
+ if (defs == nullptr) {
+ defs = repr->document()->createElement("svg:defs");
+ repr->root()->addChild(defs, nullptr);
+ }
+
+ // Find style.
+ SPCSSAttr* css = sp_repr_css_attr ( repr, "style" );
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ // Individual items ----------------------------------
+
+ // SVG 2 marker attribute orient:auto-start-reverse:
+ if ( prefs->getBool("/options/svgexport/marker_autostartreverse", false) ) {
+ // Do "marker-start" first for efficiency reasons.
+ remove_marker_auto_start_reverse(repr, defs, css, "marker-start");
+ remove_marker_auto_start_reverse(repr, defs, css, "marker");
+ }
+
+ // SVG 2 paint values 'context-fill', 'context-stroke':
+ if ( prefs->getBool("/options/svgexport/marker_contextpaint", false) ) {
+ remove_marker_context_paint(repr, defs);
+ }
+
+ // *** To Do ***
+ // Context fill & stroke outside of markers
+ // Paint-Order
+ // Meshes
+ // Hatches
+
+ for ( Node *child = repr->firstChild(); child; child = child->next() ) {
+ transform_2_to_1 (child, defs);
+ }
+ }
+}
+
+
+
+
+/**
\return None
\brief What would an SVG editor be without loading/saving SVG
files. This function sets that up.
@@ -372,19 +855,35 @@ Svg::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *filena
|| !strcmp (mod->get_id(), SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE)
|| !strcmp (mod->get_id(), SP_MODULE_KEY_OUTPUT_SVGZ_INKSCAPE));
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool const transform_2_to_1_flag =
+ prefs->getBool("/dialogs/save_as/enable_svgexport", false);
+
+ bool const insert_text_fallback_flag =
+ prefs->getBool("/options/svgexport/text_insertfallback", true);
+ bool const insert_mesh_polyfill_flag =
+ prefs->getBool("/options/svgexport/mesh_insertpolyfill", true);
+
+ bool createNewDoc =
+ !exportExtensions ||
+ transform_2_to_1_flag ||
+ insert_text_fallback_flag ||
+ insert_mesh_polyfill_flag;
+
// We prune the in-use document and deliberately loose data, because there
// is no known use for this data at the present time.
pruneProprietaryGarbage(rdoc->root());
- if (!exportExtensions) {
+ if (createNewDoc) {
+
// We make a duplicate document so we don't prune the in-use document
// and loose data. Perhaps the user intends to save as inkscape-svg next.
Inkscape::XML::Document *new_rdoc = new Inkscape::XML::SimpleDocument();
// Comments and PI nodes are not included in this duplication
// TODO: Move this code into xml/document.h and duplicate rdoc instead of root.
- new_rdoc->setAttribute("version", "1.0");
new_rdoc->setAttribute("standalone", "no");
+ new_rdoc->setAttribute("version", "2.0");
// Get a new xml repr for the svg root node
Inkscape::XML::Node *root = rdoc->root()->duplicate(new_rdoc);
@@ -393,7 +892,23 @@ Svg::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *filena
new_rdoc->appendChild(root);
Inkscape::GC::release(root);
- pruneExtendedNamespaces(root);
+ if (!exportExtensions) {
+ pruneExtendedNamespaces(root);
+ }
+
+ if (transform_2_to_1_flag) {
+ transform_2_to_1 (root);
+ new_rdoc->setAttribute("version", "1.1");
+ }
+
+ if (insert_text_fallback_flag) {
+ insert_text_fallback (root, doc);
+ }
+
+ if (insert_mesh_polyfill_flag) {
+ insert_mesh_polyfill (root);
+ }
+
rdoc = new_rdoc;
}
@@ -403,7 +918,7 @@ Svg::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *filena
throw Inkscape::Extension::Output::save_failed();
}
- if (!exportExtensions) {
+ if (createNewDoc) {
Inkscape::GC::release(rdoc);
}
diff --git a/src/ui/dialog/filedialogimpl-gtkmm.cpp b/src/ui/dialog/filedialogimpl-gtkmm.cpp
index a3ff0cf35..8652be3a8 100644
--- a/src/ui/dialog/filedialogimpl-gtkmm.cpp
+++ b/src/ui/dialog/filedialogimpl-gtkmm.cpp
@@ -540,13 +540,19 @@ void FileDialogBaseGtk::internalSetup()
// Open executable file dialogs don't need the preview panel
if (_dialogType != EXE_TYPES) {
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
- bool enablePreview = prefs->getBool(preferenceBase + "/enable_preview", true);
+ bool enablePreview = prefs->getBool(preferenceBase + "/enable_preview", true);
+ bool enableSVGExport = prefs->getBool(preferenceBase + "/enable_svgexport", false);
previewCheckbox.set_label(Glib::ustring(_("Enable preview")));
previewCheckbox.set_active(enablePreview);
previewCheckbox.signal_toggled().connect(sigc::mem_fun(*this, &FileDialogBaseGtk::_previewEnabledCB));
+ svgexportCheckbox.set_label(Glib::ustring(_("Export as SVG 1.1 per settings in Preference Dialog.")));
+ svgexportCheckbox.set_active(enableSVGExport);
+
+ svgexportCheckbox.signal_toggled().connect(sigc::mem_fun(*this, &FileDialogBaseGtk::_svgexportEnabledCB));
+
// Catch selection-changed events, so we can adjust the text widget
signal_update_preview().connect(sigc::mem_fun(*this, &FileDialogBaseGtk::_updatePreviewCallback));
@@ -581,6 +587,13 @@ void FileDialogBaseGtk::_previewEnabledCB()
}
}
+void FileDialogBaseGtk::_svgexportEnabledCB()
+{
+ bool enabled = svgexportCheckbox.get_active();
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool(preferenceBase + "/enable_svgexport", enabled);
+}
+
/**
@@ -931,6 +944,7 @@ FileSaveDialogImplGtk::FileSaveDialogImplGtk(Gtk::Window &parentWindow, const Gl
childBox.pack_end(fileTypeComboBox);
checksBox.pack_start(fileTypeCheckbox);
checksBox.pack_start(previewCheckbox);
+ checksBox.pack_start(svgexportCheckbox);
set_extra_widget(childBox);
diff --git a/src/ui/dialog/filedialogimpl-gtkmm.h b/src/ui/dialog/filedialogimpl-gtkmm.h
index 82f56d311..9a351ff36 100644
--- a/src/ui/dialog/filedialogimpl-gtkmm.h
+++ b/src/ui/dialog/filedialogimpl-gtkmm.h
@@ -189,9 +189,10 @@ protected:
SVGPreview svgPreview;
/**
- * Child widgets
- */
+ * Child widgets
+ */
Gtk::CheckButton previewCheckbox;
+ Gtk::CheckButton svgexportCheckbox;
private:
void internalSetup();
@@ -205,6 +206,11 @@ private:
* Callback for seeing if the preview needs to be drawn
*/
void _updatePreviewCallback();
+
+ /**
+ * Callback to for SVG 2 to SVG 1.1 export.
+ */
+ void _svgexportEnabledCB();
};
diff --git a/src/ui/dialog/inkscape-preferences.cpp b/src/ui/dialog/inkscape-preferences.cpp
index ec630dcb3..c301f724b 100644
--- a/src/ui/dialog/inkscape-preferences.cpp
+++ b/src/ui/dialog/inkscape-preferences.cpp
@@ -1265,6 +1265,28 @@ void InkscapePreferences::initPageIO()
this->AddPage(_page_svgoutput, _("SVG output"), iter_io, PREFS_PAGE_IO_SVGOUTPUT);
+ // SVG Export Options ==========================================
+
+ // SVG 2 Fallbacks
+ _page_svgexport.add_group_header( _("SVG 2"));
+ _svgexport_insert_text_fallback.init( _("Insert SVG 1.1 fallback in text."), "/options/svgexport/text_insertfallback", true );
+ _svgexport_insert_mesh_polyfill.init( _("Insert Mesh Gradient JavaScript polyfill."), "/options/svgexport/mesh_insertpolyfill", true );
+
+ _page_svgexport.add_line( false, "", _svgexport_insert_text_fallback, "", _("Adds fallback options for non-SVG 2 renderers."), false);
+ _page_svgexport.add_line( false, "", _svgexport_insert_mesh_polyfill, "", _("Adds JavaScript polyfill to render meshes (only fill."), false);
+
+ // SVG Export Options (SVG 2 -> SVG 1)
+ _page_svgexport.add_group_header( _("SVG 2 to SVG 1.1"));
+
+ _svgexport_remove_marker_auto_start_reverse.init( _("Replace markers with 'auto_start_reverse'."), "/options/svgexport/marker_autostartreverse", false);
+ _svgexport_remove_marker_context_paint.init( _("Replace markers using 'context_paint' or 'context_fill'."), "/options/svgexport/marker_contextpaint", false);
+
+ _page_svgexport.add_line( false, "", _svgexport_remove_marker_auto_start_reverse, "", _("SVG 2 allows markers to automatically be reversed at start of path."), false);
+ _page_svgexport.add_line( false, "", _svgexport_remove_marker_context_paint, "", _("SVG 2 allows markers to automatically match stroke color."), false);
+
+ this->AddPage(_page_svgexport, _("SVG export"), iter_io, PREFS_PAGE_IO_SVGEXPORT);
+
+
// CMS options
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
int const numIntents = 4;
diff --git a/src/ui/dialog/inkscape-preferences.h b/src/ui/dialog/inkscape-preferences.h
index ee3d8daa4..124b29139 100644
--- a/src/ui/dialog/inkscape-preferences.h
+++ b/src/ui/dialog/inkscape-preferences.h
@@ -85,6 +85,7 @@ enum {
PREFS_PAGE_IO,
PREFS_PAGE_IO_MOUSE,
PREFS_PAGE_IO_SVGOUTPUT,
+ PREFS_PAGE_IO_SVGEXPORT,
PREFS_PAGE_IO_CMS,
PREFS_PAGE_IO_AUTOSAVE,
PREFS_PAGE_IO_OPENCLIPART,
@@ -178,6 +179,7 @@ protected:
UI::Widget::DialogPage _page_io;
UI::Widget::DialogPage _page_mouse;
UI::Widget::DialogPage _page_svgoutput;
+ UI::Widget::DialogPage _page_svgexport;
UI::Widget::DialogPage _page_cms;
UI::Widget::DialogPage _page_autosave;
UI::Widget::DialogPage _page_openclipart;
@@ -474,9 +476,16 @@ protected:
UI::Widget::PrefCheckButton _svgoutput_check_editing;
UI::Widget::PrefCheckButton _svgoutput_check_writing;
+ // SVG Output export:
+ UI::Widget::PrefCheckButton _svgexport_insert_text_fallback;
+ UI::Widget::PrefCheckButton _svgexport_insert_mesh_polyfill;
+ UI::Widget::PrefCheckButton _svgexport_remove_marker_auto_start_reverse;
+ UI::Widget::PrefCheckButton _svgexport_remove_marker_context_paint;
+
+
UI::Widget::PrefEntryButtonHBox _importexport_ocal_url;
- UI::Widget::PrefEntry _importexport_ocal_username;
- UI::Widget::PrefEntry _importexport_ocal_password;
+ UI::Widget::PrefEntry _importexport_ocal_username;
+ UI::Widget::PrefEntry _importexport_ocal_password;
/*
* Keyboard shortcut members