diff options
| author | ruki <waruqi@gmail.com> | 2018-11-08 00:38:48 +0800 |
|---|---|---|
| committer | ruki <waruqi@gmail.com> | 2018-11-07 21:53:09 +0800 |
| commit | 26105034da4fcce7ac883c899d781f016559310d (patch) | |
| tree | c459a5dc4e3aa0972d9919033ece511ce76dd129 /node_modules/svgo/plugins | |
| parent | 2c77f00f1a7ecb6c8192f9c16d3b2001b254a107 (diff) | |
| download | xmake-docs-26105034da4fcce7ac883c899d781f016559310d.tar.gz xmake-docs-26105034da4fcce7ac883c899d781f016559310d.zip | |
switch to vuepress
Diffstat (limited to 'node_modules/svgo/plugins')
45 files changed, 8322 insertions, 0 deletions
diff --git a/node_modules/svgo/plugins/_collections.js b/node_modules/svgo/plugins/_collections.js new file mode 100644 index 00000000..8ff3d1fb --- /dev/null +++ b/node_modules/svgo/plugins/_collections.js @@ -0,0 +1,2529 @@ +'use strict'; + +// http://www.w3.org/TR/SVG/intro.html#Definitions +exports.elemsGroups = { + animation: ['animate', 'animateColor', 'animateMotion', 'animateTransform', 'set'], + descriptive: ['desc', 'metadata', 'title'], + shape: ['circle', 'ellipse', 'line', 'path', 'polygon', 'polyline', 'rect'], + structural: ['defs', 'g', 'svg', 'symbol', 'use'], + paintServer: ['solidColor', 'linearGradient', 'radialGradient', 'meshGradient', 'pattern', 'hatch'], + nonRendering: ['linearGradient', 'radialGradient', 'pattern', 'clipPath', 'mask', 'marker', 'symbol', 'filter', 'solidColor'], + container: ['a', 'defs', 'g', 'marker', 'mask', 'missing-glyph', 'pattern', 'svg', 'switch', 'symbol', 'foreignObject'], + textContent: ['altGlyph', 'altGlyphDef', 'altGlyphItem', 'glyph', 'glyphRef', 'textPath', 'text', 'tref', 'tspan'], + textContentChild: ['altGlyph', 'textPath', 'tref', 'tspan'], + lightSource: ['feDiffuseLighting', 'feSpecularLighting', 'feDistantLight', 'fePointLight', 'feSpotLight'], + filterPrimitive: ['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feFlood', 'feGaussianBlur', 'feImage', 'feMerge', 'feMorphology', 'feOffset', 'feSpecularLighting', 'feTile', 'feTurbulence'] +}; + +exports.pathElems = ['path', 'glyph', 'missing-glyph']; + +// http://www.w3.org/TR/SVG/intro.html#Definitions +exports.attrsGroups = { + animationAddition: ['additive', 'accumulate'], + animationAttributeTarget: ['attributeType', 'attributeName'], + animationEvent: ['onbegin', 'onend', 'onrepeat', 'onload'], + animationTiming: ['begin', 'dur', 'end', 'min', 'max', 'restart', 'repeatCount', 'repeatDur', 'fill'], + animationValue: ['calcMode', 'values', 'keyTimes', 'keySplines', 'from', 'to', 'by'], + conditionalProcessing: ['requiredFeatures', 'requiredExtensions', 'systemLanguage'], + core: ['id', 'tabindex', 'xml:base', 'xml:lang', 'xml:space'], + graphicalEvent: ['onfocusin', 'onfocusout', 'onactivate', 'onclick', 'onmousedown', 'onmouseup', 'onmouseover', 'onmousemove', 'onmouseout', 'onload'], + presentation: [ + 'alignment-baseline', + 'baseline-shift', + 'buffered-rendering', + 'clip', + 'clip-path', + 'clip-rule', + 'color', + 'color-interpolation', + 'color-interpolation-filters', + 'color-profile', + 'color-rendering', + 'cursor', + 'direction', + 'display', + 'dominant-baseline', + 'enable-background', + 'fill', + 'fill-opacity', + 'fill-rule', + 'filter', + 'flood-color', + 'flood-opacity', + 'font-family', + 'font-size', + 'font-size-adjust', + 'font-stretch', + 'font-style', + 'font-variant', + 'font-weight', + 'glyph-orientation-horizontal', + 'glyph-orientation-vertical', + 'image-rendering', + 'kerning', + 'letter-spacing', + 'lighting-color', + 'marker-end', + 'marker-mid', + 'marker-start', + 'mask', + 'opacity', + 'overflow', + 'pointer-events', + 'shape-rendering', + 'solid-color', + 'solid-opacity', + 'stop-color', + 'stop-opacity', + 'stroke', + 'stroke-dasharray', + 'stroke-dashoffset', + 'stroke-linecap', + 'stroke-linejoin', + 'stroke-miterlimit', + 'stroke-opacity', + 'stroke-width', + 'paint-order', + 'text-anchor', + 'text-decoration', + 'text-overflow', + 'white-space', + 'text-rendering', + 'unicode-bidi', + 'vector-effect', + 'viewport-fill', + 'viewport-fill-opacity', + 'visibility', + 'white-space', + 'word-spacing', + 'writing-mode' + ], + xlink: ['xlink:href', 'xlink:show', 'xlink:actuate', 'xlink:type', 'xlink:role', 'xlink:arcrole', 'xlink:title'], + documentEvent: ['onunload', 'onabort', 'onerror', 'onresize', 'onscroll', 'onzoom'], + filterPrimitive: ['x', 'y', 'width', 'height', 'result'], + transferFunction: ['type', 'tableValues', 'slope', 'intercept', 'amplitude', 'exponent', 'offset'] +}; + +exports.attrsGroupsDefaults = { + core: {'xml:space': 'preserve'}, + filterPrimitive: {x: '0', y: '0', width: '100%', height: '100%'}, + presentation: { + clip: 'auto', + 'clip-path': 'none', + 'clip-rule': 'nonzero', + mask: 'none', + opacity: '1', + 'solid-color': '#000', + 'solid-opacity': '1', + 'stop-color': '#000', + 'stop-opacity': '1', + 'fill-opacity': '1', + 'fill-rule': 'nonzero', + fill: '#000', + stroke: 'none', + 'stroke-width': '1', + 'stroke-linecap': 'butt', + 'stroke-linejoin': 'miter', + 'stroke-miterlimit': '4', + 'stroke-dasharray': 'none', + 'stroke-dashoffset': '0', + 'stroke-opacity': '1', + 'paint-order': 'normal', + 'vector-effect': 'none', + 'viewport-fill': 'none', + 'viewport-fill-opacity': '1', + display: 'inline', + visibility: 'visible', + 'marker-start': 'none', + 'marker-mid': 'none', + 'marker-end': 'none', + 'color-interpolation': 'sRGB', + 'color-interpolation-filters': 'linearRGB', + 'color-rendering': 'auto', + 'shape-rendering': 'auto', + 'text-rendering': 'auto', + 'image-rendering': 'auto', + 'buffered-rendering': 'auto', + 'font-style': 'normal', + 'font-variant': 'normal', + 'font-weight': 'normal', + 'font-stretch': 'normal', + 'font-size': 'medium', + 'font-size-adjust': 'none', + kerning: 'auto', + 'letter-spacing': 'normal', + 'word-spacing': 'normal', + 'text-decoration': 'none', + 'text-anchor': 'start', + 'text-overflow': 'clip', + 'writing-mode': 'lr-tb', + 'glyph-orientation-vertical': 'auto', + 'glyph-orientation-horizontal': '0deg', + direction: 'ltr', + 'unicode-bidi': 'normal', + 'dominant-baseline': 'auto', + 'alignment-baseline': 'baseline', + 'baseline-shift': 'baseline' + }, + transferFunction: {slope: '1', intercept: '0', amplitude: '1', exponent: '1', offset: '0'} +}; + +// http://www.w3.org/TR/SVG/eltindex.html +exports.elems = { + a: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'graphicalEvent', + 'presentation', + 'xlink' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'transform', + 'target' + ], + defaults: { + target: '_self' + }, + contentGroups: [ + 'animation', + 'descriptive', + 'shape', + 'structural', + 'paintServer' + ], + content: [ + 'a', + 'altGlyphDef', + 'clipPath', + 'color-profile', + 'cursor', + 'filter', + 'font', + 'font-face', + 'foreignObject', + 'image', + 'marker', + 'mask', + 'pattern', + 'script', + 'style', + 'switch', + 'text', + 'view' + ] + }, + altGlyph: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'graphicalEvent', + 'presentation', + 'xlink' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'x', + 'y', + 'dx', + 'dy', + 'glyphRef', + 'format', + 'rotate' + ] + }, + altGlyphDef: { + attrsGroups: [ + 'core' + ], + content: [ + 'glyphRef' + ] + }, + altGlyphItem: { + attrsGroups: [ + 'core' + ], + content: [ + 'glyphRef', + 'altGlyphItem' + ] + }, + animate: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'animationAddition', + 'animationAttributeTarget', + 'animationEvent', + 'animationTiming', + 'animationValue', + 'presentation', + 'xlink' + ], + attrs: [ + 'externalResourcesRequired' + ], + contentGroups: [ + 'descriptive' + ] + }, + animateColor: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'animationEvent', + 'xlink', + 'animationAttributeTarget', + 'animationTiming', + 'animationValue', + 'animationAddition', + 'presentation' + ], + attrs: [ + 'externalResourcesRequired' + ], + contentGroups: [ + 'descriptive' + ] + }, + animateMotion: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'animationEvent', + 'xlink', + 'animationTiming', + 'animationValue', + 'animationAddition' + ], + attrs: [ + 'externalResourcesRequired', + 'path', + 'keyPoints', + 'rotate', + 'origin' + ], + defaults: { + 'rotate': '0' + }, + contentGroups: [ + 'descriptive' + ], + content: [ + 'mpath' + ] + }, + animateTransform: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'animationEvent', + 'xlink', + 'animationAttributeTarget', + 'animationTiming', + 'animationValue', + 'animationAddition' + ], + attrs: [ + 'externalResourcesRequired', + 'type' + ], + contentGroups: [ + 'descriptive' + ] + }, + circle: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'graphicalEvent', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'transform', + 'cx', + 'cy', + 'r' + ], + defaults: { + cx: '0', + cy: '0' + }, + contentGroups: [ + 'animation', + 'descriptive' + ] + }, + clipPath: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'transform', + 'clipPathUnits' + ], + defaults: { + clipPathUnits: 'userSpaceOnUse' + }, + contentGroups: [ + 'animation', + 'descriptive', + 'shape' + ], + content: [ + 'text', + 'use' + ] + }, + 'color-profile': { + attrsGroups: [ + 'core', + 'xlink' + ], + attrs: [ + 'local', + 'name', + 'rendering-intent' + ], + defaults: { + name: 'sRGB', + 'rendering-intent': 'auto' + }, + contentGroups: [ + 'descriptive' + ] + }, + cursor: { + attrsGroups: [ + 'core', + 'conditionalProcessing', + 'xlink' + ], + attrs: [ + 'externalResourcesRequired', + 'x', + 'y' + ], + defaults: { + x: '0', + y: '0' + }, + contentGroups: [ + 'descriptive' + ] + }, + defs: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'graphicalEvent', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'transform' + ], + contentGroups: [ + 'animation', + 'descriptive', + 'shape', + 'structural', + 'paintServer' + ], + content: [ + 'a', + 'altGlyphDef', + 'clipPath', + 'color-profile', + 'cursor', + 'filter', + 'font', + 'font-face', + 'foreignObject', + 'image', + 'marker', + 'mask', + 'pattern', + 'script', + 'style', + 'switch', + 'text', + 'view' + ] + }, + desc: { + attrsGroups: [ + 'core' + ], + attrs: [ + 'class', + 'style' + ] + }, + ellipse: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'graphicalEvent', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'transform', + 'cx', + 'cy', + 'rx', + 'ry' + ], + defaults: { + cx: '0', + cy: '0' + }, + contentGroups: [ + 'animation', + 'descriptive' + ] + }, + feBlend: { + attrsGroups: [ + 'core', + 'presentation', + 'filterPrimitive' + ], + attrs: [ + 'class', + 'style', + // TODO: in - 'If no value is provided and this is the first filter primitive, + // then this filter primitive will use SourceGraphic as its input' + 'in', + 'in2', + 'mode' + ], + defaults: { + mode: 'normal' + }, + content: [ + 'animate', + 'set' + ] + }, + feColorMatrix: { + attrsGroups: [ + 'core', + 'presentation', + 'filterPrimitive' + ], + attrs: [ + 'class', + 'style', + 'in', + 'type', + 'values' + ], + defaults: { + type: 'matrix' + }, + content: [ + 'animate', + 'set' + ] + }, + feComponentTransfer: { + attrsGroups: [ + 'core', + 'presentation', + 'filterPrimitive' + ], + attrs: [ + 'class', + 'style', + 'in' + ], + content: [ + 'feFuncA', + 'feFuncB', + 'feFuncG', + 'feFuncR' + ] + }, + feComposite: { + attrsGroups: [ + 'core', + 'presentation', + 'filterPrimitive' + ], + attrs: [ + 'class', + 'style', + 'in', + 'in2', + 'operator', + 'k1', + 'k2', + 'k3', + 'k4' + ], + defaults: { + operator: 'over', + k1: '0', + k2: '0', + k3: '0', + k4: '0' + }, + content: [ + 'animate', + 'set' + ] + }, + feConvolveMatrix: { + attrsGroups: [ + 'core', + 'presentation', + 'filterPrimitive' + ], + attrs: [ + 'class', + 'style', + 'in', + 'order', + 'kernelMatrix', + // TODO: divisor - 'The default value is the sum of all values in kernelMatrix, + // with the exception that if the sum is zero, then the divisor is set to 1' + 'divisor', + 'bias', + // TODO: targetX - 'By default, the convolution matrix is centered in X over each + // pixel of the input image (i.e., targetX = floor ( orderX / 2 ))' + 'targetX', + 'targetY', + 'edgeMode', + // TODO: kernelUnitLength - 'The first number is the <dx> value. The second number + // is the <dy> value. If the <dy> value is not specified, it defaults to the same value as <dx>' + 'kernelUnitLength', + 'preserveAlpha' + ], + defaults: { + order: '3', + bias: '0', + edgeMode: 'duplicate', + preserveAlpha: 'false' + }, + content: [ + 'animate', + 'set' + ] + }, + feDiffuseLighting: { + attrsGroups: [ + 'core', + 'presentation', + 'filterPrimitive' + ], + attrs: [ + 'class', + 'style', + 'in', + 'surfaceScale', + 'diffuseConstant', + 'kernelUnitLength' + ], + defaults: { + surfaceScale: '1', + diffuseConstant: '1' + }, + contentGroups: [ + 'descriptive' + ], + content: [ + // TODO: 'exactly one light source element, in any order' + 'feDistantLight', + 'fePointLight', + 'feSpotLight' + ] + }, + feDisplacementMap: { + attrsGroups: [ + 'core', + 'presentation', + 'filterPrimitive' + ], + attrs: [ + 'class', + 'style', + 'in', + 'in2', + 'scale', + 'xChannelSelector', + 'yChannelSelector' + ], + defaults: { + scale: '0', + xChannelSelector: 'A', + yChannelSelector: 'A' + }, + content: [ + 'animate', + 'set' + ] + }, + feDistantLight: { + attrsGroups: [ + 'core' + ], + attrs: [ + 'azimuth', + 'elevation' + ], + defaults: { + azimuth: '0', + elevation: '0' + }, + content: [ + 'animate', + 'set' + ] + }, + feFlood: { + attrsGroups: [ + 'core', + 'presentation', + 'filterPrimitive' + ], + attrs: [ + 'class', + 'style' + ], + content: [ + 'animate', + 'animateColor', + 'set' + ] + }, + feFuncA: { + attrsGroups: [ + 'core', + 'transferFunction' + ], + content: [ + 'set', + 'animate' + ] + }, + feFuncB: { + attrsGroups: [ + 'core', + 'transferFunction' + ], + content: [ + 'set', + 'animate' + ] + }, + feFuncG: { + attrsGroups: [ + 'core', + 'transferFunction' + ], + content: [ + 'set', + 'animate' + ] + }, + feFuncR: { + attrsGroups: [ + 'core', + 'transferFunction' + ], + content: [ + 'set', + 'animate' + ] + }, + feGaussianBlur: { + attrsGroups: [ + 'core', + 'presentation', + 'filterPrimitive' + ], + attrs: [ + 'class', + 'style', + 'in', + 'stdDeviation' + ], + defaults: { + stdDeviation: '0' + }, + content: [ + 'set', + 'animate' + ] + }, + feImage: { + attrsGroups: [ + 'core', + 'presentation', + 'filterPrimitive', + 'xlink' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'preserveAspectRatio', + 'xlink:href' + ], + defaults: { + preserveAspectRatio: 'xMidYMid meet' + }, + content: [ + 'animate', + 'animateTransform', + 'set' + ] + }, + feMerge: { + attrsGroups: [ + 'core', + 'presentation', + 'filterPrimitive' + ], + attrs: [ + 'class', + 'style' + ], + content: [ + 'feMergeNode' + ] + }, + feMergeNode: { + attrsGroups: [ + 'core' + ], + attrs: [ + 'in' + ], + content: [ + 'animate', + 'set' + ] + }, + feMorphology: { + attrsGroups: [ + 'core', + 'presentation', + 'filterPrimitive' + ], + attrs: [ + 'class', + 'style', + 'in', + 'operator', + 'radius' + ], + defaults: { + operator: 'erode', + radius: '0' + }, + content: [ + 'animate', + 'set' + ] + }, + feOffset: { + attrsGroups: [ + 'core', + 'presentation', + 'filterPrimitive' + ], + attrs: [ + 'class', + 'style', + 'in', + 'dx', + 'dy' + ], + defaults: { + dx: '0', + dy: '0' + }, + content: [ + 'animate', + 'set' + ] + }, + fePointLight: { + attrsGroups: [ + 'core' + ], + attrs: [ + 'x', + 'y', + 'z' + ], + defaults: { + x: '0', + y: '0', + z: '0' + }, + content: [ + 'animate', + 'set' + ] + }, + feSpecularLighting: { + attrsGroups: [ + 'core', + 'presentation', + 'filterPrimitive' + ], + attrs: [ + 'class', + 'style', + 'in', + 'surfaceScale', + 'specularConstant', + 'specularExponent', + 'kernelUnitLength' + ], + defaults: { + surfaceScale: '1', + specularConstant: '1', + specularExponent: '1' + }, + contentGroups: [ + 'descriptive', + // TODO: exactly one 'light source element' + 'lightSource' + ] + }, + feSpotLight: { + attrsGroups: [ + 'core' + ], + attrs: [ + 'x', + 'y', + 'z', + 'pointsAtX', + 'pointsAtY', + 'pointsAtZ', + 'specularExponent', + 'limitingConeAngle' + ], + defaults: { + x: '0', + y: '0', + z: '0', + pointsAtX: '0', + pointsAtY: '0', + pointsAtZ: '0', + specularExponent: '1' + }, + content: [ + 'animate', + 'set' + ] + }, + feTile: { + attrsGroups: [ + 'core', + 'presentation', + 'filterPrimitive' + ], + attrs: [ + 'class', + 'style', + 'in' + ], + content: [ + 'animate', + 'set' + ] + }, + feTurbulence: { + attrsGroups: [ + 'core', + 'presentation', + 'filterPrimitive' + ], + attrs: [ + 'class', + 'style', + 'baseFrequency', + 'numOctaves', + 'seed', + 'stitchTiles', + 'type' + ], + defaults: { + baseFrequency: '0', + numOctaves: '1', + seed: '0', + stitchTiles: 'noStitch', + type: 'turbulence' + }, + content: [ + 'animate', + 'set' + ] + }, + filter: { + attrsGroups: [ + 'core', + 'presentation', + 'xlink' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'x', + 'y', + 'width', + 'height', + 'filterRes', + 'filterUnits', + 'primitiveUnits', + 'xlink:href' + ], + defaults: { + primitiveUnits: 'userSpaceOnUse', + x: '-10%', + y: '-10%', + width: '120%', + height: '120%' + }, + contentGroups: [ + 'descriptive', + 'filterPrimitive' + ], + content: [ + 'animate', + 'set' + ] + }, + font: { + attrsGroups: [ + 'core', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'horiz-origin-x', + 'horiz-origin-y', + 'horiz-adv-x', + 'vert-origin-x', + 'vert-origin-y', + 'vert-adv-y' + ], + defaults: { + 'horiz-origin-x': '0', + 'horiz-origin-y': '0' + }, + contentGroups: [ + 'descriptive' + ], + content: [ + 'font-face', + 'glyph', + 'hkern', + 'missing-glyph', + 'vkern' + ] + }, + 'font-face': { + attrsGroups: [ + 'core' + ], + attrs: [ + 'font-family', + 'font-style', + 'font-variant', + 'font-weight', + 'font-stretch', + 'font-size', + 'unicode-range', + 'units-per-em', + 'panose-1', + 'stemv', + 'stemh', + 'slope', + 'cap-height', + 'x-height', + 'accent-height', + 'ascent', + 'descent', + 'widths', + 'bbox', + 'ideographic', + 'alphabetic', + 'mathematical', + 'hanging', + 'v-ideographic', + 'v-alphabetic', + 'v-mathematical', + 'v-hanging', + 'underline-position', + 'underline-thickness', + 'strikethrough-position', + 'strikethrough-thickness', + 'overline-position', + 'overline-thickness' + ], + defaults: { + 'font-style': 'all', + 'font-variant': 'normal', + 'font-weight': 'all', + 'font-stretch': 'normal', + 'unicode-range': 'U+0-10FFFF', + 'units-per-em': '1000', + 'panose-1': '0 0 0 0 0 0 0 0 0 0', + 'slope': '0' + }, + contentGroups: [ + 'descriptive' + ], + content: [ + // TODO: "at most one 'font-face-src' element" + 'font-face-src' + ] + }, + // TODO: empty content + 'font-face-format': { + attrsGroups: [ + 'core' + ], + attrs: [ + 'string' + ] + }, + 'font-face-name': { + attrsGroups: [ + 'core' + ], + attrs: [ + 'name' + ] + }, + 'font-face-src': { + attrsGroups: [ + 'core' + ], + content: [ + 'font-face-name', + 'font-face-uri' + ] + }, + 'font-face-uri': { + attrsGroups: [ + 'core', + 'xlink' + ], + attrs: [ + 'xlink:href' + ], + content: [ + 'font-face-format' + ] + }, + foreignObject: { + attrsGroups: [ + 'core', + 'conditionalProcessing', + 'graphicalEvent', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'transform', + 'x', + 'y', + 'width', + 'height' + ], + defaults: { + x: 0, + y: 0 + } + }, + g: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'graphicalEvent', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'transform' + ], + contentGroups: [ + 'animation', + 'descriptive', + 'shape', + 'structural', + 'paintServer' + ], + content: [ + 'a', + 'altGlyphDef', + 'clipPath', + 'color-profile', + 'cursor', + 'filter', + 'font', + 'font-face', + 'foreignObject', + 'image', + 'marker', + 'mask', + 'pattern', + 'script', + 'style', + 'switch', + 'text', + 'view' + ] + }, + glyph: { + attrsGroups: [ + 'core', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'd', + 'horiz-adv-x', + 'vert-origin-x', + 'vert-origin-y', + 'vert-adv-y', + 'unicode', + 'glyph-name', + 'orientation', + 'arabic-form', + 'lang' + ], + defaults: { + 'arabic-form': 'initial' + }, + contentGroups: [ + 'animation', + 'descriptive', + 'shape', + 'structural', + 'paintServer' + ], + content: [ + 'a', + 'altGlyphDef', + 'clipPath', + 'color-profile', + 'cursor', + 'filter', + 'font', + 'font-face', + 'foreignObject', + 'image', + 'marker', + 'mask', + 'pattern', + 'script', + 'style', + 'switch', + 'text', + 'view' + ], + }, + glyphRef: { + attrsGroups: [ + 'core', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'd', + 'horiz-adv-x', + 'vert-origin-x', + 'vert-origin-y', + 'vert-adv-y' + ], + contentGroups: [ + 'animation', + 'descriptive', + 'shape', + 'structural', + 'paintServer' + ], + content: [ + 'a', + 'altGlyphDef', + 'clipPath', + 'color-profile', + 'cursor', + 'filter', + 'font', + 'font-face', + 'foreignObject', + 'image', + 'marker', + 'mask', + 'pattern', + 'script', + 'style', + 'switch', + 'text', + 'view' + ] + }, + hatch: { + attrsGroups: [ + 'core', + 'presentation', + 'xlink' + ], + attrs: [ + 'class', + 'style', + 'x', + 'y', + 'pitch', + 'rotate', + 'hatchUnits', + 'hatchContentUnits', + 'transform' + ], + defaults: { + hatchUnits: 'objectBoundingBox', + hatchContentUnits: 'userSpaceOnUse', + x: '0', + y: '0', + pitch: '0', + rotate: '0' + }, + contentGroups: [ + 'animation', + 'descriptive' + ], + content: [ + 'hatchPath' + ] + }, + hatchPath: { + attrsGroups: [ + 'core', + 'presentation', + 'xlink' + ], + attrs: [ + 'class', + 'style', + 'd', + 'offset' + ], + defaults: { + offset: '0' + }, + contentGroups: [ + 'animation', + 'descriptive' + ] + }, + hkern: { + attrsGroups: [ + 'core' + ], + attrs: [ + 'u1', + 'g1', + 'u2', + 'g2', + 'k' + ] + }, + image: { + attrsGroups: [ + 'core', + 'conditionalProcessing', + 'graphicalEvent', + 'xlink', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'preserveAspectRatio', + 'transform', + 'x', + 'y', + 'width', + 'height', + 'xlink:href' + ], + defaults: { + x: '0', + y: '0', + preserveAspectRatio: 'xMidYMid meet' + }, + contentGroups: [ + 'animation', + 'descriptive' + ] + }, + line: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'graphicalEvent', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'transform', + 'x1', + 'y1', + 'x2', + 'y2' + ], + defaults: { + x1: '0', + y1: '0', + x2: '0', + y2: '0' + }, + contentGroups: [ + 'animation', + 'descriptive' + ] + }, + linearGradient: { + attrsGroups: [ + 'core', + 'presentation', + 'xlink' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'x1', + 'y1', + 'x2', + 'y2', + 'gradientUnits', + 'gradientTransform', + 'spreadMethod', + 'xlink:href' + ], + defaults: { + x1: '0', + y1: '0', + x2: '100%', + y2: '0', + spreadMethod: 'pad' + }, + contentGroups: [ + 'descriptive' + ], + content: [ + 'animate', + 'animateTransform', + 'set', + 'stop' + ] + }, + marker: { + attrsGroups: [ + 'core', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'viewBox', + 'preserveAspectRatio', + 'refX', + 'refY', + 'markerUnits', + 'markerWidth', + 'markerHeight', + 'orient' + ], + defaults: { + markerUnits: 'strokeWidth', + refX: '0', + refY: '0', + markerWidth: '3', + markerHeight: '3' + }, + contentGroups: [ + 'animation', + 'descriptive', + 'shape', + 'structural', + 'paintServer' + ], + content: [ + 'a', + 'altGlyphDef', + 'clipPath', + 'color-profile', + 'cursor', + 'filter', + 'font', + 'font-face', + 'foreignObject', + 'image', + 'marker', + 'mask', + 'pattern', + 'script', + 'style', + 'switch', + 'text', + 'view' + ] + }, + mask: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'x', + 'y', + 'width', + 'height', + 'maskUnits', + 'maskContentUnits' + ], + defaults: { + maskUnits: 'objectBoundingBox', + maskContentUnits: 'userSpaceOnUse', + x: '-10%', + y: '-10%', + width: '120%', + height: '120%' + }, + contentGroups: [ + 'animation', + 'descriptive', + 'shape', + 'structural', + 'paintServer' + ], + content: [ + 'a', + 'altGlyphDef', + 'clipPath', + 'color-profile', + 'cursor', + 'filter', + 'font', + 'font-face', + 'foreignObject', + 'image', + 'marker', + 'mask', + 'pattern', + 'script', + 'style', + 'switch', + 'text', + 'view' + ] + }, + metadata: { + attrsGroups: [ + 'core' + ] + }, + 'missing-glyph': { + attrsGroups: [ + 'core', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'd', + 'horiz-adv-x', + 'vert-origin-x', + 'vert-origin-y', + 'vert-adv-y' + ], + contentGroups: [ + 'animation', + 'descriptive', + 'shape', + 'structural', + 'paintServer' + ], + content: [ + 'a', + 'altGlyphDef', + 'clipPath', + 'color-profile', + 'cursor', + 'filter', + 'font', + 'font-face', + 'foreignObject', + 'image', + 'marker', + 'mask', + 'pattern', + 'script', + 'style', + 'switch', + 'text', + 'view' + ] + }, + mpath: { + attrsGroups: [ + 'core', + 'xlink' + ], + attrs: [ + 'externalResourcesRequired', + 'xlink:href' + ], + contentGroups: [ + 'descriptive' + ] + }, + path: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'graphicalEvent', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'transform', + 'd', + 'pathLength' + ], + contentGroups: [ + 'animation', + 'descriptive' + ] + }, + pattern: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'presentation', + 'xlink' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'viewBox', + 'preserveAspectRatio', + 'x', + 'y', + 'width', + 'height', + 'patternUnits', + 'patternContentUnits', + 'patternTransform', + 'xlink:href' + ], + defaults: { + patternUnits: 'objectBoundingBox', + patternContentUnits: 'userSpaceOnUse', + x: '0', + y: '0', + width: '0', + height: '0', + preserveAspectRatio: 'xMidYMid meet' + }, + contentGroups: [ + 'animation', + 'descriptive', + 'paintServer', + 'shape', + 'structural' + ], + content: [ + 'a', + 'altGlyphDef', + 'clipPath', + 'color-profile', + 'cursor', + 'filter', + 'font', + 'font-face', + 'foreignObject', + 'image', + 'marker', + 'mask', + 'pattern', + 'script', + 'style', + 'switch', + 'text', + 'view' + ] + }, + polygon: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'graphicalEvent', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'transform', + 'points' + ], + contentGroups: [ + 'animation', + 'descriptive' + ] + }, + polyline: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'graphicalEvent', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'transform', + 'points' + ], + contentGroups: [ + 'animation', + 'descriptive' + ] + }, + radialGradient: { + attrsGroups: [ + 'core', + 'presentation', + 'xlink' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'cx', + 'cy', + 'r', + 'fx', + 'fy', + 'fr', + 'gradientUnits', + 'gradientTransform', + 'spreadMethod', + 'xlink:href' + ], + defaults: { + gradientUnits: 'objectBoundingBox', + cx: '50%', + cy: '50%', + r: '50%' + }, + contentGroups: [ + 'descriptive' + ], + content: [ + 'animate', + 'animateTransform', + 'set', + 'stop' + ] + }, + meshGradient: { + attrsGroups: [ + 'core', + 'presentation', + 'xlink' + ], + attrs: [ + 'class', + 'style', + 'x', + 'y', + 'gradientUnits', + 'transform' + ], + contentGroups: [ + 'descriptive', + 'paintServer', + 'animation', + ], + content: [ + 'meshRow' + ] + }, + meshRow: { + attrsGroups: [ + 'core', + 'presentation' + ], + attrs: [ + 'class', + 'style' + ], + contentGroups: [ + 'descriptive' + ], + content: [ + 'meshPatch' + ] + }, + meshPatch: { + attrsGroups: [ + 'core', + 'presentation' + ], + attrs: [ + 'class', + 'style' + ], + contentGroups: [ + 'descriptive' + ], + content: [ + 'stop' + ] + }, + rect: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'graphicalEvent', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'transform', + 'x', + 'y', + 'width', + 'height', + 'rx', + 'ry' + ], + defaults: { + x: '0', + y: '0' + }, + contentGroups: [ + 'animation', + 'descriptive' + ] + }, + script: { + attrsGroups: [ + 'core', + 'xlink' + ], + attrs: [ + 'externalResourcesRequired', + 'type', + 'xlink:href' + ] + }, + set: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'animation', + 'xlink', + 'animationAttributeTarget', + 'animationTiming', + ], + attrs: [ + 'externalResourcesRequired', + 'to' + ], + contentGroups: [ + 'descriptive' + ] + }, + solidColor: { + attrsGroups: [ + 'core', + 'presentation' + ], + attrs: [ + 'class', + 'style' + ], + contentGroups: [ + 'paintServer' + ] + }, + stop: { + attrsGroups: [ + 'core', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'offset', + 'path' + ], + content: [ + 'animate', + 'animateColor', + 'set' + ] + }, + style: { + attrsGroups: [ + 'core' + ], + attrs: [ + 'type', + 'media', + 'title' + ], + defaults: { + type: 'text/css' + } + }, + svg: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'documentEvent', + 'graphicalEvent', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'x', + 'y', + 'width', + 'height', + 'viewBox', + 'preserveAspectRatio', + 'zoomAndPan', + 'version', + 'baseProfile', + 'contentScriptType', + 'contentStyleType' + ], + defaults: { + x: '0', + y: '0', + width: '100%', + height: '100%', + preserveAspectRatio: 'xMidYMid meet', + zoomAndPan: 'magnify', + version: '1.1', + baseProfile: 'none', + contentScriptType: 'application/ecmascript', + contentStyleType: 'text/css' + }, + contentGroups: [ + 'animation', + 'descriptive', + 'shape', + 'structural', + 'paintServer' + ], + content: [ + 'a', + 'altGlyphDef', + 'clipPath', + 'color-profile', + 'cursor', + 'filter', + 'font', + 'font-face', + 'foreignObject', + 'image', + 'marker', + 'mask', + 'pattern', + 'script', + 'style', + 'switch', + 'text', + 'view' + ] + }, + switch: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'graphicalEvent', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'transform' + ], + contentGroups: [ + 'animation', + 'descriptive', + 'shape' + ], + content: [ + 'a', + 'foreignObject', + 'g', + 'image', + 'svg', + 'switch', + 'text', + 'use' + ] + }, + symbol: { + attrsGroups: [ + 'core', + 'graphicalEvent', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'preserveAspectRatio', + 'viewBox', + 'refX', + 'refY' + ], + defaults: { + refX: 0, + refY: 0 + }, + contentGroups: [ + 'animation', + 'descriptive', + 'shape', + 'structural', + 'paintServer' + ], + content: [ + 'a', + 'altGlyphDef', + 'clipPath', + 'color-profile', + 'cursor', + 'filter', + 'font', + 'font-face', + 'foreignObject', + 'image', + 'marker', + 'mask', + 'pattern', + 'script', + 'style', + 'switch', + 'text', + 'view' + ] + }, + text: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'graphicalEvent', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'transform', + 'lengthAdjust', + 'x', + 'y', + 'dx', + 'dy', + 'rotate', + 'textLength' + ], + defaults: { + x: '0', + y: '0', + lengthAdjust: 'spacing' + }, + contentGroups: [ + 'animation', + 'descriptive', + 'textContentChild' + ], + content: [ + 'a' + ] + }, + textPath: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'graphicalEvent', + 'presentation', + 'xlink' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'xlink:href', + 'startOffset', + 'method', + 'spacing', + 'd' + ], + defaults: { + startOffset: '0', + method: 'align', + spacing: 'exact' + }, + contentGroups: [ + 'descriptive' + ], + content: [ + 'a', + 'altGlyph', + 'animate', + 'animateColor', + 'set', + 'tref', + 'tspan' + ] + }, + title: { + attrsGroups: [ + 'core' + ], + attrs: [ + 'class', + 'style' + ] + }, + tref: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'graphicalEvent', + 'presentation', + 'xlink' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'xlink:href' + ], + contentGroups: [ + 'descriptive' + ], + content: [ + 'animate', + 'animateColor', + 'set' + ] + }, + tspan: { + attrsGroups: [ + 'conditionalProcessing', + 'core', + 'graphicalEvent', + 'presentation' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'x', + 'y', + 'dx', + 'dy', + 'rotate', + 'textLength', + 'lengthAdjust' + ], + contentGroups: [ + 'descriptive' + ], + content: [ + 'a', + 'altGlyph', + 'animate', + 'animateColor', + 'set', + 'tref', + 'tspan' + ] + }, + use: { + attrsGroups: [ + 'core', + 'conditionalProcessing', + 'graphicalEvent', + 'presentation', + 'xlink' + ], + attrs: [ + 'class', + 'style', + 'externalResourcesRequired', + 'transform', + 'x', + 'y', + 'width', + 'height', + 'xlink:href' + ], + defaults: { + x: '0', + y: '0' + }, + contentGroups: [ + 'animation', + 'descriptive' + ] + }, + view: { + attrsGroups: [ + 'core' + ], + attrs: [ + 'externalResourcesRequired', + 'viewBox', + 'preserveAspectRatio', + 'zoomAndPan', + 'viewTarget' + ], + contentGroups: [ + 'descriptive' + ] + }, + vkern: { + attrsGroups: [ + 'core' + ], + attrs: [ + 'u1', + 'g1', + 'u2', + 'g2', + 'k' + ] + } +}; + +// http://wiki.inkscape.org/wiki/index.php/Inkscape-specific_XML_attributes +exports.editorNamespaces = [ + 'http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd', + 'http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd', + 'http://www.inkscape.org/namespaces/inkscape', + 'http://www.bohemiancoding.com/sketch/ns', + 'http://ns.adobe.com/AdobeIllustrator/10.0/', + 'http://ns.adobe.com/Graphs/1.0/', + 'http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/', + 'http://ns.adobe.com/Variables/1.0/', + 'http://ns.adobe.com/SaveForWeb/1.0/', + 'http://ns.adobe.com/Extensibility/1.0/', + 'http://ns.adobe.com/Flows/1.0/', + 'http://ns.adobe.com/ImageReplacement/1.0/', + 'http://ns.adobe.com/GenericCustomNamespace/1.0/', + 'http://ns.adobe.com/XPath/1.0/' +]; + +// http://www.w3.org/TR/SVG/linking.html#processingIRI +exports.referencesProps = [ + 'clip-path', + 'color-profile', + 'fill', + 'filter', + 'marker-start', + 'marker-mid', + 'marker-end', + 'mask', + 'stroke', + 'style' +]; + +// http://www.w3.org/TR/SVG/propidx.html +exports.inheritableAttrs = [ + 'clip-rule', + 'color', + 'color-interpolation', + 'color-interpolation-filters', + 'color-profile', + 'color-rendering', + 'cursor', + 'direction', + 'fill', + 'fill-opacity', + 'fill-rule', + 'font', + 'font-family', + 'font-size', + 'font-size-adjust', + 'font-stretch', + 'font-style', + 'font-variant', + 'font-weight', + 'glyph-orientation-horizontal', + 'glyph-orientation-vertical', + 'image-rendering', + 'kerning', + 'letter-spacing', + 'marker', + 'marker-end', + 'marker-mid', + 'marker-start', + 'pointer-events', + 'shape-rendering', + 'stroke', + 'stroke-dasharray', + 'stroke-dashoffset', + 'stroke-linecap', + 'stroke-linejoin', + 'stroke-miterlimit', + 'stroke-opacity', + 'stroke-width', + 'text-anchor', + 'text-rendering', + 'transform', + 'visibility', + 'white-space', + 'word-spacing', + 'writing-mode' +]; + +// http://www.w3.org/TR/SVG/single-page.html#types-ColorKeywords +exports.colorsNames = { + 'aliceblue': '#f0f8ff', + 'antiquewhite': '#faebd7', + 'aqua': '#0ff', + 'aquamarine': '#7fffd4', + 'azure': '#f0ffff', + 'beige': '#f5f5dc', + 'bisque': '#ffe4c4', + 'black': '#000', + 'blanchedalmond': '#ffebcd', + 'blue': '#00f', + 'blueviolet': '#8a2be2', + 'brown': '#a52a2a', + 'burlywood': '#deb887', + 'cadetblue': '#5f9ea0', + 'chartreuse': '#7fff00', + 'chocolate': '#d2691e', + 'coral': '#ff7f50', + 'cornflowerblue': '#6495ed', + 'cornsilk': '#fff8dc', + 'crimson': '#dc143c', + 'cyan': '#0ff', + 'darkblue': '#00008b', + 'darkcyan': '#008b8b', + 'darkgoldenrod': '#b8860b', + 'darkgray': '#a9a9a9', + 'darkgreen': '#006400', + 'darkkhaki': '#bdb76b', + 'darkmagenta': '#8b008b', + 'darkolivegreen': '#556b2f', + 'darkorange': '#ff8c00', + 'darkorchid': '#9932cc', + 'darkred': '#8b0000', + 'darksalmon': '#e9967a', + 'darkseagreen': '#8fbc8f', + 'darkslateblue': '#483d8b', + 'darkslategray': '#2f4f4f', + 'darkturquoise': '#00ced1', + 'darkviolet': '#9400d3', + 'deeppink': '#ff1493', + 'deepskyblue': '#00bfff', + 'dimgray': '#696969', + 'dodgerblue': '#1e90ff', + 'firebrick': '#b22222', + 'floralwhite': '#fffaf0', + 'forestgreen': '#228b22', + 'fuchsia': '#f0f', + 'gainsboro': '#dcdcdc', + 'ghostwhite': '#f8f8ff', + 'gold': '#ffd700', + 'goldenrod': '#daa520', + 'gray': '#808080', + 'green': '#008000', + 'greenyellow': '#adff2f', + 'honeydew': '#f0fff0', + 'hotpink': '#ff69b4', + 'indianred': '#cd5c5c', + 'indigo': '#4b0082', + 'ivory': '#fffff0', + 'khaki': '#f0e68c', + 'lavender': '#e6e6fa', + 'lavenderblush': '#fff0f5', + 'lawngreen': '#7cfc00', + 'lemonchiffon': '#fffacd', + 'lightblue': '#add8e6', + 'lightcoral': '#f08080', + 'lightcyan': '#e0ffff', + 'lightgoldenrodyellow': '#fafad2', + 'lightgreen': '#90ee90', + 'lightgrey': '#d3d3d3', + 'lightpink': '#ffb6c1', + 'lightsalmon': '#ffa07a', + 'lightseagreen': '#20b2aa', + 'lightskyblue': '#87cefa', + 'lightslategray': '#789', + 'lightsteelblue': '#b0c4de', + 'lightyellow': '#ffffe0', + 'lime': '#0f0', + 'limegreen': '#32cd32', + 'linen': '#faf0e6', + 'magenta': '#f0f', + 'maroon': '#800000', + 'mediumaquamarine': '#66cdaa', + 'mediumblue': '#0000cd', + 'mediumorchid': '#ba55d3', + 'mediumpurple': '#9370db', + 'mediumseagreen': '#3cb371', + 'mediumslateblue': '#7b68ee', + 'mediumspringgreen': '#00fa9a', + 'mediumturquoise': '#48d1cc', + 'mediumvioletred': '#c71585', + 'midnightblue': '#191970', + 'mintcream': '#f5fffa', + 'mistyrose': '#ffe4e1', + 'moccasin': '#ffe4b5', + 'navajowhite': '#ffdead', + 'navy': '#000080', + 'oldlace': '#fdf5e6', + 'olive': '#808000', + 'olivedrab': '#6b8e23', + 'orange': '#ffa500', + 'orangered': '#ff4500', + 'orchid': '#da70d6', + 'palegoldenrod': '#eee8aa', + 'palegreen': '#98fb98', + 'paleturquoise': '#afeeee', + 'palevioletred': '#db7093', + 'papayawhip': '#ffefd5', + 'peachpuff': '#ffdab9', + 'peru': '#cd853f', + 'pink': '#ffc0cb', + 'plum': '#dda0dd', + 'powderblue': '#b0e0e6', + 'purple': '#800080', + 'red': '#f00', + 'rosybrown': '#bc8f8f', + 'royalblue': '#4169e1', + 'saddlebrown': '#8b4513', + 'salmon': '#fa8072', + 'sandybrown': '#f4a460', + 'seagreen': '#2e8b57', + 'seashell': '#fff5ee', + 'sienna': '#a0522d', + 'silver': '#c0c0c0', + 'skyblue': '#87ceeb', + 'slateblue': '#6a5acd', + 'slategray': '#708090', + 'snow': '#fffafa', + 'springgreen': '#00ff7f', + 'steelblue': '#4682b4', + 'tan': '#d2b48c', + 'teal': '#008080', + 'thistle': '#d8bfd8', + 'tomato': '#ff6347', + 'turquoise': '#40e0d0', + 'violet': '#ee82ee', + 'wheat': '#f5deb3', + 'white': '#fff', + 'whitesmoke': '#f5f5f5', + 'yellow': '#ff0', + 'yellowgreen': '#9acd32' +}; + +exports.colorsShortNames = { + '#f0ffff': 'azure', + '#f5f5dc': 'beige', + '#ffe4c4': 'bisque', + '#a52a2a': 'brown', + '#ff7f50': 'coral', + '#ffd700': 'gold', + '#808080': 'gray', + '#008000': 'green', + '#4b0082': 'indigo', + '#fffff0': 'ivory', + '#f0e68c': 'khaki', + '#faf0e6': 'linen', + '#800000': 'maroon', + '#000080': 'navy', + '#808000': 'olive', + '#ffa500': 'orange', + '#da70d6': 'orchid', + '#cd853f': 'peru', + '#ffc0cb': 'pink', + '#dda0dd': 'plum', + '#800080': 'purple', + '#f00': 'red', + '#fa8072': 'salmon', + '#a0522d': 'sienna', + '#c0c0c0': 'silver', + '#fffafa': 'snow', + '#d2b48c': 'tan', + '#008080': 'teal', + '#ff6347': 'tomato', + '#ee82ee': 'violet', + '#f5deb3': 'wheat' +}; + +// http://www.w3.org/TR/SVG/single-page.html#types-DataTypeColor +exports.colorsProps = [ + 'color', 'fill', 'stroke', 'stop-color', 'flood-color', 'lighting-color' +]; diff --git a/node_modules/svgo/plugins/_path.js b/node_modules/svgo/plugins/_path.js new file mode 100644 index 00000000..2aa567a0 --- /dev/null +++ b/node_modules/svgo/plugins/_path.js @@ -0,0 +1,962 @@ +/* global a2c */ +'use strict'; + +var regPathInstructions = /([MmLlHhVvCcSsQqTtAaZz])\s*/, + regPathData = /[-+]?(?:\d*\.\d+|\d+\.?)([eE][-+]?\d+)?/g, + regNumericValues = /[-+]?(\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/, + transform2js = require('./_transforms').transform2js, + transformsMultiply = require('./_transforms').transformsMultiply, + transformArc = require('./_transforms').transformArc, + collections = require('./_collections.js'), + referencesProps = collections.referencesProps, + defaultStrokeWidth = collections.attrsGroupsDefaults.presentation['stroke-width'], + cleanupOutData = require('../lib/svgo/tools').cleanupOutData, + removeLeadingZero = require('../lib/svgo/tools').removeLeadingZero, + prevCtrlPoint; + +/** + * Convert path string to JS representation. + * + * @param {String} pathString input string + * @param {Object} params plugin params + * @return {Array} output array + */ +exports.path2js = function(path) { + if (path.pathJS) return path.pathJS; + + var paramsLength = { // Number of parameters of every path command + H: 1, V: 1, M: 2, L: 2, T: 2, Q: 4, S: 4, C: 6, A: 7, + h: 1, v: 1, m: 2, l: 2, t: 2, q: 4, s: 4, c: 6, a: 7 + }, + pathData = [], // JS representation of the path data + instruction, // current instruction context + startMoveto = false; + + // splitting path string into array like ['M', '10 50', 'L', '20 30'] + path.attr('d').value.split(regPathInstructions).forEach(function(data) { + if (!data) return; + if (!startMoveto) { + if (data == 'M' || data == 'm') { + startMoveto = true; + } else return; + } + + // instruction item + if (regPathInstructions.test(data)) { + instruction = data; + + // z - instruction w/o data + if (instruction == 'Z' || instruction == 'z') { + pathData.push({ + instruction: 'z' + }); + } + // data item + } else { + data = data.match(regPathData); + if (!data) return; + + data = data.map(Number); + + // Subsequent moveto pairs of coordinates are threated as implicit lineto commands + // http://www.w3.org/TR/SVG/paths.html#PathDataMovetoCommands + if (instruction == 'M' || instruction == 'm') { + pathData.push({ + instruction: pathData.length == 0 ? 'M' : instruction, + data: data.splice(0, 2) + }); + instruction = instruction == 'M' ? 'L' : 'l'; + } + + for (var pair = paramsLength[instruction]; data.length;) { + pathData.push({ + instruction: instruction, + data: data.splice(0, pair) + }); + } + } + }); + + // First moveto is actually absolute. Subsequent coordinates were separated above. + if (pathData.length && pathData[0].instruction == 'm') { + pathData[0].instruction = 'M'; + } + path.pathJS = pathData; + + return pathData; +}; + +/** + * Convert relative Path data to absolute. + * + * @param {Array} data input data + * @return {Array} output data + */ +var relative2absolute = exports.relative2absolute = function(data) { + var currentPoint = [0, 0], + subpathPoint = [0, 0], + i; + + data = data.map(function(item) { + + var instruction = item.instruction, + itemData = item.data && item.data.slice(); + + if (instruction == 'M') { + + set(currentPoint, itemData); + set(subpathPoint, itemData); + + } else if ('mlcsqt'.indexOf(instruction) > -1) { + + for (i = 0; i < itemData.length; i++) { + itemData[i] += currentPoint[i % 2]; + } + set(currentPoint, itemData); + + if (instruction == 'm') { + set(subpathPoint, itemData); + } + + } else if (instruction == 'a') { + + itemData[5] += currentPoint[0]; + itemData[6] += currentPoint[1]; + set(currentPoint, itemData); + + } else if (instruction == 'h') { + + itemData[0] += currentPoint[0]; + currentPoint[0] = itemData[0]; + + } else if (instruction == 'v') { + + itemData[0] += currentPoint[1]; + currentPoint[1] = itemData[0]; + + } else if ('MZLCSQTA'.indexOf(instruction) > -1) { + + set(currentPoint, itemData); + + } else if (instruction == 'H') { + + currentPoint[0] = itemData[0]; + + } else if (instruction == 'V') { + + currentPoint[1] = itemData[0]; + + } else if (instruction == 'z') { + + set(currentPoint, subpathPoint); + + } + + return instruction == 'z' ? + { instruction: 'z' } : + { + instruction: instruction.toUpperCase(), + data: itemData + }; + + }); + + return data; +}; + +/** + * Apply transformation(s) to the Path data. + * + * @param {Object} elem current element + * @param {Array} path input path data + * @param {Object} params whether to apply transforms to stroked lines and transform precision (used for stroke width) + * @return {Array} output path data + */ +exports.applyTransforms = function(elem, path, params) { + // if there are no 'stroke' attr and references to other objects such as + // gradiends or clip-path which are also subjects to transform. + if (!elem.hasAttr('transform') || !elem.attr('transform').value || + elem.someAttr(function(attr) { + return ~referencesProps.indexOf(attr.name) && ~attr.value.indexOf('url('); + })) + return path; + + var matrix = transformsMultiply(transform2js(elem.attr('transform').value)), + stroke = elem.computedAttr('stroke'), + id = elem.computedAttr('id'), + transformPrecision = params.transformPrecision, + newPoint, scale; + + if (stroke && stroke != 'none') { + if (!params.applyTransformsStroked || + (matrix.data[0] != matrix.data[3] || matrix.data[1] != -matrix.data[2]) && + (matrix.data[0] != -matrix.data[3] || matrix.data[1] != matrix.data[2])) + return path; + + // "stroke-width" should be inside the part with ID, otherwise it can be overrided in <use> + if (id) { + var idElem = elem, + hasStrokeWidth = false; + + do { + if (idElem.hasAttr('stroke-width')) hasStrokeWidth = true; + } while (!idElem.hasAttr('id', id) && !hasStrokeWidth && (idElem = idElem.parentNode)); + + if (!hasStrokeWidth) return path; + } + + scale = +Math.sqrt(matrix.data[0] * matrix.data[0] + matrix.data[1] * matrix.data[1]).toFixed(transformPrecision); + + if (scale !== 1) { + var strokeWidth = elem.computedAttr('stroke-width') || defaultStrokeWidth; + + if (elem.hasAttr('stroke-width')) { + elem.attrs['stroke-width'].value = elem.attrs['stroke-width'].value.trim() + .replace(regNumericValues, function(num) { return removeLeadingZero(num * scale) }); + } else { + elem.addAttr({ + name: 'stroke-width', + prefix: '', + local: 'stroke-width', + value: strokeWidth.replace(regNumericValues, function(num) { return removeLeadingZero(num * scale) }) + }); + } + } + } else if (id) { // Stroke and stroke-width can be redefined with <use> + return path; + } + + path.forEach(function(pathItem) { + + if (pathItem.data) { + + // h -> l + if (pathItem.instruction === 'h') { + + pathItem.instruction = 'l'; + pathItem.data[1] = 0; + + // v -> l + } else if (pathItem.instruction === 'v') { + + pathItem.instruction = 'l'; + pathItem.data[1] = pathItem.data[0]; + pathItem.data[0] = 0; + + } + + // if there is a translate() transform + if (pathItem.instruction === 'M' && + (matrix.data[4] !== 0 || + matrix.data[5] !== 0) + ) { + + // then apply it only to the first absoluted M + newPoint = transformPoint(matrix.data, pathItem.data[0], pathItem.data[1]); + set(pathItem.data, newPoint); + set(pathItem.coords, newPoint); + + // clear translate() data from transform matrix + matrix.data[4] = 0; + matrix.data[5] = 0; + + } else { + + if (pathItem.instruction == 'a') { + + transformArc(pathItem.data, matrix.data); + + // reduce number of digits in rotation angle + if (Math.abs(pathItem.data[2]) > 80) { + var a = pathItem.data[0], + rotation = pathItem.data[2]; + pathItem.data[0] = pathItem.data[1]; + pathItem.data[1] = a; + pathItem.data[2] = rotation + (rotation > 0 ? -90 : 90); + } + + newPoint = transformPoint(matrix.data, pathItem.data[5], pathItem.data[6]); + pathItem.data[5] = newPoint[0]; + pathItem.data[6] = newPoint[1]; + + } else { + + for (var i = 0; i < pathItem.data.length; i += 2) { + newPoint = transformPoint(matrix.data, pathItem.data[i], pathItem.data[i + 1]); + pathItem.data[i] = newPoint[0]; + pathItem.data[i + 1] = newPoint[1]; + } + } + + pathItem.coords[0] = pathItem.base[0] + pathItem.data[pathItem.data.length - 2]; + pathItem.coords[1] = pathItem.base[1] + pathItem.data[pathItem.data.length - 1]; + + } + + } + + }); + + // remove transform attr + elem.removeAttr('transform'); + + return path; +}; + +/** + * Apply transform 3x3 matrix to x-y point. + * + * @param {Array} matrix transform 3x3 matrix + * @param {Array} point x-y point + * @return {Array} point with new coordinates + */ +function transformPoint(matrix, x, y) { + + return [ + matrix[0] * x + matrix[2] * y + matrix[4], + matrix[1] * x + matrix[3] * y + matrix[5] + ]; + +} + +/** + * Compute Cubic Bézie bounding box. + * + * @see http://processingjs.nihongoresources.com/bezierinfo/ + * + * @param {Float} xa + * @param {Float} ya + * @param {Float} xb + * @param {Float} yb + * @param {Float} xc + * @param {Float} yc + * @param {Float} xd + * @param {Float} yd + * + * @return {Object} + */ +exports.computeCubicBoundingBox = function(xa, ya, xb, yb, xc, yc, xd, yd) { + + var minx = Number.POSITIVE_INFINITY, + miny = Number.POSITIVE_INFINITY, + maxx = Number.NEGATIVE_INFINITY, + maxy = Number.NEGATIVE_INFINITY, + ts, + t, + x, + y, + i; + + // X + if (xa < minx) { minx = xa; } + if (xa > maxx) { maxx = xa; } + if (xd < minx) { minx= xd; } + if (xd > maxx) { maxx = xd; } + + ts = computeCubicFirstDerivativeRoots(xa, xb, xc, xd); + + for (i = 0; i < ts.length; i++) { + + t = ts[i]; + + if (t >= 0 && t <= 1) { + x = computeCubicBaseValue(t, xa, xb, xc, xd); + // y = computeCubicBaseValue(t, ya, yb, yc, yd); + + if (x < minx) { minx = x; } + if (x > maxx) { maxx = x; } + } + + } + + // Y + if (ya < miny) { miny = ya; } + if (ya > maxy) { maxy = ya; } + if (yd < miny) { miny = yd; } + if (yd > maxy) { maxy = yd; } + + ts = computeCubicFirstDerivativeRoots(ya, yb, yc, yd); + + for (i = 0; i < ts.length; i++) { + + t = ts[i]; + + if (t >= 0 && t <= 1) { + // x = computeCubicBaseValue(t, xa, xb, xc, xd); + y = computeCubicBaseValue(t, ya, yb, yc, yd); + + if (y < miny) { miny = y; } + if (y > maxy) { maxy = y; } + } + + } + + return { + minx: minx, + miny: miny, + maxx: maxx, + maxy: maxy + }; + +}; + +// compute the value for the cubic bezier function at time=t +function computeCubicBaseValue(t, a, b, c, d) { + + var mt = 1 - t; + + return mt * mt * mt * a + 3 * mt * mt * t * b + 3 * mt * t * t * c + t * t * t * d; + +} + +// compute the value for the first derivative of the cubic bezier function at time=t +function computeCubicFirstDerivativeRoots(a, b, c, d) { + + var result = [-1, -1], + tl = -a + 2 * b - c, + tr = -Math.sqrt(-a * (c - d) + b * b - b * (c + d) + c * c), + dn = -a + 3 * b - 3 * c + d; + + if (dn !== 0) { + result[0] = (tl + tr) / dn; + result[1] = (tl - tr) / dn; + } + + return result; + +} + +/** + * Compute Quadratic Bézier bounding box. + * + * @see http://processingjs.nihongoresources.com/bezierinfo/ + * + * @param {Float} xa + * @param {Float} ya + * @param {Float} xb + * @param {Float} yb + * @param {Float} xc + * @param {Float} yc + * + * @return {Object} + */ +exports.computeQuadraticBoundingBox = function(xa, ya, xb, yb, xc, yc) { + + var minx = Number.POSITIVE_INFINITY, + miny = Number.POSITIVE_INFINITY, + maxx = Number.NEGATIVE_INFINITY, + maxy = Number.NEGATIVE_INFINITY, + t, + x, + y; + + // X + if (xa < minx) { minx = xa; } + if (xa > maxx) { maxx = xa; } + if (xc < minx) { minx = xc; } + if (xc > maxx) { maxx = xc; } + + t = computeQuadraticFirstDerivativeRoot(xa, xb, xc); + + if (t >= 0 && t <= 1) { + x = computeQuadraticBaseValue(t, xa, xb, xc); + // y = computeQuadraticBaseValue(t, ya, yb, yc); + + if (x < minx) { minx = x; } + if (x > maxx) { maxx = x; } + } + + // Y + if (ya < miny) { miny = ya; } + if (ya > maxy) { maxy = ya; } + if (yc < miny) { miny = yc; } + if (yc > maxy) { maxy = yc; } + + t = computeQuadraticFirstDerivativeRoot(ya, yb, yc); + + if (t >= 0 && t <=1 ) { + // x = computeQuadraticBaseValue(t, xa, xb, xc); + y = computeQuadraticBaseValue(t, ya, yb, yc); + + if (y < miny) { miny = y; } + if (y > maxy) { maxy = y ; } + + } + + return { + minx: minx, + miny: miny, + maxx: maxx, + maxy: maxy + }; + +}; + +// compute the value for the quadratic bezier function at time=t +function computeQuadraticBaseValue(t, a, b, c) { + + var mt = 1 - t; + + return mt * mt * a + 2 * mt * t * b + t * t * c; + +} + +// compute the value for the first derivative of the quadratic bezier function at time=t +function computeQuadraticFirstDerivativeRoot(a, b, c) { + + var t = -1, + denominator = a - 2 * b + c; + + if (denominator !== 0) { + t = (a - b) / denominator; + } + + return t; + +} + +/** + * Convert path array to string. + * + * @param {Array} path input path data + * @param {Object} params plugin params + * @return {String} output path string + */ +exports.js2path = function(path, data, params) { + + path.pathJS = data; + + if (params.collapseRepeated) { + data = collapseRepeated(data); + } + + path.attr('d').value = data.reduce(function(pathString, item) { + return pathString += item.instruction + (item.data ? cleanupOutData(item.data, params) : ''); + }, ''); + +}; + +/** + * Collapse repeated instructions data + * + * @param {Array} path input path data + * @return {Array} output path data + */ +function collapseRepeated(data) { + + var prev, + prevIndex; + + // copy an array and modifieds item to keep original data untouched + data = data.reduce(function(newPath, item) { + if ( + prev && item.data && + item.instruction == prev.instruction + ) { + // concat previous data with current + if (item.instruction != 'M') { + prev = newPath[prevIndex] = { + instruction: prev.instruction, + data: prev.data.concat(item.data), + coords: item.coords, + base: prev.base + }; + } else { + prev.data = item.data; + prev.coords = item.coords; + } + } else { + newPath.push(item); + prev = item; + prevIndex = newPath.length - 1; + } + + return newPath; + }, []); + + return data; + +} + +function set(dest, source) { + dest[0] = source[source.length - 2]; + dest[1] = source[source.length - 1]; + return dest; +} + +/** + * Checks if two paths have an intersection by checking convex hulls + * collision using Gilbert-Johnson-Keerthi distance algorithm + * http://entropyinteractive.com/2011/04/gjk-algorithm/ + * + * @param {Array} path1 JS path representation + * @param {Array} path2 JS path representation + * @return {Boolean} + */ +exports.intersects = function(path1, path2) { + if (path1.length < 3 || path2.length < 3) return false; // nothing to fill + + // Collect points of every subpath. + var points1 = relative2absolute(path1).reduce(gatherPoints, []), + points2 = relative2absolute(path2).reduce(gatherPoints, []); + + // Axis-aligned bounding box check. + if (points1.maxX <= points2.minX || points2.maxX <= points1.minX || + points1.maxY <= points2.minY || points2.maxY <= points1.minY || + points1.every(function (set1) { + return points2.every(function (set2) { + return set1[set1.maxX][0] <= set2[set2.minX][0] || + set2[set2.maxX][0] <= set1[set1.minX][0] || + set1[set1.maxY][1] <= set2[set2.minY][1] || + set2[set2.maxY][1] <= set1[set1.minY][1]; + }); + }) + ) return false; + + // Get a convex hull from points of each subpath. Has the most complexity O(n·log n). + var hullNest1 = points1.map(convexHull), + hullNest2 = points2.map(convexHull); + + // Check intersection of every subpath of the first path with every subpath of the second. + return hullNest1.some(function(hull1) { + if (hull1.length < 3) return false; + + return hullNest2.some(function(hull2) { + if (hull2.length < 3) return false; + + var simplex = [getSupport(hull1, hull2, [1, 0])], // create the initial simplex + direction = minus(simplex[0]); // set the direction to point towards the origin + + var iterations = 1e4; // infinite loop protection, 10 000 iterations is more than enough + while (true) { + if (iterations-- == 0) { + console.error('Error: infinite loop while processing mergePaths plugin.'); + return true; // true is the safe value that means “do nothing with paths” + } + // add a new point + simplex.push(getSupport(hull1, hull2, direction)); + // see if the new point was on the correct side of the origin + if (dot(direction, simplex[simplex.length - 1]) <= 0) return false; + // process the simplex + if (processSimplex(simplex, direction)) return true; + } + }); + }); + + function getSupport(a, b, direction) { + return sub(supportPoint(a, direction), supportPoint(b, minus(direction))); + } + + // Computes farthest polygon point in particular direction. + // Thanks to knowledge of min/max x and y coordinates we can choose a quadrant to search in. + // Since we're working on convex hull, the dot product is increasing until we find the farthest point. + function supportPoint(polygon, direction) { + var index = direction[1] >= 0 ? + direction[0] < 0 ? polygon.maxY : polygon.maxX : + direction[0] < 0 ? polygon.minX : polygon.minY, + max = -Infinity, + value; + while ((value = dot(polygon[index], direction)) > max) { + max = value; + index = ++index % polygon.length; + } + return polygon[(index || polygon.length) - 1]; + } +}; + +function processSimplex(simplex, direction) { + /* jshint -W004 */ + + // we only need to handle to 1-simplex and 2-simplex + if (simplex.length == 2) { // 1-simplex + var a = simplex[1], + b = simplex[0], + AO = minus(simplex[1]), + AB = sub(b, a); + // AO is in the same direction as AB + if (dot(AO, AB) > 0) { + // get the vector perpendicular to AB facing O + set(direction, orth(AB, a)); + } else { + set(direction, AO); + // only A remains in the simplex + simplex.shift(); + } + } else { // 2-simplex + var a = simplex[2], // [a, b, c] = simplex + b = simplex[1], + c = simplex[0], + AB = sub(b, a), + AC = sub(c, a), + AO = minus(a), + ACB = orth(AB, AC), // the vector perpendicular to AB facing away from C + ABC = orth(AC, AB); // the vector perpendicular to AC facing away from B + + if (dot(ACB, AO) > 0) { + if (dot(AB, AO) > 0) { // region 4 + set(direction, ACB); + simplex.shift(); // simplex = [b, a] + } else { // region 5 + set(direction, AO); + simplex.splice(0, 2); // simplex = [a] + } + } else if (dot(ABC, AO) > 0) { + if (dot(AC, AO) > 0) { // region 6 + set(direction, ABC); + simplex.splice(1, 1); // simplex = [c, a] + } else { // region 5 (again) + set(direction, AO); + simplex.splice(0, 2); // simplex = [a] + } + } else // region 7 + return true; + } + return false; +} + +function minus(v) { + return [-v[0], -v[1]]; +} + +function sub(v1, v2) { + return [v1[0] - v2[0], v1[1] - v2[1]]; +} + +function dot(v1, v2) { + return v1[0] * v2[0] + v1[1] * v2[1]; +} + +function orth(v, from) { + var o = [-v[1], v[0]]; + return dot(o, minus(from)) < 0 ? minus(o) : o; +} + +function gatherPoints(points, item, index, path) { + + var subPath = points.length && points[points.length - 1], + prev = index && path[index - 1], + basePoint = subPath.length && subPath[subPath.length - 1], + data = item.data, + ctrlPoint = basePoint; + + switch (item.instruction) { + case 'M': + points.push(subPath = []); + break; + case 'H': + addPoint(subPath, [data[0], basePoint[1]]); + break; + case 'V': + addPoint(subPath, [basePoint[0], data[0]]); + break; + case 'Q': + addPoint(subPath, data.slice(0, 2)); + prevCtrlPoint = [data[2] - data[0], data[3] - data[1]]; // Save control point for shorthand + break; + case 'T': + if (prev.instruction == 'Q' && prev.instruction == 'T') { + ctrlPoint = [basePoint[0] + prevCtrlPoint[0], basePoint[1] + prevCtrlPoint[1]]; + addPoint(subPath, ctrlPoint); + prevCtrlPoint = [data[0] - ctrlPoint[0], data[1] - ctrlPoint[1]]; + } + break; + case 'C': + // Approximate quibic Bezier curve with middle points between control points + addPoint(subPath, [.5 * (basePoint[0] + data[0]), .5 * (basePoint[1] + data[1])]); + addPoint(subPath, [.5 * (data[0] + data[2]), .5 * (data[1] + data[3])]); + addPoint(subPath, [.5 * (data[2] + data[4]), .5 * (data[3] + data[5])]); + prevCtrlPoint = [data[4] - data[2], data[5] - data[3]]; // Save control point for shorthand + break; + case 'S': + if (prev.instruction == 'C' && prev.instruction == 'S') { + addPoint(subPath, [basePoint[0] + .5 * prevCtrlPoint[0], basePoint[1] + .5 * prevCtrlPoint[1]]); + ctrlPoint = [basePoint[0] + prevCtrlPoint[0], basePoint[1] + prevCtrlPoint[1]]; + } + addPoint(subPath, [.5 * (ctrlPoint[0] + data[0]), .5 * (ctrlPoint[1]+ data[1])]); + addPoint(subPath, [.5 * (data[0] + data[2]), .5 * (data[1] + data[3])]); + prevCtrlPoint = [data[2] - data[0], data[3] - data[1]]; + break; + case 'A': + // Convert the arc to bezier curves and use the same approximation + var curves = a2c.apply(0, basePoint.concat(data)); + for (var cData; (cData = curves.splice(0,6).map(toAbsolute)).length;) { + addPoint(subPath, [.5 * (basePoint[0] + cData[0]), .5 * (basePoint[1] + cData[1])]); + addPoint(subPath, [.5 * (cData[0] + cData[2]), .5 * (cData[1] + cData[3])]); + addPoint(subPath, [.5 * (cData[2] + cData[4]), .5 * (cData[3] + cData[5])]); + if (curves.length) addPoint(subPath, basePoint = cData.slice(-2)); + } + break; + } + // Save final command coordinates + if (data && data.length >= 2) addPoint(subPath, data.slice(-2)); + return points; + + function toAbsolute(n, i) { return n + basePoint[i % 2] } + + // Writes data about the extreme points on each axle + function addPoint(path, point) { + if (!path.length || point[1] > path[path.maxY][1]) { + path.maxY = path.length; + points.maxY = points.length ? Math.max(point[1], points.maxY) : point[1]; + } + if (!path.length || point[0] > path[path.maxX][0]) { + path.maxX = path.length; + points.maxX = points.length ? Math.max(point[0], points.maxX) : point[0]; + } + if (!path.length || point[1] < path[path.minY][1]) { + path.minY = path.length; + points.minY = points.length ? Math.min(point[1], points.minY) : point[1]; + } + if (!path.length || point[0] < path[path.minX][0]) { + path.minX = path.length; + points.minX = points.length ? Math.min(point[0], points.minX) : point[0]; + } + path.push(point); + } +} + +/** + * Forms a convex hull from set of points of every subpath using monotone chain convex hull algorithm. + * http://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain + * + * @param points An array of [X, Y] coordinates + */ +function convexHull(points) { + /* jshint -W004 */ + + points.sort(function(a, b) { + return a[0] == b[0] ? a[1] - b[1] : a[0] - b[0]; + }); + + var lower = [], + minY = 0, + bottom = 0; + for (var i = 0; i < points.length; i++) { + while (lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], points[i]) <= 0) { + lower.pop(); + } + if (points[i][1] < points[minY][1]) { + minY = i; + bottom = lower.length; + } + lower.push(points[i]); + } + + var upper = [], + maxY = points.length - 1, + top = 0; + for (var i = points.length; i--;) { + while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], points[i]) <= 0) { + upper.pop(); + } + if (points[i][1] > points[maxY][1]) { + maxY = i; + top = upper.length; + } + upper.push(points[i]); + } + + // last points are equal to starting points of the other part + upper.pop(); + lower.pop(); + + var hull = lower.concat(upper); + + hull.minX = 0; // by sorting + hull.maxX = lower.length; + hull.minY = bottom; + hull.maxY = (lower.length + top) % hull.length; + + return hull; +} + +function cross(o, a, b) { + return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]); +} + +/* Based on code from Snap.svg (Apache 2 license). http://snapsvg.io/ + * Thanks to Dmitry Baranovskiy for his great work! + */ + +// jshint ignore: start +function a2c(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) { + // for more information of where this Math came from visit: + // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + var _120 = Math.PI * 120 / 180, + rad = Math.PI / 180 * (+angle || 0), + res = [], + rotateX = function(x, y, rad) { return x * Math.cos(rad) - y * Math.sin(rad) }, + rotateY = function(x, y, rad) { return x * Math.sin(rad) + y * Math.cos(rad) }; + if (!recursive) { + x1 = rotateX(x1, y1, -rad); + y1 = rotateY(x1, y1, -rad); + x2 = rotateX(x2, y2, -rad); + y2 = rotateY(x2, y2, -rad); + var x = (x1 - x2) / 2, + y = (y1 - y2) / 2; + var h = (x * x) / (rx * rx) + (y * y) / (ry * ry); + if (h > 1) { + h = Math.sqrt(h); + rx = h * rx; + ry = h * ry; + } + var rx2 = rx * rx, + ry2 = ry * ry, + k = (large_arc_flag == sweep_flag ? -1 : 1) * + Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))), + cx = k * rx * y / ry + (x1 + x2) / 2, + cy = k * -ry * x / rx + (y1 + y2) / 2, + f1 = Math.asin(((y1 - cy) / ry).toFixed(9)), + f2 = Math.asin(((y2 - cy) / ry).toFixed(9)); + + f1 = x1 < cx ? Math.PI - f1 : f1; + f2 = x2 < cx ? Math.PI - f2 : f2; + f1 < 0 && (f1 = Math.PI * 2 + f1); + f2 < 0 && (f2 = Math.PI * 2 + f2); + if (sweep_flag && f1 > f2) { + f1 = f1 - Math.PI * 2; + } + if (!sweep_flag && f2 > f1) { + f2 = f2 - Math.PI * 2; + } + } else { + f1 = recursive[0]; + f2 = recursive[1]; + cx = recursive[2]; + cy = recursive[3]; + } + var df = f2 - f1; + if (Math.abs(df) > _120) { + var f2old = f2, + x2old = x2, + y2old = y2; + f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1); + x2 = cx + rx * Math.cos(f2); + y2 = cy + ry * Math.sin(f2); + res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]); + } + df = f2 - f1; + var c1 = Math.cos(f1), + s1 = Math.sin(f1), + c2 = Math.cos(f2), + s2 = Math.sin(f2), + t = Math.tan(df / 4), + hx = 4 / 3 * rx * t, + hy = 4 / 3 * ry * t, + m = [ + - hx * s1, hy * c1, + x2 + hx * s2 - x1, y2 - hy * c2 - y1, + x2 - x1, y2 - y1 + ]; + if (recursive) { + return m.concat(res); + } else { + res = m.concat(res); + var newres = []; + for (var i = 0, n = res.length; i < n; i++) { + newres[i] = i % 2 ? rotateY(res[i - 1], res[i], rad) : rotateX(res[i], res[i + 1], rad); + } + return newres; + } +} +// jshint ignore: end diff --git a/node_modules/svgo/plugins/_transforms.js b/node_modules/svgo/plugins/_transforms.js new file mode 100644 index 00000000..796ca259 --- /dev/null +++ b/node_modules/svgo/plugins/_transforms.js @@ -0,0 +1,308 @@ +'use strict'; + +var regTransformTypes = /matrix|translate|scale|rotate|skewX|skewY/, + regTransformSplit = /\s*(matrix|translate|scale|rotate|skewX|skewY)\s*\(\s*(.+?)\s*\)[\s,]*/, + regNumericValues = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g; + +/** + * Convert transform string to JS representation. + * + * @param {String} transformString input string + * @param {Object} params plugin params + * @return {Array} output array + */ +exports.transform2js = function(transformString) { + + // JS representation of the transform data + var transforms = [], + // current transform context + current; + + // split value into ['', 'translate', '10 50', '', 'scale', '2', '', 'rotate', '-45', ''] + transformString.split(regTransformSplit).forEach(function(item) { + /*jshint -W084 */ + var num; + + if (item) { + // if item is a translate function + if (regTransformTypes.test(item)) { + // then collect it and change current context + transforms.push(current = { name: item }); + // else if item is data + } else { + // then split it into [10, 50] and collect as context.data + while (num = regNumericValues.exec(item)) { + num = Number(num); + if (current.data) + current.data.push(num); + else + current.data = [num]; + } + } + } + }); + + return transforms; + +}; + +/** + * Multiply transforms into one. + * + * @param {Array} input transforms array + * @return {Array} output matrix array + */ +exports.transformsMultiply = function(transforms) { + + // convert transforms objects to the matrices + transforms = transforms.map(function(transform) { + if (transform.name === 'matrix') { + return transform.data; + } + return transformToMatrix(transform); + }); + + // multiply all matrices into one + transforms = { + name: 'matrix', + data: transforms.reduce(function(a, b) { + return multiplyTransformMatrices(a, b); + }) + }; + + return transforms; + +}; + +/** + * Do math like a schoolgirl. + * + * @type {Object} + */ +var mth = exports.mth = { + + rad: function(deg) { + return deg * Math.PI / 180; + }, + + deg: function(rad) { + return rad * 180 / Math.PI; + }, + + cos: function(deg) { + return Math.cos(this.rad(deg)); + }, + + acos: function(val, floatPrecision) { + return +(this.deg(Math.acos(val)).toFixed(floatPrecision)); + }, + + sin: function(deg) { + return Math.sin(this.rad(deg)); + }, + + asin: function(val, floatPrecision) { + return +(this.deg(Math.asin(val)).toFixed(floatPrecision)); + }, + + tan: function(deg) { + return Math.tan(this.rad(deg)); + }, + + atan: function(val, floatPrecision) { + return +(this.deg(Math.atan(val)).toFixed(floatPrecision)); + } + +}; + +/** + * Decompose matrix into simple transforms. See + * http://www.maths-informatique-jeux.com/blog/frederic/?post/2013/12/01/Decomposition-of-2D-transform-matrices + * + * @param {Object} data matrix transform object + * @return {Object|Array} transforms array or original transform object + */ +exports.matrixToTransform = function(transform, params) { + var floatPrecision = params.floatPrecision, + data = transform.data, + transforms = [], + sx = +Math.sqrt(data[0] * data[0] + data[1] * data[1]).toFixed(params.transformPrecision), + sy = +((data[0] * data[3] - data[1] * data[2]) / sx).toFixed(params.transformPrecision), + colsSum = data[0] * data[2] + data[1] * data[3], + rowsSum = data[0] * data[1] + data[2] * data[3], + scaleBefore = rowsSum || +(sx == sy); + + // [..., ..., ..., ..., tx, ty] → translate(tx, ty) + if (data[4] || data[5]) { + transforms.push({ name: 'translate', data: data.slice(4, data[5] ? 6 : 5) }); + } + + // [sx, 0, tan(a)·sy, sy, 0, 0] → skewX(a)·scale(sx, sy) + if (!data[1] && data[2]) { + transforms.push({ name: 'skewX', data: [mth.atan(data[2] / sy, floatPrecision)] }); + + // [sx, sx·tan(a), 0, sy, 0, 0] → skewY(a)·scale(sx, sy) + } else if (data[1] && !data[2]) { + transforms.push({ name: 'skewY', data: [mth.atan(data[1] / data[0], floatPrecision)] }); + sx = data[0]; + sy = data[3]; + + // [sx·cos(a), sx·sin(a), sy·-sin(a), sy·cos(a), x, y] → rotate(a[, cx, cy])·(scale or skewX) or + // [sx·cos(a), sy·sin(a), sx·-sin(a), sy·cos(a), x, y] → scale(sx, sy)·rotate(a[, cx, cy]) (if !scaleBefore) + } else if (!colsSum || (sx == 1 && sy == 1) || !scaleBefore) { + if (!scaleBefore) { + sx = (data[0] < 0 ? -1 : 1) * Math.sqrt(data[0] * data[0] + data[2] * data[2]); + sy = (data[3] < 0 ? -1 : 1) * Math.sqrt(data[1] * data[1] + data[3] * data[3]); + transforms.push({ name: 'scale', data: [sx, sy] }); + } + var rotate = [mth.acos(data[0] / sx, floatPrecision) * (data[1] * sy < 0 ? -1 : 1)]; + + if (rotate[0]) transforms.push({ name: 'rotate', data: rotate }); + + if (rowsSum && colsSum) transforms.push({ + name: 'skewX', + data: [mth.atan(colsSum / (sx * sx), floatPrecision)] + }); + + // rotate(a, cx, cy) can consume translate() within optional arguments cx, cy (rotation point) + if (rotate[0] && (data[4] || data[5])) { + transforms.shift(); + var cos = data[0] / sx, + sin = data[1] / (scaleBefore ? sx : sy), + x = data[4] * (scaleBefore || sy), + y = data[5] * (scaleBefore || sx), + denom = (Math.pow(1 - cos, 2) + Math.pow(sin, 2)) * (scaleBefore || sx * sy); + rotate.push(((1 - cos) * x - sin * y) / denom); + rotate.push(((1 - cos) * y + sin * x) / denom); + } + + // Too many transformations, return original matrix if it isn't just a scale/translate + } else if (data[1] || data[2]) { + return transform; + } + + if (scaleBefore && (sx != 1 || sy != 1) || !transforms.length) transforms.push({ + name: 'scale', + data: sx == sy ? [sx] : [sx, sy] + }); + + return transforms; +}; + +/** + * Convert transform to the matrix data. + * + * @param {Object} transform transform object + * @return {Array} matrix data + */ +function transformToMatrix(transform) { + + if (transform.name === 'matrix') return transform.data; + + var matrix; + + switch (transform.name) { + case 'translate': + // [1, 0, 0, 1, tx, ty] + matrix = [1, 0, 0, 1, transform.data[0], transform.data[1] || 0]; + break; + case 'scale': + // [sx, 0, 0, sy, 0, 0] + matrix = [transform.data[0], 0, 0, transform.data[1] || transform.data[0], 0, 0]; + break; + case 'rotate': + // [cos(a), sin(a), -sin(a), cos(a), x, y] + var cos = mth.cos(transform.data[0]), + sin = mth.sin(transform.data[0]), + cx = transform.data[1] || 0, + cy = transform.data[2] || 0; + + matrix = [cos, sin, -sin, cos, (1 - cos) * cx + sin * cy, (1 - cos) * cy - sin * cx]; + break; + case 'skewX': + // [1, 0, tan(a), 1, 0, 0] + matrix = [1, 0, mth.tan(transform.data[0]), 1, 0, 0]; + break; + case 'skewY': + // [1, tan(a), 0, 1, 0, 0] + matrix = [1, mth.tan(transform.data[0]), 0, 1, 0, 0]; + break; + } + + return matrix; + +} + +/** + * Applies transformation to an arc. To do so, we represent ellipse as a matrix, multiply it + * by the transformation matrix and use a singular value decomposition to represent in a form + * rotate(θ)·scale(a b)·rotate(φ). This gives us new ellipse params a, b and θ. + * SVD is being done with the formulae provided by Wolffram|Alpha (svd {{m0, m2}, {m1, m3}}) + * + * @param {Array} arc [a, b, rotation in deg] + * @param {Array} transform transformation matrix + * @return {Array} arc transformed input arc + */ +exports.transformArc = function(arc, transform) { + + var a = arc[0], + b = arc[1], + rot = arc[2] * Math.PI / 180, + cos = Math.cos(rot), + sin = Math.sin(rot), + h = Math.pow(arc[5] * cos + arc[6] * sin, 2) / (4 * a * a) + + Math.pow(arc[6] * cos - arc[5] * sin, 2) / (4 * b * b); + if (h > 1) { + h = Math.sqrt(h); + a *= h; + b *= h; + } + var ellipse = [a * cos, a * sin, -b * sin, b * cos, 0, 0], + m = multiplyTransformMatrices(transform, ellipse), + // Decompose the new ellipse matrix + lastCol = m[2] * m[2] + m[3] * m[3], + squareSum = m[0] * m[0] + m[1] * m[1] + lastCol, + root = Math.sqrt( + (Math.pow(m[0] - m[3], 2) + Math.pow(m[1] + m[2], 2)) * + (Math.pow(m[0] + m[3], 2) + Math.pow(m[1] - m[2], 2)) + ); + + if (!root) { // circle + arc[0] = arc[1] = Math.sqrt(squareSum / 2); + arc[2] = 0; + } else { + var majorAxisSqr = (squareSum + root) / 2, + minorAxisSqr = (squareSum - root) / 2, + major = Math.abs(majorAxisSqr - lastCol) > 1e-6, + sub = (major ? majorAxisSqr : minorAxisSqr) - lastCol, + rowsSum = m[0] * m[2] + m[1] * m[3], + term1 = m[0] * sub + m[2] * rowsSum, + term2 = m[1] * sub + m[3] * rowsSum; + arc[0] = Math.sqrt(majorAxisSqr); + arc[1] = Math.sqrt(minorAxisSqr); + arc[2] = ((major ? term2 < 0 : term1 > 0) ? -1 : 1) * + Math.acos((major ? term1 : term2) / Math.sqrt(term1 * term1 + term2 * term2)) * 180 / Math.PI; + } + return arc; + +}; + +/** + * Multiply transformation matrices. + * + * @param {Array} a matrix A data + * @param {Array} b matrix B data + * @return {Array} result + */ +function multiplyTransformMatrices(a, b) { + + return [ + a[0] * b[0] + a[2] * b[1], + a[1] * b[0] + a[3] * b[1], + a[0] * b[2] + a[2] * b[3], + a[1] * b[2] + a[3] * b[3], + a[0] * b[4] + a[2] * b[5] + a[4], + a[1] * b[4] + a[3] * b[5] + a[5] + ]; + +} diff --git a/node_modules/svgo/plugins/addAttributesToSVGElement.js b/node_modules/svgo/plugins/addAttributesToSVGElement.js new file mode 100644 index 00000000..9b97c95c --- /dev/null +++ b/node_modules/svgo/plugins/addAttributesToSVGElement.js @@ -0,0 +1,57 @@ +'use strict'; + +exports.type = 'full'; + +exports.active = false; + +exports.description = 'adds attributes to an outer <svg> element'; + +var ENOCLS = 'Error in plugin "addAttributesToSVGElement": absent parameters.\n\ +It should have a list of classes in "attributes" or one "attribute".\n\ +Config example:\n\n\ +\ +plugins:\n\ +- addAttributesToSVGElement:\n\ + attribute: "mySvg"\n\n\ +\ +plugins:\n\ +- addAttributesToSVGElement:\n\ + attributes: ["mySvg", "size-big"]\n'; + +/** + * Add attributes to an outer <svg> element. Example config: + * + * plugins: + * - addAttributesToSVGElement: + * attribute: 'data-icon' + * + * plugins: + * - addAttributesToSVGElement: + * attributes: ['data-icon', 'data-disabled'] + * + * @author April Arcus + */ +exports.fn = function(data, params) { + if (!params || !(Array.isArray(params.attributes) && params.attributes.some(String) || params.attribute)) { + console.error(ENOCLS); + return data; + } + + var attributes = params.attributes || [ params.attribute ], + svg = data.content[0]; + + if (svg.isElem('svg')) { + attributes.forEach(function (attribute) { + if (!svg.hasAttr(attribute)) { + svg.addAttr({ + name: attribute, + prefix: '', + local: attribute + }); + } + }); + } + + return data; + +}; diff --git a/node_modules/svgo/plugins/addClassesToSVGElement.js b/node_modules/svgo/plugins/addClassesToSVGElement.js new file mode 100644 index 00000000..3f9596aa --- /dev/null +++ b/node_modules/svgo/plugins/addClassesToSVGElement.js @@ -0,0 +1,64 @@ +'use strict'; + +exports.type = 'full'; + +exports.active = false; + +exports.description = 'adds classnames to an outer <svg> element'; + +var ENOCLS = 'Error in plugin "addClassesToSVGElement": absent parameters.\n\ +It should have a list of classes in "classNames" or one "className".\n\ +Config example:\n\n\ +\ +plugins:\n\ +- addClassesToSVGElement:\n\ + className: "mySvg"\n\n\ +\ +plugins:\n\ +- addClassesToSVGElement:\n\ + classNames: ["mySvg", "size-big"]\n'; + +/** + * Add classnames to an outer <svg> element. Example config: + * + * plugins: + * - addClassesToSVGElement: + * className: 'mySvg' + * + * plugins: + * - addClassesToSVGElement: + * classNames: ['mySvg', 'size-big'] + * + * @author April Arcus + */ +exports.fn = function(data, params) { + if (!params || !(Array.isArray(params.classNames) && params.classNames.some(String) || params.className)) { + console.error(ENOCLS); + return data; + } + + var classNames = params.classNames || [ params.className ], + svg = data.content[0]; + + if (svg.isElem('svg')) { + if (svg.hasAttr('class')) { + var classes = svg.attr('class').value.split(' '); + classNames.forEach(function(className){ + if (classes.indexOf(className) < 0) { + classes.push(className); + } + }); + svg.attr('class').value = classes.join(' '); + } else { + svg.addAttr({ + name: 'class', + value: classNames.join(' '), + prefix: '', + local: 'class' + }); + } + } + + return data; + +}; diff --git a/node_modules/svgo/plugins/cleanupAttrs.js b/node_modules/svgo/plugins/cleanupAttrs.js new file mode 100644 index 00000000..47e77ac7 --- /dev/null +++ b/node_modules/svgo/plugins/cleanupAttrs.js @@ -0,0 +1,56 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = true; + +exports.description = 'cleanups attributes from newlines, trailing and repeating spaces'; + +exports.params = { + newlines: true, + trim: true, + spaces: true +}; + +var regNewlinesNeedSpace = /(\S)\r?\n(\S)/g, + regNewlines = /\r?\n/g, + regSpaces = /\s{2,}/g; + +/** + * Cleanup attributes values from newlines, trailing and repeating spaces. + * + * @param {Object} item current iteration item + * @param {Object} params plugin params + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(item, params) { + + if (item.isElem()) { + + item.eachAttr(function(attr) { + + if (params.newlines) { + // new line which requires a space instead of themselve + attr.value = attr.value.replace(regNewlinesNeedSpace, function(match, p1, p2) { + return p1 + ' ' + p2; + }); + + // simple new line + attr.value = attr.value.replace(regNewlines, ''); + } + + if (params.trim) { + attr.value = attr.value.trim(); + } + + if (params.spaces) { + attr.value = attr.value.replace(regSpaces, ' '); + } + + }); + + } + +}; diff --git a/node_modules/svgo/plugins/cleanupEnableBackground.js b/node_modules/svgo/plugins/cleanupEnableBackground.js new file mode 100644 index 00000000..e6384ab5 --- /dev/null +++ b/node_modules/svgo/plugins/cleanupEnableBackground.js @@ -0,0 +1,84 @@ +'use strict'; + +exports.type = 'full'; + +exports.active = true; + +exports.description = 'remove or cleanup enable-background attribute when possible'; + +/** + * Remove or cleanup enable-background attr which coincides with a width/height box. + * + * @see http://www.w3.org/TR/SVG/filters.html#EnableBackgroundProperty + * + * @example + * <svg width="100" height="50" enable-background="new 0 0 100 50"> + * ⬇ + * <svg width="100" height="50"> + * + * @param {Object} item current iteration item + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(data) { + + var regEnableBackground = /^new\s0\s0\s([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)\s([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)$/, + hasFilter = false, + elems = ['svg', 'mask', 'pattern']; + + function checkEnableBackground(item) { + if ( + item.isElem(elems) && + item.hasAttr('enable-background') && + item.hasAttr('width') && + item.hasAttr('height') + ) { + + var match = item.attr('enable-background').value.match(regEnableBackground); + + if (match) { + if ( + item.attr('width').value === match[1] && + item.attr('height').value === match[3] + ) { + if (item.isElem('svg')) { + item.removeAttr('enable-background'); + } else { + item.attr('enable-background').value = 'new'; + } + } + } + + } + } + + function checkForFilter(item) { + if (item.isElem('filter')) { + hasFilter = true; + } + } + + function monkeys(items, fn) { + items.content.forEach(function(item) { + fn(item); + + if (item.content) { + monkeys(item, fn); + } + }); + return items; + } + + var firstStep = monkeys(data, function(item) { + checkEnableBackground(item); + if (!hasFilter) { + checkForFilter(item); + } + }); + + return hasFilter ? firstStep : monkeys(firstStep, function(item) { + //we don't need 'enable-background' if we have no filters + item.removeAttr('enable-background'); + }); +}; diff --git a/node_modules/svgo/plugins/cleanupIDs.js b/node_modules/svgo/plugins/cleanupIDs.js new file mode 100644 index 00000000..38b5cfb4 --- /dev/null +++ b/node_modules/svgo/plugins/cleanupIDs.js @@ -0,0 +1,208 @@ +'use strict'; + +exports.type = 'full'; + +exports.active = true; + +exports.description = 'removes unused IDs and minifies used'; + +exports.params = { + remove: true, + minify: true, + prefix: '' +}; + +var referencesProps = require('./_collections').referencesProps, + regReferencesUrl = /\burl\(("|')?#(.+?)\1\)/, + regReferencesHref = /^#(.+?)$/, + regReferencesBegin = /^(\w+?)\./, + styleOrScript = ['style', 'script'], + generateIDchars = [ + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' + ], + maxIDindex = generateIDchars.length - 1; + +/** + * Remove unused and minify used IDs + * (only if there are no any <style> or <script>). + * + * @param {Object} item current iteration item + * @param {Object} params plugin params + * + * @author Kir Belevich + */ +exports.fn = function(data, params) { + + var currentID, + currentIDstring, + IDs = Object.create(null), + referencesIDs = Object.create(null), + idPrefix = 'id-', // prefix IDs so that values like '__proto__' don't break the work + hasStyleOrScript = false; + + /** + * Bananas! + * + * @param {Array} items input items + * @return {Array} output items + */ + function monkeys(items) { + + for (var i = 0; i < items.content.length && !hasStyleOrScript; i++) { + + var item = items.content[i], + match; + + // check if <style> of <script> presents + if (item.isElem(styleOrScript)) { + hasStyleOrScript = true; + continue; + } + + // …and don't remove any ID if yes + if (item.isElem()) { + + item.eachAttr(function(attr) { + var key; + // save IDs + if (attr.name === 'id') { + key = idPrefix + attr.value; + if (key in IDs) { + item.removeAttr('id'); + } else { + IDs[key] = item; + } + } + + // save IDs url() references + else if (referencesProps.indexOf(attr.name) > -1) { + match = attr.value.match(regReferencesUrl); + + if (match) { + key = idPrefix + match[2]; + if (referencesIDs[key]) { + referencesIDs[key].push(attr); + } else { + referencesIDs[key] = [attr]; + } + } + } + + // save IDs href references + else if ( + attr.local === 'href' && (match = attr.value.match(regReferencesHref)) || + attr.name === 'begin' && (match = attr.value.match(regReferencesBegin)) + ) { + key = idPrefix + match[1]; + if (referencesIDs[key]) { + referencesIDs[key].push(attr); + } else { + referencesIDs[key] = [attr]; + } + } + }); + + } + + // go deeper + if (item.content) { + monkeys(item); + } + } + + return items; + + } + + data = monkeys(data); + + if (hasStyleOrScript) { + return data; + } + + var idKey; + for (var k in referencesIDs) { + if (IDs[k]) { + idKey = k; + k = k.replace(idPrefix, ''); + // replace referenced IDs with the minified ones + if (params.minify) { + currentIDstring = getIDstring(currentID = generateID(currentID), params); + IDs[idKey].attr('id').value = currentIDstring; + + referencesIDs[idKey].forEach(function(attr) { + attr.value = attr.value + .replace('#' + k, '#' + currentIDstring) + .replace(k + '.', currentIDstring + '.'); + }); + + idKey = idPrefix + k; + } + + // don't remove referenced IDs + delete IDs[idKey]; + } + } + + // remove non-referenced IDs attributes from elements + if (params.remove) { + + for(var ID in IDs) { + IDs[ID].removeAttr('id'); + } + + } + + return data; + +}; + +/** + * Generate unique minimal ID. + * + * @param {Array} [currentID] current ID + * @return {Array} generated ID array + */ +function generateID(currentID) { + + if (!currentID) return [0]; + + currentID[currentID.length - 1]++; + + for(var i = currentID.length - 1; i > 0; i--) { + if (currentID[i] > maxIDindex) { + currentID[i] = 0; + + if (currentID[i - 1] !== undefined) { + currentID[i - 1]++; + } + } + } + + if (currentID[0] > maxIDindex) { + currentID[0] = 0; + currentID.unshift(0); + } + + return currentID; + +} + +/** + * Get string from generated ID array. + * + * @param {Array} arr input ID array + * @return {String} output ID string + */ +function getIDstring(arr, params) { + + var str = params.prefix; + + arr.forEach(function(i) { + str += generateIDchars[i]; + }); + + return str; + +} diff --git a/node_modules/svgo/plugins/cleanupListOfValues.js b/node_modules/svgo/plugins/cleanupListOfValues.js new file mode 100644 index 00000000..e75944da --- /dev/null +++ b/node_modules/svgo/plugins/cleanupListOfValues.js @@ -0,0 +1,141 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = false; + +exports.description = 'rounds list of values to the fixed precision'; + +exports.params = { + floatPrecision: 3, + leadingZero: true, + defaultPx: true, + convertToPx: true +}; + +var regNumericValues = /^([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)(px|pt|pc|mm|cm|m|in|ft|em|ex|%)?$/, + regSeparator = /\s+,?\s*|,\s*/, + removeLeadingZero = require('../lib/svgo/tools').removeLeadingZero, + absoluteLengths = { // relative to px + cm: 96/2.54, + mm: 96/25.4, + in: 96, + pt: 4/3, + pc: 16 + }; + +/** + * Round list of values to the fixed precision. + * + * @example + * <svg viewBox="0 0 200.28423 200.28423" enable-background="new 0 0 200.28423 200.28423"> + * ⬇ + * <svg viewBox="0 0 200.284 200.284" enable-background="new 0 0 200.284 200.284"> + * + * + * <polygon points="208.250977 77.1308594 223.069336 ... "/> + * ⬇ + * <polygon points="208.251 77.131 223.069 ... "/> + * + * + * @param {Object} item current iteration item + * @param {Object} params plugin params + * @return {Boolean} if false, item will be filtered out + * + * @author kiyopikko + */ +exports.fn = function(item, params) { + + + if ( item.hasAttr('points') ) { + roundValues(item.attrs.points); + } + + if ( item.hasAttr('enable-background') ) { + roundValues(item.attrs['enable-background']); + } + + if ( item.hasAttr('viewBox') ) { + roundValues(item.attrs.viewBox); + } + + if ( item.hasAttr('stroke-dasharray') ) { + roundValues(item.attrs['stroke-dasharray']); + } + + if ( item.hasAttr('dx') ) { + roundValues(item.attrs.dx); + } + + if ( item.hasAttr('dy') ) { + roundValues(item.attrs.dy); + } + + if ( item.hasAttr('x') ) { + roundValues(item.attrs.x); + } + + if ( item.hasAttr('y') ) { + roundValues(item.attrs.y); + } + + + function roundValues($prop){ + + var num, units, + match, + matchNew, + lists = $prop.value, + listsArr = lists.split(regSeparator), + roundedListArr = [], + roundedList; + + listsArr.forEach(function(elem){ + + match = elem.match(regNumericValues); + matchNew = elem.match(/new/); + + // if attribute value matches regNumericValues + if(match){ + + // round it to the fixed precision + num = +(+match[1]).toFixed(params.floatPrecision), + units = match[3] || ''; + + // convert absolute values to pixels + if (params.convertToPx && units && (units in absoluteLengths)) { + var pxNum = +(absoluteLengths[units] * match[1]).toFixed(params.floatPrecision); + + if (String(pxNum).length < match[0].length) + num = pxNum, + units = 'px'; + } + + // and remove leading zero + if (params.leadingZero) { + num = removeLeadingZero(num); + } + + // remove default 'px' units + if (params.defaultPx && units === 'px') { + units = ''; + } + + roundedListArr.push(num+units); + + } + // if attribute value is "new"(only enable-background). + else if(matchNew){ + + roundedListArr.push('new'); + + } + + }); + + roundedList = roundedListArr.join(' '); + $prop.value = roundedList; + + } + +}; diff --git a/node_modules/svgo/plugins/cleanupNumericValues.js b/node_modules/svgo/plugins/cleanupNumericValues.js new file mode 100644 index 00000000..487443a5 --- /dev/null +++ b/node_modules/svgo/plugins/cleanupNumericValues.js @@ -0,0 +1,76 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = true; + +exports.description = 'rounds numeric values to the fixed precision, removes default ‘px’ units'; + +exports.params = { + floatPrecision: 3, + leadingZero: true, + defaultPx: true, + convertToPx: true +}; + +var regNumericValues = /^([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)(px|pt|pc|mm|cm|m|in|ft|em|ex|%)?$/, + removeLeadingZero = require('../lib/svgo/tools').removeLeadingZero, + absoluteLengths = { // relative to px + cm: 96/2.54, + mm: 96/25.4, + in: 96, + pt: 4/3, + pc: 16 + }; + +/** + * Round numeric values to the fixed precision, + * remove default 'px' units. + * + * @param {Object} item current iteration item + * @param {Object} params plugin params + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(item, params) { + + if (item.isElem()) { + + var match; + + item.eachAttr(function(attr) { + match = attr.value.match(regNumericValues); + + // if attribute value matches regNumericValues + if (match) { + // round it to the fixed precision + var num = +(+match[1]).toFixed(params.floatPrecision), + units = match[3] || ''; + + // convert absolute values to pixels + if (params.convertToPx && units && (units in absoluteLengths)) { + var pxNum = +(absoluteLengths[units] * match[1]).toFixed(params.floatPrecision); + + if (String(pxNum).length < match[0].length) + num = pxNum, + units = 'px'; + } + + // and remove leading zero + if (params.leadingZero) { + num = removeLeadingZero(num); + } + + // remove default 'px' units + if (params.defaultPx && units === 'px') { + units = ''; + } + + attr.value = num + units; + } + }); + + } + +}; diff --git a/node_modules/svgo/plugins/collapseGroups.js b/node_modules/svgo/plugins/collapseGroups.js new file mode 100644 index 00000000..adc97638 --- /dev/null +++ b/node_modules/svgo/plugins/collapseGroups.js @@ -0,0 +1,91 @@ +'use strict'; + +exports.type = 'perItemReverse'; + +exports.active = true; + +exports.description = 'collapses useless groups'; + +var collections = require('./_collections'), + attrsInheritable = collections.inheritableAttrs, + animationElems = collections.elemsGroups.animation; + +function hasAnimatedAttr(item) { + /* jshint validthis:true */ + return item.isElem(animationElems) && item.hasAttr('attributeName', this) || + !item.isEmpty() && item.content.some(hasAnimatedAttr, this); +} + +/* + * Collapse useless groups. + * + * @example + * <g> + * <g attr1="val1"> + * <path d="..."/> + * </g> + * </g> + * ⬇ + * <g> + * <g> + * <path attr1="val1" d="..."/> + * </g> + * </g> + * ⬇ + * <path attr1="val1" d="..."/> + * + * @param {Object} item current iteration item + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(item) { + + // non-empty elements + if (item.isElem() && !item.isElem('switch') && !item.isEmpty()) { + + item.content.forEach(function(g, i) { + + // non-empty groups + if (g.isElem('g') && !g.isEmpty()) { + + // move group attibutes to the single content element + if (g.hasAttr() && g.content.length === 1) { + var inner = g.content[0]; + + if (inner.isElem() && !inner.hasAttr('id') && + !(g.hasAttr('class') && inner.hasAttr('class')) && ( + !g.hasAttr('clip-path') && !g.hasAttr('mask') || + inner.isElem('g') && !g.hasAttr('transform') && !inner.hasAttr('transform') + ) + ) { + g.eachAttr(function(attr) { + if (g.content.some(hasAnimatedAttr, attr.name)) return; + + if (!inner.hasAttr(attr.name)) { + inner.addAttr(attr); + } else if (attr.name == 'transform') { + inner.attr(attr.name).value = attr.value + ' ' + inner.attr(attr.name).value; + } else if ( + attrsInheritable.indexOf(attr.name) < 0 && + !inner.hasAttr(attr.name, attr.value) + ) { + return; + } + + g.removeAttr(attr.name); + }); + } + } + + // collapse groups without attributes + if (!g.hasAttr() && !g.content.some(function(item) { return item.isElem(animationElems) })) { + item.spliceContent(i, 1, g.content); + } + } + + }); + + } + +}; diff --git a/node_modules/svgo/plugins/convertColors.js b/node_modules/svgo/plugins/convertColors.js new file mode 100644 index 00000000..b856e130 --- /dev/null +++ b/node_modules/svgo/plugins/convertColors.js @@ -0,0 +1,127 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = true; + +exports.description = 'converts colors: rgb() to #rrggbb and #rrggbb to #rgb'; + +exports.params = { + currentColor: false, + names2hex: true, + rgb2hex: true, + shorthex: true, + shortname: true +}; + +var collections = require('./_collections'), + rNumber = '([+-]?(?:\\d*\\.\\d+|\\d+\\.?)%?)', + rComma = '\\s*,\\s*', + regRGB = new RegExp('^rgb\\(\\s*' + rNumber + rComma + rNumber + rComma + rNumber + '\\s*\\)$'), + regHEX = /^\#(([a-fA-F0-9])\2){3}$/, + none = /\bnone\b/i; + +/** + * Convert different colors formats in element attributes to hex. + * + * @see http://www.w3.org/TR/SVG/types.html#DataTypeColor + * @see http://www.w3.org/TR/SVG/single-page.html#types-ColorKeywords + * + * @example + * Convert color name keyword to long hex: + * fuchsia ➡ #ff00ff + * + * Convert rgb() to long hex: + * rgb(255, 0, 255) ➡ #ff00ff + * rgb(50%, 100, 100%) ➡ #7f64ff + * + * Convert long hex to short hex: + * #aabbcc ➡ #abc + * + * Convert hex to short name + * #000080 ➡ navy + * + * @param {Object} item current iteration item + * @param {Object} params plugin params + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(item, params) { + + if (item.elem) { + + item.eachAttr(function(attr) { + + if (collections.colorsProps.indexOf(attr.name) > -1) { + + var val = attr.value, + match; + + // Convert colors to currentColor + if (params.currentColor) { + if (typeof params.currentColor === 'string') { + match = val === params.currentColor; + } else if (params.currentColor.exec) { + match = params.currentColor.exec(val); + } else { + match = !val.match(none); + } + if (match) { + val = 'currentColor'; + } + } + + // Convert color name keyword to long hex + if (params.names2hex && val.toLowerCase() in collections.colorsNames) { + val = collections.colorsNames[val.toLowerCase()]; + } + + // Convert rgb() to long hex + if (params.rgb2hex && (match = val.match(regRGB))) { + match = match.slice(1, 4).map(function(m) { + if (m.indexOf('%') > -1) + m = Math.round(parseFloat(m) * 2.55); + + return Math.max(0, Math.min(m, 255)); + }); + + val = rgb2hex(match); + } + + // Convert long hex to short hex + if (params.shorthex && (match = val.match(regHEX))) { + val = '#' + match[0][1] + match[0][3] + match[0][5]; + } + + // Convert hex to short name + if (params.shortname && val in collections.colorsShortNames) { + val = collections.colorsShortNames[val]; + } + + attr.value = val; + + } + + }); + + } + +}; + +/** + * Convert [r, g, b] to #rrggbb. + * + * @see https://gist.github.com/983535 + * + * @example + * rgb2hex([255, 255, 255]) // '#ffffff' + * + * @param {Array} rgb [r, g, b] + * @return {String} #rrggbb + * + * @author Jed Schmidt + */ +function rgb2hex(rgb) { + return '#' + ('00000' + (rgb[0] << 16 | rgb[1] << 8 | rgb[2]).toString(16)).slice(-6).toUpperCase(); +} diff --git a/node_modules/svgo/plugins/convertPathData.js b/node_modules/svgo/plugins/convertPathData.js new file mode 100644 index 00000000..c76a52c2 --- /dev/null +++ b/node_modules/svgo/plugins/convertPathData.js @@ -0,0 +1,957 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = true; + +exports.description = 'optimizes path data: writes in shorter form, applies transformations'; + +exports.params = { + applyTransforms: true, + applyTransformsStroked: true, + makeArcs: { + threshold: 2.5, // coefficient of rounding error + tolerance: 0.5 // percentage of radius + }, + straightCurves: true, + lineShorthands: true, + curveSmoothShorthands: true, + floatPrecision: 3, + transformPrecision: 5, + removeUseless: true, + collapseRepeated: true, + utilizeAbsolute: true, + leadingZero: true, + negativeExtraSpace: true +}; + +var pathElems = require('./_collections.js').pathElems, + path2js = require('./_path.js').path2js, + js2path = require('./_path.js').js2path, + applyTransforms = require('./_path.js').applyTransforms, + cleanupOutData = require('../lib/svgo/tools').cleanupOutData, + roundData, + precision, + error, + arcThreshold, + arcTolerance, + hasMarkerMid; + +/** + * Convert absolute Path to relative, + * collapse repeated instructions, + * detect and convert Lineto shorthands, + * remove useless instructions like "l0,0", + * trim useless delimiters and leading zeros, + * decrease accuracy of floating-point numbers. + * + * @see http://www.w3.org/TR/SVG/paths.html#PathData + * + * @param {Object} item current iteration item + * @param {Object} params plugin params + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(item, params) { + + if (item.isElem(pathElems) && item.hasAttr('d')) { + + precision = params.floatPrecision; + error = precision !== false ? +Math.pow(.1, precision).toFixed(precision) : 1e-2; + roundData = precision > 0 && precision < 20 ? strongRound : round; + if (params.makeArcs) { + arcThreshold = params.makeArcs.threshold; + arcTolerance = params.makeArcs.tolerance; + } + hasMarkerMid = item.hasAttr('marker-mid'); + + var data = path2js(item); + + // TODO: get rid of functions returns + if (data.length) { + convertToRelative(data); + + if (params.applyTransforms) { + data = applyTransforms(item, data, params); + } + + data = filters(data, params); + + if (params.utilizeAbsolute) { + data = convertToMixed(data, params); + } + + js2path(item, data, params); + } + + } + +}; + +/** + * Convert absolute path data coordinates to relative. + * + * @param {Array} path input path data + * @param {Object} params plugin params + * @return {Array} output path data + */ +function convertToRelative(path) { + + var point = [0, 0], + subpathPoint = [0, 0], + baseItem; + + path.forEach(function(item, index) { + + var instruction = item.instruction, + data = item.data; + + // data !== !z + if (data) { + + // already relative + // recalculate current point + if ('mcslqta'.indexOf(instruction) > -1) { + + point[0] += data[data.length - 2]; + point[1] += data[data.length - 1]; + + if (instruction === 'm') { + subpathPoint[0] = point[0]; + subpathPoint[1] = point[1]; + baseItem = item; + } + + } else if (instruction === 'h') { + + point[0] += data[0]; + + } else if (instruction === 'v') { + + point[1] += data[0]; + + } + + // convert absolute path data coordinates to relative + // if "M" was not transformed from "m" + // M → m + if (instruction === 'M') { + + if (index > 0) instruction = 'm'; + + data[0] -= point[0]; + data[1] -= point[1]; + + subpathPoint[0] = point[0] += data[0]; + subpathPoint[1] = point[1] += data[1]; + + baseItem = item; + + } + + // L → l + // T → t + else if ('LT'.indexOf(instruction) > -1) { + + instruction = instruction.toLowerCase(); + + // x y + // 0 1 + data[0] -= point[0]; + data[1] -= point[1]; + + point[0] += data[0]; + point[1] += data[1]; + + // C → c + } else if (instruction === 'C') { + + instruction = 'c'; + + // x1 y1 x2 y2 x y + // 0 1 2 3 4 5 + data[0] -= point[0]; + data[1] -= point[1]; + data[2] -= point[0]; + data[3] -= point[1]; + data[4] -= point[0]; + data[5] -= point[1]; + + point[0] += data[4]; + point[1] += data[5]; + + // S → s + // Q → q + } else if ('SQ'.indexOf(instruction) > -1) { + + instruction = instruction.toLowerCase(); + + // x1 y1 x y + // 0 1 2 3 + data[0] -= point[0]; + data[1] -= point[1]; + data[2] -= point[0]; + data[3] -= point[1]; + + point[0] += data[2]; + point[1] += data[3]; + + // A → a + } else if (instruction === 'A') { + + instruction = 'a'; + + // rx ry x-axis-rotation large-arc-flag sweep-flag x y + // 0 1 2 3 4 5 6 + data[5] -= point[0]; + data[6] -= point[1]; + + point[0] += data[5]; + point[1] += data[6]; + + // H → h + } else if (instruction === 'H') { + + instruction = 'h'; + + data[0] -= point[0]; + + point[0] += data[0]; + + // V → v + } else if (instruction === 'V') { + + instruction = 'v'; + + data[0] -= point[1]; + + point[1] += data[0]; + + } + + item.instruction = instruction; + item.data = data; + + // store absolute coordinates for later use + item.coords = point.slice(-2); + + } + + // !data === z, reset current point + else if (instruction == 'z') { + if (baseItem) { + item.coords = baseItem.coords; + } + point[0] = subpathPoint[0]; + point[1] = subpathPoint[1]; + } + + item.base = index > 0 ? path[index - 1].coords : [0, 0]; + + }); + + return path; + +} + +/** + * Main filters loop. + * + * @param {Array} path input path data + * @param {Object} params plugin params + * @return {Array} output path data + */ +function filters(path, params) { + + var stringify = data2Path.bind(null, params), + relSubpoint = [0, 0], + pathBase = [0, 0], + prev = {}; + + path = path.filter(function(item, index, path) { + + var instruction = item.instruction, + data = item.data, + next = path[index + 1]; + + if (data) { + + var sdata = data, + circle; + + if (instruction === 's') { + sdata = [0, 0].concat(data); + + if ('cs'.indexOf(prev.instruction) > -1) { + var pdata = prev.data, + n = pdata.length; + + // (-x, -y) of the prev tangent point relative to the current point + sdata[0] = pdata[n - 2] - pdata[n - 4]; + sdata[1] = pdata[n - 1] - pdata[n - 3]; + } + + } + + // convert curves to arcs if possible + if ( + params.makeArcs && + (instruction == 'c' || instruction == 's') && + isConvex(sdata) && + (circle = findCircle(sdata)) + ) { + var r = roundData([circle.radius])[0], + angle = findArcAngle(sdata, circle), + sweep = sdata[5] * sdata[0] - sdata[4] * sdata[1] > 0 ? 1 : 0, + arc = { + instruction: 'a', + data: [r, r, 0, 0, sweep, sdata[4], sdata[5]], + coords: item.coords.slice(), + base: item.base + }, + output = [arc], + // relative coordinates to adjust the found circle + relCenter = [circle.center[0] - sdata[4], circle.center[1] - sdata[5]], + relCircle = { center: relCenter, radius: circle.radius }, + arcCurves = [item], + hasPrev = 0, + suffix = '', + nextLonghand; + + if ( + prev.instruction == 'c' && isConvex(prev.data) && isArcPrev(prev.data, circle) || + prev.instruction == 'a' && prev.sdata && isArcPrev(prev.sdata, circle) + ) { + arcCurves.unshift(prev); + arc.base = prev.base; + arc.data[5] = arc.coords[0] - arc.base[0]; + arc.data[6] = arc.coords[1] - arc.base[1]; + var prevData = prev.instruction == 'a' ? prev.sdata : prev.data; + angle += findArcAngle(prevData, + { + center: [prevData[4] + relCenter[0], prevData[5] + relCenter[1]], + radius: circle.radius + } + ); + if (angle > Math.PI) arc.data[3] = 1; + hasPrev = 1; + } + + // check if next curves are fitting the arc + for (var j = index; (next = path[++j]) && ~'cs'.indexOf(next.instruction);) { + var nextData = next.data; + if (next.instruction == 's') { + nextLonghand = makeLonghand({instruction: 's', data: next.data.slice() }, + path[j - 1].data); + nextData = nextLonghand.data; + nextLonghand.data = nextData.slice(0, 2); + suffix = stringify([nextLonghand]); + } + if (isConvex(nextData) && isArc(nextData, relCircle)) { + angle += findArcAngle(nextData, relCircle); + if (angle - 2 * Math.PI > 1e-3) break; // more than 360° + if (angle > Math.PI) arc.data[3] = 1; + arcCurves.push(next); + if (2 * Math.PI - angle > 1e-3) { // less than 360° + arc.coords = next.coords; + arc.data[5] = arc.coords[0] - arc.base[0]; + arc.data[6] = arc.coords[1] - arc.base[1]; + } else { + // full circle, make a half-circle arc and add a second one + arc.data[5] = 2 * (relCircle.center[0] - nextData[4]); + arc.data[6] = 2 * (relCircle.center[1] - nextData[5]); + arc.coords = [arc.base[0] + arc.data[5], arc.base[1] + arc.data[6]]; + arc = { + instruction: 'a', + data: [r, r, 0, 0, sweep, + next.coords[0] - arc.coords[0], next.coords[1] - arc.coords[1]], + coords: next.coords, + base: arc.coords + }; + output.push(arc); + j++; + break; + } + relCenter[0] -= nextData[4]; + relCenter[1] -= nextData[5]; + } else break; + } + + if ((stringify(output) + suffix).length < stringify(arcCurves).length) { + if (path[j] && path[j].instruction == 's') { + makeLonghand(path[j], path[j - 1].data); + } + if (hasPrev) { + var prevArc = output.shift(); + roundData(prevArc.data); + relSubpoint[0] += prevArc.data[5] - prev.data[prev.data.length - 2]; + relSubpoint[1] += prevArc.data[6] - prev.data[prev.data.length - 1]; + prev.instruction = 'a'; + prev.data = prevArc.data; + item.base = prev.coords = prevArc.coords; + } + arc = output.shift(); + if (arcCurves.length == 1) { + item.sdata = sdata.slice(); // preserve curve data for future checks + } else if (arcCurves.length - 1 - hasPrev > 0) { + // filter out consumed next items + path.splice.apply(path, [index + 1, arcCurves.length - 1 - hasPrev].concat(output)); + } + if (!arc) return false; + instruction = 'a'; + data = arc.data; + item.coords = arc.coords; + } + } + + // Rounding relative coordinates, taking in account accummulating error + // to get closer to absolute coordinates. Sum of rounded value remains same: + // l .25 3 .25 2 .25 3 .25 2 -> l .3 3 .2 2 .3 3 .2 2 + if (precision !== false) { + if ('mltqsc'.indexOf(instruction) > -1) { + for (var i = data.length; i--;) { + data[i] += item.base[i % 2] - relSubpoint[i % 2]; + } + } else if (instruction == 'h') { + data[0] += item.base[0] - relSubpoint[0]; + } else if (instruction == 'v') { + data[0] += item.base[1] - relSubpoint[1]; + } else if (instruction == 'a') { + data[5] += item.base[0] - relSubpoint[0]; + data[6] += item.base[1] - relSubpoint[1]; + } + roundData(data); + + if (instruction == 'h') relSubpoint[0] += data[0]; + else if (instruction == 'v') relSubpoint[1] += data[0]; + else { + relSubpoint[0] += data[data.length - 2]; + relSubpoint[1] += data[data.length - 1]; + } + roundData(relSubpoint); + + if (instruction.toLowerCase() == 'm') { + pathBase[0] = relSubpoint[0]; + pathBase[1] = relSubpoint[1]; + } + } + + // convert straight curves into lines segments + if (params.straightCurves) { + + if ( + instruction === 'c' && + isCurveStraightLine(data) || + instruction === 's' && + isCurveStraightLine(sdata) + ) { + if (next && next.instruction == 's') + makeLonghand(next, data); // fix up next curve + instruction = 'l'; + data = data.slice(-2); + } + + else if ( + instruction === 'q' && + isCurveStraightLine(data) + ) { + if (next && next.instruction == 't') + makeLonghand(next, data); // fix up next curve + instruction = 'l'; + data = data.slice(-2); + } + + else if ( + instruction === 't' && + prev.instruction !== 'q' && + prev.instruction !== 't' + ) { + instruction = 'l'; + data = data.slice(-2); + } + + else if ( + instruction === 'a' && + (data[0] === 0 || data[1] === 0) + ) { + instruction = 'l'; + data = data.slice(-2); + } + } + + // horizontal and vertical line shorthands + // l 50 0 → h 50 + // l 0 50 → v 50 + if ( + params.lineShorthands && + instruction === 'l' + ) { + if (data[1] === 0) { + instruction = 'h'; + data.pop(); + } else if (data[0] === 0) { + instruction = 'v'; + data.shift(); + } + } + + // collapse repeated commands + // h 20 h 30 -> h 50 + if ( + params.collapseRepeated && + !hasMarkerMid && + ('mhv'.indexOf(instruction) > -1) && + prev.instruction && + instruction == prev.instruction.toLowerCase() && + ( + (instruction != 'h' && instruction != 'v') || + (prev.data[0] >= 0) == (item.data[0] >= 0) + )) { + prev.data[0] += data[0]; + if (instruction != 'h' && instruction != 'v') { + prev.data[1] += data[1]; + } + prev.coords = item.coords; + path[index] = prev; + return false; + } + + // convert curves into smooth shorthands + if (params.curveSmoothShorthands && prev.instruction) { + + // curveto + if (instruction === 'c') { + + // c + c → c + s + if ( + prev.instruction === 'c' && + data[0] === -(prev.data[2] - prev.data[4]) && + data[1] === -(prev.data[3] - prev.data[5]) + ) { + instruction = 's'; + data = data.slice(2); + } + + // s + c → s + s + else if ( + prev.instruction === 's' && + data[0] === -(prev.data[0] - prev.data[2]) && + data[1] === -(prev.data[1] - prev.data[3]) + ) { + instruction = 's'; + data = data.slice(2); + } + + // [^cs] + c → [^cs] + s + else if ( + 'cs'.indexOf(prev.instruction) === -1 && + data[0] === 0 && + data[1] === 0 + ) { + instruction = 's'; + data = data.slice(2); + } + + } + + // quadratic Bézier curveto + else if (instruction === 'q') { + + // q + q → q + t + if ( + prev.instruction === 'q' && + data[0] === (prev.data[2] - prev.data[0]) && + data[1] === (prev.data[3] - prev.data[1]) + ) { + instruction = 't'; + data = data.slice(2); + } + + // t + q → t + t + else if ( + prev.instruction === 't' && + data[2] === prev.data[0] && + data[3] === prev.data[1] + ) { + instruction = 't'; + data = data.slice(2); + } + + } + + } + + // remove useless non-first path segments + if (params.removeUseless) { + + // l 0,0 / h 0 / v 0 / q 0,0 0,0 / t 0,0 / c 0,0 0,0 0,0 / s 0,0 0,0 + if ( + ( + 'lhvqtcs'.indexOf(instruction) > -1 + ) && + data.every(function(i) { return i === 0; }) + ) { + path[index] = prev; + return false; + } + + // a 25,25 -30 0,1 0,0 + if ( + instruction === 'a' && + data[5] === 0 && + data[6] === 0 + ) { + path[index] = prev; + return false; + } + + } + + item.instruction = instruction; + item.data = data; + + prev = item; + + } else { + + // z resets coordinates + relSubpoint[0] = pathBase[0]; + relSubpoint[1] = pathBase[1]; + if (prev.instruction == 'z') return false; + prev = item; + + } + + return true; + + }); + + return path; + +} + +/** + * Writes data in shortest form using absolute or relative coordinates. + * + * @param {Array} data input path data + * @return {Boolean} output + */ +function convertToMixed(path, params) { + + var prev = path[0]; + + path = path.filter(function(item, index) { + + if (index == 0) return true; + if (!item.data) { + prev = item; + return true; + } + + var instruction = item.instruction, + data = item.data, + adata = data && data.slice(0); + + if ('mltqsc'.indexOf(instruction) > -1) { + for (var i = adata.length; i--;) { + adata[i] += item.base[i % 2]; + } + } else if (instruction == 'h') { + adata[0] += item.base[0]; + } else if (instruction == 'v') { + adata[0] += item.base[1]; + } else if (instruction == 'a') { + adata[5] += item.base[0]; + adata[6] += item.base[1]; + } + + roundData(adata); + + var absoluteDataStr = cleanupOutData(adata, params), + relativeDataStr = cleanupOutData(data, params); + + // Convert to absolute coordinates if it's shorter. + // v-20 -> V0 + // Don't convert if it fits following previous instruction. + // l20 30-10-50 instead of l20 30L20 30 + if ( + absoluteDataStr.length < relativeDataStr.length && + !( + params.negativeExtraSpace && + instruction == prev.instruction && + prev.instruction.charCodeAt(0) > 96 && + absoluteDataStr.length == relativeDataStr.length - 1 && + (data[0] < 0 || /^0\./.test(data[0]) && prev.data[prev.data.length - 1] % 1) + ) + ) { + item.instruction = instruction.toUpperCase(); + item.data = adata; + } + + prev = item; + + return true; + + }); + + return path; + +} + +/** + * Checks if curve is convex. Control points of such a curve must form + * a convex quadrilateral with diagonals crosspoint inside of it. + * + * @param {Array} data input path data + * @return {Boolean} output + */ +function isConvex(data) { + + var center = getIntersection([0, 0, data[2], data[3], data[0], data[1], data[4], data[5]]); + + return center && + (data[2] < center[0] == center[0] < 0) && + (data[3] < center[1] == center[1] < 0) && + (data[4] < center[0] == center[0] < data[0]) && + (data[5] < center[1] == center[1] < data[1]); + +} + +/** + * Computes lines equations by two points and returns their intersection point. + * + * @param {Array} coords 8 numbers for 4 pairs of coordinates (x,y) + * @return {Array|undefined} output coordinate of lines' crosspoint + */ +function getIntersection(coords) { + + // Prev line equation parameters. + var a1 = coords[1] - coords[3], // y1 - y2 + b1 = coords[2] - coords[0], // x2 - x1 + c1 = coords[0] * coords[3] - coords[2] * coords[1], // x1 * y2 - x2 * y1 + + // Next line equation parameters + a2 = coords[5] - coords[7], // y1 - y2 + b2 = coords[6] - coords[4], // x2 - x1 + c2 = coords[4] * coords[7] - coords[5] * coords[6], // x1 * y2 - x2 * y1 + denom = (a1 * b2 - a2 * b1); + + if (!denom) return; // parallel lines havn't an intersection + + var cross = [ + (b1 * c2 - b2 * c1) / denom, + (a1 * c2 - a2 * c1) / -denom + ]; + if ( + !isNaN(cross[0]) && !isNaN(cross[1]) && + isFinite(cross[0]) && isFinite(cross[1]) + ) { + return cross; + } + +} + +/** + * Decrease accuracy of floating-point numbers + * in path data keeping a specified number of decimals. + * Smart rounds values like 2.3491 to 2.35 instead of 2.349. + * Doesn't apply "smartness" if the number precision fits already. + * + * @param {Array} data input data array + * @return {Array} output data array + */ +function strongRound(data) { + for (var i = data.length; i-- > 0;) { + if (data[i].toFixed(precision) != data[i]) { + var rounded = +data[i].toFixed(precision - 1); + data[i] = +Math.abs(rounded - data[i]).toFixed(precision + 1) >= error ? + +data[i].toFixed(precision) : + rounded; + } + } + return data; +} + +/** + * Simple rounding function if precision is 0. + * + * @param {Array} data input data array + * @return {Array} output data array + */ +function round(data) { + for (var i = data.length; i-- > 0;) { + data[i] = Math.round(data[i]); + } + return data; +} + +/** + * Checks if a curve is a straight line by measuring distance + * from middle points to the line formed by end points. + * + * @param {Array} xs array of curve points x-coordinates + * @param {Array} ys array of curve points y-coordinates + * @return {Boolean} + */ + +function isCurveStraightLine(data) { + + // Get line equation a·x + b·y + c = 0 coefficients a, b (c = 0) by start and end points. + var i = data.length - 2, + a = -data[i + 1], // y1 − y2 (y1 = 0) + b = data[i], // x2 − x1 (x1 = 0) + d = 1 / (a * a + b * b); // same part for all points + + if (i <= 1 || !isFinite(d)) return false; // curve that ends at start point isn't the case + + // Distance from point (x0, y0) to the line is sqrt((c − a·x0 − b·y0)² / (a² + b²)) + while ((i -= 2) >= 0) { + if (Math.sqrt(Math.pow(a * data[i] + b * data[i + 1], 2) * d) > error) + return false; + } + + return true; + +} + +/** + * Converts next curve from shorthand to full form using the current curve data. + * + * @param {Object} item curve to convert + * @param {Array} data current curve data + */ + +function makeLonghand(item, data) { + switch (item.instruction) { + case 's': item.instruction = 'c'; break; + case 't': item.instruction = 'q'; break; + } + item.data.unshift(data[data.length - 2] - data[data.length - 4], data[data.length - 1] - data[data.length - 3]); + return item; +} + +/** + * Returns distance between two points + * + * @param {Array} point1 first point coordinates + * @param {Array} point2 second point coordinates + * @return {Number} distance + */ + +function getDistance(point1, point2) { + return Math.sqrt(Math.pow(point1[0] - point2[0], 2) + Math.pow(point1[1] - point2[1], 2)); +} + +/** + * Returns coordinates of the curve point corresponding to the certain t + * a·(1 - t)³·p1 + b·(1 - t)²·t·p2 + c·(1 - t)·t²·p3 + d·t³·p4, + * where pN are control points and p1 is zero due to relative coordinates. + * + * @param {Array} curve array of curve points coordinates + * @param {Number} t parametric position from 0 to 1 + * @return {Array} Point coordinates + */ + +function getCubicBezierPoint(curve, t) { + var sqrT = t * t, + cubT = sqrT * t, + mt = 1 - t, + sqrMt = mt * mt; + + return [ + 3 * sqrMt * t * curve[0] + 3 * mt * sqrT * curve[2] + cubT * curve[4], + 3 * sqrMt * t * curve[1] + 3 * mt * sqrT * curve[3] + cubT * curve[5] + ]; +} + +/** + * Finds circle by 3 points of the curve and checks if the curve fits the found circle. + * + * @param {Array} curve + * @return {Object|undefined} circle + */ + +function findCircle(curve) { + var midPoint = getCubicBezierPoint(curve, 1/2), + m1 = [midPoint[0] / 2, midPoint[1] / 2], + m2 = [(midPoint[0] + curve[4]) / 2, (midPoint[1] + curve[5]) / 2], + center = getIntersection([ + m1[0], m1[1], + m1[0] + m1[1], m1[1] - m1[0], + m2[0], m2[1], + m2[0] + (m2[1] - midPoint[1]), m2[1] - (m2[0] - midPoint[0]) + ]), + radius = center && getDistance([0, 0], center), + tolerance = Math.min(arcThreshold * error, arcTolerance * radius / 100); + + if (center && [1/4, 3/4].every(function(point) { + return Math.abs(getDistance(getCubicBezierPoint(curve, point), center) - radius) <= tolerance; + })) + return { center: center, radius: radius}; +} + +/** + * Checks if a curve fits the given circe. + * + * @param {Object} circle + * @param {Array} curve + * @return {Boolean} + */ + +function isArc(curve, circle) { + var tolerance = Math.min(arcThreshold * error, arcTolerance * circle.radius / 100); + + return [0, 1/4, 1/2, 3/4, 1].every(function(point) { + return Math.abs(getDistance(getCubicBezierPoint(curve, point), circle.center) - circle.radius) <= tolerance; + }); +} + +/** + * Checks if a previos curve fits the given circe. + * + * @param {Object} circle + * @param {Array} curve + * @return {Boolean} + */ + +function isArcPrev(curve, circle) { + return isArc(curve, { + center: [circle.center[0] + curve[4], circle.center[1] + curve[5]], + radius: circle.radius + }); +} + +/** + * Finds angle of a curve fitting the given arc. + + * @param {Array} curve + * @param {Object} relCircle + * @return {Number} angle + */ + +function findArcAngle(curve, relCircle) { + var x1 = -relCircle.center[0], + y1 = -relCircle.center[1], + x2 = curve[4] - relCircle.center[0], + y2 = curve[5] - relCircle.center[1]; + + return Math.acos( + (x1 * x2 + y1 * y2) / + Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2)) + ); +} + +/** + * Converts given path data to string. + * + * @param {Object} params + * @param {Array} pathData + * @return {String} + */ + +function data2Path(params, pathData) { + return pathData.reduce(function(pathString, item) { + return pathString += item.instruction + (item.data ? cleanupOutData(roundData(item.data.slice()), params) : ''); + }, ''); +} diff --git a/node_modules/svgo/plugins/convertShapeToPath.js b/node_modules/svgo/plugins/convertShapeToPath.js new file mode 100644 index 00000000..6d328049 --- /dev/null +++ b/node_modules/svgo/plugins/convertShapeToPath.js @@ -0,0 +1,103 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = true; + +exports.description = 'converts basic shapes to more compact path form'; + +var none = { value: 0 }, + regNumber = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g; + +/** + * Converts basic shape to more compact path. + * It also allows further optimizations like + * combining paths with similar attributes. + * + * @see http://www.w3.org/TR/SVG/shapes.html + * + * @param {Object} item current iteration item + * @param {Object} params plugin params + * @return {Boolean} if false, item will be filtered out + * + * @author Lev Solntsev + */ +exports.fn = function(item) { + + if ( + item.isElem('rect') && + item.hasAttr('width') && + item.hasAttr('height') && + !item.hasAttr('rx') && + !item.hasAttr('ry') + ) { + + var x = +(item.attr('x') || none).value, + y = +(item.attr('y') || none).value, + width = +item.attr('width').value, + height = +item.attr('height').value; + + // Values like '100%' compute to NaN, thus running after + // cleanupNumericValues when 'px' units has already been removed. + // TODO: Calculate sizes from % and non-px units if possible. + if (isNaN(x - y + width - height)) return; + + var pathData = + 'M' + x + ' ' + y + + 'H' + (x + width) + + 'V' + (y + height) + + 'H' + x + + 'z'; + + item.addAttr({ + name: 'd', + value: pathData, + prefix: '', + local: 'd' + }); + + item.renameElem('path') + .removeAttr(['x', 'y', 'width', 'height']); + + } else if (item.isElem('line')) { + + var x1 = +(item.attr('x1') || none).value, + y1 = +(item.attr('y1') || none).value, + x2 = +(item.attr('x2') || none).value, + y2 = +(item.attr('y2') || none).value; + if (isNaN(x1 - y1 + x2 - y2)) return; + + item.addAttr({ + name: 'd', + value: 'M' + x1 + ' ' + y1 + 'L' + x2 + ' ' + y2, + prefix: '', + local: 'd' + }); + + item.renameElem('path') + .removeAttr(['x1', 'y1', 'x2', 'y2']); + + } else if (( + item.isElem('polyline') || + item.isElem('polygon') + ) && + item.hasAttr('points') + ) { + + var coords = (item.attr('points').value.match(regNumber) || []).map(Number); + if (coords.length < 4) return false; + + item.addAttr({ + name: 'd', + value: 'M' + coords.slice(0,2).join(' ') + + 'L' + coords.slice(2).join(' ') + + (item.isElem('polygon') ? 'z' : ''), + prefix: '', + local: 'd' + }); + + item.renameElem('path') + .removeAttr('points'); + } + +}; diff --git a/node_modules/svgo/plugins/convertStyleToAttrs.js b/node_modules/svgo/plugins/convertStyleToAttrs.js new file mode 100644 index 00000000..02bbc550 --- /dev/null +++ b/node_modules/svgo/plugins/convertStyleToAttrs.js @@ -0,0 +1,117 @@ +/* jshint quotmark: false */ +'use strict'; + +exports.type = 'perItem'; + +exports.active = true; + +exports.description = 'converts style to attributes'; + +var EXTEND = require('whet.extend'), + stylingProps = require('./_collections').attrsGroups.presentation, + rEscape = '\\\\(?:[0-9a-f]{1,6}\\s?|\\r\\n|.)', // Like \" or \2051. Code points consume one space. + rAttr = '\\s*(' + g('[^:;\\\\]', rEscape) + '*?)\\s*', // attribute name like ‘fill’ + rSingleQuotes = "'(?:[^'\\n\\r\\\\]|" + rEscape + ")*?(?:'|$)", // string in single quotes: 'smth' + rQuotes = '"(?:[^"\\n\\r\\\\]|' + rEscape + ')*?(?:"|$)', // string in double quotes: "smth" + rQuotedString = new RegExp('^' + g(rSingleQuotes, rQuotes) + '$'), + + // Parentheses, E.g.: url(data:image/png;base64,iVBO...). + // ':' and ';' inside of it should be threated as is. (Just like in strings.) + rParenthesis = '\\(' + g('[^\'"()\\\\]+', rEscape, rSingleQuotes, rQuotes) + '*?' + '\\)', + + // The value. It can have strings and parentheses (see above). Fallbacks to anything in case of unexpected input. + rValue = '\\s*(' + g('[^\'"();\\\\]+?', rEscape, rSingleQuotes, rQuotes, rParenthesis, '[^;]*?') + '*?' + ')', + + // End of declaration. Spaces outside of capturing groups help to do natural trimming. + rDeclEnd = '\\s*(?:;\\s*|$)', + + // Final RegExp to parse CSS declarations. + regDeclarationBlock = new RegExp(rAttr + ':' + rValue + rDeclEnd, 'ig'), + + // Comments expression. Honors escape sequences and strings. + regStripComments = new RegExp(g(rEscape, rSingleQuotes, rQuotes, '/\\*[^]*?\\*/'), 'ig'); + +/** + * Convert style in attributes. Cleanups comments and illegal declarations (without colon) as a side effect. + * + * @example + * <g style="fill:#000; color: #fff;"> + * ⬇ + * <g fill="#000" color="#fff"> + * + * @example + * <g style="fill:#000; color: #fff; -webkit-blah: blah"> + * ⬇ + * <g fill="#000" color="#fff" style="-webkit-blah: blah"> + * + * @param {Object} item current iteration item + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(item) { + /* jshint boss: true */ + + if (item.elem && item.hasAttr('style')) { + // ['opacity: 1', 'color: #000'] + var styleValue = item.attr('style').value, + styles = [], + attrs = {}; + + // Strip CSS comments preserving escape sequences and strings. + styleValue = styleValue.replace(regStripComments, function(match) { + return match[0] == '/' ? '' : + match[0] == '\\' && /[-g-z]/i.test(match[1]) ? match[1] : match; + }); + + regDeclarationBlock.lastIndex = 0; + for (var rule; rule = regDeclarationBlock.exec(styleValue);) { + styles.push([rule[1], rule[2]]); + } + + if (styles.length) { + + styles = styles.filter(function(style) { + if (style[0]) { + var prop = style[0].toLowerCase(), + val = style[1]; + + if (rQuotedString.test(val)) { + val = val.slice(1, -1); + } + + if (stylingProps.indexOf(prop) > -1) { + + attrs[prop] = { + name: prop, + value: val, + local: prop, + prefix: '' + }; + + return false; + } + } + + return true; + }); + + EXTEND(item.attrs, attrs); + + if (styles.length) { + item.attr('style').value = styles + .map(function(declaration) { return declaration.join(':') }) + .join(';'); + } else { + item.removeAttr('style'); + } + + } + + } + +}; + +function g() { + return '(?:' + Array.prototype.join.call(arguments, '|') + ')'; +} diff --git a/node_modules/svgo/plugins/convertTransform.js b/node_modules/svgo/plugins/convertTransform.js new file mode 100644 index 00000000..c2085892 --- /dev/null +++ b/node_modules/svgo/plugins/convertTransform.js @@ -0,0 +1,364 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = true; + +exports.description = 'collapses multiple transformations and optimizes it'; + +exports.params = { + convertToShorts: true, + // degPrecision: 3, // transformPrecision (or matrix precision) - 2 by default + floatPrecision: 3, + transformPrecision: 5, + matrixToTransform: true, + shortTranslate: true, + shortScale: true, + shortRotate: true, + removeUseless: true, + collapseIntoOne: true, + leadingZero: true, + negativeExtraSpace: false +}; + +var cleanupOutData = require('../lib/svgo/tools').cleanupOutData, + EXTEND = require('whet.extend'), + transform2js = require('./_transforms.js').transform2js, + transformsMultiply = require('./_transforms.js').transformsMultiply, + matrixToTransform = require('./_transforms.js').matrixToTransform, + degRound, + floatRound, + transformRound; + +/** + * Convert matrices to the short aliases, + * convert long translate, scale or rotate transform notations to the shorts ones, + * convert transforms to the matrices and multiply them all into one, + * remove useless transforms. + * + * @see http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined + * + * @param {Object} item current iteration item + * @param {Object} params plugin params + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(item, params) { + + if (item.elem) { + + // transform + if (item.hasAttr('transform')) { + convertTransform(item, 'transform', params); + } + + // gradientTransform + if (item.hasAttr('gradientTransform')) { + convertTransform(item, 'gradientTransform', params); + } + + // patternTransform + if (item.hasAttr('patternTransform')) { + convertTransform(item, 'patternTransform', params); + } + + } + +}; + +/** + * Main function. + * + * @param {Object} item input item + * @param {String} attrName attribute name + * @param {Object} params plugin params + */ +function convertTransform(item, attrName, params) { + var data = transform2js(item.attr(attrName).value); + params = definePrecision(data, params); + + if (params.collapseIntoOne && data.length > 1) { + data = [transformsMultiply(data)]; + } + + if (params.convertToShorts) { + data = convertToShorts(data, params); + } else { + data.forEach(roundTransform); + } + + if (params.removeUseless) { + data = removeUseless(data); + } + + if (data.length) { + item.attr(attrName).value = js2transform(data, params); + } else { + item.removeAttr(attrName); + } +} + +/** + * Defines precision to work with certain parts. + * transformPrecision - for scale and four first matrix parameters (needs a better precision due to multiplying), + * floatPrecision - for translate including two last matrix and rotate parameters, + * degPrecision - for rotate and skew. By default it's equal to (rougly) + * transformPrecision - 2 or floatPrecision whichever is lower. Can be set in params. + * + * @param {Array} transforms input array + * @param {Object} params plugin params + * @return {Array} output array + */ +function definePrecision(data, params) { + /* jshint validthis: true */ + var matrixData = data.reduce(getMatrixData, []), + significantDigits = params.transformPrecision; + + // Clone params so it don't affect other elements transformations. + params = EXTEND({}, params); + + // Limit transform precision with matrix one. Calculating with larger precision doesn't add any value. + if (matrixData.length) { + params.transformPrecision = Math.min(params.transformPrecision, + Math.max.apply(Math, matrixData.map(floatDigits)) || params.transformPrecision); + + significantDigits = Math.max.apply(Math, matrixData.map(function(n) { + return String(n).replace(/\D+/g, '').length; // Number of digits in a number. 123.45 → 5 + })); + } + // No sense in angle precision more then number of significant digits in matrix. + if (!('degPrecision' in params)) { + params.degPrecision = Math.max(0, Math.min(params.floatPrecision, significantDigits - 2)); + } + + floatRound = params.floatPrecision >= 1 && params.floatPrecision < 20 ? + smartRound.bind(this, params.floatPrecision) : + round; + degRound = params.degPrecision >= 1 && params.floatPrecision < 20 ? + smartRound.bind(this, params.degPrecision) : + round; + transformRound = params.transformPrecision >= 1 && params.floatPrecision < 20 ? + smartRound.bind(this, params.transformPrecision) : + round; + + return params; +} + +/** + * Gathers four first matrix parameters. + * + * @param {Array} a array of data + * @param {Object} transform + * @return {Array} output array + */ +function getMatrixData(a, b) { + return b.name == 'matrix' ? a.concat(b.data.slice(0, 4)) : a; +} + +/** + * Returns number of digits after the point. 0.125 → 3 + */ +function floatDigits(n) { + return (n = String(n)).slice(n.indexOf('.')).length - 1; +} + +/** + * Convert transforms to the shorthand alternatives. + * + * @param {Array} transforms input array + * @param {Object} params plugin params + * @return {Array} output array + */ +function convertToShorts(transforms, params) { + + for(var i = 0; i < transforms.length; i++) { + + var transform = transforms[i]; + + // convert matrix to the short aliases + if ( + params.matrixToTransform && + transform.name === 'matrix' + ) { + var decomposed = matrixToTransform(transform, params); + if (decomposed != transform && + js2transform(decomposed, params).length <= js2transform([transform], params).length) { + + transforms.splice.apply(transforms, [i, 1].concat(decomposed)); + } + transform = transforms[i]; + } + + // fixed-point numbers + // 12.754997 → 12.755 + roundTransform(transform); + + // convert long translate transform notation to the shorts one + // translate(10 0) → translate(10) + if ( + params.shortTranslate && + transform.name === 'translate' && + transform.data.length === 2 && + !transform.data[1] + ) { + transform.data.pop(); + } + + // convert long scale transform notation to the shorts one + // scale(2 2) → scale(2) + if ( + params.shortScale && + transform.name === 'scale' && + transform.data.length === 2 && + transform.data[0] === transform.data[1] + ) { + transform.data.pop(); + } + + // convert long rotate transform notation to the short one + // translate(cx cy) rotate(a) translate(-cx -cy) → rotate(a cx cy) + if ( + params.shortRotate && + transforms[i - 2] && + transforms[i - 2].name === 'translate' && + transforms[i - 1].name === 'rotate' && + transforms[i].name === 'translate' && + transforms[i - 2].data[0] === -transforms[i].data[0] && + transforms[i - 2].data[1] === -transforms[i].data[1] + ) { + transforms.splice(i - 2, 3, { + name: 'rotate', + data: [ + transforms[i - 1].data[0], + transforms[i - 2].data[0], + transforms[i - 2].data[1] + ] + }); + + // splice compensation + i -= 2; + + transform = transforms[i]; + } + + } + + return transforms; + +} + +/** + * Remove useless transforms. + * + * @param {Array} transforms input array + * @return {Array} output array + */ +function removeUseless(transforms) { + + return transforms.filter(function(transform) { + + // translate(0), rotate(0[, cx, cy]), skewX(0), skewY(0) + if ( + ['translate', 'rotate', 'skewX', 'skewY'].indexOf(transform.name) > -1 && + (transform.data.length == 1 || transform.name == 'rotate') && + !transform.data[0] || + + // translate(0, 0) + transform.name == 'translate' && + !transform.data[0] && + !transform.data[1] || + + // scale(1) + transform.name == 'scale' && + transform.data[0] == 1 && + (transform.data.length < 2 || transform.data[1] == 1) || + + // matrix(1 0 0 1 0 0) + transform.name == 'matrix' && + transform.data[0] == 1 && + transform.data[3] == 1 && + !(transform.data[1] || transform.data[2] || transform.data[4] || transform.data[5]) + ) { + return false; + } + + return true; + + }); + +} + +/** + * Convert transforms JS representation to string. + * + * @param {Array} transformJS JS representation array + * @param {Object} params plugin params + * @return {String} output string + */ +function js2transform(transformJS, params) { + + var transformString = ''; + + // collect output value string + transformJS.forEach(function(transform) { + roundTransform(transform); + transformString += (transformString && ' ') + transform.name + '(' + cleanupOutData(transform.data, params) + ')'; + }); + + return transformString; + +} + +function roundTransform(transform) { + switch (transform.name) { + case 'translate': + transform.data = floatRound(transform.data); + break; + case 'rotate': + transform.data = degRound(transform.data.slice(0, 1)).concat(floatRound(transform.data.slice(1))); + break; + case 'skewX': + case 'skewY': + transform.data = degRound(transform.data); + break; + case 'scale': + transform.data = transformRound(transform.data); + break; + case 'matrix': + transform.data = transformRound(transform.data.slice(0, 4)).concat(floatRound(transform.data.slice(4))); + break; + } + return transform; +} + +/** + * Rounds numbers in array. + * + * @param {Array} data input data array + * @return {Array} output data array + */ +function round(data) { + return data.map(Math.round); +} + +/** + * Decrease accuracy of floating-point numbers + * in transforms keeping a specified number of decimals. + * Smart rounds values like 2.349 to 2.35. + * + * @param {Number} fixed number of decimals + * @param {Array} data input data array + * @return {Array} output data array + */ +function smartRound(precision, data) { + for (var i = data.length, tolerance = +Math.pow(.1, precision).toFixed(precision); i--;) { + if (data[i].toFixed(precision) != data[i]) { + var rounded = +data[i].toFixed(precision - 1); + data[i] = +Math.abs(rounded - data[i]).toFixed(precision + 1) >= tolerance ? + +data[i].toFixed(precision) : + rounded; + } + } + return data; +} diff --git a/node_modules/svgo/plugins/mergePaths.js b/node_modules/svgo/plugins/mergePaths.js new file mode 100644 index 00000000..63cf56e8 --- /dev/null +++ b/node_modules/svgo/plugins/mergePaths.js @@ -0,0 +1,71 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = true; + +exports.description = 'merges multiple paths in one if possible'; + +exports.params = { + collapseRepeated: true, + leadingZero: true, + negativeExtraSpace: true +}; + +var path2js = require('./_path.js').path2js, + js2path = require('./_path.js').js2path, + intersects = require('./_path.js').intersects; + +/** + * Merge multiple Paths into one. + * + * @param {Object} item current iteration item + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich, Lev Solntsev + */ +exports.fn = function(item, params) { + + if (!item.isElem() || item.isEmpty()) return; + + var prevContentItem = null, + prevContentItemKeys = null; + + item.content = item.content.filter(function(contentItem) { + + if (prevContentItem && + prevContentItem.isElem('path') && + prevContentItem.isEmpty() && + prevContentItem.hasAttr('d') && + contentItem.isElem('path') && + contentItem.isEmpty() && + contentItem.hasAttr('d') + ) { + + if (!prevContentItemKeys) { + prevContentItemKeys = Object.keys(prevContentItem.attrs); + } + + var contentItemAttrs = Object.keys(contentItem.attrs), + equalData = prevContentItemKeys.length == contentItemAttrs.length && + contentItemAttrs.every(function(key) { + return key == 'd' || + prevContentItem.hasAttr(key) && + prevContentItem.attr(key).value == contentItem.attr(key).value; + }), + prevPathJS = path2js(prevContentItem), + curPathJS = path2js(contentItem); + + if (equalData && !intersects(prevPathJS, curPathJS)) { + js2path(prevContentItem, prevPathJS.concat(curPathJS), params); + return false; + } + } + + prevContentItem = contentItem; + prevContentItemKeys = null; + return true; + + }); + +}; diff --git a/node_modules/svgo/plugins/minifyStyles.js b/node_modules/svgo/plugins/minifyStyles.js new file mode 100644 index 00000000..3977b367 --- /dev/null +++ b/node_modules/svgo/plugins/minifyStyles.js @@ -0,0 +1,45 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = true; + +exports.params = { + svgo: {} +}; + +exports.description = 'minifies existing styles in svg'; + +var csso = require('csso'); + +/** + * Minifies styles (<style> element + style attribute) using svgo + * + * @param {Object} item current iteration item + * @return {Boolean} if false, item will be filtered out + * + * @author strarsis <strarsis@gmail.com> + */ +exports.fn = function(item, svgoOptions) { + + if(item.elem) { + if(item.isElem('style') && !item.isEmpty()) { + var styleCss = item.content[0].text || item.content[0].cdata || [], + DATA = styleCss.indexOf('>') >= 0 || styleCss.indexOf('<') >= 0 ? 'cdata' : 'text'; + if(styleCss.length > 0) { + var styleCssMinified = csso.minify(styleCss, svgoOptions); + item.content[0][DATA] = styleCssMinified.css; + } + } + + if(item.hasAttr('style')) { + var itemCss = item.attr('style').value; + if(itemCss.length > 0) { + var itemCssMinified = csso.minifyBlock(itemCss, svgoOptions); + item.attr('style').value = itemCssMinified.css; + } + } + } + + return item; +}; diff --git a/node_modules/svgo/plugins/moveElemsAttrsToGroup.js b/node_modules/svgo/plugins/moveElemsAttrsToGroup.js new file mode 100644 index 00000000..0f3b65c7 --- /dev/null +++ b/node_modules/svgo/plugins/moveElemsAttrsToGroup.js @@ -0,0 +1,126 @@ +'use strict'; + +exports.type = 'perItemReverse'; + +exports.active = true; + +exports.description = 'moves elements attributes to the existing group wrapper'; + +var inheritableAttrs = require('./_collections').inheritableAttrs, + pathElems = require('./_collections.js').pathElems; + +/** + * Collapse content's intersected and inheritable + * attributes to the existing group wrapper. + * + * @example + * <g attr1="val1"> + * <g attr2="val2"> + * text + * </g> + * <circle attr2="val2" attr3="val3"/> + * </g> + * ⬇ + * <g attr1="val1" attr2="val2"> + * <g> + * text + * </g> + * <circle attr3="val3"/> + * </g> + * + * @param {Object} item current iteration item + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(item) { + + if (item.isElem('g') && !item.isEmpty() && item.content.length > 1) { + + var intersection = {}, + hasTransform = false, + hasClip = item.hasAttr('clip-path') || item.hasAttr('mask'), + intersected = item.content.every(function(inner) { + if (inner.isElem() && inner.hasAttr()) { + // don't mess with possible styles (hack until CSS parsing is implemented) + if (inner.hasAttr('class')) return false; + if (!Object.keys(intersection).length) { + intersection = inner.attrs; + } else { + intersection = intersectInheritableAttrs(intersection, inner.attrs); + + if (!intersection) return false; + } + + return true; + } + }), + allPath = item.content.every(function(inner) { + return inner.isElem(pathElems); + }); + + if (intersected) { + + item.content.forEach(function(g) { + + for (var name in intersection) { + + if (!allPath && !hasClip || name !== 'transform') { + + g.removeAttr(name); + + if (name === 'transform') { + if (!hasTransform) { + if (item.hasAttr('transform')) { + item.attr('transform').value += ' ' + intersection[name].value; + } else { + item.addAttr(intersection[name]); + } + + hasTransform = true; + } + } else { + item.addAttr(intersection[name]); + } + + } + } + + }); + + } + + } + +}; + +/** + * Intersect inheritable attributes. + * + * @param {Object} a first attrs object + * @param {Object} b second attrs object + * + * @return {Object} intersected attrs object + */ +function intersectInheritableAttrs(a, b) { + + var c = {}; + + for (var n in a) { + if ( + b.hasOwnProperty(n) && + inheritableAttrs.indexOf(n) > -1 && + a[n].name === b[n].name && + a[n].value === b[n].value && + a[n].prefix === b[n].prefix && + a[n].local === b[n].local + ) { + c[n] = a[n]; + } + } + + if (!Object.keys(c).length) return false; + + return c; + +} diff --git a/node_modules/svgo/plugins/moveGroupAttrsToElems.js b/node_modules/svgo/plugins/moveGroupAttrsToElems.js new file mode 100644 index 00000000..a966f882 --- /dev/null +++ b/node_modules/svgo/plugins/moveGroupAttrsToElems.js @@ -0,0 +1,63 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = true; + +exports.description = 'moves some group attributes to the content elements'; + +var collections = require('./_collections.js'), + pathElems = collections.pathElems.concat(['g', 'text']), + referencesProps = collections.referencesProps; + +/** + * Move group attrs to the content elements. + * + * @example + * <g transform="scale(2)"> + * <path transform="rotate(45)" d="M0,0 L10,20"/> + * <path transform="translate(10, 20)" d="M0,10 L20,30"/> + * </g> + * ⬇ + * <g> + * <path transform="scale(2) rotate(45)" d="M0,0 L10,20"/> + * <path transform="scale(2) translate(10, 20)" d="M0,10 L20,30"/> + * </g> + * + * @param {Object} item current iteration item + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(item) { + + // move group transform attr to content's pathElems + if ( + item.isElem('g') && + item.hasAttr('transform') && + !item.isEmpty() && + !item.someAttr(function(attr) { + return ~referencesProps.indexOf(attr.name) && ~attr.value.indexOf('url('); + }) && + item.content.every(function(inner) { + return inner.isElem(pathElems) && !inner.hasAttr('id'); + }) + ) { + item.content.forEach(function(inner) { + var attr = item.attr('transform'); + if (inner.hasAttr('transform')) { + inner.attr('transform').value = attr.value + ' ' + inner.attr('transform').value; + } else { + inner.addAttr({ + 'name': attr.name, + 'local': attr.local, + 'prefix': attr.prefix, + 'value': attr.value + }); + } + }); + + item.removeAttr('transform'); + } + +}; diff --git a/node_modules/svgo/plugins/removeAttrs.js b/node_modules/svgo/plugins/removeAttrs.js new file mode 100644 index 00000000..bd2fca62 --- /dev/null +++ b/node_modules/svgo/plugins/removeAttrs.js @@ -0,0 +1,119 @@ +'use strict'; + +var ELEM_SEP = ':'; + +exports.type = 'perItem'; + +exports.active = false; + +exports.description = 'removes specified attributes'; + +exports.params = { + attrs: [] +}; + +/** + * Remove attributes + * + * @param attrs: + * + * format: [ element* : attribute* ] + * + * element : regexp (wrapped into ^...$), single * or omitted > all elements + * attribute : regexp (wrapped into ^...$) + * + * examples: + * + * > basic: remove fill attribute + * --- + * removeAttrs: + * attrs: 'fill' + * + * > remove fill attribute on path element + * --- + * attrs: 'path:fill' + * + * + * > remove all fill and stroke attribute + * --- + * attrs: + * - 'fill' + * - 'stroke' + * + * [is same as] + * + * attrs: '(fill|stroke)' + * + * [is same as] + * + * attrs: '*:(fill|stroke)' + * + * [is same as] + * + * attrs: '.*:(fill|stroke)' + * + * + * > remove all stroke related attributes + * ---- + * attrs: 'stroke.*' + * + * + * @param {Object} item current iteration item + * @param {Object} params plugin params + * @return {Boolean} if false, item will be filtered out + * + * @author Benny Schudel + */ +exports.fn = function(item, params) { + + // wrap into an array if params is not + if (!Array.isArray(params.attrs)) { + params.attrs = [params.attrs]; + } + + if (item.isElem()) { + + // prepare patterns + var patterns = params.attrs.map(function(pattern) { + + // apply to all elements if specifc element is omitted + if (pattern.indexOf(ELEM_SEP) === -1) { + pattern = ['.*', ELEM_SEP, pattern].join(''); + } + + // create regexps for element and attribute name + return pattern.split(ELEM_SEP) + .map(function(value) { + + // adjust single * to match anything + if (value === '*') { value = '.*'; } + + return new RegExp(['^', value, '$'].join(''), 'i'); + }); + + }); + + // loop patterns + patterns.forEach(function(pattern) { + + // matches element + if (pattern[0].test(item.elem)) { + + // loop attributes + item.eachAttr(function(attr) { + var name = attr.name; + + // matches attribute name + if (pattern[1].test(name)) { + item.removeAttr(name); + } + + }); + + } + + }); + + } + +}; diff --git a/node_modules/svgo/plugins/removeComments.js b/node_modules/svgo/plugins/removeComments.js new file mode 100644 index 00000000..8bfd21b0 --- /dev/null +++ b/node_modules/svgo/plugins/removeComments.js @@ -0,0 +1,27 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = true; + +exports.description = 'removes comments'; + +/** + * Remove comments. + * + * @example + * <!-- Generator: Adobe Illustrator 15.0.0, SVG Export + * Plug-In . SVG Version: 6.00 Build 0) --> + * + * @param {Object} item current iteration item + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(item) { + + if (item.comment && item.comment.charAt(0) !== '!') { + return false; + } + +}; diff --git a/node_modules/svgo/plugins/removeDesc.js b/node_modules/svgo/plugins/removeDesc.js new file mode 100644 index 00000000..e2848c6c --- /dev/null +++ b/node_modules/svgo/plugins/removeDesc.js @@ -0,0 +1,32 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = true; + +exports.params = { + removeAny: false +}; + +exports.description = 'removes <desc> (only non-meaningful by default)'; + +var standardDescs = /^Created with/; + +/** + * Removes <desc>. + * Removes only standard editors content or empty elements 'cause it can be used for accessibility. + * Enable parameter 'removeAny' to remove any description. + * + * https://developer.mozilla.org/en-US/docs/Web/SVG/Element/desc + * + * @param {Object} item current iteration item + * @return {Boolean} if false, item will be filtered out + * + * @author Daniel Wabyick + */ +exports.fn = function(item, params) { + + return !item.isElem('desc') || !(params.removeAny || item.isEmpty() || + standardDescs.test(item.content[0].text)); + +}; diff --git a/node_modules/svgo/plugins/removeDimensions.js b/node_modules/svgo/plugins/removeDimensions.js new file mode 100644 index 00000000..b25ff8f7 --- /dev/null +++ b/node_modules/svgo/plugins/removeDimensions.js @@ -0,0 +1,32 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = false; + +exports.description = 'removes width and height in presence of viewBox'; + +/** + * Remove width/height attributes when a viewBox attribute is present. + * + * @example + * <svg width="100" height="50" viewBox="0 0 100 50"> + * ↓ + * <svg viewBox="0 0 100 50"> + * + * @param {Object} item current iteration item + * @return {Boolean} if true, with and height will be filtered out + * + * @author Benny Schudel + */ +exports.fn = function(item) { + + if ( + item.isElem('svg') && + item.hasAttr('viewBox') + ) { + item.removeAttr('width'); + item.removeAttr('height'); + } + +}; diff --git a/node_modules/svgo/plugins/removeDoctype.js b/node_modules/svgo/plugins/removeDoctype.js new file mode 100644 index 00000000..32ee4f46 --- /dev/null +++ b/node_modules/svgo/plugins/removeDoctype.js @@ -0,0 +1,40 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = true; + +exports.description = 'removes doctype declaration'; + +/** + * Remove DOCTYPE declaration. + * + * "Unfortunately the SVG DTDs are a source of so many + * issues that the SVG WG has decided not to write one + * for the upcoming SVG 1.2 standard. In fact SVG WG + * members are even telling people not to use a DOCTYPE + * declaration in SVG 1.0 and 1.1 documents" + * https://jwatt.org/svg/authoring/#doctype-declaration + * + * @example + * <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" + * q"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> + * + * @example + * <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + * "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" [ + * <!-- an internal subset can be embedded here --> + * ]> + * + * @param {Object} item current iteration item + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(item) { + + if (item.doctype) { + return false; + } + +}; diff --git a/node_modules/svgo/plugins/removeEditorsNSData.js b/node_modules/svgo/plugins/removeEditorsNSData.js new file mode 100644 index 00000000..9e5c8cd8 --- /dev/null +++ b/node_modules/svgo/plugins/removeEditorsNSData.js @@ -0,0 +1,65 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = true; + +exports.description = 'removes editors namespaces, elements and attributes'; + +var editorNamespaces = require('./_collections').editorNamespaces, + prefixes = []; + +exports.params = { + additionalNamespaces: [] +}; + +/** + * Remove editors namespaces, elements and attributes. + * + * @example + * <svg xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"> + * <sodipodi:namedview/> + * <path sodipodi:nodetypes="cccc"/> + * + * @param {Object} item current iteration item + * @param {Object} params plugin params + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(item, params) { + + if (Array.isArray(params.additionalNamespaces)) { + editorNamespaces = editorNamespaces.concat(params.additionalNamespaces); + } + + if (item.elem) { + + if (item.isElem('svg')) { + + item.eachAttr(function(attr) { + if (attr.prefix === 'xmlns' && editorNamespaces.indexOf(attr.value) > -1) { + prefixes.push(attr.local); + + // <svg xmlns:sodipodi=""> + item.removeAttr(attr.name); + } + }); + + } + + // <* sodipodi:*=""> + item.eachAttr(function(attr) { + if (prefixes.indexOf(attr.prefix) > -1) { + item.removeAttr(attr.name); + } + }); + + // <sodipodi:*> + if (prefixes.indexOf(item.prefix) > -1) { + return false; + } + + } + +}; diff --git a/node_modules/svgo/plugins/removeElementsByAttr.js b/node_modules/svgo/plugins/removeElementsByAttr.js new file mode 100644 index 00000000..c726f708 --- /dev/null +++ b/node_modules/svgo/plugins/removeElementsByAttr.js @@ -0,0 +1,80 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = false; + +exports.description = 'removes arbitrary elements by ID or className (disabled by default)'; + +exports.params = { + id: [], + class: [] +}; + +/** + * Remove arbitrary SVG elements by ID or className. + * + * @param id + * examples: + * + * > single: remove element with ID of `elementID` + * --- + * removeElementsByAttr: + * id: 'elementID' + * + * > list: remove multiple elements by ID + * --- + * removeElementsByAttr: + * id: + * - 'elementID' + * - 'anotherID' + * + * @param class + * examples: + * + * > single: remove all elements with class of `elementClass` + * --- + * removeElementsByAttr: + * class: 'elementClass' + * + * > list: remove all elements with class of `elementClass` or `anotherClass` + * --- + * removeElementsByAttr: + * class: + * - 'elementClass' + * - 'anotherClass' + * + * @param {Object} item current iteration item + * @param {Object} params plugin params + * @return {Boolean} if false, item will be filtered out + * + * @author Eli Dupuis (@elidupuis) + */ +exports.fn = function(item, params) { + var elemId, elemClass; + + // wrap params in an array if not already + ['id', 'class'].forEach(function(key) { + if (!Array.isArray(params[key])) { + params[key] = [ params[key] ]; + } + }); + + // abort if current item is no an element + if (!item.isElem()) { + return; + } + + // remove element if it's `id` matches configured `id` params + elemId = item.attr('id'); + if (elemId) { + return params.id.indexOf(elemId.value) === -1; + } + + // remove element if it's `class` contains any of the configured `class` params + elemClass = item.attr('class'); + if (elemClass) { + var hasClassRegex = new RegExp(params.class.join('|')); + return !hasClassRegex.test(elemClass.value); + } +}; diff --git a/node_modules/svgo/plugins/removeEmptyAttrs.js b/node_modules/svgo/plugins/removeEmptyAttrs.js new file mode 100644 index 00000000..ae73e6c9 --- /dev/null +++ b/node_modules/svgo/plugins/removeEmptyAttrs.js @@ -0,0 +1,29 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = true; + +exports.description = 'removes empty attributes'; + +/** + * Remove attributes with empty values. + * + * @param {Object} item current iteration item + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(item) { + + if (item.elem) { + + item.eachAttr(function(attr) { + if (attr.value === '') { + item.removeAttr(attr.name); + } + }); + + } + +}; diff --git a/node_modules/svgo/plugins/removeEmptyContainers.js b/node_modules/svgo/plugins/removeEmptyContainers.js new file mode 100644 index 00000000..356a5835 --- /dev/null +++ b/node_modules/svgo/plugins/removeEmptyContainers.js @@ -0,0 +1,32 @@ +'use strict'; + +exports.type = 'perItemReverse'; + +exports.active = true; + +exports.description = 'removes empty container elements'; + +var container = require('./_collections').elemsGroups.container; + +/** + * Remove empty containers. + * + * @see http://www.w3.org/TR/SVG/intro.html#TermContainerElement + * + * @example + * <defs/> + * + * @example + * <g><marker><a/></marker></g> + * + * @param {Object} item current iteration item + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(item) { + + return !(item.isElem(container) && !item.isElem('svg') && item.isEmpty() && + (!item.isElem('pattern') || !item.hasAttrLocal('href'))); + +}; diff --git a/node_modules/svgo/plugins/removeEmptyText.js b/node_modules/svgo/plugins/removeEmptyText.js new file mode 100644 index 00000000..049b1819 --- /dev/null +++ b/node_modules/svgo/plugins/removeEmptyText.js @@ -0,0 +1,59 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = true; + +exports.description = 'removes empty <text> elements'; + +exports.params = { + text: true, + tspan: true, + tref: true +}; + +/** + * Remove empty Text elements. + * + * @see http://www.w3.org/TR/SVG/text.html + * + * @example + * Remove empty text element: + * <text/> + * + * Remove empty tspan element: + * <tspan/> + * + * Remove tref with empty xlink:href attribute: + * <tref xlink:href=""/> + * + * @param {Object} item current iteration item + * @param {Object} params plugin params + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(item, params) { + + // Remove empty text element + if ( + params.text && + item.isElem('text') && + item.isEmpty() + ) return false; + + // Remove empty tspan element + if ( + params.tspan && + item.isElem('tspan') && + item.isEmpty() + ) return false; + + // Remove tref with empty xlink:href attribute + if ( + params.tref && + item.isElem('tref') && + !item.hasAttrLocal('href') + ) return false; + +}; diff --git a/node_modules/svgo/plugins/removeHiddenElems.js b/node_modules/svgo/plugins/removeHiddenElems.js new file mode 100644 index 00000000..231a2bdf --- /dev/null +++ b/node_modules/svgo/plugins/removeHiddenElems.js @@ -0,0 +1,218 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = true; + +exports.description = 'removes hidden elements (zero sized, with absent attributes)'; + +exports.params = { + displayNone: true, + opacity0: true, + circleR0: true, + ellipseRX0: true, + ellipseRY0: true, + rectWidth0: true, + rectHeight0: true, + patternWidth0: true, + patternHeight0: true, + imageWidth0: true, + imageHeight0: true, + pathEmptyD: true, + polylineEmptyPoints: true, + polygonEmptyPoints: true +}; + +var regValidPath = /M\s*(?:[-+]?(?:\d*\.\d+|\d+(?:\.|(?!\.)))([eE][-+]?\d+)?(?!\d)\s*,?\s*){2}\D*\d/i; + +/** + * Remove hidden elements with disabled rendering: + * - display="none" + * - opacity="0" + * - circle with zero radius + * - ellipse with zero x-axis or y-axis radius + * - rectangle with zero width or height + * - pattern with zero width or height + * - image with zero width or height + * - path with empty data + * - polyline with empty points + * - polygon with empty points + * + * @param {Object} item current iteration item + * @param {Object} params plugin params + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(item, params) { + + if (item.elem) { + + // display="none" + // + // http://www.w3.org/TR/SVG/painting.html#DisplayProperty + // "A value of display: none indicates that the given element + // and its children shall not be rendered directly" + if ( + params.displayNone && + item.hasAttr('display', 'none') + ) return false; + + // opacity="0" + // + // http://www.w3.org/TR/SVG/masking.html#ObjectAndGroupOpacityProperties + if ( + params.opacity0 && + item.hasAttr('opacity', '0') + ) return false; + + // Circles with zero radius + // + // http://www.w3.org/TR/SVG/shapes.html#CircleElementRAttribute + // "A value of zero disables rendering of the element" + // + // <circle r="0"> + if ( + params.circleR0 && + item.isElem('circle') && + item.isEmpty() && + item.hasAttr('r', '0') + ) return false; + + // Ellipse with zero x-axis radius + // + // http://www.w3.org/TR/SVG/shapes.html#EllipseElementRXAttribute + // "A value of zero disables rendering of the element" + // + // <ellipse rx="0"> + if ( + params.ellipseRX0 && + item.isElem('ellipse') && + item.isEmpty() && + item.hasAttr('rx', '0') + ) return false; + + // Ellipse with zero y-axis radius + // + // http://www.w3.org/TR/SVG/shapes.html#EllipseElementRYAttribute + // "A value of zero disables rendering of the element" + // + // <ellipse ry="0"> + if ( + params.ellipseRY0 && + item.isElem('ellipse') && + item.isEmpty() && + item.hasAttr('ry', '0') + ) return false; + + // Rectangle with zero width + // + // http://www.w3.org/TR/SVG/shapes.html#RectElementWidthAttribute + // "A value of zero disables rendering of the element" + // + // <rect width="0"> + if ( + params.rectWidth0 && + item.isElem('rect') && + item.isEmpty() && + item.hasAttr('width', '0') + ) return false; + + // Rectangle with zero height + // + // http://www.w3.org/TR/SVG/shapes.html#RectElementHeightAttribute + // "A value of zero disables rendering of the element" + // + // <rect height="0"> + if ( + params.rectHeight0 && + params.rectWidth0 && + item.isElem('rect') && + item.isEmpty() && + item.hasAttr('height', '0') + ) return false; + + // Pattern with zero width + // + // http://www.w3.org/TR/SVG/pservers.html#PatternElementWidthAttribute + // "A value of zero disables rendering of the element (i.e., no paint is applied)" + // + // <pattern width="0"> + if ( + params.patternWidth0 && + item.isElem('pattern') && + item.hasAttr('width', '0') + ) return false; + + // Pattern with zero height + // + // http://www.w3.org/TR/SVG/pservers.html#PatternElementHeightAttribute + // "A value of zero disables rendering of the element (i.e., no paint is applied)" + // + // <pattern height="0"> + if ( + params.patternHeight0 && + item.isElem('pattern') && + item.hasAttr('height', '0') + ) return false; + + // Image with zero width + // + // http://www.w3.org/TR/SVG/struct.html#ImageElementWidthAttribute + // "A value of zero disables rendering of the element" + // + // <image width="0"> + if ( + params.imageWidth0 && + item.isElem('image') && + item.hasAttr('width', '0') + ) return false; + + // Image with zero height + // + // http://www.w3.org/TR/SVG/struct.html#ImageElementHeightAttribute + // "A value of zero disables rendering of the element" + // + // <image height="0"> + if ( + params.imageHeight0 && + item.isElem('image') && + item.hasAttr('height', '0') + ) return false; + + // Path with empty data + // + // http://www.w3.org/TR/SVG/paths.html#DAttribute + // + // <path d=""/> + if ( + params.pathEmptyD && + item.isElem('path') && + (!item.hasAttr('d') || !regValidPath.test(item.attr('d').value)) + ) return false; + + // Polyline with empty points + // + // http://www.w3.org/TR/SVG/shapes.html#PolylineElementPointsAttribute + // + // <polyline points=""> + if ( + params.polylineEmptyPoints && + item.isElem('polyline') && + !item.hasAttr('points') + ) return false; + + // Polygon with empty points + // + // http://www.w3.org/TR/SVG/shapes.html#PolygonElementPointsAttribute + // + // <polygon points=""> + if ( + params.polygonEmptyPoints && + item.isElem('polygon') && + !item.hasAttr('points') + ) return false; + + } + +}; diff --git a/node_modules/svgo/plugins/removeMetadata.js b/node_modules/svgo/plugins/removeMetadata.js new file mode 100644 index 00000000..fa7a5795 --- /dev/null +++ b/node_modules/svgo/plugins/removeMetadata.js @@ -0,0 +1,23 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = true; + +exports.description = 'removes <metadata>'; + +/** + * Remove <metadata>. + * + * http://www.w3.org/TR/SVG/metadata.html + * + * @param {Object} item current iteration item + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(item) { + + return !item.isElem('metadata'); + +}; diff --git a/node_modules/svgo/plugins/removeNonInheritableGroupAttrs.js b/node_modules/svgo/plugins/removeNonInheritableGroupAttrs.js new file mode 100644 index 00000000..f999d568 --- /dev/null +++ b/node_modules/svgo/plugins/removeNonInheritableGroupAttrs.js @@ -0,0 +1,40 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = true; + +exports.description = 'removes non-inheritable group’s presentational attributes'; + +var inheritableAttrs = require('./_collections').inheritableAttrs, + attrsGroups = require('./_collections').attrsGroups, + excludedAttrs = ['display', 'opacity']; + +/** + * Remove non-inheritable group's "presentation" attributes. + * + * @param {Object} item current iteration item + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(item) { + + if (item.isElem('g')) { + + item.eachAttr(function(attr) { + if ( + ~attrsGroups.presentation.indexOf(attr.name) && + ~attrsGroups.graphicalEvent.indexOf(attr.name) && + ~attrsGroups.core.indexOf(attr.name) && + ~attrsGroups.conditionalProcessing.indexOf(attr.name) && + !~excludedAttrs.indexOf(attr.name) && + !~inheritableAttrs.indexOf(attr.name) + ) { + item.removeAttr(attr.name); + } + }); + + } + +}; diff --git a/node_modules/svgo/plugins/removeRasterImages.js b/node_modules/svgo/plugins/removeRasterImages.js new file mode 100644 index 00000000..cbddebe3 --- /dev/null +++ b/node_modules/svgo/plugins/removeRasterImages.js @@ -0,0 +1,28 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = false; + +exports.description = 'removes raster images (disabled by default)'; + +/** + * Remove raster images references in <image>. + * + * @see https://bugs.webkit.org/show_bug.cgi?id=63548 + * + * @param {Object} item current iteration item + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(item) { + + if ( + item.isElem('image') && + item.hasAttrLocal('href', /(\.|image\/)(jpg|png|gif)/) + ) { + return false; + } + +}; diff --git a/node_modules/svgo/plugins/removeStyleElement.js b/node_modules/svgo/plugins/removeStyleElement.js new file mode 100644 index 00000000..964ce45c --- /dev/null +++ b/node_modules/svgo/plugins/removeStyleElement.js @@ -0,0 +1,23 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = false; + +exports.description = 'removes <style> element (disabled by default)'; + +/** + * Remove <style>. + * + * http://www.w3.org/TR/SVG/styling.html#StyleElement + * + * @param {Object} item current iteration item + * @return {Boolean} if false, item will be filtered out + * + * @author Betsy Dupuis + */ +exports.fn = function(item) { + + return !item.isElem('style'); + +}; diff --git a/node_modules/svgo/plugins/removeTitle.js b/node_modules/svgo/plugins/removeTitle.js new file mode 100644 index 00000000..3d077e45 --- /dev/null +++ b/node_modules/svgo/plugins/removeTitle.js @@ -0,0 +1,24 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = false; + +exports.description = 'removes <title> (disabled by default)'; + +/** + * Remove <title>. + * Disabled by default cause it may be used for accessibility. + * + * https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title + * + * @param {Object} item current iteration item + * @return {Boolean} if false, item will be filtered out + * + * @author Igor Kalashnikov + */ +exports.fn = function(item) { + + return !item.isElem('title'); + +}; diff --git a/node_modules/svgo/plugins/removeUnknownsAndDefaults.js b/node_modules/svgo/plugins/removeUnknownsAndDefaults.js new file mode 100644 index 00000000..341b1fd7 --- /dev/null +++ b/node_modules/svgo/plugins/removeUnknownsAndDefaults.js @@ -0,0 +1,143 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = true; + +exports.description = 'removes unknown elements content and attributes, removes attrs with default values'; + +exports.params = { + unknownContent: true, + unknownAttrs: true, + defaultAttrs: true, + uselessOverrides: true, + keepDataAttrs: true +}; + +var collections = require('./_collections'), + elems = collections.elems, + attrsGroups = collections.attrsGroups, + elemsGroups = collections.elemsGroups, + attrsGroupsDefaults = collections.attrsGroupsDefaults, + attrsInheritable = collections.inheritableAttrs; + +// collect and extend all references +for (var elem in elems) { + elem = elems[elem]; + + if (elem.attrsGroups) { + elem.attrs = elem.attrs || []; + + elem.attrsGroups.forEach(function(attrsGroupName) { + elem.attrs = elem.attrs.concat(attrsGroups[attrsGroupName]); + + var groupDefaults = attrsGroupsDefaults[attrsGroupName]; + + if (groupDefaults) { + elem.defaults = elem.defaults || {}; + + for (var attrName in groupDefaults) { + elem.defaults[attrName] = groupDefaults[attrName]; + } + } + }); + + } + + if (elem.contentGroups) { + elem.content = elem.content || []; + + elem.contentGroups.forEach(function(contentGroupName) { + elem.content = elem.content.concat(elemsGroups[contentGroupName]); + }); + } +} + +/** + * Remove unknown elements content and attributes, + * remove attributes with default values. + * + * @param {Object} item current iteration item + * @param {Object} params plugin params + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(item, params) { + + // elems w/o namespace prefix + if (item.isElem() && !item.prefix) { + + var elem = item.elem; + + // remove unknown element's content + if ( + params.unknownContent && + !item.isEmpty() && + elems[elem] && // make sure we know of this element before checking its children + elem !== 'foreignObject' // Don't check foreignObject + ) { + item.content.forEach(function(content, i) { + if ( + content.isElem() && + !content.prefix && + ( + ( + elems[elem].content && // Do we have a record of its permitted content? + elems[elem].content.indexOf(content.elem) === -1 + ) || + ( + !elems[elem].content && // we dont know about its permitted content + !elems[content.elem] // check that we know about the element at all + ) + ) + ) { + item.content.splice(i, 1); + } + }); + } + + // remove element's unknown attrs and attrs with default values + if (elems[elem] && elems[elem].attrs) { + + item.eachAttr(function(attr) { + + if ( + attr.name !== 'xmlns' && + (attr.prefix === 'xml' || !attr.prefix) && + (!params.keepDataAttrs || attr.name.indexOf('data-') != 0) + ) { + if ( + // unknown attrs + ( + params.unknownAttrs && + elems[elem].attrs.indexOf(attr.name) === -1 + ) || + // attrs with default values + ( + params.defaultAttrs && + elems[elem].defaults && + elems[elem].defaults[attr.name] === attr.value && ( + attrsInheritable.indexOf(attr.name) < 0 || + !item.parentNode.computedAttr(attr.name) + ) + ) || + // useless overrides + ( + params.uselessOverrides && + attr.name !== 'transform' && + attrsInheritable.indexOf(attr.name) > -1 && + item.parentNode.computedAttr(attr.name, attr.value) + ) + ) { + item.removeAttr(attr.name); + } + } + + }); + + } + + } + +}; diff --git a/node_modules/svgo/plugins/removeUnusedNS.js b/node_modules/svgo/plugins/removeUnusedNS.js new file mode 100644 index 00000000..c4787915 --- /dev/null +++ b/node_modules/svgo/plugins/removeUnusedNS.js @@ -0,0 +1,107 @@ +'use strict'; + +exports.type = 'full'; + +exports.active = true; + +exports.description = 'removes unused namespaces declaration'; + +/** + * Remove unused namespaces declaration. + * + * @param {Object} item current iteration item + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(data) { + + var svgElem, + xmlnsCollection = []; + + /** + * Remove namespace from collection. + * + * @param {String} ns namescape name + */ + function removeNSfromCollection(ns) { + + var pos = xmlnsCollection.indexOf(ns); + + // if found - remove ns from the namespaces collection + if (pos > -1) { + xmlnsCollection.splice(pos, 1); + } + + } + + /** + * Bananas! + * + * @param {Array} items input items + * + * @return {Array} output items + */ + function monkeys(items) { + + var i = 0, + length = items.content.length; + + while(i < length) { + + var item = items.content[i]; + + if (item.isElem('svg')) { + + item.eachAttr(function(attr) { + // collect namespaces + if (attr.prefix === 'xmlns' && attr.local) { + xmlnsCollection.push(attr.local); + } + }); + + // if svg element has ns-attr + if (xmlnsCollection.length) { + // save svg element + svgElem = item; + } + + } else if (xmlnsCollection.length) { + + // check item for the ns-attrs + if (item.prefix) { + removeNSfromCollection(item.prefix); + } + + // check each attr for the ns-attrs + item.eachAttr(function(attr) { + removeNSfromCollection(attr.prefix); + }); + + } + + // if nothing is found - go deeper + if (xmlnsCollection.length && item.content) { + monkeys(item); + } + + i++; + + } + + return items; + + } + + data = monkeys(data); + + // remove svg element ns-attributes if they are not used even once + if (xmlnsCollection.length) { + xmlnsCollection.forEach(function(name) { + svgElem.removeAttr('xmlns:' + name); + }); + } + + return data; + +}; diff --git a/node_modules/svgo/plugins/removeUselessDefs.js b/node_modules/svgo/plugins/removeUselessDefs.js new file mode 100644 index 00000000..b8c97a52 --- /dev/null +++ b/node_modules/svgo/plugins/removeUselessDefs.js @@ -0,0 +1,51 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = true; + +exports.description = 'removes elements in <defs> without id'; + +var nonRendering = require('./_collections').elemsGroups.nonRendering, + defs; + +/** + * Removes content of defs and properties that aren't rendered directly without ids. + * + * @param {Object} item current iteration item + * @return {Boolean} if false, item will be filtered out + * + * @author Lev Solntsev + */ +exports.fn = function(item) { + + if (item.isElem('defs')) { + + defs = item; + item.content = (item.content || []).reduce(getUsefulItems, []); + + if (item.isEmpty()) return false; + + } else if (item.isElem(nonRendering) && !item.hasAttr('id')) { + + return false; + + } + +}; + +function getUsefulItems(usefulItems, item) { + + if (item.hasAttr('id') || item.isElem('style')) { + + usefulItems.push(item); + item.parentNode = defs; + + } else if (!item.isEmpty()) { + + item.content.reduce(getUsefulItems, usefulItems); + + } + + return usefulItems; +} diff --git a/node_modules/svgo/plugins/removeUselessStrokeAndFill.js b/node_modules/svgo/plugins/removeUselessStrokeAndFill.js new file mode 100644 index 00000000..47090eb8 --- /dev/null +++ b/node_modules/svgo/plugins/removeUselessStrokeAndFill.js @@ -0,0 +1,92 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = true; + +exports.description = 'removes useless stroke and fill attributes'; + +exports.params = { + stroke: true, + fill: true +}; + +var shape = require('./_collections').elemsGroups.shape, + regStrokeProps = /^stroke/, + regFillProps = /^fill-/, + styleOrScript = ['style', 'script'], + hasStyleOrScript = false; + +/** + * Remove useless stroke and fill attrs. + * + * @param {Object} item current iteration item + * @param {Object} params plugin params + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(item, params) { + + if (item.isElem(styleOrScript)) { + hasStyleOrScript = true; + } + + if (!hasStyleOrScript && item.isElem(shape) && !item.computedAttr('id')) { + + var stroke = params.stroke && item.computedAttr('stroke'), + fill = params.fill && !item.computedAttr('fill', 'none'); + + // remove stroke* + if ( + params.stroke && + (!stroke || + stroke == 'none' || + item.computedAttr('stroke-opacity', '0') || + item.computedAttr('stroke-width', '0') + ) + ) { + var parentStroke = item.parentNode.computedAttr('stroke'), + declineStroke = parentStroke && parentStroke != 'none'; + + item.eachAttr(function(attr) { + if (regStrokeProps.test(attr.name)) { + item.removeAttr(attr.name); + } + }); + + if (declineStroke) item.addAttr({ + name: 'stroke', + value: 'none', + prefix: '', + local: 'stroke' + }); + } + + // remove fill* + if ( + params.fill && + (!fill || item.computedAttr('fill-opacity', '0')) + ) { + item.eachAttr(function(attr) { + if (regFillProps.test(attr.name)) { + item.removeAttr(attr.name); + } + }); + + if (fill) { + if (item.hasAttr('fill')) + item.attr('fill').value = 'none'; + else + item.addAttr({ + name: 'fill', + value: 'none', + prefix: '', + local: 'fill' + }); + } + } + + } + +}; diff --git a/node_modules/svgo/plugins/removeViewBox.js b/node_modules/svgo/plugins/removeViewBox.js new file mode 100644 index 00000000..33595656 --- /dev/null +++ b/node_modules/svgo/plugins/removeViewBox.js @@ -0,0 +1,49 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = false; + +exports.description = 'removes viewBox attribute when possible (disabled by default)'; + +var regViewBox = /^0\s0\s([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)\s([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)$/, + viewBoxElems = ['svg', 'pattern']; + +/** + * Remove viewBox attr which coincides with a width/height box. + * + * @see http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute + * + * @example + * <svg width="100" height="50" viewBox="0 0 100 50"> + * ⬇ + * <svg width="100" height="50"> + * + * @param {Object} item current iteration item + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(item) { + + if ( + item.isElem(viewBoxElems) && + item.hasAttr('viewBox') && + item.hasAttr('width') && + item.hasAttr('height') + ) { + + var match = item.attr('viewBox').value.match(regViewBox); + + if (match) { + if ( + item.attr('width').value === match[1] && + item.attr('height').value === match[3] + ) { + item.removeAttr('viewBox'); + } + } + + } + +}; diff --git a/node_modules/svgo/plugins/removeXMLNS.js b/node_modules/svgo/plugins/removeXMLNS.js new file mode 100644 index 00000000..afda99d3 --- /dev/null +++ b/node_modules/svgo/plugins/removeXMLNS.js @@ -0,0 +1,28 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = false; + +exports.description = 'removes xmlns attribute (for inline svg, disabled by default)'; + +/** + * Remove the xmlns attribute when present. + * + * @example + * <svg viewBox="0 0 100 50" xmlns="http://www.w3.org/2000/svg"> + * ↓ + * <svg viewBox="0 0 100 50"> + * + * @param {Object} item current iteration item + * @return {Boolean} if true, xmlns will be filtered out + * + * @author Ricardo Tomasi + */ +exports.fn = function(item) { + + if (item.isElem('svg') && item.hasAttr('xmlns')) { + item.removeAttr('xmlns'); + } + +};
\ No newline at end of file diff --git a/node_modules/svgo/plugins/removeXMLProcInst.js b/node_modules/svgo/plugins/removeXMLProcInst.js new file mode 100644 index 00000000..afd425d5 --- /dev/null +++ b/node_modules/svgo/plugins/removeXMLProcInst.js @@ -0,0 +1,24 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = true; + +exports.description = 'removes XML processing instructions'; + +/** + * Remove XML Processing Instruction. + * + * @example + * <?xml version="1.0" encoding="utf-8"?> + * + * @param {Object} item current iteration item + * @return {Boolean} if false, item will be filtered out + * + * @author Kir Belevich + */ +exports.fn = function(item) { + + return !(item.processinginstruction && item.processinginstruction.name === 'xml'); + +}; diff --git a/node_modules/svgo/plugins/sortAttrs.js b/node_modules/svgo/plugins/sortAttrs.js new file mode 100644 index 00000000..221a4bf5 --- /dev/null +++ b/node_modules/svgo/plugins/sortAttrs.js @@ -0,0 +1,81 @@ +'use strict'; + +exports.type = 'perItem'; + +exports.active = false; + +exports.description = 'sorts element attributes (disabled by default)'; + +exports.params = { + order: [ + 'id', + 'width', 'height', + 'x', 'x1', 'x2', + 'y', 'y1', 'y2', + 'cx', 'cy', 'r', + 'fill', 'stroke', 'marker', + 'd', 'points' + ] +}; + +/** + * Sort element attributes for epic readability. + * + * @param {Object} item current iteration item + * @param {Object} params plugin params + * + * @author Nikolay Frantsev + */ +exports.fn = function(item, params) { + + var attrs = [], + sorted = {}, + orderlen = params.order.length + 1; + + if (item.elem) { + + item.eachAttr(function(attr) { + attrs.push(attr); + }); + + attrs.sort(function(a, b) { + if (a.prefix != b.prefix) { + // xmlns attributes implicitly have the prefix xmlns + if (a.prefix == 'xmlns') + return -1; + if (b.prefix == 'xmlns') + return 1; + return a.prefix < b.prefix ? -1 : 1; + } + + var aindex = orderlen; + var bindex = orderlen; + + for (var i = 0; i < params.order.length; i++) { + if (a.name == params.order[i]) { + aindex = i; + } else if (a.name.indexOf(params.order[i] + '-') === 0) { + aindex = i + .5; + } + if (b.name == params.order[i]) { + bindex = i; + } else if (b.name.indexOf(params.order[i] + '-') === 0) { + bindex = i + .5; + } + } + + if (aindex != bindex) { + return aindex - bindex; + } + return a.name < b.name ? -1 : 1; + }); + + attrs.forEach(function (attr) { + sorted[attr.name] = attr; + }); + + item.attrs = sorted; + + } + +}; diff --git a/node_modules/svgo/plugins/transformsWithOnePath.js b/node_modules/svgo/plugins/transformsWithOnePath.js new file mode 100644 index 00000000..f4dec7e6 --- /dev/null +++ b/node_modules/svgo/plugins/transformsWithOnePath.js @@ -0,0 +1,327 @@ +'use strict'; + +/* + * Thanks to http://fontello.com project for sponsoring this plugin + */ + +exports.type = 'full'; + +exports.active = false; + +exports.description = 'performs a set of operations on SVG with one path inside (disabled by default)'; + +exports.params = { + // width and height to resize SVG and rescale inner Path + width: false, + height: false, + + // scale inner Path without resizing SVG + scale: false, + + // shiftX/Y inner Path + shiftX: false, + shiftY: false, + + // crop SVG width along the real width of inner Path + hcrop: false, + + // vertical center inner Path inside SVG height + vcenter: false, + + // stringify params + floatPrecision: 3, + leadingZero: true, + negativeExtraSpace: true +}; + +var _path = require('./_path.js'), + relative2absolute = _path.relative2absolute, + computeCubicBoundingBox = _path.computeCubicBoundingBox, + computeQuadraticBoundingBox = _path.computeQuadraticBoundingBox, + applyTransforms = _path.applyTransforms, + js2path = _path.js2path, + path2js = _path.path2js, + EXTEND = require('whet.extend'); + +exports.fn = function(data, params) { + + data.content.forEach(function(item) { + + // only for SVG with one Path inside + if (item.isElem('svg') && + item.content.length === 1 && + item.content[0].isElem('path') + ) { + + var svgElem = item, + pathElem = svgElem.content[0], + // get absoluted Path data + path = relative2absolute(EXTEND(true, [], path2js(pathElem))), + xs = [], + ys = [], + cubicСontrolPoint = [0, 0], + quadraticСontrolPoint = [0, 0], + lastPoint = [0, 0], + cubicBoundingBox, + quadraticBoundingBox, + i, + segment; + + path.forEach(function(pathItem) { + + // ML + if ('ML'.indexOf(pathItem.instruction) > -1) { + + for (i = 0; i < pathItem.data.length; i++) { + if (i % 2 === 0) { + xs.push(pathItem.data[i]); + } else { + ys.push(pathItem.data[i]); + } + } + + lastPoint = cubicСontrolPoint = quadraticСontrolPoint = pathItem.data.slice(-2); + + // H + } else if (pathItem.instruction === 'H') { + + pathItem.data.forEach(function(d) { + xs.push(d); + }); + + lastPoint[0] = cubicСontrolPoint[0] = quadraticСontrolPoint[0] = pathItem.data[pathItem.data.length - 2]; + + // V + } else if (pathItem.instruction === 'V') { + + pathItem.data.forEach(function(d) { + ys.push(d); + }); + + lastPoint[1] = cubicСontrolPoint[1] = quadraticСontrolPoint[1] = pathItem.data[pathItem.data.length - 1]; + + // C + } else if (pathItem.instruction === 'C') { + + for (i = 0; i < pathItem.data.length; i += 6) { + + segment = pathItem.data.slice(i, i + 6); + + cubicBoundingBox = computeCubicBoundingBox.apply(this, lastPoint.concat(segment)); + + xs.push(cubicBoundingBox.minx); + xs.push(cubicBoundingBox.maxx); + + ys.push(cubicBoundingBox.miny); + ys.push(cubicBoundingBox.maxy); + + // reflected control point for the next possible S + cubicСontrolPoint = [ + 2 * segment[4] - segment[2], + 2 * segment[5] - segment[3] + ]; + + lastPoint = segment.slice(-2); + + } + + // S + } else if (pathItem.instruction === 'S') { + + for (i = 0; i < pathItem.data.length; i += 4) { + + segment = pathItem.data.slice(i, i + 4); + + cubicBoundingBox = computeCubicBoundingBox.apply(this, lastPoint.concat(cubicСontrolPoint).concat(segment)); + + xs.push(cubicBoundingBox.minx); + xs.push(cubicBoundingBox.maxx); + + ys.push(cubicBoundingBox.miny); + ys.push(cubicBoundingBox.maxy); + + // reflected control point for the next possible S + cubicСontrolPoint = [ + 2 * segment[2] - cubicСontrolPoint[0], + 2 * segment[3] - cubicСontrolPoint[1], + ]; + + lastPoint = segment.slice(-2); + + } + + // Q + } else if (pathItem.instruction === 'Q') { + + for (i = 0; i < pathItem.data.length; i += 4) { + + segment = pathItem.data.slice(i, i + 4); + + quadraticBoundingBox = computeQuadraticBoundingBox.apply(this, lastPoint.concat(segment)); + + xs.push(quadraticBoundingBox.minx); + xs.push(quadraticBoundingBox.maxx); + + ys.push(quadraticBoundingBox.miny); + ys.push(quadraticBoundingBox.maxy); + + // reflected control point for the next possible T + quadraticСontrolPoint = [ + 2 * segment[2] - segment[0], + 2 * segment[3] - segment[1] + ]; + + lastPoint = segment.slice(-2); + + } + + // S + } else if (pathItem.instruction === 'T') { + + for (i = 0; i < pathItem.data.length; i += 2) { + + segment = pathItem.data.slice(i, i + 2); + + quadraticBoundingBox = computeQuadraticBoundingBox.apply(this, lastPoint.concat(quadraticСontrolPoint).concat(segment)); + + xs.push(quadraticBoundingBox.minx); + xs.push(quadraticBoundingBox.maxx); + + ys.push(quadraticBoundingBox.miny); + ys.push(quadraticBoundingBox.maxy); + + // reflected control point for the next possible T + quadraticСontrolPoint = [ + 2 * segment[0] - quadraticСontrolPoint[0], + 2 * segment[1] - quadraticСontrolPoint[1] + ]; + + lastPoint = segment.slice(-2); + + } + + } + + }); + + var xmin = Math.min.apply(this, xs).toFixed(params.floatPrecision), + xmax = Math.max.apply(this, xs).toFixed(params.floatPrecision), + ymin = Math.min.apply(this, ys).toFixed(params.floatPrecision), + ymax = Math.max.apply(this, ys).toFixed(params.floatPrecision), + svgWidth = +svgElem.attr('width').value, + svgHeight = +svgElem.attr('height').value, + realWidth = Math.round(xmax - xmin), + realHeight = Math.round(ymax - ymin), + transform = '', + scale; + + // width & height + if (params.width && params.height) { + + scale = Math.min(params.width / svgWidth, params.height / svgHeight); + + realWidth = realWidth * scale; + realHeight = realHeight * scale; + + svgWidth = svgElem.attr('width').value = params.width; + svgHeight = svgElem.attr('height').value = params.height; + + transform += ' scale(' + scale + ')'; + + // width + } else if (params.width && !params.height) { + + scale = params.width / svgWidth; + + realWidth = realWidth * scale; + realHeight = realHeight * scale; + + svgWidth = svgElem.attr('width').value = params.width; + svgHeight = svgElem.attr('height').value = svgHeight * scale; + + transform += ' scale(' + scale + ')'; + + // height + } else if (params.height && !params.width) { + + scale = params.height / svgHeight; + + realWidth = realWidth * scale; + realHeight = realHeight * scale; + + svgWidth = svgElem.attr('width').value = svgWidth * scale; + svgHeight = svgElem.attr('height').value = params.height; + + transform += ' scale(' + scale + ')'; + + } + + // shiftX + if (params.shiftX) { + transform += ' translate(' + realWidth * params.shiftX + ', 0)'; + } + + // shiftY + if (params.shiftY) { + transform += ' translate(0, ' + realHeight * params.shiftY + ')'; + } + + // scale + if (params.scale) { + scale = params.scale; + + var shiftX = svgWidth / 2, + shiftY = svgHeight / 2; + + realWidth = realWidth * scale; + realHeight = realHeight * scale; + + if (params.shiftX || params.shiftY) { + transform += ' scale(' + scale + ')'; + } else { + transform += ' translate(' + shiftX + ' ' + shiftY + ') scale(' + scale + ') translate(-' + shiftX + ' -' + shiftY + ')'; + } + } + + // hcrop + if (params.hcrop) { + transform += ' translate(' + (-xmin) + ' 0)'; + + svgElem.attr('width').value = realWidth; + } + + // vcenter + if (params.vcenter) { + transform += ' translate(0 ' + (((svgHeight - realHeight) / 2) - ymin) + ')'; + } + + if (transform) { + pathElem.addAttr({ + name: 'transform', + prefix: '', + local: 'transform', + value: transform + }); + + path = applyTransforms(pathElem, pathElem.pathJS, true, params.floatPrecision); + + // transformed data rounding + path.forEach(function(pathItem) { + if (pathItem.data) { + pathItem.data = pathItem.data.map(function(num) { + return +num.toFixed(params.floatPrecision); + }); + } + }); + + // save new + js2path(pathElem, path, params); + } + + } + + }); + + return data; + +}; |
