From c853ffb5b2f75f5a889ed2e3ef89b818a736e87a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 13 Apr 2024 13:50:49 +0200 Subject: Adding upstream version 1.3+ds. Signed-off-by: Daniel Baumann --- src/extension/internal/polyfill/README.md | 19 + src/extension/internal/polyfill/hatch.js | 401 + .../internal/polyfill/hatch_compressed.include | 4 + .../internal/polyfill/hatch_tests/hatch.svg | 63 + .../polyfill/hatch_tests/hatch01_with_js.svg | 133 + .../internal/polyfill/hatch_tests/hatch_test.svg | 11730 +++++++++++++++++++ src/extension/internal/polyfill/mesh.js | 1192 ++ .../internal/polyfill/mesh_compressed.include | 4 + 8 files changed, 13546 insertions(+) create mode 100644 src/extension/internal/polyfill/README.md create mode 100644 src/extension/internal/polyfill/hatch.js create mode 100644 src/extension/internal/polyfill/hatch_compressed.include create mode 100644 src/extension/internal/polyfill/hatch_tests/hatch.svg create mode 100644 src/extension/internal/polyfill/hatch_tests/hatch01_with_js.svg create mode 100644 src/extension/internal/polyfill/hatch_tests/hatch_test.svg create mode 100644 src/extension/internal/polyfill/mesh.js create mode 100644 src/extension/internal/polyfill/mesh_compressed.include (limited to 'src/extension/internal/polyfill') diff --git a/src/extension/internal/polyfill/README.md b/src/extension/internal/polyfill/README.md new file mode 100644 index 0000000..2677a50 --- /dev/null +++ b/src/extension/internal/polyfill/README.md @@ -0,0 +1,19 @@ +# JavaScript polyfills + +This directory contains JavaScript "Polyfills" to support rendering of SVG 2 +features that are not well supported by browsers, but appeared in the 2016 +[specification](https://www.w3.org/TR/2016/CR-SVG2-20160915/pservers.html#MeshGradients) + +The included files are: + - `mesh.js` mesh gradients supporting bicubic meshes and mesh on strokes. + - `mesh_compressed.include` mesh.js minified and wrapped as a C++11 raw string literal. + - `hatch.js` hatch paint server supporting linear and absolute paths hatches + (relative paths are not fully supported) + - `hatch_tests` folder with tests used for `hatch.js` rendering + +## Details +The coding standard used is [semistandard](https://github.com/Flet/semistandard), +a more permissive (allows endrow semicolons) over the famous, open-source +[standardjs](https://standardjs.com/). + +The minifier used for the compressed version is [JavaScript minifier](https://javascript-minifier.com/). diff --git a/src/extension/internal/polyfill/hatch.js b/src/extension/internal/polyfill/hatch.js new file mode 100644 index 0000000..c805425 --- /dev/null +++ b/src/extension/internal/polyfill/hatch.js @@ -0,0 +1,401 @@ +// SPDX-License-Identifier: CC0 +/** @file + * Use patterns to render a hatch paint server via this polyfill + *//* + * Authors: + * - Valentin Ionita (2019) + * License: CC0 / Public Domain + */ + +(function () { + // Name spaces ----------------------------------- + const svgNS = 'http://www.w3.org/2000/svg'; + const xlinkNS = 'http://www.w3.org/1999/xlink'; + const unitObjectBoundingBox = 'objectBoundingBox'; + const unitUserSpace = 'userSpaceOnUse'; + + // Set multiple attributes to an element + const setAttributes = (el, attrs) => { + for (let key in attrs) { + el.setAttribute(key, attrs[key]); + } + }; + + // Copy attributes from the hatch with 'id' to the current element + const setReference = (el, id) => { + const attr = [ + 'x', 'y', 'pitch', 'rotate', + 'hatchUnits', 'hatchContentUnits', 'transform' + ]; + const template = document.getElementById(id.slice(1)); + + if (template && template.nodeName === 'hatch') { + attr.forEach(a => { + let t = template.getAttribute(a); + if (el.getAttribute(a) === null && t !== null) { + el.setAttribute(a, t); + } + }); + + if (el.children.length === 0) { + Array.from(template.children).forEach(c => { + el.appendChild(c.cloneNode(true)); + }); + } + } + }; + + // Order pain-order of hatchpaths relative to their pitch + const orderHatchPaths = (paths) => { + const nodeArray = []; + paths.forEach(p => nodeArray.push(p)); + + return nodeArray.sort((a, b) => + // (pitch - a.offset) - (pitch - b.offset) + Number(b.getAttribute('offset')) - Number(a.getAttribute('offset')) + ); + }; + + // Generate x-axis coordinates for the pattern paths + const generatePositions = (width, diagonal, initial, distance) => { + const offset = (diagonal - width) / 2; + const leftDistance = initial + offset; + const rightDistance = width + offset + distance; + const units = Math.round(leftDistance / distance) + 1; + let array = []; + + for (let i = initial - units * distance; i < rightDistance; i += distance) { + array.push(i); + } + + return array; + }; + + // Turn a path array into a tokenized version of it + const parsePath = (data) => { + let array = []; + let i = 0; + let len = data.length; + let last = 0; + + /* + * Last state (last) index map + * 0 => () + * 1 => (x y) + * 2 => (x) + * 3 => (y) + * 4 => (x1 y1 x2 y2 x y) + * 5 => (x2 y2 x y) + * 6 => (_ _ _ _ _ x y) + * 7 => (_) + */ + + while (i < len) { + switch (data[i].toUpperCase()) { + case 'Z': + array.push(data[i]); + i += 1; + last = 0; + break; + case 'M': + case 'L': + case 'T': + array.push(data[i], new Point(Number(data[i + 1]), Number(data[i + 2]))); + i += 3; + last = 1; + break; + case 'H': + array.push(data[i], new Point(Number(data[i + 1]), null)); + i += 2; + last = 2; + break; + case 'V': + array.push(data[i], new Point(null, Number(data[i + 1]))); + i += 2; + last = 3; + break; + case 'C': + array.push( + data[i], new Point(Number(data[i + 1]), Number(data[i + 2])), + new Point(Number(data[i + 3]), Number(data[i + 4])), + new Point(Number(data[i + 5]), Number(data[i + 6])) + ); + i += 7; + last = 4; + break; + case 'S': + case 'Q': + array.push( + data[i], new Point(Number(data[i + 1]), Number(data[i + 2])), + new Point(Number(data[i + 3]), Number(data[i + 4])) + ); + i += 5; + last = 5; + break; + case 'A': + array.push( + data[i], data[i + 1], data[i + 2], data[i + 3], data[i + 4], + data[i + 5], new Point(Number(data[i + 6]), Number(data[i + 7])) + ); + i += 8; + last = 6; + break; + case 'B': + array.push(data[i], data[i + 1]); + i += 2; + last = 7; + break; + default: + switch (last) { + case 1: + array.push(new Point(Number(data[i]), Number(data[i + 1]))); + i += 2; + break; + case 2: + array.push(new Point(Number(data[i]), null)); + i += 1; + break; + case 3: + array.push(new Point(null, Number(data[i]))); + i += 1; + break; + case 4: + array.push( + new Point(Number(data[i]), Number(data[i + 1])), + new Point(Number(data[i + 2]), Number(data[i + 3])), + new Point(Number(data[i + 4]), Number(data[i + 5])) + ); + i += 6; + break; + case 5: + array.push( + new Point(Number(data[i]), Number(data[i + 1])), + new Point(Number(data[i + 2]), Number(data[i + 3])) + ); + i += 4; + break; + case 6: + array.push( + data[i], data[i + 1], data[i + 2], data[i + 3], data[i + 4], + new Point(Number(data[i + 5]), Number(data[i + 6])) + ); + i += 7; + break; + default: + array.push(data[i]); + i += 1; + } + } + } + + return array; + }; + + const getYDistance = (hatchpath) => { + const path = document.createElementNS(svgNS, 'path'); + let d = hatchpath.getAttribute('d'); + + if (d[0].toUpperCase() !== 'M') { + d = `M 0,0 ${d}`; + } + + path.setAttribute('d', d); + + return path.getPointAtLength(path.getTotalLength()).y - + path.getPointAtLength(0).y; + }; + + // Point class -------------------------------------- + class Point { + constructor (x, y) { + this.x = x; + this.y = y; + } + + toString () { + return `${this.x} ${this.y}`; + } + + isPoint () { + return true; + } + + clone () { + return new Point(this.x, this.y); + } + + add (v) { + return new Point(this.x + v.x, this.y + v.y); + } + + distSquared (v) { + let x = this.x - v.x; + let y = this.y - v.y; + return (x * x + y * y); + } + } + + // Start of document processing --------------------- + const shapes = document.querySelectorAll('rect,circle,ellipse,path,text'); + + shapes.forEach((shape, i) => { + // Get id. If no id, create one. + let shapeId = shape.getAttribute('id'); + if (!shapeId) { + shapeId = 'hatch_shape_' + i; + shape.setAttribute('id', shapeId); + } + + const fill = shape.getAttribute('fill') || shape.style.fill; + const fillURL = fill.match(/^url\(\s*"?\s*#([^\s"]+)"?\s*\)/); + + if (fillURL && fillURL[1]) { + const hatch = document.getElementById(fillURL[1]); + + if (hatch && hatch.nodeName === 'hatch') { + const href = hatch.getAttributeNS(xlinkNS, 'href'); + + if (href !== null && href !== '') { + setReference(hatch, href); + } + + // Degenerate hatch, with no hatchpath children + if (hatch.children.length === 0) { + return; + } + + const bbox = shape.getBBox(); + const hatchDiag = Math.ceil(Math.sqrt( + bbox.width * bbox.width + bbox.height * bbox.height + )); + + // Hatch variables + const units = hatch.getAttribute('hatchUnits') || unitObjectBoundingBox; + const contentUnits = hatch.getAttribute('hatchContentUnits') || unitUserSpace; + const rotate = Number(hatch.getAttribute('rotate')) || 0; + const transform = hatch.getAttribute('transform') || + hatch.getAttribute('hatchTransform') || ''; + const hatchpaths = orderHatchPaths(hatch.querySelectorAll('hatchpath,hatchPath')); + const x = units === unitObjectBoundingBox + ? (Number(hatch.getAttribute('x')) * bbox.width) || 0 + : Number(hatch.getAttribute('x')) || 0; + const y = units === unitObjectBoundingBox + ? (Number(hatch.getAttribute('y')) * bbox.width) || 0 + : Number(hatch.getAttribute('y')) || 0; + let pitch = units === unitObjectBoundingBox + ? (Number(hatch.getAttribute('pitch')) * bbox.width) || 0 + : Number(hatch.getAttribute('pitch')) || 0; + + if (contentUnits === unitObjectBoundingBox && bbox.height) { + pitch /= bbox.height; + } + + // A negative value is an error. + // A value of zero disables rendering of the element + if (pitch <= 0) { + console.error('Non-positive pitch'); + return; + } + + // Pattern variables + const pattern = document.createElementNS(svgNS, 'pattern'); + const patternId = `${fillURL[1]}_pattern`; + let patternWidth = bbox.width - bbox.width % pitch; + let patternHeight = 0; + + const xPositions = generatePositions(patternWidth, hatchDiag, x, pitch); + + hatchpaths.forEach(hatchpath => { + let offset = Number(hatchpath.getAttribute('offset')) || 0; + offset = offset > pitch ? (offset % pitch) : offset; + const currentXPositions = xPositions.map(p => p + offset); + + const path = document.createElementNS(svgNS, 'path'); + let d = ''; + + for (let j = 0; j < hatchpath.attributes.length; ++j) { + const attr = hatchpath.attributes.item(j); + if (attr.name !== 'd') { + path.setAttribute(attr.name, attr.value); + } + } + + if (hatchpath.getAttribute('d') === null) { + d += currentXPositions.reduce( + (acc, xPos) => `${acc}M ${xPos} ${y} V ${hatchDiag} `, '' + ); + patternHeight = hatchDiag; + } else { + const hatchData = hatchpath.getAttribute('d'); + const data = parsePath( + hatchData.match(/([+-]?(\d+(\.\d+)?))|[MmZzLlHhVvCcSsQqTtAaBb]/g) + ); + const len = data.length; + const startsWithM = data[0] === 'M'; + const relative = data[0].toLowerCase() === data[0]; + const point = new Point(0, 0); + let yOffset = getYDistance(hatchpath); + + if (data[len - 1].y !== undefined && yOffset < data[len - 1].y) { + yOffset = data[len - 1].y; + } + + // The offset must be positive + if (yOffset <= 0) { + console.error('y offset is non-positive'); + return; + } + patternHeight = bbox.height - bbox.height % yOffset; + + const currentYPositions = generatePositions( + patternHeight, hatchDiag, y, yOffset + ); + + currentXPositions.forEach(xPos => { + point.x = xPos; + + if (!startsWithM && !relative) { + d += `M ${xPos} 0`; + } + + currentYPositions.forEach(yPos => { + point.y = yPos; + + if (relative) { + // Path is relative, set the first point in each path render + d += `M ${xPos} ${yPos} ${hatchData}`; + } else { + // Path is absolute, translate every point + d += data.map(e => e.isPoint && e.isPoint() ? e.add(point) : e) + .map(e => e.isPoint && e.isPoint() ? e.toString() : e) + .reduce((acc, e) => `${acc} ${e}`, ''); + } + }); + }); + + // The hatchpaths are infinite, so they have no fill + path.style.fill = 'none'; + } + + path.setAttribute('d', d); + pattern.appendChild(path); + }); + + setAttributes(pattern, { + 'id': patternId, + 'patternUnits': unitUserSpace, + 'patternContentUnits': contentUnits, + 'width': patternWidth, + 'height': patternHeight, + 'x': bbox.x, + 'y': bbox.y, + 'patternTransform': `rotate(${rotate} ${0} ${0}) ${transform}` + }); + hatch.parentElement.insertBefore(pattern, hatch); + + shape.style.fill = `url(#${patternId})`; + shape.setAttribute('fill', `url(#${patternId})`); + } + } + }); +})(); diff --git a/src/extension/internal/polyfill/hatch_compressed.include b/src/extension/internal/polyfill/hatch_compressed.include new file mode 100644 index 0000000..cdd893f --- /dev/null +++ b/src/extension/internal/polyfill/hatch_compressed.include @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: CC0 +R"=====( +!function(){const t="http://www.w3.org/2000/svg",e=(t,e,r,n)=>{const u=(e-t)/2,i=r+u,s=t+u+n;let h=[];for(let t=r-(Math.round(i/n)+1)*n;t{let i=n.getAttribute("id");i||(i="hatch_shape_"+u,n.setAttribute("id",i));const s=(n.getAttribute("fill")||n.style.fill).match(/^url\(\s*"?\s*#([^\s"]+)"?\s*\)/);if(s&&s[1]){const u=document.getElementById(s[1]);if(u&&"hatch"===u.nodeName){const i=u.getAttributeNS("http://www.w3.org/1999/xlink","href");if(null!==i&&""!==i&&((t,e)=>{const r=["x","y","pitch","rotate","hatchUnits","hatchContentUnits","transform"],n=document.getElementById(e.slice(1));n&&"hatch"===n.nodeName&&(r.forEach(e=>{let r=n.getAttribute(e);null===t.getAttribute(e)&&null!==r&&t.setAttribute(e,r)}),0===t.children.length&&Array.from(n.children).forEach(e=>{t.appendChild(e.cloneNode(!0))}))})(u,i),0===u.children.length)return;const h=n.getBBox(),o=Math.ceil(Math.sqrt(h.width*h.width+h.height*h.height)),a=u.getAttribute("hatchUnits")||"objectBoundingBox",c=u.getAttribute("hatchContentUnits")||"userSpaceOnUse",b=Number(u.getAttribute("rotate"))||0,l=u.getAttribute("transform")||u.getAttribute("hatchTransform")||"",m=(t=>{const e=[];return t.forEach(t=>e.push(t)),e.sort((t,e)=>Number(e.getAttribute("offset"))-Number(t.getAttribute("offset")))})(u.querySelectorAll("hatchpath,hatchPath")),d="objectBoundingBox"===a?Number(u.getAttribute("x"))*h.width||0:Number(u.getAttribute("x"))||0,g="objectBoundingBox"===a?Number(u.getAttribute("y"))*h.width||0:Number(u.getAttribute("y"))||0;let p="objectBoundingBox"===a?Number(u.getAttribute("pitch"))*h.width||0:Number(u.getAttribute("pitch"))||0;if("objectBoundingBox"===c&&h.height&&(p/=h.height),p<=0)return void console.error("Non-positive pitch");const N=document.createElementNS(t,"pattern"),f=`${s[1]}_pattern`;let w=h.width-h.width%p,A=0;const y=e(w,o,d,p);m.forEach(n=>{let u=Number(n.getAttribute("offset"))||0;u=u>p?u%p:u;const i=y.map(t=>t+u),s=document.createElementNS(t,"path");let a="";for(let t=0;t`${t}M ${e} ${g} V ${o} `,""),A=o;else{const u=n.getAttribute("d"),c=(t=>{let e=[],n=0,u=t.length,i=0;for(;n{const r=document.createElementNS(t,"path");let n=e.getAttribute("d");return"M"!==n[0].toUpperCase()&&(n=`M 0,0 ${n}`),r.setAttribute("d",n),r.getPointAtLength(r.getTotalLength()).y-r.getPointAtLength(0).y})(n);if(void 0!==c[b-1].y&&p{d.x=t,l||m||(a+=`M ${t} 0`),N.forEach(e=>{d.y=e,a+=m?`M ${t} ${e} ${u}`:c.map(t=>t.isPoint&&t.isPoint()?t.add(d):t).map(t=>t.isPoint&&t.isPoint()?t.toString():t).reduce((t,e)=>`${t} ${e}`,"")})}),s.style.fill="none"}s.setAttribute("d",a),N.appendChild(s)}),((t,e)=>{for(let r in e)t.setAttribute(r,e[r])})(N,{id:f,patternUnits:"userSpaceOnUse",patternContentUnits:c,width:w,height:A,x:h.x,y:h.y,patternTransform:`rotate(${b} 0 0) ${l}`}),u.parentElement.insertBefore(N,u),n.style.fill=`url(#${f})`,n.setAttribute("fill",`url(#${f})`)}}})}(); +)=====" diff --git a/src/extension/internal/polyfill/hatch_tests/hatch.svg b/src/extension/internal/polyfill/hatch_tests/hatch.svg new file mode 100644 index 0000000..7e2f8de --- /dev/null +++ b/src/extension/internal/polyfill/hatch_tests/hatch.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/extension/internal/polyfill/hatch_tests/hatch01_with_js.svg b/src/extension/internal/polyfill/hatch_tests/hatch01_with_js.svg new file mode 100644 index 0000000..9c45296 --- /dev/null +++ b/src/extension/internal/polyfill/hatch_tests/hatch01_with_js.svg @@ -0,0 +1,133 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/extension/internal/polyfill/hatch_tests/hatch_test.svg b/src/extension/internal/polyfill/hatch_tests/hatch_test.svg new file mode 100644 index 0000000..fd45a8d --- /dev/null +++ b/src/extension/internal/polyfill/hatch_tests/hatch_test.svg @@ -0,0 +1,11730 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + Simple hatches + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Since hatchUnits="userSpaceOnUse" is usedthe rendering will match when hatched shapeis moved to the point 0,0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <hatch id="simple1" hatchUnits="userSpaceOnUse" pitch="15"> <hatchPath stroke="#a080ff" offset="5" stroke-width="2"/></hatch> + <hatch id="simple2" hatchUnits="userSpaceOnUse" pitch="15"> <hatchPath stroke="#a080ff" offset="15"/></hatch> + <hatch id="simple3" hatchUnits="userSpaceOnUse" pitch="15"> <hatchPath stroke="#a080ff" offset="5" d="M 0,0 5,10"/></hatch> + <hatch id="simple4" hatchUnits="userSpaceOnUse" pitch="15"> <hatchPath stroke="#a080ff" offset="5" d="L 0,0 5,10"/></hatch> + <hatch id="simple5" hatchUnits="userSpaceOnUse" pitch="15"> <hatchPath stroke="#a080ff" offset="5" d="M 0,0 5,10 10,5"/></hatch> + <hatch id="simple6" hatchUnits="userSpaceOnUse" pitch="15"> <hatchPath stroke="#a080ff" offset="5" d="m 0,0 5,10 5,-5"/></hatch> + <hatch id="simple7" hatchUnits="userSpaceOnUse" pitch="15"> <hatchPath stroke="#a080ff" offset="5" d="M 0,0 5,10 M 5,20"/></hatch>  + + + + + + + + + + + + + + + + + + + + + + + + + + <hatch id="transform1" hatchUnits="userSpaceOnUse" pitch="15"> <hatchPath stroke="#a080ff" offset="5" d="L 0,0 5,10 0,20"/></hatch> + <hatch id="transform2" hatchUnits="userSpaceOnUse" pitch="15" rotate="30"> <hatchPath stroke="#a080ff" offset="10" d="L 0,0 5,10 0,20"/></hatch>  + <hatch id="transform4" hatchUnits="userSpaceOnUse" pitch="15" rotate="45"> <hatchPath stroke="#a080ff" offset="10" d="L 0,0 5,10 0,20"/></hatch> + <hatch id="transform7" hatchUnits="userSpaceOnUse" pitch="15" x="-5" y="-10"> <hatchPath stroke="#a080ff" offset="10" d="L 0,0 5,10 0,20"/></hatch> + <hatch id="transform8" hatchUnits="userSpaceOnUse" pitch="15" x="-5" y="-10" rotate="30"> <hatchPath stroke="#a080ff" offset="10" d="L 0,0 5,10 0,20"/></hatch> + <hatch id="transform9" hatchUnits="userSpaceOnUse" pitch="15" rotate="30" x="-5" y="-10" hatchTransform="matrix(0.96592583,-0.25881905,0.25881905,0.96592583,-8.4757068,43.273395)"> <hatchPath stroke="#a080ff" offset="10" d="L 0,0 5,10 0,20"/></hatch> + + + <hatch id="multiple1" hatchUnits="userSpaceOnUse" pitch="15"> <hatchPath stroke="#a080ff" offset="5"/> <hatchPath stroke="#32ff3f" offset="10"/></hatch> + <hatch id="multiple2" hatchUnits="userSpaceOnUse" pitch="15"> <hatchPath stroke="#a080ff" offset="5"/> <hatchPath stroke="#a080ff" offset="10" d="L 0,0 5,10"/></hatch> + <hatch id="multiple3" hatchUnits="userSpaceOnUse" pitch="15"> <hatchPath stroke="#a080ff" offset="5" d="L 0,0 5,17" /> <hatchPath stroke="#a080ff" offset="10" d="L 0,0 5,10"/></hatch> + + + + <hatch id="ref1" hatchUnits="userSpaceOnUse" pitch="15"> <hatchPath stroke="#a080ff" offset="5"/></hatch> + <hatch id="ref2" xlink:href="#ref1"></hatch> + <hatch id="ref3" xlink:href="#ref1" pitch="45"></hatch> + + <hatch id="stroke1" hatchUnits="userSpaceOnUse" pitch="15"> <hatchPath stroke="#a080ff" offset="5" stroke-width="5" stroke-dasharray="10 4 2 4"/></hatch>  + + + + + + + + + <hatch id="overflow1" hatchUnits="userSpaceOnUse" pitch="15"> <hatchPath stroke="#a080ff" offset="5" d="L 0,0 5,5 -5,15, 0,20"/></hatch> + <hatch id="overflow2" style="overflow:hidden" hatchUnits="userSpaceOnUse" pitch="15"> <hatchPath stroke="#a080ff" offset="0" d="L 0,0 5,5 -5,15, 0,20"/></hatch> + <hatch id="overflow3" style="overflow:visible" hatchUnits="userSpaceOnUse" pitch="15"> <hatchPath stroke="#a080ff" offset="0" d="L 0,0 5,5 -5,15, 0,20"/></hatch>  + <hatch id="overflow4" style="overflow:visible" hatchUnits="userSpaceOnUse" pitch="15"> <hatchPath stroke="#32ff3f" offset="5" > <hatchPath stroke="#ff0000" offset="20" ></hatch> + <hatch id="degenerate1" pitch="45"></hatch> + <hatch id="degenerate2" xlink:href="#nonexisting" pitch="45"></hatch> + <hatch id="degenerate3" pitch="30"> <hatchPath stroke="#a080ff" offset="10" d="L 0,0 5,10 0,15"/></hatch> + <hatch id="degenerate4" pitch="30"> <hatchPath stroke="#a080ff" offset="10" d="L 0,0 5,10 -5,15"/></hatch> + Hatch transforms + Multiple hatch paths + Hatch linking + Stroke style + Overflow property + Degenerate cases + + + + diff --git a/src/extension/internal/polyfill/mesh.js b/src/extension/internal/polyfill/mesh.js new file mode 100644 index 0000000..bcce389 --- /dev/null +++ b/src/extension/internal/polyfill/mesh.js @@ -0,0 +1,1192 @@ +// SPDX-License-Identifier: CC0 +/** @file + * Use Canvas to render a mesh gradient, passing the rendering to an image via a data stream. + *//* + * Authors: + * - Tavmjong Bah 2018 + * - Valentin Ionita (2019) + * License: CC0 / Public Domain + */ + +(function () { + // Name spaces ----------------------------------- + const svgNS = 'http://www.w3.org/2000/svg'; + const xlinkNS = 'http://www.w3.org/1999/xlink'; + const xhtmlNS = 'http://www.w3.org/1999/xhtml'; + /* + * Maximum threshold for Bezier step size + * Larger values leave holes, smaller take longer to render. + */ + const maxBezierStep = 2.0; + + // Test if mesh gradients are supported. + if (document.createElementNS(svgNS, 'meshgradient').x) { + return; + } + + /* + * Utility functions ----------------------------- + */ + // Split Bezier using de Casteljau's method. + const splitBezier = (p0, p1, p2, p3) => { + let tmp = new Point((p1.x + p2.x) * 0.5, (p1.y + p2.y) * 0.5); + let p01 = new Point((p0.x + p1.x) * 0.5, (p0.y + p1.y) * 0.5); + let p12 = new Point((p2.x + p3.x) * 0.5, (p2.y + p3.y) * 0.5); + let p02 = new Point((tmp.x + p01.x) * 0.5, (tmp.y + p01.y) * 0.5); + let p11 = new Point((tmp.x + p12.x) * 0.5, (tmp.y + p12.y) * 0.5); + let p03 = new Point((p02.x + p11.x) * 0.5, (p02.y + p11.y) * 0.5); + + return ([ + [p0, p01, p02, p03], + [p03, p11, p12, p3] + ]); + }; + + // See Cairo: cairo-mesh-pattern-rasterizer.c + const bezierStepsSquared = (points) => { + let tmp0 = points[0].distSquared(points[1]); + let tmp1 = points[2].distSquared(points[3]); + let tmp2 = points[0].distSquared(points[2]) * 0.25; + let tmp3 = points[1].distSquared(points[3]) * 0.25; + + let max1 = tmp0 > tmp1 ? tmp0 : tmp1; + + let max2 = tmp2 > tmp3 ? tmp2 : tmp3; + + let max = max1 > max2 ? max1 : max2; + + return max * 18; + }; + + // Euclidean distance + const distance = (p0, p1) => Math.sqrt(p0.distSquared(p1)); + + // Weighted average to find Bezier points for linear sides. + const wAvg = (p0, p1) => p0.scale(2.0 / 3.0).add(p1.scale(1.0 / 3.0)); + + // Browsers return a string rather than a transform list for gradientTransform! + const parseTransform = (t) => { + let affine = new Affine(); + let trans, scale, radian, tan, skewx, skewy, rotate; + let transforms = t.match(/(\w+\(\s*[^)]+\))+/g); + + transforms.forEach((i) => { + let c = i.match(/[\w.-]+/g); + let type = c.shift(); + + switch (type) { + case 'translate': + if (c.length === 2) { + trans = new Affine(1, 0, 0, 1, c[0], c[1]); + } else { + console.error('mesh.js: translate does not have 2 arguments!'); + trans = new Affine(1, 0, 0, 1, 0, 0); + } + affine = affine.append(trans); + break; + + case '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.error('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) { + trans = new Affine(1, 0, 0, 1, c[1], c[2]); + affine = affine.append(trans); + } + if (c[0]) { + radian = c[0] * Math.PI / 180.0; + let cos = Math.cos(radian); + let 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; + } + rotate = new Affine(cos, sin, -sin, cos, 0, 0); + affine = affine.append(rotate); + } else { + console.error('math.js: No argument to rotate transform!'); + } + if (c.length === 3) { + trans = new Affine(1, 0, 0, 1, -c[1], -c[2]); + affine = affine.append(trans); + } + break; + + case 'skewX': + if (c[0]) { + radian = c[0] * Math.PI / 180.0; + tan = Math.tan(radian); + skewx = new Affine(1, 0, tan, 1, 0, 0); + affine = affine.append(skewx); + } else { + console.error('math.js: No argument to skewX transform!'); + } + break; + + case 'skewY': + if (c[0]) { + radian = c[0] * Math.PI / 180.0; + tan = Math.tan(radian); + skewy = new Affine(1, tan, 0, 1, 0, 0); + affine = affine.append(skewy); + } else { + console.error('math.js: No argument to skewY transform!'); + } + break; + + case 'matrix': + if (c.length === 6) { + affine = affine.append(new Affine(...c)); + } else { + console.error('math.js: Incorrect number of arguments for matrix!'); + } + break; + + default: + console.error('mesh.js: Unhandled transform type: ' + type); + break; + } + }); + + return affine; + }; + + const parsePoints = (s) => { + let points = []; + let values = s.split(/[ ,]+/); + for (let i = 0, imax = values.length - 1; i < imax; i += 2) { + points.push(new Point(parseFloat(values[i]), parseFloat(values[i + 1]))); + } + return points; + }; + + // Set multiple attributes to an element + const setAttributes = (el, attrs) => { + for (let key in attrs) { + el.setAttribute(key, attrs[key]); + } + }; + + // Find the slope of point p_k by the values in p_k-1 and p_k+1 + const finiteDifferences = (c0, c1, c2, d01, d12) => { + let slope = [0, 0, 0, 0]; + let slow, shigh; + + for (let k = 0; k < 3; ++k) { + if ((c1[k] < c0[k] && c1[k] < c2[k]) || (c0[k] < c1[k] && c2[k] < c1[k])) { + slope[k] = 0; + } else { + slope[k] = 0.5 * ((c1[k] - c0[k]) / d01 + (c2[k] - c1[k]) / d12); + slow = Math.abs(3.0 * (c1[k] - c0[k]) / d01); + shigh = Math.abs(3.0 * (c2[k] - c1[k]) / d12); + + if (slope[k] > slow) { + slope[k] = slow; + } else if (slope[k] > shigh) { + slope[k] = shigh; + } + } + } + + return slope; + }; + + // Coefficient matrix used for solving linear system + const A = [ + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [-3, 3, 0, 0, -2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [2, -2, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, -3, 3, 0, 0, -2, -1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 2, -2, 0, 0, 1, 1, 0, 0], + [-3, 0, 3, 0, 0, 0, 0, 0, -2, 0, -1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, -3, 0, 3, 0, 0, 0, 0, 0, -2, 0, -1, 0], + [9, -9, -9, 9, 6, 3, -6, -3, 6, -6, 3, -3, 4, 2, 2, 1], + [-6, 6, 6, -6, -3, -3, 3, 3, -4, 4, -2, 2, -2, -2, -1, -1], + [2, 0, -2, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 2, 0, -2, 0, 0, 0, 0, 0, 1, 0, 1, 0], + [-6, 6, 6, -6, -4, -2, 4, 2, -3, 3, -3, 3, -2, -1, -2, -1], + [4, -4, -4, 4, 2, 2, -2, -2, 2, -2, 2, -2, 1, 1, 1, 1] + ]; + + // Solve the linear system for bicubic interpolation + const solveLinearSystem = (v) => { + let alpha = []; + + for (let i = 0; i < 16; ++i) { + alpha[i] = 0; + for (let j = 0; j < 16; ++j) { + alpha[i] += A[i][j] * v[j]; + } + } + + return alpha; + }; + + // Evaluate the interpolation parameters at (y, x) + const evaluateSolution = (alpha, x, y) => { + const xx = x * x; + const yy = y * y; + const xxx = x * x * x; + const yyy = y * y * y; + + let result = + alpha[0] + + alpha[1] * x + + alpha[2] * xx + + alpha[3] * xxx + + alpha[4] * y + + alpha[5] * y * x + + alpha[6] * y * xx + + alpha[7] * y * xxx + + alpha[8] * yy + + alpha[9] * yy * x + + alpha[10] * yy * xx + + alpha[11] * yy * xxx + + alpha[12] * yyy + + alpha[13] * yyy * x + + alpha[14] * yyy * xx + + alpha[15] * yyy * xxx; + + return result; + }; + + // Split a patch into 8x8 smaller patches + const splitPatch = (patch) => { + let yPatches = []; + let xPatches = []; + let patches = []; + + // Horizontal splitting + for (let i = 0; i < 4; ++i) { + yPatches[i] = []; + yPatches[i][0] = splitBezier( + patch[0][i], patch[1][i], + patch[2][i], patch[3][i] + ); + + yPatches[i][1] = []; + yPatches[i][1].push(...splitBezier(...yPatches[i][0][0])); + yPatches[i][1].push(...splitBezier(...yPatches[i][0][1])); + + yPatches[i][2] = []; + yPatches[i][2].push(...splitBezier(...yPatches[i][1][0])); + yPatches[i][2].push(...splitBezier(...yPatches[i][1][1])); + yPatches[i][2].push(...splitBezier(...yPatches[i][1][2])); + yPatches[i][2].push(...splitBezier(...yPatches[i][1][3])); + } + + // Vertical splitting + for (let i = 0; i < 8; ++i) { + xPatches[i] = []; + + for (let j = 0; j < 4; ++j) { + xPatches[i][j] = []; + xPatches[i][j][0] = splitBezier( + yPatches[0][2][i][j], yPatches[1][2][i][j], + yPatches[2][2][i][j], yPatches[3][2][i][j] + ); + + xPatches[i][j][1] = []; + xPatches[i][j][1].push(...splitBezier(...xPatches[i][j][0][0])); + xPatches[i][j][1].push(...splitBezier(...xPatches[i][j][0][1])); + + xPatches[i][j][2] = []; + xPatches[i][j][2].push(...splitBezier(...xPatches[i][j][1][0])); + xPatches[i][j][2].push(...splitBezier(...xPatches[i][j][1][1])); + xPatches[i][j][2].push(...splitBezier(...xPatches[i][j][1][2])); + xPatches[i][j][2].push(...splitBezier(...xPatches[i][j][1][3])); + } + } + + for (let i = 0; i < 8; ++i) { + patches[i] = []; + + for (let j = 0; j < 8; ++j) { + patches[i][j] = []; + + patches[i][j][0] = xPatches[i][0][2][j]; + patches[i][j][1] = xPatches[i][1][2][j]; + patches[i][j][2] = xPatches[i][2][2][j]; + patches[i][j][3] = xPatches[i][3][2][j]; + } + } + + return patches; + }; + + // Point class ----------------------------------- + class Point { + constructor (x, y) { + this.x = x || 0; + this.y = y || 0; + } + + toString () { + return `(x=${this.x}, y=${this.y})`; + } + + clone () { + return new Point(this.x, this.y); + } + + add (v) { + return new Point(this.x + v.x, this.y + v.y); + } + + scale (v) { + if (v.x === undefined) { + return new Point(this.x * v, this.y * v); + } + return new Point(this.x * v.x, this.y * v.y); + } + + distSquared (v) { + let x = this.x - v.x; + let y = this.y - v.y; + return (x * x + y * y); + } + + // Transform by affine + transform (affine) { + let x = this.x * affine.a + this.y * affine.c + affine.e; + let y = this.x * affine.b + this.y * affine.d + affine.f; + return new Point(x, y); + } + } + + /* + * Affine class ------------------------------------- + * + * As defined in the SVG spec + * | a c e | + * | b d f | + * | 0 0 1 | + * + */ + + class Affine { + constructor (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; + } + } + + toString () { + return `affine: ${this.a} ${this.c} ${this.e} \n\ + ${this.b} ${this.d} ${this.f}`; + } + + append (v) { + if (!(v instanceof Affine)) { + console.error(`mesh.js: argument to Affine.append is not affine!`); + } + let a = this.a * v.a + this.c * v.b; + let b = this.b * v.a + this.d * v.b; + let c = this.a * v.c + this.c * v.d; + let d = this.b * v.c + this.d * v.d; + let e = this.a * v.e + this.c * v.f + this.e; + let f = this.b * v.e + this.d * v.f + this.f; + return new Affine(a, b, c, d, e, f); + } + } + + // Curve class -------------------------------------- + class Curve { + constructor (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 canvas.width + * h is canvas.height + */ + paintCurve (v, w) { + // If inside, see if we need to split + if (bezierStepsSquared(this.nodes) > maxBezierStep) { + const beziers = splitBezier(...this.nodes); + // ([start][end]) + let colors0 = [[], []]; + let colors1 = [[], []]; + + /* + * Linear horizontal interpolation of the middle value for every + * patch exceeding thereshold + */ + for (let 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] = colors0[1][i]; + colors1[1][i] = this.colors[1][i]; + } + let curve0 = new Curve(beziers[0], colors0); + let curve1 = new Curve(beziers[1], colors1); + curve0.paintCurve(v, w); + curve1.paintCurve(v, w); + } else { + // Directly write data + let x = Math.round(this.nodes[0].x); + if (x >= 0 && x < w) { + let index = (~~this.nodes[0].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 + } + } + } + } + + // Patch class ------------------------------------- + class Patch { + constructor (nodes, colors) { + this.nodes = nodes; // 4x4 array of points + this.colors = colors; // 2x2x4 colors (four corners x R+G+B+A) + } + + // Split patch horizontally into two patches. + split () { + let nodes0 = [[], [], [], []]; + let nodes1 = [[], [], [], []]; + let colors0 = [ + [[], []], + [[], []] + ]; + let colors1 = [ + [[], []], + [[], []] + ]; + + for (let i = 0; i < 4; ++i) { + const beziers = splitBezier( + this.nodes[0][i], this.nodes[1][i], + this.nodes[2][i], this.nodes[3][i] + ); + + 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]; + } + + /* + * Linear vertical interpolation of the middle value for every + * patch exceeding thereshold + */ + for (let 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] = colors0[1][0][i]; + colors1[0][1][i] = colors0[1][1][i]; + colors1[1][0][i] = this.colors[1][0][i]; + colors1[1][1][i] = this.colors[1][1][i]; + } + + return ([new Patch(nodes0, colors0), new Patch(nodes1, colors1)]); + } + + paint (v, w) { + // Check if we need to split + let larger = false; + let step; + for (let i = 0; i < 4; ++i) { + step = bezierStepsSquared([ + this.nodes[0][i], this.nodes[1][i], + this.nodes[2][i], this.nodes[3][i] + ]); + + if (step > maxBezierStep) { + larger = true; + break; + } + } + + if (larger) { + let patches = this.split(); + patches[0].paint(v, w); + patches[1].paint(v, w); + } else { + /* + * 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. + */ + let curve = new Curve([...this.nodes[0]], [...this.colors[0]]); + curve.paintCurve(v, w); + } + } + } + + // Mesh class --------------------------------------- + class Mesh { + constructor (mesh) { + this.readMesh(mesh); + this.type = mesh.getAttribute('type') || 'bilinear'; + } + + // Function to parse an SVG mesh and set the nodes (points) and colors + readMesh (mesh) { + let nodes = [[]]; + let colors = [[]]; + + let x = Number(mesh.getAttribute('x')); + let y = Number(mesh.getAttribute('y')); + nodes[0][0] = new Point(x, y); + + let rows = mesh.children; + for (let i = 0, imax = rows.length; i < imax; ++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. + + let patches = rows[i].children; + for (let j = 0, jmax = patches.length; j < jmax; ++j) { + let stops = patches[j].children; + for (let k = 0, kmax = stops.length; k < kmax; ++k) { + let l = k; + if (i !== 0) { + ++l; // There is no top if row isn't first row. + } + let path = stops[k].getAttribute('path'); + let parts; + + let type = 'l'; // We need to still find mid-points even if no path. + if (path != null) { + parts = path.match(/\s*([lLcC])\s*(.*)/); + type = parts[1]; + } + let stopNodes = parsePoints(parts[2]); + + switch (type) { + case 'l': + if (l === 0) { // Top + nodes[3 * i][3 * j + 3] = stopNodes[0].add(nodes[3 * i][3 * j]); + nodes[3 * i][3 * j + 1] = wAvg(nodes[3 * i][3 * j], nodes[3 * i][3 * j + 3]); + nodes[3 * i][3 * j + 2] = wAvg(nodes[3 * i][3 * j + 3], nodes[3 * i][3 * j]); + } else if (l === 1) { // Right + nodes[3 * i + 3][3 * j + 3] = stopNodes[0].add(nodes[3 * i][3 * j + 3]); + nodes[3 * i + 1][3 * j + 3] = wAvg(nodes[3 * i][3 * j + 3], nodes[3 * i + 3][3 * j + 3]); + nodes[3 * i + 2][3 * j + 3] = wAvg(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] = stopNodes[0].add(nodes[3 * i + 3][3 * j + 3]); + } + nodes[3 * i + 3][3 * j + 1] = wAvg(nodes[3 * i + 3][3 * j], nodes[3 * i + 3][3 * j + 3]); + nodes[3 * i + 3][3 * j + 2] = wAvg(nodes[3 * i + 3][3 * j + 3], nodes[3 * i + 3][3 * j]); + } else { // Left + nodes[3 * i + 1][3 * j] = wAvg(nodes[3 * i][3 * j], nodes[3 * i + 3][3 * j]); + nodes[3 * i + 2][3 * j] = wAvg(nodes[3 * i + 3][3 * j], nodes[3 * i][3 * j]); + } + break; + case 'L': + if (l === 0) { // Top + nodes[3 * i][3 * j + 3] = stopNodes[0]; + nodes[3 * i][3 * j + 1] = wAvg(nodes[3 * i][3 * j], nodes[3 * i][3 * j + 3]); + nodes[3 * i][3 * j + 2] = wAvg(nodes[3 * i][3 * j + 3], nodes[3 * i][3 * j]); + } else if (l === 1) { // Right + nodes[3 * i + 3][3 * j + 3] = stopNodes[0]; + nodes[3 * i + 1][3 * j + 3] = wAvg(nodes[3 * i][3 * j + 3], nodes[3 * i + 3][3 * j + 3]); + nodes[3 * i + 2][3 * j + 3] = wAvg(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] = stopNodes[0]; + } + nodes[3 * i + 3][3 * j + 1] = wAvg(nodes[3 * i + 3][3 * j], nodes[3 * i + 3][3 * j + 3]); + nodes[3 * i + 3][3 * j + 2] = wAvg(nodes[3 * i + 3][3 * j + 3], nodes[3 * i + 3][3 * j]); + } else { // Left + nodes[3 * i + 1][3 * j] = wAvg(nodes[3 * i][3 * j], nodes[3 * i + 3][3 * j]); + nodes[3 * i + 2][3 * j] = wAvg(nodes[3 * i + 3][3 * j], nodes[3 * i][3 * j]); + } + break; + case 'c': + if (l === 0) { // Top + nodes[3 * i][3 * j + 1] = stopNodes[0].add(nodes[3 * i][3 * j]); + nodes[3 * i][3 * j + 2] = stopNodes[1].add(nodes[3 * i][3 * j]); + nodes[3 * i][3 * j + 3] = stopNodes[2].add(nodes[3 * i][3 * j]); + } else if (l === 1) { // Right + nodes[3 * i + 1][3 * j + 3] = stopNodes[0].add(nodes[3 * i][3 * j + 3]); + nodes[3 * i + 2][3 * j + 3] = stopNodes[1].add(nodes[3 * i][3 * j + 3]); + nodes[3 * i + 3][3 * j + 3] = stopNodes[2].add(nodes[3 * i][3 * j + 3]); + } else if (l === 2) { // Bottom + nodes[3 * i + 3][3 * j + 2] = stopNodes[0].add(nodes[3 * i + 3][3 * j + 3]); + nodes[3 * i + 3][3 * j + 1] = stopNodes[1].add(nodes[3 * i + 3][3 * j + 3]); + if (j === 0) { + nodes[3 * i + 3][3 * j + 0] = stopNodes[2].add(nodes[3 * i + 3][3 * j + 3]); + } + } else { // Left + nodes[3 * i + 2][3 * j] = stopNodes[0].add(nodes[3 * i + 3][3 * j]); + nodes[3 * i + 1][3 * j] = stopNodes[1].add(nodes[3 * i + 3][3 * j]); + } + break; + case 'C': + if (l === 0) { // Top + nodes[3 * i][3 * j + 1] = stopNodes[0]; + nodes[3 * i][3 * j + 2] = stopNodes[1]; + nodes[3 * i][3 * j + 3] = stopNodes[2]; + } else if (l === 1) { // Right + nodes[3 * i + 1][3 * j + 3] = stopNodes[0]; + nodes[3 * i + 2][3 * j + 3] = stopNodes[1]; + nodes[3 * i + 3][3 * j + 3] = stopNodes[2]; + } else if (l === 2) { // Bottom + nodes[3 * i + 3][3 * j + 2] = stopNodes[0]; + nodes[3 * i + 3][3 * j + 1] = stopNodes[1]; + if (j === 0) { + nodes[3 * i + 3][3 * j + 0] = stopNodes[2]; + } + } else { // Left + nodes[3 * i + 2][3 * j] = stopNodes[0]; + nodes[3 * i + 1][3 * j] = stopNodes[1]; + } + break; + default: + console.error('mesh.js: ' + type + ' invalid path type.'); + } + + if ((i === 0 && j === 0) || k > 0) { + let colorRaw = window.getComputedStyle(stops[k]).stopColor + .match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i); + let alphaRaw = window.getComputedStyle(stops[k]).stopOpacity; + let alpha = 255; + if (alphaRaw) { + alpha = Math.floor(alphaRaw * 255); + } + + if (colorRaw) { + if (l === 0) { // upper left corner + colors[i][j] = []; + colors[i][j][0] = Math.floor(colorRaw[1]); + colors[i][j][1] = Math.floor(colorRaw[2]); + colors[i][j][2] = Math.floor(colorRaw[3]); + colors[i][j][3] = alpha; // Alpha + } else if (l === 1) { // upper right corner + colors[i][j + 1] = []; + colors[i][j + 1][0] = Math.floor(colorRaw[1]); + colors[i][j + 1][1] = Math.floor(colorRaw[2]); + colors[i][j + 1][2] = Math.floor(colorRaw[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] = Math.floor(colorRaw[1]); + colors[i + 1][j + 1][1] = Math.floor(colorRaw[2]); + colors[i + 1][j + 1][2] = Math.floor(colorRaw[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] = Math.floor(colorRaw[1]); + colors[i + 1][j][1] = Math.floor(colorRaw[2]); + colors[i + 1][j][2] = Math.floor(colorRaw[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; + } + } + + this.nodes = nodes; // (m*3+1) x (n*3+1) points + this.colors = colors; // (m+1) x (n+1) x 4 colors (R+G+B+A) + } + + // Extracts out each patch and then paints it + paintMesh (v, w) { + let imax = (this.nodes.length - 1) / 3; + let jmax = (this.nodes[0].length - 1) / 3; + + if (this.type === 'bilinear' || imax < 2 || jmax < 2) { + let patch; + + for (let i = 0; i < imax; ++i) { + for (let j = 0; j < jmax; ++j) { + let sliceNodes = []; + for (let k = i * 3, kmax = (i * 3) + 4; k < kmax; ++k) { + sliceNodes.push(this.nodes[k].slice(j * 3, (j * 3) + 4)); + } + + let sliceColors = []; + sliceColors.push(this.colors[i].slice(j, j + 2)); + sliceColors.push(this.colors[i + 1].slice(j, j + 2)); + + patch = new Patch(sliceNodes, sliceColors); + patch.paint(v, w); + } + } + } else { + // Reference: + // https://en.wikipedia.org/wiki/Bicubic_interpolation#Computation + let d01, d12, patch, sliceNodes, nodes, f, alpha; + const ilast = imax; + const jlast = jmax; + imax++; + jmax++; + + /* + * d = the interpolation data + * d[i][j] = a node record (Point, color_array, color_dx, color_dy) + * d[i][j][0] : Point + * d[i][j][1] : [RGBA] + * d[i][j][2] = dx [RGBA] + * d[i][j][3] = dy [RGBA] + * d[i][j][][k] : color channel k + */ + let d = new Array(imax); + + // Setting the node and the colors + for (let i = 0; i < imax; ++i) { + d[i] = new Array(jmax); + for (let j = 0; j < jmax; ++j) { + d[i][j] = []; + d[i][j][0] = this.nodes[3 * i][3 * j]; + d[i][j][1] = this.colors[i][j]; + } + } + + // Calculate the inner derivatives + for (let i = 0; i < imax; ++i) { + for (let j = 0; j < jmax; ++j) { + // dx + if (i !== 0 && i !== ilast) { + d01 = distance(d[i - 1][j][0], d[i][j][0]); + d12 = distance(d[i + 1][j][0], d[i][j][0]); + d[i][j][2] = finiteDifferences(d[i - 1][j][1], d[i][j][1], + d[i + 1][j][1], d01, d12); + } + + // dy + if (j !== 0 && j !== jlast) { + d01 = distance(d[i][j - 1][0], d[i][j][0]); + d12 = distance(d[i][j + 1][0], d[i][j][0]); + d[i][j][3] = finiteDifferences(d[i][j - 1][1], d[i][j][1], + d[i][j + 1][1], d01, d12); + } + + // dxy is, by standard, set to 0 + } + } + + /* + * Calculate the exterior derivatives + * We fit the exterior derivatives onto parabolas generated by + * the point and the interior derivatives. + */ + for (let j = 0; j < jmax; ++j) { + d[0][j][2] = []; + d[ilast][j][2] = []; + + for (let k = 0; k < 4; ++k) { + d01 = distance(d[1][j][0], d[0][j][0]); + d12 = distance(d[ilast][j][0], d[ilast - 1][j][0]); + + if (d01 > 0) { + d[0][j][2][k] = 2.0 * (d[1][j][1][k] - d[0][j][1][k]) / d01 - + d[1][j][2][k]; + } else { + console.log(`0 was 0! (j: ${j}, k: ${k})`); + d[0][j][2][k] = 0; + } + + if (d12 > 0) { + d[ilast][j][2][k] = 2.0 * (d[ilast][j][1][k] - d[ilast - 1][j][1][k]) / + d12 - d[ilast - 1][j][2][k]; + } else { + console.log(`last was 0! (j: ${j}, k: ${k})`); + d[ilast][j][2][k] = 0; + } + } + } + + for (let i = 0; i < imax; ++i) { + d[i][0][3] = []; + d[i][jlast][3] = []; + + for (let k = 0; k < 4; ++k) { + d01 = distance(d[i][1][0], d[i][0][0]); + d12 = distance(d[i][jlast][0], d[i][jlast - 1][0]); + + if (d01 > 0) { + d[i][0][3][k] = 2.0 * (d[i][1][1][k] - d[i][0][1][k]) / d01 - + d[i][1][3][k]; + } else { + console.log(`0 was 0! (i: ${i}, k: ${k})`); + d[i][0][3][k] = 0; + } + + if (d12 > 0) { + d[i][jlast][3][k] = 2.0 * (d[i][jlast][1][k] - d[i][jlast - 1][1][k]) / + d12 - d[i][jlast - 1][3][k]; + } else { + console.log(`last was 0! (i: ${i}, k: ${k})`); + d[i][jlast][3][k] = 0; + } + } + } + + // Fill patches + for (let i = 0; i < ilast; ++i) { + for (let j = 0; j < jlast; ++j) { + let dLeft = distance(d[i][j][0], d[i + 1][j][0]); + let dRight = distance(d[i][j + 1][0], d[i + 1][j + 1][0]); + let dTop = distance(d[i][j][0], d[i][j + 1][0]); + let dBottom = distance(d[i + 1][j][0], d[i + 1][j + 1][0]); + let r = [[], [], [], []]; + + for (let k = 0; k < 4; ++k) { + f = []; + + f[0] = d[i][j][1][k]; + f[1] = d[i + 1][j][1][k]; + f[2] = d[i][j + 1][1][k]; + f[3] = d[i + 1][j + 1][1][k]; + f[4] = d[i][j][2][k] * dLeft; + f[5] = d[i + 1][j][2][k] * dLeft; + f[6] = d[i][j + 1][2][k] * dRight; + f[7] = d[i + 1][j + 1][2][k] * dRight; + f[8] = d[i][j][3][k] * dTop; + f[9] = d[i + 1][j][3][k] * dBottom; + f[10] = d[i][j + 1][3][k] * dTop; + f[11] = d[i + 1][j + 1][3][k] * dBottom; + f[12] = 0; // dxy + f[13] = 0; // dxy + f[14] = 0; // dxy + f[15] = 0; // dxy + + // get alpha values + alpha = solveLinearSystem(f); + + for (let l = 0; l < 9; ++l) { + r[k][l] = []; + + for (let m = 0; m < 9; ++m) { + // evaluation + r[k][l][m] = evaluateSolution(alpha, l / 8, m / 8); + + if (r[k][l][m] > 255) { + r[k][l][m] = 255; + } else if (r[k][l][m] < 0.0) { + r[k][l][m] = 0.0; + } + } + } + } + + // split the bezier patch into 8x8 patches + sliceNodes = []; + for (let k = i * 3, kmax = (i * 3) + 4; k < kmax; ++k) { + sliceNodes.push(this.nodes[k].slice(j * 3, (j * 3) + 4)); + } + + nodes = splitPatch(sliceNodes); + + // Create patches and paint the bilinearliy + for (let l = 0; l < 8; ++l) { + for (let m = 0; m < 8; ++m) { + patch = new Patch( + nodes[l][m], + [[ + [r[0][l][m], r[1][l][m], r[2][l][m], r[3][l][m]], + [r[0][l][m + 1], r[1][l][m + 1], r[2][l][m + 1], r[3][l][m + 1]] + ], [ + [r[0][l + 1][m], r[1][l + 1][m], r[2][l + 1][m], r[3][l + 1][m]], + [r[0][l + 1][m + 1], r[1][l + 1][m + 1], r[2][l + 1][m + 1], r[3][l + 1][m + 1]] + ]] + ); + + patch.paint(v, w); + } + } + } + } + } + } + + // Transforms mesh into coordinate space of canvas (t is either Point or Affine). + transform (t) { + if (t instanceof Point) { + for (let i = 0, imax = this.nodes.length; i < imax; ++i) { + for (let j = 0, jmax = this.nodes[0].length; j < jmax; ++j) { + this.nodes[i][j] = this.nodes[i][j].add(t); + } + } + } else if (t instanceof Affine) { + for (let i = 0, imax = this.nodes.length; i < imax; ++i) { + for (let j = 0, jmax = this.nodes[0].length; j < jmax; ++j) { + this.nodes[i][j] = this.nodes[i][j].transform(t); + } + } + } + } + + // Scale mesh into coordinate space of canvas (t is a Point). + scale (t) { + for (let i = 0, imax = this.nodes.length; i < imax; ++i) { + for (let j = 0, jmax = this.nodes[0].length; j < jmax; ++j) { + this.nodes[i][j] = this.nodes[i][j].scale(t); + } + } + } + } + + // Start of document processing --------------------- + const shapes = document.querySelectorAll('rect,circle,ellipse,path,text'); + + shapes.forEach((shape, i) => { + // Get id. If no id, create one. + let shapeId = shape.getAttribute('id'); + if (!shapeId) { + shapeId = 'patchjs_shape' + i; + shape.setAttribute('id', shapeId); + } + + const fillURL = shape.style.fill.match(/^url\(\s*"?\s*#([^\s"]+)"?\s*\)/); + const strokeURL = shape.style.stroke.match(/^url\(\s*"?\s*#([^\s"]+)"?\s*\)/); + + if (fillURL && fillURL[1]) { + const mesh = document.getElementById(fillURL[1]); + + if (mesh && mesh.nodeName === 'meshgradient') { + const bbox = shape.getBBox(); + + // Create temporary canvas + let myCanvas = document.createElementNS(xhtmlNS, 'canvas'); + setAttributes(myCanvas, { + 'width': bbox.width, + 'height': bbox.height + }); + + const myContext = myCanvas.getContext('2d'); + let myCanvasImage = myContext.createImageData(bbox.width, bbox.height); + + // Draw a mesh + const myMesh = new Mesh(mesh); + + // Adjust for bounding box if necessary. + if (mesh.getAttribute('gradientUnits') === 'objectBoundingBox') { + myMesh.scale(new Point(bbox.width, bbox.height)); + } + + // Apply gradient transform. + const gradientTransform = mesh.getAttribute('gradientTransform'); + if (gradientTransform != null) { + myMesh.transform(parseTransform(gradientTransform)); + } + + // Position to Canvas coordinate. + if (mesh.getAttribute('gradientUnits') === 'userSpaceOnUse') { + myMesh.transform(new Point(-bbox.x, -bbox.y)); + } + + // Paint + myMesh.paintMesh(myCanvasImage.data, myCanvas.width); + myContext.putImageData(myCanvasImage, 0, 0); + + // Create image element of correct size + const myImage = document.createElementNS(svgNS, 'image'); + setAttributes(myImage, { + 'width': bbox.width, + 'height': bbox.height, + 'x': bbox.x, + 'y': bbox.y + }); + + // Set image to data url + let myPNG = myCanvas.toDataURL(); + myImage.setAttributeNS(xlinkNS, 'xlink:href', myPNG); + + // Insert image into document + shape.parentNode.insertBefore(myImage, shape); + shape.style.fill = 'none'; + + // Create clip referencing shape and insert into document + const use = document.createElementNS(svgNS, 'use'); + use.setAttributeNS(xlinkNS, 'xlink:href', '#' + shapeId); + + const clipId = 'patchjs_clip' + i; + const clip = document.createElementNS(svgNS, 'clipPath'); + clip.setAttribute('id', clipId); + clip.appendChild(use); + shape.parentElement.insertBefore(clip, shape); + myImage.setAttribute('clip-path', 'url(#' + clipId + ')'); + + // Force the Garbage Collector to free the space + myCanvasImage = null; + myCanvas = null; + myPNG = null; + } + } + + if (strokeURL && strokeURL[1]) { + const mesh = document.getElementById(strokeURL[1]); + + if (mesh && mesh.nodeName === 'meshgradient') { + const strokeWidth = parseFloat(shape.style.strokeWidth.slice(0, -2)); + const strokeMiterlimit = parseFloat(shape.style.strokeMiterlimit) || + parseFloat(shape.getAttribute('stroke-miterlimit')) || 1; + const phase = strokeWidth * strokeMiterlimit; + + const bbox = shape.getBBox(); + const boxWidth = Math.trunc(bbox.width + phase); + const boxHeight = Math.trunc(bbox.height + phase); + const boxX = Math.trunc(bbox.x - phase / 2); + const boxY = Math.trunc(bbox.y - phase / 2); + + // Create temporary canvas + let myCanvas = document.createElementNS(xhtmlNS, 'canvas'); + setAttributes(myCanvas, { + 'width': boxWidth, + 'height': boxHeight + }); + + const myContext = myCanvas.getContext('2d'); + let myCanvasImage = myContext.createImageData(boxWidth, boxHeight); + + // Draw a mesh + const myMesh = new Mesh(mesh); + + // Adjust for bounding box if necessary. + if (mesh.getAttribute('gradientUnits') === 'objectBoundingBox') { + myMesh.scale(new Point(boxWidth, boxHeight)); + } + + // Apply gradient transform. + const gradientTransform = mesh.getAttribute('gradientTransform'); + if (gradientTransform != null) { + myMesh.transform(parseTransform(gradientTransform)); + } + + // Position to Canvas coordinate. + if (mesh.getAttribute('gradientUnits') === 'userSpaceOnUse') { + myMesh.transform(new Point(-boxX, -boxY)); + } + + // Paint + myMesh.paintMesh(myCanvasImage.data, myCanvas.width); + myContext.putImageData(myCanvasImage, 0, 0); + + // Create image element of correct size + const myImage = document.createElementNS(svgNS, 'image'); + setAttributes(myImage, { + 'width': boxWidth, + 'height': boxHeight, + 'x': 0, + 'y': 0 + }); + + // Set image to data url + let myPNG = myCanvas.toDataURL(); + myImage.setAttributeNS(xlinkNS, 'xlink:href', myPNG); + + // Create pattern to hold the stroke image + const patternId = 'pattern_clip' + i; + const myPattern = document.createElementNS(svgNS, 'pattern'); + setAttributes(myPattern, { + 'id': patternId, + 'patternUnits': 'userSpaceOnUse', + 'width': boxWidth, + 'height': boxHeight, + 'x': boxX, + 'y': boxY + }); + myPattern.appendChild(myImage); + + // Insert image into document + mesh.parentNode.appendChild(myPattern); + shape.style.stroke = 'url(#' + patternId + ')'; + + // Force the Garbage Collector to free the space + myCanvasImage = null; + myCanvas = null; + myPNG = null; + } + } + }); +})(); diff --git a/src/extension/internal/polyfill/mesh_compressed.include b/src/extension/internal/polyfill/mesh_compressed.include new file mode 100644 index 0000000..275be96 --- /dev/null +++ b/src/extension/internal/polyfill/mesh_compressed.include @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: CC0 +R"=====( +!function(){const t="http://www.w3.org/2000/svg",e="http://www.w3.org/1999/xlink",s="http://www.w3.org/1999/xhtml",r=2;if(document.createElementNS(t,"meshgradient").x)return;const n=(t,e,s,r)=>{let n=new x(.5*(e.x+s.x),.5*(e.y+s.y)),o=new x(.5*(t.x+e.x),.5*(t.y+e.y)),i=new x(.5*(s.x+r.x),.5*(s.y+r.y)),a=new x(.5*(n.x+o.x),.5*(n.y+o.y)),h=new x(.5*(n.x+i.x),.5*(n.y+i.y)),l=new x(.5*(a.x+h.x),.5*(a.y+h.y));return[[t,o,a,l],[l,h,i,r]]},o=t=>{let e=t[0].distSquared(t[1]),s=t[2].distSquared(t[3]),r=.25*t[0].distSquared(t[2]),n=.25*t[1].distSquared(t[3]),o=e>s?e:s,i=r>n?r:n;return 18*(o>i?o:i)},i=(t,e)=>Math.sqrt(t.distSquared(e)),a=(t,e)=>t.scale(2/3).add(e.scale(1/3)),h=t=>{let e,s,r,n,o,i,a,h=new g;return t.match(/(\w+\(\s*[^)]+\))+/g).forEach(t=>{let l=t.match(/[\w.-]+/g),d=l.shift();switch(d){case"translate":2===l.length?e=new g(1,0,0,1,l[0],l[1]):(console.error("mesh.js: translate does not have 2 arguments!"),e=new g(1,0,0,1,0,0)),h=h.append(e);break;case"scale":1===l.length?s=new g(l[0],0,0,l[0],0,0):2===l.length?s=new g(l[0],0,0,l[1],0,0):(console.error("mesh.js: scale does not have 1 or 2 arguments!"),s=new g(1,0,0,1,0,0)),h=h.append(s);break;case"rotate":if(3===l.length&&(e=new g(1,0,0,1,l[1],l[2]),h=h.append(e)),l[0]){r=l[0]*Math.PI/180;let t=Math.cos(r),e=Math.sin(r);Math.abs(t)<1e-16&&(t=0),Math.abs(e)<1e-16&&(e=0),a=new g(t,e,-e,t,0,0),h=h.append(a)}else console.error("math.js: No argument to rotate transform!");3===l.length&&(e=new g(1,0,0,1,-l[1],-l[2]),h=h.append(e));break;case"skewX":l[0]?(r=l[0]*Math.PI/180,n=Math.tan(r),o=new g(1,0,n,1,0,0),h=h.append(o)):console.error("math.js: No argument to skewX transform!");break;case"skewY":l[0]?(r=l[0]*Math.PI/180,n=Math.tan(r),i=new g(1,n,0,1,0,0),h=h.append(i)):console.error("math.js: No argument to skewY transform!");break;case"matrix":6===l.length?h=h.append(new g(...l)):console.error("math.js: Incorrect number of arguments for matrix!");break;default:console.error("mesh.js: Unhandled transform type: "+d)}}),h},l=t=>{let e=[],s=t.split(/[ ,]+/);for(let t=0,r=s.length-1;t{for(let s in e)t.setAttribute(s,e[s])},c=(t,e,s,r,n)=>{let o,i,a=[0,0,0,0];for(let h=0;h<3;++h)e[h]o?a[h]=o:a[h]>i&&(a[h]=i));return a},u=[[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0],[-3,3,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0],[2,-2,0,0,1,1,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0],[0,0,0,0,0,0,0,0,-3,3,0,0,-2,-1,0,0],[0,0,0,0,0,0,0,0,2,-2,0,0,1,1,0,0],[-3,0,3,0,0,0,0,0,-2,0,-1,0,0,0,0,0],[0,0,0,0,-3,0,3,0,0,0,0,0,-2,0,-1,0],[9,-9,-9,9,6,3,-6,-3,6,-6,3,-3,4,2,2,1],[-6,6,6,-6,-3,-3,3,3,-4,4,-2,2,-2,-2,-1,-1],[2,0,-2,0,0,0,0,0,1,0,1,0,0,0,0,0],[0,0,0,0,2,0,-2,0,0,0,0,0,1,0,1,0],[-6,6,6,-6,-4,-2,4,2,-3,3,-3,3,-2,-1,-2,-1],[4,-4,-4,4,2,2,-2,-2,2,-2,2,-2,1,1,1,1]],f=t=>{let e=[];for(let s=0;s<16;++s){e[s]=0;for(let r=0;r<16;++r)e[s]+=u[s][r]*t[r]}return e},p=(t,e,s)=>{const r=e*e,n=s*s,o=e*e*e,i=s*s*s;return t[0]+t[1]*e+t[2]*r+t[3]*o+t[4]*s+t[5]*s*e+t[6]*s*r+t[7]*s*o+t[8]*n+t[9]*n*e+t[10]*n*r+t[11]*n*o+t[12]*i+t[13]*i*e+t[14]*i*r+t[15]*i*o},y=t=>{let e=[],s=[],r=[];for(let s=0;s<4;++s)e[s]=[],e[s][0]=n(t[0][s],t[1][s],t[2][s],t[3][s]),e[s][1]=[],e[s][1].push(...n(...e[s][0][0])),e[s][1].push(...n(...e[s][0][1])),e[s][2]=[],e[s][2].push(...n(...e[s][1][0])),e[s][2].push(...n(...e[s][1][1])),e[s][2].push(...n(...e[s][1][2])),e[s][2].push(...n(...e[s][1][3]));for(let t=0;t<8;++t){s[t]=[];for(let r=0;r<4;++r)s[t][r]=[],s[t][r][0]=n(e[0][2][t][r],e[1][2][t][r],e[2][2][t][r],e[3][2][t][r]),s[t][r][1]=[],s[t][r][1].push(...n(...s[t][r][0][0])),s[t][r][1].push(...n(...s[t][r][0][1])),s[t][r][2]=[],s[t][r][2].push(...n(...s[t][r][1][0])),s[t][r][2].push(...n(...s[t][r][1][1])),s[t][r][2].push(...n(...s[t][r][1][2])),s[t][r][2].push(...n(...s[t][r][1][3]))}for(let t=0;t<8;++t){r[t]=[];for(let e=0;e<8;++e)r[t][e]=[],r[t][e][0]=s[t][0][2][e],r[t][e][1]=s[t][1][2][e],r[t][e][2]=s[t][2][2][e],r[t][e][3]=s[t][3][2][e]}return r};class x{constructor(t,e){this.x=t||0,this.y=e||0}toString(){return`(x=${this.x}, y=${this.y})`}clone(){return new x(this.x,this.y)}add(t){return new x(this.x+t.x,this.y+t.y)}scale(t){return void 0===t.x?new x(this.x*t,this.y*t):new x(this.x*t.x,this.y*t.y)}distSquared(t){let e=this.x-t.x,s=this.y-t.y;return e*e+s*s}transform(t){let e=this.x*t.a+this.y*t.c+t.e,s=this.x*t.b+this.y*t.d+t.f;return new x(e,s)}}class g{constructor(t,e,s,r,n,o){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=r,this.e=n,this.f=o)}toString(){return`affine: ${this.a} ${this.c} ${this.e} \n ${this.b} ${this.d} ${this.f}`}append(t){t instanceof g||console.error("mesh.js: argument to Affine.append is not affine!");let e=this.a*t.a+this.c*t.b,s=this.b*t.a+this.d*t.b,r=this.a*t.c+this.c*t.d,n=this.b*t.c+this.d*t.d,o=this.a*t.e+this.c*t.f+this.e,i=this.b*t.e+this.d*t.f+this.f;return new g(e,s,r,n,o,i)}}class w{constructor(t,e){this.nodes=t,this.colors=e}paintCurve(t,e){if(o(this.nodes)>r){const s=n(...this.nodes);let r=[[],[]],o=[[],[]];for(let t=0;t<4;++t)r[0][t]=this.colors[0][t],r[1][t]=(this.colors[0][t]+this.colors[1][t])/2,o[0][t]=r[1][t],o[1][t]=this.colors[1][t];let i=new w(s[0],r),a=new w(s[1],o);i.paintCurve(t,e),a.paintCurve(t,e)}else{let s=Math.round(this.nodes[0].x);if(s>=0&&sr){n=!0;break}if(n){let s=this.split();s[0].paint(t,e),s[1].paint(t,e)}else{new w([...this.nodes[0]],[...this.colors[0]]).paintCurve(t,e)}}}class b{constructor(t){this.readMesh(t),this.type=t.getAttribute("type")||"bilinear"}readMesh(t){let e=[[]],s=[[]],r=Number(t.getAttribute("x")),n=Number(t.getAttribute("y"));e[0][0]=new x(r,n);let o=t.children;for(let t=0,r=o.length;t0){let e=window.getComputedStyle(o[r]).stopColor.match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i),a=window.getComputedStyle(o[r]).stopOpacity,h=255;a&&(h=Math.floor(255*a)),e&&(0===i?(s[t][n]=[],s[t][n][0]=Math.floor(e[1]),s[t][n][1]=Math.floor(e[2]),s[t][n][2]=Math.floor(e[3]),s[t][n][3]=h):1===i?(s[t][n+1]=[],s[t][n+1][0]=Math.floor(e[1]),s[t][n+1][1]=Math.floor(e[2]),s[t][n+1][2]=Math.floor(e[3]),s[t][n+1][3]=h):2===i?(s[t+1][n+1]=[],s[t+1][n+1][0]=Math.floor(e[1]),s[t+1][n+1][1]=Math.floor(e[2]),s[t+1][n+1][2]=Math.floor(e[3]),s[t+1][n+1][3]=h):3===i&&(s[t+1][n]=[],s[t+1][n][0]=Math.floor(e[1]),s[t+1][n][1]=Math.floor(e[2]),s[t+1][n][2]=Math.floor(e[3]),s[t+1][n][3]=h))}}e[3*t+1][3*n+1]=new x,e[3*t+1][3*n+2]=new x,e[3*t+2][3*n+1]=new x,e[3*t+2][3*n+2]=new x,e[3*t+1][3*n+1].x=(-4*e[3*t][3*n].x+6*(e[3*t][3*n+1].x+e[3*t+1][3*n].x)+-2*(e[3*t][3*n+3].x+e[3*t+3][3*n].x)+3*(e[3*t+3][3*n+1].x+e[3*t+1][3*n+3].x)+-1*e[3*t+3][3*n+3].x)/9,e[3*t+1][3*n+2].x=(-4*e[3*t][3*n+3].x+6*(e[3*t][3*n+2].x+e[3*t+1][3*n+3].x)+-2*(e[3*t][3*n].x+e[3*t+3][3*n+3].x)+3*(e[3*t+3][3*n+2].x+e[3*t+1][3*n].x)+-1*e[3*t+3][3*n].x)/9,e[3*t+2][3*n+1].x=(-4*e[3*t+3][3*n].x+6*(e[3*t+3][3*n+1].x+e[3*t+2][3*n].x)+-2*(e[3*t+3][3*n+3].x+e[3*t][3*n].x)+3*(e[3*t][3*n+1].x+e[3*t+2][3*n+3].x)+-1*e[3*t][3*n+3].x)/9,e[3*t+2][3*n+2].x=(-4*e[3*t+3][3*n+3].x+6*(e[3*t+3][3*n+2].x+e[3*t+2][3*n+3].x)+-2*(e[3*t+3][3*n].x+e[3*t][3*n+3].x)+3*(e[3*t][3*n+2].x+e[3*t+2][3*n].x)+-1*e[3*t][3*n].x)/9,e[3*t+1][3*n+1].y=(-4*e[3*t][3*n].y+6*(e[3*t][3*n+1].y+e[3*t+1][3*n].y)+-2*(e[3*t][3*n+3].y+e[3*t+3][3*n].y)+3*(e[3*t+3][3*n+1].y+e[3*t+1][3*n+3].y)+-1*e[3*t+3][3*n+3].y)/9,e[3*t+1][3*n+2].y=(-4*e[3*t][3*n+3].y+6*(e[3*t][3*n+2].y+e[3*t+1][3*n+3].y)+-2*(e[3*t][3*n].y+e[3*t+3][3*n+3].y)+3*(e[3*t+3][3*n+2].y+e[3*t+1][3*n].y)+-1*e[3*t+3][3*n].y)/9,e[3*t+2][3*n+1].y=(-4*e[3*t+3][3*n].y+6*(e[3*t+3][3*n+1].y+e[3*t+2][3*n].y)+-2*(e[3*t+3][3*n+3].y+e[3*t][3*n].y)+3*(e[3*t][3*n+1].y+e[3*t+2][3*n+3].y)+-1*e[3*t][3*n+3].y)/9,e[3*t+2][3*n+2].y=(-4*e[3*t+3][3*n+3].y+6*(e[3*t+3][3*n+2].y+e[3*t+2][3*n+3].y)+-2*(e[3*t+3][3*n].y+e[3*t][3*n+3].y)+3*(e[3*t][3*n+2].y+e[3*t+2][3*n].y)+-1*e[3*t][3*n].y)/9}}this.nodes=e,this.colors=s}paintMesh(t,e){let s=(this.nodes.length-1)/3,r=(this.nodes[0].length-1)/3;if("bilinear"===this.type||s<2||r<2){let n;for(let o=0;o0?2*(w[1][t][1][e]-w[0][t][1][e])/n-w[1][t][2][e]:0,w[x][t][2][e]=o>0?2*(w[x][t][1][e]-w[x-1][t][1][e])/o-w[x-1][t][2][e]:0}for(let t=0;t0?2*(w[t][1][1][e]-w[t][0][1][e])/n-w[t][1][3][e]:0,w[t][g][3][e]=o>0?2*(w[t][g][1][e]-w[t][g-1][1][e])/o-w[t][g-1][3][e]:0}for(let s=0;s255?g[t][e][s]=255:g[t][e][s]<0&&(g[t][e][s]=0)}}h=[];for(let t=3*s,e=3*s+4;t{let o=r.getAttribute("id");o||(o="patchjs_shape"+n,r.setAttribute("id",o));const i=r.style.fill.match(/^url\(\s*"?\s*#([^\s"]+)"?\s*\)/),a=r.style.stroke.match(/^url\(\s*"?\s*#([^\s"]+)"?\s*\)/);if(i&&i[1]){const a=document.getElementById(i[1]);if(a&&"meshgradient"===a.nodeName){const i=r.getBBox();let l=document.createElementNS(s,"canvas");d(l,{width:i.width,height:i.height});const c=l.getContext("2d");let u=c.createImageData(i.width,i.height);const f=new b(a);"objectBoundingBox"===a.getAttribute("gradientUnits")&&f.scale(new x(i.width,i.height));const p=a.getAttribute("gradientTransform");null!=p&&f.transform(h(p)),"userSpaceOnUse"===a.getAttribute("gradientUnits")&&f.transform(new x(-i.x,-i.y)),f.paintMesh(u.data,l.width),c.putImageData(u,0,0);const y=document.createElementNS(t,"image");d(y,{width:i.width,height:i.height,x:i.x,y:i.y});let g=l.toDataURL();y.setAttributeNS(e,"xlink:href",g),r.parentNode.insertBefore(y,r),r.style.fill="none";const w=document.createElementNS(t,"use");w.setAttributeNS(e,"xlink:href","#"+o);const m="patchjs_clip"+n,M=document.createElementNS(t,"clipPath");M.setAttribute("id",m),M.appendChild(w),r.parentElement.insertBefore(M,r),y.setAttribute("clip-path","url(#"+m+")"),u=null,l=null,g=null}}if(a&&a[1]){const o=document.getElementById(a[1]);if(o&&"meshgradient"===o.nodeName){const i=parseFloat(r.style.strokeWidth.slice(0,-2))*(parseFloat(r.style.strokeMiterlimit)||parseFloat(r.getAttribute("stroke-miterlimit"))||1),a=r.getBBox(),l=Math.trunc(a.width+i),c=Math.trunc(a.height+i),u=Math.trunc(a.x-i/2),f=Math.trunc(a.y-i/2);let p=document.createElementNS(s,"canvas");d(p,{width:l,height:c});const y=p.getContext("2d");let g=y.createImageData(l,c);const w=new b(o);"objectBoundingBox"===o.getAttribute("gradientUnits")&&w.scale(new x(l,c));const m=o.getAttribute("gradientTransform");null!=m&&w.transform(h(m)),"userSpaceOnUse"===o.getAttribute("gradientUnits")&&w.transform(new x(-u,-f)),w.paintMesh(g.data,p.width),y.putImageData(g,0,0);const M=document.createElementNS(t,"image");d(M,{width:l,height:c,x:0,y:0});let S=p.toDataURL();M.setAttributeNS(e,"xlink:href",S);const k="pattern_clip"+n,A=document.createElementNS(t,"pattern");d(A,{id:k,patternUnits:"userSpaceOnUse",width:l,height:c,x:u,y:f}),A.appendChild(M),o.parentNode.appendChild(A),r.style.stroke="url(#"+k+")",g=null,p=null,S=null}}})}(); +)=====" -- cgit v1.2.3