diff options
| author | Alexander Valavanis <valavanisalex@gmail.com> | 2018-12-07 17:20:05 +0000 |
|---|---|---|
| committer | Alexander Valavanis <valavanisalex@gmail.com> | 2018-12-07 17:20:05 +0000 |
| commit | dbc26e64c62b757cb0c41658d37bee016367e1d4 (patch) | |
| tree | 315e3251bded0cb3027ab625a617a0ea95fb2155 /src | |
| parent | desktop-widget: Make canvas private (diff) | |
| parent | Add some SVG 2 fallbacks: (diff) | |
| download | inkscape-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/README | 5 | ||||
| -rw-r--r-- | src/extension/internal/polyfill/mesh.js | 857 | ||||
| -rw-r--r-- | src/extension/internal/polyfill/mesh_compressed.include | 3 | ||||
| -rw-r--r-- | src/extension/internal/svg.cpp | 525 | ||||
| -rw-r--r-- | src/ui/dialog/filedialogimpl-gtkmm.cpp | 16 | ||||
| -rw-r--r-- | src/ui/dialog/filedialogimpl-gtkmm.h | 10 | ||||
| -rw-r--r-- | src/ui/dialog/inkscape-preferences.cpp | 22 | ||||
| -rw-r--r-- | src/ui/dialog/inkscape-preferences.h | 13 |
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 |
