Initial commit: igny8 project

This commit is contained in:
igny8
2025-11-09 10:27:02 +00:00
commit 60b8188111
27265 changed files with 4360521 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
// Karma configuration
const karmaCommon = require('./karma.conf.common.cjs')
let chromeBin = 'ChromeHeadless'
if (process.platform === 'linux') {
// We need to choose either Chrome or Chromium.
// Canary is not available on linux.
// If we do not find Chromium then we can deduce that
// either Chrome is installed or there is no Chrome variant at all,
// in which case karma-chrome-launcher will output an error.
// If `which` finds nothing it will throw an error.
const { execSync } = require('child_process')
try {
if (execSync('which chromium-browser')) chromeBin = 'ChromiumHeadless'
} catch (e) {}
}
module.exports = function (config) {
config.set(
Object.assign(karmaCommon(config), {
files: [
'spec/RAFPlugin.js',
{
pattern: 'spec/fixtures/fixture.css',
included: false,
served: true
},
{
pattern: 'spec/fixtures/pixel.png',
included: false,
served: true
},
{
pattern: 'src/**/*.js',
included: false,
served: true,
type: 'modules'
},
{
pattern: 'spec/helpers.js',
included: false,
served: true,
type: 'module'
},
{
pattern: 'spec/setupBrowser.js',
included: true,
type: 'module'
},
{
pattern: 'spec/spec/*/**/*.js',
included: true,
type: 'module'
}
],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
'src/**/*.js': ['coverage']
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress', 'coverage'],
coverageReporter: {
// Specify a reporter type.
type: 'lcov',
dir: 'coverage/',
subdir: function (browser) {
// normalization process to keep a consistent browser name accross different OS
return browser.toLowerCase().split(/[ /-]/)[0] // output the results into: './coverage/firefox/'
},
instrumenterOptions: {
istanbul: {
esModules: true
}
}
},
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: [chromeBin, 'FirefoxHeadless']
})
)
}

View File

@@ -0,0 +1,67 @@
// Karma shared configuration
const os = require('os')
const cpuCount = os.cpus().length
module.exports = function (config) {
return {
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '../',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine'],
// list of files / patterns to load in the browser
files: [
'.config/pretest.js',
'spec/RAFPlugin.js',
{
pattern: 'spec/fixtures/fixture.css',
included: false,
served: true
},
{
pattern: 'spec/fixtures/fixture.svg',
included: false,
served: true
},
{
pattern: 'spec/fixtures/pixel.png',
included: false,
served: true
},
'dist/svg.js',
'spec/spec/*.js'
],
proxies: {
'/fixtures/': '/base/spec/fixtures/',
'/spec/': '/base/spec/'
},
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: false,
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: true,
// Concurrency level
// how many browser should be started simultaneous
concurrency: cpuCount || Infinity,
// list of files to exclude
exclude: []
}
}

View File

@@ -0,0 +1,144 @@
// Karma configuration
// https://wiki.saucelabs.com/display/DOCS/Platform+Configurator
// TODO: remove dotenv after local test
// require('dotenv').config()
const karmaCommon = require('./karma.conf.common.cjs')
const SauceLabsLaunchers = {
/** Real mobile devices are not available
* Your account does not have access to Android devices.
* Please contact sales@saucelabs.com to add this feature to your account. */
/* sl_android_chrome: {
base: 'SauceLabs',
appiumVersion: '1.5.3',
deviceName: 'Samsung Galaxy S7 Device',
deviceOrientation: 'portrait',
browserName: 'Chrome',
platformVersion: '6.0',
platformName: 'Android'
}, */
/* sl_android: {
base: 'SauceLabs',
browserName: 'Android',
deviceName: 'Android Emulator',
deviceOrientation: 'portrait'
}, */
SL_firefox_latest: {
base: 'SauceLabs',
browserName: 'firefox',
version: 'latest'
},
SL_chrome_latest: {
base: 'SauceLabs',
browserName: 'chrome',
version: 'latest'
},
SL_InternetExplorer: {
base: 'SauceLabs',
browserName: 'internet explorer',
version: '11.0'
} /*
sl_windows_edge: {
base: 'SauceLabs',
browserName: 'MicrosoftEdge',
version: 'latest',
platform: 'Windows 10'
},
sl_macos_safari: {
base: 'SauceLabs',
browserName: 'safari',
platform: 'macOS 10.13',
version: '12.0',
recordVideo: true,
recordScreenshots: true,
screenResolution: '1024x768'
} */ /*,
sl_macos_iphone: {
base: 'SauceLabs',
browserName: 'Safari',
deviceName: 'iPhone SE Simulator',
deviceOrientation: 'portrait',
platformVersion: '10.2',
platformName: 'iOS'
}
'SL_Chrome': {
base: 'SauceLabs',
browserName: 'chrome',
version: '48.0',
platform: 'Linux'
},
'SL_Firefox': {
base: 'SauceLabs',
browserName: 'firefox',
version: '50.0',
platform: 'Windows 10'
},
'SL_Safari': {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.11',
version: '10.0'
} */
}
module.exports = function (config) {
if (!process.env.SAUCE_USERNAME || !process.env.SAUCE_ACCESS_KEY) {
console.error(
'SAUCE_USERNAME and SAUCE_ACCESS_KEY must be provided as environment variables.'
)
console.warn('Aborting Sauce Labs test')
process.exit(1)
}
const settings = Object.assign(karmaCommon(config), {
// Concurrency level
// how many browser should be started simultaneous
// Saucelabs allow up to 5 concurrent sessions on the free open source tier.
concurrency: 5,
// this specifies which plugins karma should load
// by default all karma plugins, starting with `karma-` will load
// so if you are really puzzled why something isn't working, then comment
// out plugins: [] - it's here to make karma load faster
// get possible karma plugins by `ls node_modules | grep 'karma-*'`
plugins: ['karma-jasmine', 'karma-sauce-launcher'],
// logLevel: config.LOG_DEBUG,
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['dots', 'saucelabs'],
customLaunchers: SauceLabsLaunchers,
// start these browsers
browsers: Object.keys(SauceLabsLaunchers),
sauceLabs: {
testName: 'SVG.js Unit Tests'
// connectOptions: {
// noSslBumpDomains: "all"
// },
// connectOptions: {
// port: 5757,
// logfile: 'sauce_connect.log'
// },
}
// The number of disconnections tolerated.
// browserDisconnectTolerance: 0, // well, sometimes it helps to just restart
// // How long does Karma wait for a browser to reconnect (in ms).
// browserDisconnectTimeout: 10 * 60 * 1000,
// // How long will Karma wait for a message from a browser before disconnecting from it (in ms). ~ macOS 10.12 needs more than 7 minutes
// browserNoActivityTimeout: 20 * 60 * 1000,
// // Timeout for capturing a browser (in ms). On newer versions of iOS simulator (10.0+), the start up time could be between 3 - 6 minutes.
// captureTimeout: 12 * 60 * 1000, // this is useful if saucelabs takes a long time to boot a vm
// // Required to make Safari on Sauce Labs play nice.
// // hostname: 'karmalocal.dev'
})
console.log(settings)
config.set(settings)
}

View File

@@ -0,0 +1,33 @@
/* global SVGElement */
/* eslint no-new-object: "off" */
import CustomEventPolyfill from '@target/custom-event-polyfill/src/index.js6'
import children from '../src/polyfills/children.js'
/* IE 11 has no innerHTML on SVGElement */
import '../src/polyfills/innerHTML.js'
/* IE 11 has no correct CustomEvent implementation */
CustomEventPolyfill()
/* IE 11 has no children on SVGElement */
try {
if (!SVGElement.prototype.children) {
Object.defineProperty(SVGElement.prototype, 'children', {
get: function () {
return children(this)
}
})
}
} catch (e) {}
/* IE 11 cannot handle getPrototypeOf(not_obj) */
try {
delete Object.getPrototypeOf('test')
} catch (e) {
var old = Object.getPrototypeOf
Object.getPrototypeOf = function (o) {
if (typeof o !== 'object') o = new Object(o)
return old.call(this, o)
}
}

View File

@@ -0,0 +1,22 @@
/* global XMLHttpRequest */
'use strict'
function get(uri) {
var xhr = new XMLHttpRequest()
xhr.open('GET', uri, false)
xhr.send()
if (xhr.status !== 200) {
console.error('SVG.js fixture could not be loaded. Tests will fail.')
}
return xhr.responseText
}
function main() {
var style = document.createElement('style')
document.head.appendChild(style)
style.sheet.insertRule(get('/fixtures/fixture.css'), 0)
document.body.innerHTML = get('/fixtures/fixture.svg')
}
main()

View File

@@ -0,0 +1,144 @@
import pkg from '../package.json' with { type: 'json' }
import babel from '@rollup/plugin-babel'
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import filesize from 'rollup-plugin-filesize'
import terser from '@rollup/plugin-terser'
const buildDate = Date()
const headerLong = `/*!
* ${pkg.name} - ${pkg.description}
* @version ${pkg.version}
* ${pkg.homepage}
*
* @copyright ${pkg.author}
* @license ${pkg.license}
*
* BUILT: ${buildDate}
*/;`
const headerShort = `/*! ${pkg.name} v${pkg.version} ${pkg.license}*/;`
const getBabelConfig = (node = false) => {
let targets = pkg.browserslist
const plugins = [
[
'@babel/transform-runtime',
{
version: '^7.24.7',
regenerator: false,
useESModules: true
}
],
[
'polyfill-corejs3',
{
method: 'usage-pure'
}
]
]
if (node) {
targets = 'maintained node versions'
}
return babel({
include: 'src/**',
babelHelpers: 'runtime',
babelrc: false,
targets: targets,
presets: [
[
'@babel/preset-env',
{
modules: false,
// useBuildins and plugin-transform-runtime are mutually exclusive
// https://github.com/babel/babel/issues/10271#issuecomment-528379505
// use babel-polyfills when released
useBuiltIns: false,
bugfixes: true,
loose: true
}
]
],
plugins
})
}
// When few of these get mangled nothing works anymore
// We loose literally nothing by let these unmangled
const classes = [
'A',
'ClipPath',
'Defs',
'Element',
'G',
'Image',
'Marker',
'Path',
'Polygon',
'Rect',
'Stop',
'Svg',
'Text',
'Tspan',
'Circle',
'Container',
'Dom',
'Ellipse',
'Gradient',
'Line',
'Mask',
'Pattern',
'Polyline',
'Shape',
'Style',
'Symbol',
'TextPath',
'Use'
]
const config = (node, min, esm = false) => ({
input: node || esm ? './src/main.js' : './src/svg.js',
output: {
file: esm
? './dist/svg.esm.js'
: node
? './dist/svg.node.cjs'
: min
? './dist/svg.min.js'
: './dist/svg.js',
format: esm ? 'esm' : node ? 'cjs' : 'iife',
name: 'SVG',
sourcemap: true,
banner: headerLong,
// remove Object.freeze
freeze: false
},
treeshake: {
// property getter have no sideeffects
propertyReadSideEffects: false
},
plugins: [
resolve({ browser: !node }),
commonjs(),
getBabelConfig(node),
filesize(),
!min
? {}
: terser({
mangle: {
reserved: classes
},
output: {
preamble: headerShort
}
})
]
})
// [node, minified, esm]
const modes = [[false], [false, true], [true], [false, false, true]]
export default modes.map((m) => config(...m))

View File

@@ -0,0 +1,20 @@
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import filesize from 'rollup-plugin-filesize'
// We dont need babel. All polyfills are compatible
const config = (ie) => ({
input: './.config/polyfillListIE.js',
output: {
file: 'dist/polyfillsIE.js',
format: 'iife'
},
plugins: [
resolve({ browser: true }),
commonjs(),
//terser(),
filesize()
]
})
export default [true].map(config)

View File

@@ -0,0 +1,55 @@
import * as pkg from '../package.json'
import babel from '@rollup/plugin-babel'
import multiEntry from '@rollup/plugin-multi-entry'
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
const getBabelConfig = (targets) =>
babel({
include: ['src/**', 'spec/**/*'],
babelHelpers: 'runtime',
babelrc: false,
presets: [
[
'@babel/preset-env',
{
modules: false,
targets: targets || pkg.browserslist,
// useBuildins and plugin-transform-runtime are mutually exclusive
// https://github.com/babel/babel/issues/10271#issuecomment-528379505
// use babel-polyfills when released
useBuiltIns: false,
// corejs: 3,
bugfixes: true
}
]
],
plugins: [
[
'@babel/plugin-transform-runtime',
{
corejs: 3,
helpers: true,
useESModules: true,
version: '^7.9.6',
regenerator: false
}
]
]
})
export default {
input: ['spec/setupBrowser.js', 'spec/spec/*/*.js'],
output: {
file: 'spec/es5TestBundle.js',
name: 'SVGTests',
format: 'iife'
},
plugins: [
resolve({ browser: true }),
commonjs(),
getBabelConfig(),
multiEntry()
],
external: ['@babel/runtime', '@babel/runtime-corejs3']
}

21
frontend/node_modules/@svgdotjs/svg.js/LICENSE.txt generated vendored Normal file
View File

@@ -0,0 +1,21 @@
Copyright (c) 2012-2018 Wout Fierens
https://svgdotjs.github.io/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

34
frontend/node_modules/@svgdotjs/svg.js/README.md generated vendored Normal file
View File

@@ -0,0 +1,34 @@
# SVG.js
[![Build Status](https://travis-ci.org/svgdotjs/svg.js.svg?branch=master)](https://travis-ci.org/svgdotjs/svg.js)
[![Coverage Status](https://coveralls.io/repos/github/svgdotjs/svg.js/badge.svg?branch=master)](https://coveralls.io/github/svgdotjs/svg.js?branch=master)
[![Cdnjs](https://img.shields.io/cdnjs/v/svg.js.svg)](https://cdnjs.com/libraries/svg.js)
[![jsdelivr](https://badgen.net/jsdelivr/v/npm/@svgdotjs/svg.js)](https://cdn.jsdelivr.net/npm/@svgdotjs/svg.js)
[![Join the chat at https://gitter.im/svgdotjs/svg.js](https://badges.gitter.im/svgdotjs/svg.js.svg)](https://gitter.im/svgdotjs/svg.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Twitter](https://img.shields.io/badge/Twitter-@svg__js-green.svg)](https://twitter.com/svg_js)
**A lightweight library for manipulating and animating SVG, without any dependencies.**
SVG.js is licensed under the terms of the MIT License.
## Installation
#### Npm:
`npm install @svgdotjs/svg.js`
#### Yarn:
`yarn add @svgdotjs/svg.js`
#### CDNs:
[https://cdnjs.com/libraries/svg.js](https://cdnjs.com/libraries/svg.js)
[https://cdn.jsdelivr.net/npm/@svgdotjs/svg.js](https://cdn.jsdelivr.net/npm/@svgdotjs/svg.js)
[https://unpkg.com/@svgdotjs/svg.js](https://unpkg.com/@svgdotjs/svg.js)
## Documentation
Check [svgjs.dev](https://svgjs.dev/docs/3.0/) to learn more.
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=ulima.ums%40googlemail.com&lc=US&item_name=SVG.JS&currency_code=EUR&bn=PP-DonationsBF%3Abtn_donate_74x21.png%3ANonHostedGuest) or [![Sponsor](https://img.shields.io/badge/Sponsor-svg.js-green.svg)](https://github.com/sponsors/Fuzzyma)

View File

@@ -0,0 +1,473 @@
(function () {
'use strict';
/* Polyfill service v3.16.0
* For detailed credits and licence information see https://github.com/financial-times/polyfill-service.
*
* UA detected: ie/9.0.0
* Features requested: CustomEvent
*
* - Event, License: CC0 (required by "CustomEvent")
* - CustomEvent, License: CC0 */
function CustomEventPolyfill() {
(function (undefined$1) {
if (!((function (global) {
if (!('Event' in global)) return false;
if (typeof global.Event === 'function') return true;
try {
// In IE 9-11, the Event object exists but cannot be instantiated
new Event('click');
return true;
} catch (e) {
return false;
}
}(this)))) {
// Event
(function () {
var unlistenableWindowEvents = {
click: 1,
dblclick: 1,
keyup: 1,
keypress: 1,
keydown: 1,
mousedown: 1,
mouseup: 1,
mousemove: 1,
mouseover: 1,
mouseenter: 1,
mouseleave: 1,
mouseout: 1,
storage: 1,
storagecommit: 1,
textinput: 1
};
function indexOf(array, element) {
var
index = -1,
length = array.length;
while (++index < length) {
if (index in array && array[index] === element) {
return index;
}
}
return -1;
}
var existingProto = (window.Event && window.Event.prototype) || null;
window.Event = Window.prototype.Event = function Event(type, eventInitDict) {
if (!type) {
throw new Error('Not enough arguments');
}
// Shortcut if browser supports createEvent
if ('createEvent' in document) {
var event = document.createEvent('Event');
var bubbles = eventInitDict && eventInitDict.bubbles !== undefined$1 ?
eventInitDict.bubbles : false;
var cancelable = eventInitDict && eventInitDict.cancelable !== undefined$1 ?
eventInitDict.cancelable : false;
event.initEvent(type, bubbles, cancelable);
return event;
}
var event = document.createEventObject();
event.type = type;
event.bubbles =
eventInitDict && eventInitDict.bubbles !== undefined$1 ? eventInitDict.bubbles : false;
event.cancelable =
eventInitDict && eventInitDict.cancelable !== undefined$1 ? eventInitDict.cancelable :
false;
return event;
};
if (existingProto) {
Object.defineProperty(window.Event, 'prototype', {
configurable: false,
enumerable: false,
writable: true,
value: existingProto
});
}
if (!('createEvent' in document)) {
window.addEventListener = Window.prototype.addEventListener =
Document.prototype.addEventListener =
Element.prototype.addEventListener = function addEventListener() {
var
element = this,
type = arguments[0],
listener = arguments[1];
if (element === window && type in unlistenableWindowEvents) {
throw new Error('In IE8 the event: ' + type +
' is not available on the window object.');
}
if (!element._events) {
element._events = {};
}
if (!element._events[type]) {
element._events[type] = function (event) {
var
list = element._events[event.type].list,
events = list.slice(),
index = -1,
length = events.length,
eventElement;
event.preventDefault = function preventDefault() {
if (event.cancelable !== false) {
event.returnValue = false;
}
};
event.stopPropagation = function stopPropagation() {
event.cancelBubble = true;
};
event.stopImmediatePropagation = function stopImmediatePropagation() {
event.cancelBubble = true;
event.cancelImmediate = true;
};
event.currentTarget = element;
event.relatedTarget = event.fromElement || null;
event.target = event.target || event.srcElement || element;
event.timeStamp = new Date().getTime();
if (event.clientX) {
event.pageX = event.clientX + document.documentElement.scrollLeft;
event.pageY = event.clientY + document.documentElement.scrollTop;
}
while (++index < length && !event.cancelImmediate) {
if (index in events) {
eventElement = events[index];
if (indexOf(list, eventElement) !== -1 &&
typeof eventElement === 'function') {
eventElement.call(element, event);
}
}
}
};
element._events[type].list = [];
if (element.attachEvent) {
element.attachEvent('on' + type, element._events[type]);
}
}
element._events[type].list.push(listener);
};
window.removeEventListener = Window.prototype.removeEventListener =
Document.prototype.removeEventListener =
Element.prototype.removeEventListener = function removeEventListener() {
var
element = this,
type = arguments[0],
listener = arguments[1],
index;
if (element._events && element._events[type] && element._events[type].list) {
index = indexOf(element._events[type].list, listener);
if (index !== -1) {
element._events[type].list.splice(index, 1);
if (!element._events[type].list.length) {
if (element.detachEvent) {
element.detachEvent('on' + type, element._events[type]);
}
delete element._events[type];
}
}
}
};
window.dispatchEvent = Window.prototype.dispatchEvent = Document.prototype.dispatchEvent =
Element.prototype.dispatchEvent = function dispatchEvent(event) {
if (!arguments.length) {
throw new Error('Not enough arguments');
}
if (!event || typeof event.type !== 'string') {
throw new Error('DOM Events Exception 0');
}
var element = this, type = event.type;
try {
if (!event.bubbles) {
event.cancelBubble = true;
var cancelBubbleEvent = function (event) {
event.cancelBubble = true;
(element || window).detachEvent('on' + type, cancelBubbleEvent);
};
this.attachEvent('on' + type, cancelBubbleEvent);
}
this.fireEvent('on' + type, event);
} catch (error) {
event.target = element;
do {
event.currentTarget = element;
if ('_events' in element && typeof element._events[type] === 'function') {
element._events[type].call(element, event);
}
if (typeof element['on' + type] === 'function') {
element['on' + type].call(element, event);
}
element = element.nodeType === 9 ? element.parentWindow : element.parentNode;
} while (element && !event.cancelBubble);
}
return true;
};
// Add the DOMContentLoaded Event
document.attachEvent('onreadystatechange', function () {
if (document.readyState === 'complete') {
document.dispatchEvent(new Event('DOMContentLoaded', {
bubbles: true
}));
}
});
}
}());
}
if (!('CustomEvent' in this &&
// In Safari, typeof CustomEvent == 'object' but it otherwise works fine
(typeof this.CustomEvent === 'function' ||
(this.CustomEvent.toString().indexOf('CustomEventConstructor') > -1)))) {
// CustomEvent
this.CustomEvent = function CustomEvent(type, eventInitDict) {
if (!type) {
throw Error(
'TypeError: Failed to construct "CustomEvent": An event name must be provided.');
}
var event;
eventInitDict = eventInitDict || {bubbles: false, cancelable: false, detail: null};
if ('createEvent' in document) {
try {
event = document.createEvent('CustomEvent');
event.initCustomEvent(type, eventInitDict.bubbles, eventInitDict.cancelable,
eventInitDict.detail);
} catch (error) {
// for browsers which don't support CustomEvent at all, we use a regular event instead
event = document.createEvent('Event');
event.initEvent(type, eventInitDict.bubbles, eventInitDict.cancelable);
event.detail = eventInitDict.detail;
}
} else {
// IE8
event = new Event(type, eventInitDict);
event.detail = eventInitDict && eventInitDict.detail || null;
}
return event;
};
CustomEvent.prototype = Event.prototype;
}
}).call('object' === typeof window && window || 'object' === typeof self && self ||
'object' === typeof global && global || {});
}
// Map function
// Filter function
function filter(array, block) {
let i;
const il = array.length;
const result = [];
for (i = 0; i < il; i++) {
if (block(array[i])) {
result.push(array[i]);
}
}
return result
}
// IE11: children does not work for svg nodes
function children(node) {
return filter(node.childNodes, function (child) {
return child.nodeType === 1
})
}
(function () {
try {
if (SVGElement.prototype.innerHTML) return
} catch (e) {
return
}
const serializeXML = function (node, output) {
const nodeType = node.nodeType;
if (nodeType === 3) {
output.push(
node.textContent
.replace(/&/, '&amp;')
.replace(/</, '&lt;')
.replace('>', '&gt;')
);
} else if (nodeType === 1) {
output.push('<', node.tagName);
if (node.hasAttributes()) {
[].forEach.call(node.attributes, function (attrNode) {
output.push(' ', attrNode.name, '="', attrNode.value, '"');
});
}
output.push('>');
if (node.hasChildNodes()) {
[].forEach.call(node.childNodes, function (childNode) {
serializeXML(childNode, output);
});
}
output.push('</', node.tagName, '>');
} else if (nodeType === 8) {
output.push('<!--', node.nodeValue, '-->');
}
};
Object.defineProperty(SVGElement.prototype, 'innerHTML', {
get: function () {
const output = [];
let childNode = this.firstChild;
while (childNode) {
serializeXML(childNode, output);
childNode = childNode.nextSibling;
}
return output.join('')
},
set: function (markupText) {
while (this.firstChild) {
this.removeChild(this.firstChild);
}
try {
const dXML = new DOMParser();
dXML.async = false;
const sXML =
"<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>" +
markupText +
'</svg>';
const svgDocElement = dXML.parseFromString(
sXML,
'text/xml'
).documentElement;
let childNode = svgDocElement.firstChild;
while (childNode) {
this.appendChild(this.ownerDocument.importNode(childNode, true));
childNode = childNode.nextSibling;
}
} catch (e) {
throw new Error('Can not set innerHTML on node')
}
}
});
Object.defineProperty(SVGElement.prototype, 'outerHTML', {
get: function () {
const output = [];
serializeXML(this, output);
return output.join('')
},
set: function (markupText) {
while (this.firstChild) {
this.removeChild(this.firstChild);
}
try {
const dXML = new DOMParser();
dXML.async = false;
const sXML =
"<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>" +
markupText +
'</svg>';
const svgDocElement = dXML.parseFromString(
sXML,
'text/xml'
).documentElement;
let childNode = svgDocElement.firstChild;
while (childNode) {
this.parentNode.insertBefore(
this.ownerDocument.importNode(childNode, true),
this
);
// this.appendChild(this.ownerDocument.importNode(childNode, true));
childNode = childNode.nextSibling;
}
} catch (e) {
throw new Error('Can not set outerHTML on node')
}
}
});
})();
/* global SVGElement */
/* eslint no-new-object: "off" */
/* IE 11 has no correct CustomEvent implementation */
CustomEventPolyfill();
/* IE 11 has no children on SVGElement */
try {
if (!SVGElement.prototype.children) {
Object.defineProperty(SVGElement.prototype, 'children', {
get: function () {
return children(this)
}
});
}
} catch (e) {}
/* IE 11 cannot handle getPrototypeOf(not_obj) */
try {
delete Object.getPrototypeOf('test');
} catch (e) {
var old = Object.getPrototypeOf;
Object.getPrototypeOf = function (o) {
if (typeof o !== 'object') o = new Object(o);
return old.call(this, o)
};
}
})();

6823
frontend/node_modules/@svgdotjs/svg.js/dist/svg.esm.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

6924
frontend/node_modules/@svgdotjs/svg.js/dist/svg.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

13
frontend/node_modules/@svgdotjs/svg.js/dist/svg.min.js generated vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

6910
frontend/node_modules/@svgdotjs/svg.js/dist/svg.node.cjs generated vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

128
frontend/node_modules/@svgdotjs/svg.js/package.json generated vendored Normal file
View File

@@ -0,0 +1,128 @@
{
"name": "@svgdotjs/svg.js",
"version": "3.2.4",
"type": "module",
"description": "A lightweight library for manipulating and animating SVG.",
"url": "https://svgjs.dev/",
"homepage": "https://svgjs.dev/",
"keywords": [
"svg",
"vector",
"graphics",
"animation"
],
"author": "Wout Fierens <wout@mick-wout.com>",
"main": "dist/svg.node.cjs",
"unpkg": "dist/svg.min.js",
"jsdelivr": "dist/svg.min.js",
"browser": "dist/svg.esm.js",
"module": "src/main.js",
"exports": {
".": {
"import": {
"types": "./svg.js.d.ts",
"default": "./src/main.js"
},
"require": {
"types": "./svg.js.d.ts",
"default": "./dist/svg.node.cjs"
}
}
},
"files": [
"/dist",
"/src",
"/svg.js.d.ts",
"/.config"
],
"maintainers": [
{
"name": "Wout Fierens",
"email": "wout@mick-wout.com"
},
{
"name": "Alex Ewerlöf",
"email": "alex@userpixel.com",
"web": "http://www.ewerlof.name"
},
{
"name": "Ulrich-Matthias Schäfer",
"email": "ulima.ums@googlemail.com",
"web": "https://svgdotjs.github.io/"
},
{
"name": "Jon Ege Ronnenberg",
"email": "jon@svgjs.dev",
"url": "https://keybase.io/dotnetcarpenter"
}
],
"licenses": [
{
"type": "MIT",
"url": "http://www.opensource.org/licenses/mit-license.php"
}
],
"repository": {
"type": "git",
"url": "https://github.com/svgdotjs/svg.js.git"
},
"github": "https://github.com/svgdotjs/svg.js",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Fuzzyma"
},
"typings": "./svg.js.d.ts",
"scripts": {
"build": "npm run format && npm run rollup",
"build:polyfills": "npx rollup -c .config/rollup.polyfills.js",
"build:tests": "npx rollup -c .config/rollup.tests.js",
"fix": "npx eslint ./src --fix",
"lint": "npx eslint ./src",
"prettier": "npx prettier --write .",
"format": "npm run fix && npm run prettier",
"rollup": "npx rollup -c .config/rollup.config.js",
"server": "npx http-server ./ -d",
"test": "npx karma start .config/karma.conf.cjs || true",
"test:ci": "karma start .config/karma.conf.saucelabs.cjs",
"test:svgdom": "node ./spec/runSVGDomTest.js || true",
"zip": "zip -j dist/svg.js.zip -- LICENSE.txt README.md CHANGELOG.md dist/svg.js dist/svg.js.map dist/svg.min.js dist/svg.min.js.map dist/polyfills.js dist/polyfillsIE.js",
"prepublishOnly": "rm -rf ./dist && npm run build && npm run build:polyfills && npm test",
"postpublish": "npm run zip",
"checkTests": "node spec/checkForAllTests.js"
},
"devDependencies": {
"@babel/core": "^7.24.7",
"@babel/eslint-parser": "^7.24.7",
"@babel/plugin-transform-runtime": "^7.24.7",
"@babel/preset-env": "^7.24.7",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-commonjs": "^26.0.1",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-terser": "^0.4.4",
"@target/custom-event-polyfill": "github:Adobe-Marketing-Cloud/custom-event-polyfill",
"@types/jasmine": "^5.1.4",
"babel-plugin-polyfill-corejs3": "^0.10.4",
"core-js": "^3.37.1",
"coveralls": "^3.1.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard": "^17.1.0",
"http-server": "^14.1.1",
"jasmine": "^5.1.0",
"jasmine-core": "^5.1.2",
"karma": "^6.4.3",
"karma-chrome-launcher": "^3.2.0",
"karma-coverage": "^2.2.1",
"karma-firefox-launcher": "^2.1.3",
"karma-jasmine": "^5.1.0",
"karma-sauce-launcher": "^4.3.6",
"prettier": "^3.3.2",
"rollup": "^4.18.0",
"rollup-plugin-filesize": "^10.0.0",
"svgdom": "^0.1.19",
"typescript": "^5.4.5",
"yargs": "^17.7.2"
},
"browserslist": ">0.3%, last 2 version, not dead, not op_mini all"
}

View File

@@ -0,0 +1,102 @@
import { globals } from '../utils/window.js'
import Queue from './Queue.js'
const Animator = {
nextDraw: null,
frames: new Queue(),
timeouts: new Queue(),
immediates: new Queue(),
timer: () => globals.window.performance || globals.window.Date,
transforms: [],
frame(fn) {
// Store the node
const node = Animator.frames.push({ run: fn })
// Request an animation frame if we don't have one
if (Animator.nextDraw === null) {
Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw)
}
// Return the node so we can remove it easily
return node
},
timeout(fn, delay) {
delay = delay || 0
// Work out when the event should fire
const time = Animator.timer().now() + delay
// Add the timeout to the end of the queue
const node = Animator.timeouts.push({ run: fn, time: time })
// Request another animation frame if we need one
if (Animator.nextDraw === null) {
Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw)
}
return node
},
immediate(fn) {
// Add the immediate fn to the end of the queue
const node = Animator.immediates.push(fn)
// Request another animation frame if we need one
if (Animator.nextDraw === null) {
Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw)
}
return node
},
cancelFrame(node) {
node != null && Animator.frames.remove(node)
},
clearTimeout(node) {
node != null && Animator.timeouts.remove(node)
},
cancelImmediate(node) {
node != null && Animator.immediates.remove(node)
},
_draw(now) {
// Run all the timeouts we can run, if they are not ready yet, add them
// to the end of the queue immediately! (bad timeouts!!! [sarcasm])
let nextTimeout = null
const lastTimeout = Animator.timeouts.last()
while ((nextTimeout = Animator.timeouts.shift())) {
// Run the timeout if its time, or push it to the end
if (now >= nextTimeout.time) {
nextTimeout.run()
} else {
Animator.timeouts.push(nextTimeout)
}
// If we hit the last item, we should stop shifting out more items
if (nextTimeout === lastTimeout) break
}
// Run all of the animation frames
let nextFrame = null
const lastFrame = Animator.frames.last()
while (nextFrame !== lastFrame && (nextFrame = Animator.frames.shift())) {
nextFrame.run(now)
}
let nextImmediate = null
while ((nextImmediate = Animator.immediates.shift())) {
nextImmediate()
}
// If we have remaining timeouts or frames, draw until we don't anymore
Animator.nextDraw =
Animator.timeouts.first() || Animator.frames.first()
? globals.window.requestAnimationFrame(Animator._draw)
: null
}
}
export default Animator

View File

@@ -0,0 +1,231 @@
import { timeline } from '../modules/core/defaults.js'
import { extend } from '../utils/adopter.js'
/***
Base Class
==========
The base stepper class that will be
***/
function makeSetterGetter(k, f) {
return function (v) {
if (v == null) return this[k]
this[k] = v
if (f) f.call(this)
return this
}
}
export const easing = {
'-': function (pos) {
return pos
},
'<>': function (pos) {
return -Math.cos(pos * Math.PI) / 2 + 0.5
},
'>': function (pos) {
return Math.sin((pos * Math.PI) / 2)
},
'<': function (pos) {
return -Math.cos((pos * Math.PI) / 2) + 1
},
bezier: function (x1, y1, x2, y2) {
// see https://www.w3.org/TR/css-easing-1/#cubic-bezier-algo
return function (t) {
if (t < 0) {
if (x1 > 0) {
return (y1 / x1) * t
} else if (x2 > 0) {
return (y2 / x2) * t
} else {
return 0
}
} else if (t > 1) {
if (x2 < 1) {
return ((1 - y2) / (1 - x2)) * t + (y2 - x2) / (1 - x2)
} else if (x1 < 1) {
return ((1 - y1) / (1 - x1)) * t + (y1 - x1) / (1 - x1)
} else {
return 1
}
} else {
return 3 * t * (1 - t) ** 2 * y1 + 3 * t ** 2 * (1 - t) * y2 + t ** 3
}
}
},
// see https://www.w3.org/TR/css-easing-1/#step-timing-function-algo
steps: function (steps, stepPosition = 'end') {
// deal with "jump-" prefix
stepPosition = stepPosition.split('-').reverse()[0]
let jumps = steps
if (stepPosition === 'none') {
--jumps
} else if (stepPosition === 'both') {
++jumps
}
// The beforeFlag is essentially useless
return (t, beforeFlag = false) => {
// Step is called currentStep in referenced url
let step = Math.floor(t * steps)
const jumping = (t * step) % 1 === 0
if (stepPosition === 'start' || stepPosition === 'both') {
++step
}
if (beforeFlag && jumping) {
--step
}
if (t >= 0 && step < 0) {
step = 0
}
if (t <= 1 && step > jumps) {
step = jumps
}
return step / jumps
}
}
}
export class Stepper {
done() {
return false
}
}
/***
Easing Functions
================
***/
export class Ease extends Stepper {
constructor(fn = timeline.ease) {
super()
this.ease = easing[fn] || fn
}
step(from, to, pos) {
if (typeof from !== 'number') {
return pos < 1 ? from : to
}
return from + (to - from) * this.ease(pos)
}
}
/***
Controller Types
================
***/
export class Controller extends Stepper {
constructor(fn) {
super()
this.stepper = fn
}
done(c) {
return c.done
}
step(current, target, dt, c) {
return this.stepper(current, target, dt, c)
}
}
function recalculate() {
// Apply the default parameters
const duration = (this._duration || 500) / 1000
const overshoot = this._overshoot || 0
// Calculate the PID natural response
const eps = 1e-10
const pi = Math.PI
const os = Math.log(overshoot / 100 + eps)
const zeta = -os / Math.sqrt(pi * pi + os * os)
const wn = 3.9 / (zeta * duration)
// Calculate the Spring values
this.d = 2 * zeta * wn
this.k = wn * wn
}
export class Spring extends Controller {
constructor(duration = 500, overshoot = 0) {
super()
this.duration(duration).overshoot(overshoot)
}
step(current, target, dt, c) {
if (typeof current === 'string') return current
c.done = dt === Infinity
if (dt === Infinity) return target
if (dt === 0) return current
if (dt > 100) dt = 16
dt /= 1000
// Get the previous velocity
const velocity = c.velocity || 0
// Apply the control to get the new position and store it
const acceleration = -this.d * velocity - this.k * (current - target)
const newPosition = current + velocity * dt + (acceleration * dt * dt) / 2
// Store the velocity
c.velocity = velocity + acceleration * dt
// Figure out if we have converged, and if so, pass the value
c.done = Math.abs(target - newPosition) + Math.abs(velocity) < 0.002
return c.done ? target : newPosition
}
}
extend(Spring, {
duration: makeSetterGetter('_duration', recalculate),
overshoot: makeSetterGetter('_overshoot', recalculate)
})
export class PID extends Controller {
constructor(p = 0.1, i = 0.01, d = 0, windup = 1000) {
super()
this.p(p).i(i).d(d).windup(windup)
}
step(current, target, dt, c) {
if (typeof current === 'string') return current
c.done = dt === Infinity
if (dt === Infinity) return target
if (dt === 0) return current
const p = target - current
let i = (c.integral || 0) + p * dt
const d = (p - (c.error || 0)) / dt
const windup = this._windup
// antiwindup
if (windup !== false) {
i = Math.max(-windup, Math.min(i, windup))
}
c.error = p
c.integral = i
c.done = Math.abs(p) < 0.001
return c.done ? target : current + (this.P * p + this.I * i + this.D * d)
}
}
extend(PID, {
windup: makeSetterGetter('_windup'),
p: makeSetterGetter('P'),
i: makeSetterGetter('I'),
d: makeSetterGetter('D')
})

View File

@@ -0,0 +1,336 @@
import { Ease } from './Controller.js'
import {
delimiter,
numberAndUnit,
isPathLetter
} from '../modules/core/regex.js'
import { extend } from '../utils/adopter.js'
import Color from '../types/Color.js'
import PathArray from '../types/PathArray.js'
import SVGArray from '../types/SVGArray.js'
import SVGNumber from '../types/SVGNumber.js'
const getClassForType = (value) => {
const type = typeof value
if (type === 'number') {
return SVGNumber
} else if (type === 'string') {
if (Color.isColor(value)) {
return Color
} else if (delimiter.test(value)) {
return isPathLetter.test(value) ? PathArray : SVGArray
} else if (numberAndUnit.test(value)) {
return SVGNumber
} else {
return NonMorphable
}
} else if (morphableTypes.indexOf(value.constructor) > -1) {
return value.constructor
} else if (Array.isArray(value)) {
return SVGArray
} else if (type === 'object') {
return ObjectBag
} else {
return NonMorphable
}
}
export default class Morphable {
constructor(stepper) {
this._stepper = stepper || new Ease('-')
this._from = null
this._to = null
this._type = null
this._context = null
this._morphObj = null
}
at(pos) {
return this._morphObj.morph(
this._from,
this._to,
pos,
this._stepper,
this._context
)
}
done() {
const complete = this._context.map(this._stepper.done).reduce(function (
last,
curr
) {
return last && curr
}, true)
return complete
}
from(val) {
if (val == null) {
return this._from
}
this._from = this._set(val)
return this
}
stepper(stepper) {
if (stepper == null) return this._stepper
this._stepper = stepper
return this
}
to(val) {
if (val == null) {
return this._to
}
this._to = this._set(val)
return this
}
type(type) {
// getter
if (type == null) {
return this._type
}
// setter
this._type = type
return this
}
_set(value) {
if (!this._type) {
this.type(getClassForType(value))
}
let result = new this._type(value)
if (this._type === Color) {
result = this._to
? result[this._to[4]]()
: this._from
? result[this._from[4]]()
: result
}
if (this._type === ObjectBag) {
result = this._to
? result.align(this._to)
: this._from
? result.align(this._from)
: result
}
result = result.toConsumable()
this._morphObj = this._morphObj || new this._type()
this._context =
this._context ||
Array.apply(null, Array(result.length))
.map(Object)
.map(function (o) {
o.done = true
return o
})
return result
}
}
export class NonMorphable {
constructor(...args) {
this.init(...args)
}
init(val) {
val = Array.isArray(val) ? val[0] : val
this.value = val
return this
}
toArray() {
return [this.value]
}
valueOf() {
return this.value
}
}
export class TransformBag {
constructor(...args) {
this.init(...args)
}
init(obj) {
if (Array.isArray(obj)) {
obj = {
scaleX: obj[0],
scaleY: obj[1],
shear: obj[2],
rotate: obj[3],
translateX: obj[4],
translateY: obj[5],
originX: obj[6],
originY: obj[7]
}
}
Object.assign(this, TransformBag.defaults, obj)
return this
}
toArray() {
const v = this
return [
v.scaleX,
v.scaleY,
v.shear,
v.rotate,
v.translateX,
v.translateY,
v.originX,
v.originY
]
}
}
TransformBag.defaults = {
scaleX: 1,
scaleY: 1,
shear: 0,
rotate: 0,
translateX: 0,
translateY: 0,
originX: 0,
originY: 0
}
const sortByKey = (a, b) => {
return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0
}
export class ObjectBag {
constructor(...args) {
this.init(...args)
}
align(other) {
const values = this.values
for (let i = 0, il = values.length; i < il; ++i) {
// If the type is the same we only need to check if the color is in the correct format
if (values[i + 1] === other[i + 1]) {
if (values[i + 1] === Color && other[i + 7] !== values[i + 7]) {
const space = other[i + 7]
const color = new Color(this.values.splice(i + 3, 5))
[space]()
.toArray()
this.values.splice(i + 3, 0, ...color)
}
i += values[i + 2] + 2
continue
}
if (!other[i + 1]) {
return this
}
// The types differ, so we overwrite the new type with the old one
// And initialize it with the types default (e.g. black for color or 0 for number)
const defaultObject = new other[i + 1]().toArray()
// Than we fix the values array
const toDelete = values[i + 2] + 3
values.splice(
i,
toDelete,
other[i],
other[i + 1],
other[i + 2],
...defaultObject
)
i += values[i + 2] + 2
}
return this
}
init(objOrArr) {
this.values = []
if (Array.isArray(objOrArr)) {
this.values = objOrArr.slice()
return
}
objOrArr = objOrArr || {}
const entries = []
for (const i in objOrArr) {
const Type = getClassForType(objOrArr[i])
const val = new Type(objOrArr[i]).toArray()
entries.push([i, Type, val.length, ...val])
}
entries.sort(sortByKey)
this.values = entries.reduce((last, curr) => last.concat(curr), [])
return this
}
toArray() {
return this.values
}
valueOf() {
const obj = {}
const arr = this.values
// for (var i = 0, len = arr.length; i < len; i += 2) {
while (arr.length) {
const key = arr.shift()
const Type = arr.shift()
const num = arr.shift()
const values = arr.splice(0, num)
obj[key] = new Type(values) // .valueOf()
}
return obj
}
}
const morphableTypes = [NonMorphable, TransformBag, ObjectBag]
export function registerMorphableType(type = []) {
morphableTypes.push(...[].concat(type))
}
export function makeMorphable() {
extend(morphableTypes, {
to(val) {
return new Morphable()
.type(this.constructor)
.from(this.toArray()) // this.valueOf())
.to(val)
},
fromArray(arr) {
this.init(arr)
return this
},
toConsumable() {
return this.toArray()
},
morph(from, to, pos, stepper, context) {
const mapper = function (i, index) {
return stepper.step(i, to[index], pos, context[index], context)
}
return this.fromArray(from.map(mapper))
}
})
}

View File

@@ -0,0 +1,62 @@
export default class Queue {
constructor() {
this._first = null
this._last = null
}
// Shows us the first item in the list
first() {
return this._first && this._first.value
}
// Shows us the last item in the list
last() {
return this._last && this._last.value
}
push(value) {
// An item stores an id and the provided value
const item =
typeof value.next !== 'undefined'
? value
: { value: value, next: null, prev: null }
// Deal with the queue being empty or populated
if (this._last) {
item.prev = this._last
this._last.next = item
this._last = item
} else {
this._last = item
this._first = item
}
// Return the current item
return item
}
// Removes the item that was returned from the push
remove(item) {
// Relink the previous item
if (item.prev) item.prev.next = item.next
if (item.next) item.next.prev = item.prev
if (item === this._last) this._last = item.prev
if (item === this._first) this._first = item.next
// Invalidate item
item.prev = null
item.next = null
}
shift() {
// Check if we have a value
const remove = this._first
if (!remove) return null
// If we do, remove it and relink things
this._first = remove.next
if (this._first) this._first.prev = null
this._last = this._first ? this._last : null
return remove.value
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,350 @@
import { globals } from '../utils/window.js'
import { registerMethods } from '../utils/methods.js'
import Animator from './Animator.js'
import EventTarget from '../types/EventTarget.js'
const makeSchedule = function (runnerInfo) {
const start = runnerInfo.start
const duration = runnerInfo.runner.duration()
const end = start + duration
return {
start: start,
duration: duration,
end: end,
runner: runnerInfo.runner
}
}
const defaultSource = function () {
const w = globals.window
return (w.performance || w.Date).now()
}
export default class Timeline extends EventTarget {
// Construct a new timeline on the given element
constructor(timeSource = defaultSource) {
super()
this._timeSource = timeSource
// terminate resets all variables to their initial state
this.terminate()
}
active() {
return !!this._nextFrame
}
finish() {
// Go to end and pause
this.time(this.getEndTimeOfTimeline() + 1)
return this.pause()
}
// Calculates the end of the timeline
getEndTime() {
const lastRunnerInfo = this.getLastRunnerInfo()
const lastDuration = lastRunnerInfo ? lastRunnerInfo.runner.duration() : 0
const lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : this._time
return lastStartTime + lastDuration
}
getEndTimeOfTimeline() {
const endTimes = this._runners.map((i) => i.start + i.runner.duration())
return Math.max(0, ...endTimes)
}
getLastRunnerInfo() {
return this.getRunnerInfoById(this._lastRunnerId)
}
getRunnerInfoById(id) {
return this._runners[this._runnerIds.indexOf(id)] || null
}
pause() {
this._paused = true
return this._continue()
}
persist(dtOrForever) {
if (dtOrForever == null) return this._persist
this._persist = dtOrForever
return this
}
play() {
// Now make sure we are not paused and continue the animation
this._paused = false
return this.updateTime()._continue()
}
reverse(yes) {
const currentSpeed = this.speed()
if (yes == null) return this.speed(-currentSpeed)
const positive = Math.abs(currentSpeed)
return this.speed(yes ? -positive : positive)
}
// schedules a runner on the timeline
schedule(runner, delay, when) {
if (runner == null) {
return this._runners.map(makeSchedule)
}
// The start time for the next animation can either be given explicitly,
// derived from the current timeline time or it can be relative to the
// last start time to chain animations directly
let absoluteStartTime = 0
const endTime = this.getEndTime()
delay = delay || 0
// Work out when to start the animation
if (when == null || when === 'last' || when === 'after') {
// Take the last time and increment
absoluteStartTime = endTime
} else if (when === 'absolute' || when === 'start') {
absoluteStartTime = delay
delay = 0
} else if (when === 'now') {
absoluteStartTime = this._time
} else if (when === 'relative') {
const runnerInfo = this.getRunnerInfoById(runner.id)
if (runnerInfo) {
absoluteStartTime = runnerInfo.start + delay
delay = 0
}
} else if (when === 'with-last') {
const lastRunnerInfo = this.getLastRunnerInfo()
const lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : this._time
absoluteStartTime = lastStartTime
} else {
throw new Error('Invalid value for the "when" parameter')
}
// Manage runner
runner.unschedule()
runner.timeline(this)
const persist = runner.persist()
const runnerInfo = {
persist: persist === null ? this._persist : persist,
start: absoluteStartTime + delay,
runner
}
this._lastRunnerId = runner.id
this._runners.push(runnerInfo)
this._runners.sort((a, b) => a.start - b.start)
this._runnerIds = this._runners.map((info) => info.runner.id)
this.updateTime()._continue()
return this
}
seek(dt) {
return this.time(this._time + dt)
}
source(fn) {
if (fn == null) return this._timeSource
this._timeSource = fn
return this
}
speed(speed) {
if (speed == null) return this._speed
this._speed = speed
return this
}
stop() {
// Go to start and pause
this.time(0)
return this.pause()
}
time(time) {
if (time == null) return this._time
this._time = time
return this._continue(true)
}
// Remove the runner from this timeline
unschedule(runner) {
const index = this._runnerIds.indexOf(runner.id)
if (index < 0) return this
this._runners.splice(index, 1)
this._runnerIds.splice(index, 1)
runner.timeline(null)
return this
}
// Makes sure, that after pausing the time doesn't jump
updateTime() {
if (!this.active()) {
this._lastSourceTime = this._timeSource()
}
return this
}
// Checks if we are running and continues the animation
_continue(immediateStep = false) {
Animator.cancelFrame(this._nextFrame)
this._nextFrame = null
if (immediateStep) return this._stepImmediate()
if (this._paused) return this
this._nextFrame = Animator.frame(this._step)
return this
}
_stepFn(immediateStep = false) {
// Get the time delta from the last time and update the time
const time = this._timeSource()
let dtSource = time - this._lastSourceTime
if (immediateStep) dtSource = 0
const dtTime = this._speed * dtSource + (this._time - this._lastStepTime)
this._lastSourceTime = time
// Only update the time if we use the timeSource.
// Otherwise use the current time
if (!immediateStep) {
// Update the time
this._time += dtTime
this._time = this._time < 0 ? 0 : this._time
}
this._lastStepTime = this._time
this.fire('time', this._time)
// This is for the case that the timeline was seeked so that the time
// is now before the startTime of the runner. That is why we need to set
// the runner to position 0
// FIXME:
// However, resetting in insertion order leads to bugs. Considering the case,
// where 2 runners change the same attribute but in different times,
// resetting both of them will lead to the case where the later defined
// runner always wins the reset even if the other runner started earlier
// and therefore should win the attribute battle
// this can be solved by resetting them backwards
for (let k = this._runners.length; k--; ) {
// Get and run the current runner and ignore it if its inactive
const runnerInfo = this._runners[k]
const runner = runnerInfo.runner
// Make sure that we give the actual difference
// between runner start time and now
const dtToStart = this._time - runnerInfo.start
// Dont run runner if not started yet
// and try to reset it
if (dtToStart <= 0) {
runner.reset()
}
}
// Run all of the runners directly
let runnersLeft = false
for (let i = 0, len = this._runners.length; i < len; i++) {
// Get and run the current runner and ignore it if its inactive
const runnerInfo = this._runners[i]
const runner = runnerInfo.runner
let dt = dtTime
// Make sure that we give the actual difference
// between runner start time and now
const dtToStart = this._time - runnerInfo.start
// Dont run runner if not started yet
if (dtToStart <= 0) {
runnersLeft = true
continue
} else if (dtToStart < dt) {
// Adjust dt to make sure that animation is on point
dt = dtToStart
}
if (!runner.active()) continue
// If this runner is still going, signal that we need another animation
// frame, otherwise, remove the completed runner
const finished = runner.step(dt).done
if (!finished) {
runnersLeft = true
// continue
} else if (runnerInfo.persist !== true) {
// runner is finished. And runner might get removed
const endTime = runner.duration() - runner.time() + this._time
if (endTime + runnerInfo.persist < this._time) {
// Delete runner and correct index
runner.unschedule()
--i
--len
}
}
}
// Basically: we continue when there are runners right from us in time
// when -->, and when runners are left from us when <--
if (
(runnersLeft && !(this._speed < 0 && this._time === 0)) ||
(this._runnerIds.length && this._speed < 0 && this._time > 0)
) {
this._continue()
} else {
this.pause()
this.fire('finished')
}
return this
}
terminate() {
// cleanup memory
// Store the timing variables
this._startTime = 0
this._speed = 1.0
// Determines how long a runner is hold in memory. Can be a dt or true/false
this._persist = 0
// Keep track of the running animations and their starting parameters
this._nextFrame = null
this._paused = true
this._runners = []
this._runnerIds = []
this._lastRunnerId = -1
this._time = 0
this._lastSourceTime = 0
this._lastStepTime = 0
// Make sure that step is always called in class context
this._step = this._stepFn.bind(this, false)
this._stepImmediate = this._stepFn.bind(this, true)
}
}
registerMethods({
Element: {
timeline: function (timeline) {
if (timeline == null) {
this._timeline = this._timeline || new Timeline()
return this._timeline
} else {
this._timeline = timeline
return this
}
}
}
})

View File

@@ -0,0 +1,83 @@
import {
nodeOrNew,
register,
wrapWithAttrCheck,
extend
} from '../utils/adopter.js'
import { registerMethods } from '../utils/methods.js'
import { xlink } from '../modules/core/namespaces.js'
import Container from './Container.js'
import * as containerGeometry from '../modules/core/containerGeometry.js'
export default class A extends Container {
constructor(node, attrs = node) {
super(nodeOrNew('a', node), attrs)
}
// Link target attribute
target(target) {
return this.attr('target', target)
}
// Link url
to(url) {
return this.attr('href', url, xlink)
}
}
extend(A, containerGeometry)
registerMethods({
Container: {
// Create a hyperlink element
link: wrapWithAttrCheck(function (url) {
return this.put(new A()).to(url)
})
},
Element: {
unlink() {
const link = this.linker()
if (!link) return this
const parent = link.parent()
if (!parent) {
return this.remove()
}
const index = parent.index(link)
parent.add(this, index)
link.remove()
return this
},
linkTo(url) {
// reuse old link if possible
let link = this.linker()
if (!link) {
link = new A()
this.wrap(link)
}
if (typeof url === 'function') {
url.call(link, link)
} else {
link.to(url)
}
return this
},
linker() {
const link = this.parent()
if (link && link.node.nodeName.toLowerCase() === 'a') {
return link
}
return null
}
}
})
register(A, 'A')

View File

@@ -0,0 +1,47 @@
import { cx, cy, height, width, x, y } from '../modules/core/circled.js'
import {
extend,
nodeOrNew,
register,
wrapWithAttrCheck
} from '../utils/adopter.js'
import { registerMethods } from '../utils/methods.js'
import SVGNumber from '../types/SVGNumber.js'
import Shape from './Shape.js'
export default class Circle extends Shape {
constructor(node, attrs = node) {
super(nodeOrNew('circle', node), attrs)
}
radius(r) {
return this.attr('r', r)
}
// Radius x value
rx(rx) {
return this.attr('r', rx)
}
// Alias radius x value
ry(ry) {
return this.rx(ry)
}
size(size) {
return this.radius(new SVGNumber(size).divide(2))
}
}
extend(Circle, { x, y, cx, cy, width, height })
registerMethods({
Container: {
// Create circle element
circle: wrapWithAttrCheck(function (size = 0) {
return this.put(new Circle()).size(size).move(0, 0)
})
}
})
register(Circle, 'Circle')

View File

@@ -0,0 +1,58 @@
import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js'
import { registerMethods } from '../utils/methods.js'
import Container from './Container.js'
import baseFind from '../modules/core/selector.js'
export default class ClipPath extends Container {
constructor(node, attrs = node) {
super(nodeOrNew('clipPath', node), attrs)
}
// Unclip all clipped elements and remove itself
remove() {
// unclip all targets
this.targets().forEach(function (el) {
el.unclip()
})
// remove clipPath from parent
return super.remove()
}
targets() {
return baseFind('svg [clip-path*=' + this.id() + ']')
}
}
registerMethods({
Container: {
// Create clipping element
clip: wrapWithAttrCheck(function () {
return this.defs().put(new ClipPath())
})
},
Element: {
// Distribute clipPath to svg element
clipper() {
return this.reference('clip-path')
},
clipWith(element) {
// use given clip or create a new one
const clipper =
element instanceof ClipPath
? element
: this.parent().clip().add(element)
// apply mask
return this.attr('clip-path', 'url(#' + clipper.id() + ')')
},
// Unclip element
unclip() {
return this.attr('clip-path', null)
}
}
})
register(ClipPath, 'ClipPath')

View File

@@ -0,0 +1,28 @@
import { register } from '../utils/adopter.js'
import Element from './Element.js'
export default class Container extends Element {
flatten() {
this.each(function () {
if (this instanceof Container) {
return this.flatten().ungroup()
}
})
return this
}
ungroup(parent = this.parent(), index = parent.index(this)) {
// when parent != this, we want append all elements to the end
index = index === -1 ? parent.children().length : index
this.each(function (i, children) {
// reverse each
return children[children.length - i - 1].toParent(parent, index)
})
return this.remove()
}
}
register(Container, 'Container')

View File

@@ -0,0 +1,18 @@
import { nodeOrNew, register } from '../utils/adopter.js'
import Container from './Container.js'
export default class Defs extends Container {
constructor(node, attrs = node) {
super(nodeOrNew('defs', node), attrs)
}
flatten() {
return this
}
ungroup() {
return this
}
}
register(Defs, 'Defs')

View File

@@ -0,0 +1,358 @@
import {
adopt,
assignNewId,
eid,
extend,
makeInstance,
create,
register
} from '../utils/adopter.js'
import { find, findOne } from '../modules/core/selector.js'
import { globals } from '../utils/window.js'
import { map } from '../utils/utils.js'
import { svg, html } from '../modules/core/namespaces.js'
import EventTarget from '../types/EventTarget.js'
import List from '../types/List.js'
import attr from '../modules/core/attr.js'
export default class Dom extends EventTarget {
constructor(node, attrs) {
super()
this.node = node
this.type = node.nodeName
if (attrs && node !== attrs) {
this.attr(attrs)
}
}
// Add given element at a position
add(element, i) {
element = makeInstance(element)
// If non-root svg nodes are added we have to remove their namespaces
if (
element.removeNamespace &&
this.node instanceof globals.window.SVGElement
) {
element.removeNamespace()
}
if (i == null) {
this.node.appendChild(element.node)
} else if (element.node !== this.node.childNodes[i]) {
this.node.insertBefore(element.node, this.node.childNodes[i])
}
return this
}
// Add element to given container and return self
addTo(parent, i) {
return makeInstance(parent).put(this, i)
}
// Returns all child elements
children() {
return new List(
map(this.node.children, function (node) {
return adopt(node)
})
)
}
// Remove all elements in this container
clear() {
// remove children
while (this.node.hasChildNodes()) {
this.node.removeChild(this.node.lastChild)
}
return this
}
// Clone element
clone(deep = true, assignNewIds = true) {
// write dom data to the dom so the clone can pickup the data
this.writeDataToDom()
// clone element
let nodeClone = this.node.cloneNode(deep)
if (assignNewIds) {
// assign new id
nodeClone = assignNewId(nodeClone)
}
return new this.constructor(nodeClone)
}
// Iterates over all children and invokes a given block
each(block, deep) {
const children = this.children()
let i, il
for (i = 0, il = children.length; i < il; i++) {
block.apply(children[i], [i, children])
if (deep) {
children[i].each(block, deep)
}
}
return this
}
element(nodeName, attrs) {
return this.put(new Dom(create(nodeName), attrs))
}
// Get first child
first() {
return adopt(this.node.firstChild)
}
// Get a element at the given index
get(i) {
return adopt(this.node.childNodes[i])
}
getEventHolder() {
return this.node
}
getEventTarget() {
return this.node
}
// Checks if the given element is a child
has(element) {
return this.index(element) >= 0
}
html(htmlOrFn, outerHTML) {
return this.xml(htmlOrFn, outerHTML, html)
}
// Get / set id
id(id) {
// generate new id if no id set
if (typeof id === 'undefined' && !this.node.id) {
this.node.id = eid(this.type)
}
// don't set directly with this.node.id to make `null` work correctly
return this.attr('id', id)
}
// Gets index of given element
index(element) {
return [].slice.call(this.node.childNodes).indexOf(element.node)
}
// Get the last child
last() {
return adopt(this.node.lastChild)
}
// matches the element vs a css selector
matches(selector) {
const el = this.node
const matcher =
el.matches ||
el.matchesSelector ||
el.msMatchesSelector ||
el.mozMatchesSelector ||
el.webkitMatchesSelector ||
el.oMatchesSelector ||
null
return matcher && matcher.call(el, selector)
}
// Returns the parent element instance
parent(type) {
let parent = this
// check for parent
if (!parent.node.parentNode) return null
// get parent element
parent = adopt(parent.node.parentNode)
if (!type) return parent
// loop through ancestors if type is given
do {
if (
typeof type === 'string' ? parent.matches(type) : parent instanceof type
)
return parent
} while ((parent = adopt(parent.node.parentNode)))
return parent
}
// Basically does the same as `add()` but returns the added element instead
put(element, i) {
element = makeInstance(element)
this.add(element, i)
return element
}
// Add element to given container and return container
putIn(parent, i) {
return makeInstance(parent).add(this, i)
}
// Remove element
remove() {
if (this.parent()) {
this.parent().removeElement(this)
}
return this
}
// Remove a given child
removeElement(element) {
this.node.removeChild(element.node)
return this
}
// Replace this with element
replace(element) {
element = makeInstance(element)
if (this.node.parentNode) {
this.node.parentNode.replaceChild(element.node, this.node)
}
return element
}
round(precision = 2, map = null) {
const factor = 10 ** precision
const attrs = this.attr(map)
for (const i in attrs) {
if (typeof attrs[i] === 'number') {
attrs[i] = Math.round(attrs[i] * factor) / factor
}
}
this.attr(attrs)
return this
}
// Import / Export raw svg
svg(svgOrFn, outerSVG) {
return this.xml(svgOrFn, outerSVG, svg)
}
// Return id on string conversion
toString() {
return this.id()
}
words(text) {
// This is faster than removing all children and adding a new one
this.node.textContent = text
return this
}
wrap(node) {
const parent = this.parent()
if (!parent) {
return this.addTo(node)
}
const position = parent.index(this)
return parent.put(node, position).put(this)
}
// write svgjs data to the dom
writeDataToDom() {
// dump variables recursively
this.each(function () {
this.writeDataToDom()
})
return this
}
// Import / Export raw svg
xml(xmlOrFn, outerXML, ns) {
if (typeof xmlOrFn === 'boolean') {
ns = outerXML
outerXML = xmlOrFn
xmlOrFn = null
}
// act as getter if no svg string is given
if (xmlOrFn == null || typeof xmlOrFn === 'function') {
// The default for exports is, that the outerNode is included
outerXML = outerXML == null ? true : outerXML
// write svgjs data to the dom
this.writeDataToDom()
let current = this
// An export modifier was passed
if (xmlOrFn != null) {
current = adopt(current.node.cloneNode(true))
// If the user wants outerHTML we need to process this node, too
if (outerXML) {
const result = xmlOrFn(current)
current = result || current
// The user does not want this node? Well, then he gets nothing
if (result === false) return ''
}
// Deep loop through all children and apply modifier
current.each(function () {
const result = xmlOrFn(this)
const _this = result || this
// If modifier returns false, discard node
if (result === false) {
this.remove()
// If modifier returns new node, use it
} else if (result && this !== _this) {
this.replace(_this)
}
}, true)
}
// Return outer or inner content
return outerXML ? current.node.outerHTML : current.node.innerHTML
}
// Act as setter if we got a string
// The default for import is, that the current node is not replaced
outerXML = outerXML == null ? false : outerXML
// Create temporary holder
const well = create('wrapper', ns)
const fragment = globals.document.createDocumentFragment()
// Dump raw svg
well.innerHTML = xmlOrFn
// Transplant nodes into the fragment
for (let len = well.children.length; len--; ) {
fragment.appendChild(well.firstElementChild)
}
const parent = this.parent()
// Add the whole fragment at once
return outerXML ? this.replace(fragment) && parent : this.add(fragment)
}
}
extend(Dom, { attr, find, findOne })
register(Dom, 'Dom')

View File

@@ -0,0 +1,182 @@
import { bbox, rbox, inside } from '../types/Box.js'
import { ctm, screenCTM } from '../types/Matrix.js'
import {
extend,
getClass,
makeInstance,
register,
root
} from '../utils/adopter.js'
import { globals } from '../utils/window.js'
import { point } from '../types/Point.js'
import { proportionalSize, writeDataToDom } from '../utils/utils.js'
import { reference } from '../modules/core/regex.js'
import Dom from './Dom.js'
import List from '../types/List.js'
import SVGNumber from '../types/SVGNumber.js'
export default class Element extends Dom {
constructor(node, attrs) {
super(node, attrs)
// initialize data object
this.dom = {}
// create circular reference
this.node.instance = this
if (node.hasAttribute('data-svgjs') || node.hasAttribute('svgjs:data')) {
// pull svgjs data from the dom (getAttributeNS doesn't work in html5)
this.setData(
JSON.parse(node.getAttribute('data-svgjs')) ??
JSON.parse(node.getAttribute('svgjs:data')) ??
{}
)
}
}
// Move element by its center
center(x, y) {
return this.cx(x).cy(y)
}
// Move by center over x-axis
cx(x) {
return x == null
? this.x() + this.width() / 2
: this.x(x - this.width() / 2)
}
// Move by center over y-axis
cy(y) {
return y == null
? this.y() + this.height() / 2
: this.y(y - this.height() / 2)
}
// Get defs
defs() {
const root = this.root()
return root && root.defs()
}
// Relative move over x and y axes
dmove(x, y) {
return this.dx(x).dy(y)
}
// Relative move over x axis
dx(x = 0) {
return this.x(new SVGNumber(x).plus(this.x()))
}
// Relative move over y axis
dy(y = 0) {
return this.y(new SVGNumber(y).plus(this.y()))
}
getEventHolder() {
return this
}
// Set height of element
height(height) {
return this.attr('height', height)
}
// Move element to given x and y values
move(x, y) {
return this.x(x).y(y)
}
// return array of all ancestors of given type up to the root svg
parents(until = this.root()) {
const isSelector = typeof until === 'string'
if (!isSelector) {
until = makeInstance(until)
}
const parents = new List()
let parent = this
while (
(parent = parent.parent()) &&
parent.node !== globals.document &&
parent.nodeName !== '#document-fragment'
) {
parents.push(parent)
if (!isSelector && parent.node === until.node) {
break
}
if (isSelector && parent.matches(until)) {
break
}
if (parent.node === this.root().node) {
// We worked our way to the root and didn't match `until`
return null
}
}
return parents
}
// Get referenced element form attribute value
reference(attr) {
attr = this.attr(attr)
if (!attr) return null
const m = (attr + '').match(reference)
return m ? makeInstance(m[1]) : null
}
// Get parent document
root() {
const p = this.parent(getClass(root))
return p && p.root()
}
// set given data to the elements data property
setData(o) {
this.dom = o
return this
}
// Set element size to given width and height
size(width, height) {
const p = proportionalSize(this, width, height)
return this.width(new SVGNumber(p.width)).height(new SVGNumber(p.height))
}
// Set width of element
width(width) {
return this.attr('width', width)
}
// write svgjs data to the dom
writeDataToDom() {
writeDataToDom(this, this.dom)
return super.writeDataToDom()
}
// Move over x-axis
x(x) {
return this.attr('x', x)
}
// Move over y-axis
y(y) {
return this.attr('y', y)
}
}
extend(Element, {
bbox,
rbox,
inside,
point,
ctm,
screenCTM
})
register(Element, 'Element')

View File

@@ -0,0 +1,36 @@
import {
extend,
nodeOrNew,
register,
wrapWithAttrCheck
} from '../utils/adopter.js'
import { proportionalSize } from '../utils/utils.js'
import { registerMethods } from '../utils/methods.js'
import SVGNumber from '../types/SVGNumber.js'
import Shape from './Shape.js'
import * as circled from '../modules/core/circled.js'
export default class Ellipse extends Shape {
constructor(node, attrs = node) {
super(nodeOrNew('ellipse', node), attrs)
}
size(width, height) {
const p = proportionalSize(this, width, height)
return this.rx(new SVGNumber(p.width).divide(2)).ry(
new SVGNumber(p.height).divide(2)
)
}
}
extend(Ellipse, circled)
registerMethods('Container', {
// Create an ellipse
ellipse: wrapWithAttrCheck(function (width = 0, height = width) {
return this.put(new Ellipse()).size(width, height).move(0, 0)
})
})
register(Ellipse, 'Ellipse')

View File

@@ -0,0 +1,19 @@
import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js'
import { registerMethods } from '../utils/methods.js'
import Element from './Element.js'
export default class ForeignObject extends Element {
constructor(node, attrs = node) {
super(nodeOrNew('foreignObject', node), attrs)
}
}
registerMethods({
Container: {
foreignObject: wrapWithAttrCheck(function (width, height) {
return this.put(new ForeignObject()).size(width, height)
})
}
})
register(ForeignObject, 'ForeignObject')

View File

@@ -0,0 +1,34 @@
import Dom from './Dom.js'
import { globals } from '../utils/window.js'
import { register, create } from '../utils/adopter.js'
class Fragment extends Dom {
constructor(node = globals.document.createDocumentFragment()) {
super(node)
}
// Import / Export raw xml
xml(xmlOrFn, outerXML, ns) {
if (typeof xmlOrFn === 'boolean') {
ns = outerXML
outerXML = xmlOrFn
xmlOrFn = null
}
// because this is a fragment we have to put all elements into a wrapper first
// before we can get the innerXML from it
if (xmlOrFn == null || typeof xmlOrFn === 'function') {
const wrapper = new Dom(create('wrapper', ns))
wrapper.add(this.node.cloneNode(true))
return wrapper.xml(false, ns)
}
// Act as setter if we got a string
return super.xml(xmlOrFn, false, ns)
}
}
register(Fragment, 'Fragment')
export default Fragment

View File

@@ -0,0 +1,28 @@
import {
nodeOrNew,
register,
wrapWithAttrCheck,
extend
} from '../utils/adopter.js'
import { registerMethods } from '../utils/methods.js'
import Container from './Container.js'
import * as containerGeometry from '../modules/core/containerGeometry.js'
export default class G extends Container {
constructor(node, attrs = node) {
super(nodeOrNew('g', node), attrs)
}
}
extend(G, containerGeometry)
registerMethods({
Container: {
// Create a group element
group: wrapWithAttrCheck(function () {
return this.put(new G())
})
}
})
register(G, 'G')

View File

@@ -0,0 +1,76 @@
import {
extend,
nodeOrNew,
register,
wrapWithAttrCheck
} from '../utils/adopter.js'
import { registerMethods } from '../utils/methods.js'
import Box from '../types/Box.js'
import Container from './Container.js'
import baseFind from '../modules/core/selector.js'
import * as gradiented from '../modules/core/gradiented.js'
export default class Gradient extends Container {
constructor(type, attrs) {
super(
nodeOrNew(type + 'Gradient', typeof type === 'string' ? null : type),
attrs
)
}
// custom attr to handle transform
attr(a, b, c) {
if (a === 'transform') a = 'gradientTransform'
return super.attr(a, b, c)
}
bbox() {
return new Box()
}
targets() {
return baseFind('svg [fill*=' + this.id() + ']')
}
// Alias string conversion to fill
toString() {
return this.url()
}
// Update gradient
update(block) {
// remove all stops
this.clear()
// invoke passed block
if (typeof block === 'function') {
block.call(this, this)
}
return this
}
// Return the fill id
url() {
return 'url(#' + this.id() + ')'
}
}
extend(Gradient, gradiented)
registerMethods({
Container: {
// Create gradient element in defs
gradient(...args) {
return this.defs().gradient(...args)
}
},
// define gradient
Defs: {
gradient: wrapWithAttrCheck(function (type, block) {
return this.put(new Gradient(type)).update(block)
})
}
})
register(Gradient, 'Gradient')

View File

@@ -0,0 +1,85 @@
import { isImage } from '../modules/core/regex.js'
import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js'
import { off, on } from '../modules/core/event.js'
import { registerAttrHook } from '../modules/core/attr.js'
import { registerMethods } from '../utils/methods.js'
import { xlink } from '../modules/core/namespaces.js'
import Pattern from './Pattern.js'
import Shape from './Shape.js'
import { globals } from '../utils/window.js'
export default class Image extends Shape {
constructor(node, attrs = node) {
super(nodeOrNew('image', node), attrs)
}
// (re)load image
load(url, callback) {
if (!url) return this
const img = new globals.window.Image()
on(
img,
'load',
function (e) {
const p = this.parent(Pattern)
// ensure image size
if (this.width() === 0 && this.height() === 0) {
this.size(img.width, img.height)
}
if (p instanceof Pattern) {
// ensure pattern size if not set
if (p.width() === 0 && p.height() === 0) {
p.size(this.width(), this.height())
}
}
if (typeof callback === 'function') {
callback.call(this, e)
}
},
this
)
on(img, 'load error', function () {
// dont forget to unbind memory leaking events
off(img)
})
return this.attr('href', (img.src = url), xlink)
}
}
registerAttrHook(function (attr, val, _this) {
// convert image fill and stroke to patterns
if (attr === 'fill' || attr === 'stroke') {
if (isImage.test(val)) {
val = _this.root().defs().image(val)
}
}
if (val instanceof Image) {
val = _this
.root()
.defs()
.pattern(0, 0, (pattern) => {
pattern.add(val)
})
}
return val
})
registerMethods({
Container: {
// create image element, load image and set its size
image: wrapWithAttrCheck(function (source, callback) {
return this.put(new Image()).size(0, 0).load(source, callback)
})
}
})
register(Image, 'Image')

View File

@@ -0,0 +1,68 @@
import {
extend,
nodeOrNew,
register,
wrapWithAttrCheck
} from '../utils/adopter.js'
import { proportionalSize } from '../utils/utils.js'
import { registerMethods } from '../utils/methods.js'
import PointArray from '../types/PointArray.js'
import Shape from './Shape.js'
import * as pointed from '../modules/core/pointed.js'
export default class Line extends Shape {
// Initialize node
constructor(node, attrs = node) {
super(nodeOrNew('line', node), attrs)
}
// Get array
array() {
return new PointArray([
[this.attr('x1'), this.attr('y1')],
[this.attr('x2'), this.attr('y2')]
])
}
// Move by left top corner
move(x, y) {
return this.attr(this.array().move(x, y).toLine())
}
// Overwrite native plot() method
plot(x1, y1, x2, y2) {
if (x1 == null) {
return this.array()
} else if (typeof y1 !== 'undefined') {
x1 = { x1, y1, x2, y2 }
} else {
x1 = new PointArray(x1).toLine()
}
return this.attr(x1)
}
// Set element size to given width and height
size(width, height) {
const p = proportionalSize(this, width, height)
return this.attr(this.array().size(p.width, p.height).toLine())
}
}
extend(Line, pointed)
registerMethods({
Container: {
// Create a line element
line: wrapWithAttrCheck(function (...args) {
// make sure plot is called as a setter
// x1 is not necessarily a number, it can also be an array, a string and a PointArray
return Line.prototype.plot.apply(
this.put(new Line()),
args[0] != null ? args : [0, 0, 0, 0]
)
})
}
})
register(Line, 'Line')

View File

@@ -0,0 +1,88 @@
import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js'
import { registerMethods } from '../utils/methods.js'
import Container from './Container.js'
export default class Marker extends Container {
// Initialize node
constructor(node, attrs = node) {
super(nodeOrNew('marker', node), attrs)
}
// Set height of element
height(height) {
return this.attr('markerHeight', height)
}
orient(orient) {
return this.attr('orient', orient)
}
// Set marker refX and refY
ref(x, y) {
return this.attr('refX', x).attr('refY', y)
}
// Return the fill id
toString() {
return 'url(#' + this.id() + ')'
}
// Update marker
update(block) {
// remove all content
this.clear()
// invoke passed block
if (typeof block === 'function') {
block.call(this, this)
}
return this
}
// Set width of element
width(width) {
return this.attr('markerWidth', width)
}
}
registerMethods({
Container: {
marker(...args) {
// Create marker element in defs
return this.defs().marker(...args)
}
},
Defs: {
// Create marker
marker: wrapWithAttrCheck(function (width, height, block) {
// Set default viewbox to match the width and height, set ref to cx and cy and set orient to auto
return this.put(new Marker())
.size(width, height)
.ref(width / 2, height / 2)
.viewbox(0, 0, width, height)
.attr('orient', 'auto')
.update(block)
})
},
marker: {
// Create and attach markers
marker(marker, width, height, block) {
let attr = ['marker']
// Build attribute name
if (marker !== 'all') attr.push(marker)
attr = attr.join('-')
// Set marker attribute
marker =
arguments[1] instanceof Marker
? arguments[1]
: this.defs().marker(width, height, block)
return this.attr(attr, marker)
}
}
})
register(Marker, 'Marker')

View File

@@ -0,0 +1,56 @@
import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js'
import { registerMethods } from '../utils/methods.js'
import Container from './Container.js'
import baseFind from '../modules/core/selector.js'
export default class Mask extends Container {
// Initialize node
constructor(node, attrs = node) {
super(nodeOrNew('mask', node), attrs)
}
// Unmask all masked elements and remove itself
remove() {
// unmask all targets
this.targets().forEach(function (el) {
el.unmask()
})
// remove mask from parent
return super.remove()
}
targets() {
return baseFind('svg [mask*=' + this.id() + ']')
}
}
registerMethods({
Container: {
mask: wrapWithAttrCheck(function () {
return this.defs().put(new Mask())
})
},
Element: {
// Distribute mask to svg element
masker() {
return this.reference('mask')
},
maskWith(element) {
// use given mask or create a new one
const masker =
element instanceof Mask ? element : this.parent().mask().add(element)
// apply mask
return this.attr('mask', 'url(#' + masker.id() + ')')
},
// Unmask element
unmask() {
return this.attr('mask', null)
}
}
})
register(Mask, 'Mask')

View File

@@ -0,0 +1,84 @@
import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js'
import { proportionalSize } from '../utils/utils.js'
import { registerMethods } from '../utils/methods.js'
import PathArray from '../types/PathArray.js'
import Shape from './Shape.js'
export default class Path extends Shape {
// Initialize node
constructor(node, attrs = node) {
super(nodeOrNew('path', node), attrs)
}
// Get array
array() {
return this._array || (this._array = new PathArray(this.attr('d')))
}
// Clear array cache
clear() {
delete this._array
return this
}
// Set height of element
height(height) {
return height == null
? this.bbox().height
: this.size(this.bbox().width, height)
}
// Move by left top corner
move(x, y) {
return this.attr('d', this.array().move(x, y))
}
// Plot new path
plot(d) {
return d == null
? this.array()
: this.clear().attr(
'd',
typeof d === 'string' ? d : (this._array = new PathArray(d))
)
}
// Set element size to given width and height
size(width, height) {
const p = proportionalSize(this, width, height)
return this.attr('d', this.array().size(p.width, p.height))
}
// Set width of element
width(width) {
return width == null
? this.bbox().width
: this.size(width, this.bbox().height)
}
// Move by left top corner over x-axis
x(x) {
return x == null ? this.bbox().x : this.move(x, this.bbox().y)
}
// Move by left top corner over y-axis
y(y) {
return y == null ? this.bbox().y : this.move(this.bbox().x, y)
}
}
// Define morphable array
Path.prototype.MorphArray = PathArray
// Add parent method
registerMethods({
Container: {
// Create a wrapped path element
path: wrapWithAttrCheck(function (d) {
// make sure plot is called as a setter
return this.put(new Path()).plot(d || new PathArray())
})
}
})
register(Path, 'Path')

View File

@@ -0,0 +1,71 @@
import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js'
import { registerMethods } from '../utils/methods.js'
import Box from '../types/Box.js'
import Container from './Container.js'
import baseFind from '../modules/core/selector.js'
export default class Pattern extends Container {
// Initialize node
constructor(node, attrs = node) {
super(nodeOrNew('pattern', node), attrs)
}
// custom attr to handle transform
attr(a, b, c) {
if (a === 'transform') a = 'patternTransform'
return super.attr(a, b, c)
}
bbox() {
return new Box()
}
targets() {
return baseFind('svg [fill*=' + this.id() + ']')
}
// Alias string conversion to fill
toString() {
return this.url()
}
// Update pattern by rebuilding
update(block) {
// remove content
this.clear()
// invoke passed block
if (typeof block === 'function') {
block.call(this, this)
}
return this
}
// Return the fill id
url() {
return 'url(#' + this.id() + ')'
}
}
registerMethods({
Container: {
// Create pattern element in defs
pattern(...args) {
return this.defs().pattern(...args)
}
},
Defs: {
pattern: wrapWithAttrCheck(function (width, height, block) {
return this.put(new Pattern()).update(block).attr({
x: 0,
y: 0,
width: width,
height: height,
patternUnits: 'userSpaceOnUse'
})
})
}
})
register(Pattern, 'Pattern')

View File

@@ -0,0 +1,32 @@
import {
extend,
nodeOrNew,
register,
wrapWithAttrCheck
} from '../utils/adopter.js'
import { registerMethods } from '../utils/methods.js'
import PointArray from '../types/PointArray.js'
import Shape from './Shape.js'
import * as pointed from '../modules/core/pointed.js'
import * as poly from '../modules/core/poly.js'
export default class Polygon extends Shape {
// Initialize node
constructor(node, attrs = node) {
super(nodeOrNew('polygon', node), attrs)
}
}
registerMethods({
Container: {
// Create a wrapped polygon element
polygon: wrapWithAttrCheck(function (p) {
// make sure plot is called as a setter
return this.put(new Polygon()).plot(p || new PointArray())
})
}
})
extend(Polygon, pointed)
extend(Polygon, poly)
register(Polygon, 'Polygon')

View File

@@ -0,0 +1,32 @@
import {
extend,
nodeOrNew,
register,
wrapWithAttrCheck
} from '../utils/adopter.js'
import { registerMethods } from '../utils/methods.js'
import PointArray from '../types/PointArray.js'
import Shape from './Shape.js'
import * as pointed from '../modules/core/pointed.js'
import * as poly from '../modules/core/poly.js'
export default class Polyline extends Shape {
// Initialize node
constructor(node, attrs = node) {
super(nodeOrNew('polyline', node), attrs)
}
}
registerMethods({
Container: {
// Create a wrapped polygon element
polyline: wrapWithAttrCheck(function (p) {
// make sure plot is called as a setter
return this.put(new Polyline()).plot(p || new PointArray())
})
}
})
extend(Polyline, pointed)
extend(Polyline, poly)
register(Polyline, 'Polyline')

View File

@@ -0,0 +1,29 @@
import {
extend,
nodeOrNew,
register,
wrapWithAttrCheck
} from '../utils/adopter.js'
import { registerMethods } from '../utils/methods.js'
import { rx, ry } from '../modules/core/circled.js'
import Shape from './Shape.js'
export default class Rect extends Shape {
// Initialize node
constructor(node, attrs = node) {
super(nodeOrNew('rect', node), attrs)
}
}
extend(Rect, { rx, ry })
registerMethods({
Container: {
// Create a rect element
rect: wrapWithAttrCheck(function (width, height) {
return this.put(new Rect()).size(width, height)
})
}
})
register(Rect, 'Rect')

View File

@@ -0,0 +1,6 @@
import { register } from '../utils/adopter.js'
import Element from './Element.js'
export default class Shape extends Element {}
register(Shape, 'Shape')

View File

@@ -0,0 +1,39 @@
import { nodeOrNew, register } from '../utils/adopter.js'
import Element from './Element.js'
import SVGNumber from '../types/SVGNumber.js'
import { registerMethods } from '../utils/methods.js'
export default class Stop extends Element {
constructor(node, attrs = node) {
super(nodeOrNew('stop', node), attrs)
}
// add color stops
update(o) {
if (typeof o === 'number' || o instanceof SVGNumber) {
o = {
offset: arguments[0],
color: arguments[1],
opacity: arguments[2]
}
}
// set attributes
if (o.opacity != null) this.attr('stop-opacity', o.opacity)
if (o.color != null) this.attr('stop-color', o.color)
if (o.offset != null) this.attr('offset', new SVGNumber(o.offset))
return this
}
}
registerMethods({
Gradient: {
// Add a color stop
stop: function (offset, color, opacity) {
return this.put(new Stop()).update(offset, color, opacity)
}
}
})
register(Stop, 'Stop')

View File

@@ -0,0 +1,53 @@
import { nodeOrNew, register } from '../utils/adopter.js'
import { registerMethods } from '../utils/methods.js'
import { unCamelCase } from '../utils/utils.js'
import Element from './Element.js'
function cssRule(selector, rule) {
if (!selector) return ''
if (!rule) return selector
let ret = selector + '{'
for (const i in rule) {
ret += unCamelCase(i) + ':' + rule[i] + ';'
}
ret += '}'
return ret
}
export default class Style extends Element {
constructor(node, attrs = node) {
super(nodeOrNew('style', node), attrs)
}
addText(w = '') {
this.node.textContent += w
return this
}
font(name, src, params = {}) {
return this.rule('@font-face', {
fontFamily: name,
src: src,
...params
})
}
rule(selector, obj) {
return this.addText(cssRule(selector, obj))
}
}
registerMethods('Dom', {
style(selector, obj) {
return this.put(new Style()).rule(selector, obj)
},
fontface(name, src, params) {
return this.put(new Style()).font(name, src, params)
}
})
register(Style, 'Style')

View File

@@ -0,0 +1,67 @@
import {
adopt,
nodeOrNew,
register,
wrapWithAttrCheck
} from '../utils/adopter.js'
import { svg, xlink, xmlns } from '../modules/core/namespaces.js'
import { registerMethods } from '../utils/methods.js'
import Container from './Container.js'
import Defs from './Defs.js'
import { globals } from '../utils/window.js'
export default class Svg extends Container {
constructor(node, attrs = node) {
super(nodeOrNew('svg', node), attrs)
this.namespace()
}
// Creates and returns defs element
defs() {
if (!this.isRoot()) return this.root().defs()
return adopt(this.node.querySelector('defs')) || this.put(new Defs())
}
isRoot() {
return (
!this.node.parentNode ||
(!(this.node.parentNode instanceof globals.window.SVGElement) &&
this.node.parentNode.nodeName !== '#document-fragment')
)
}
// Add namespaces
namespace() {
if (!this.isRoot()) return this.root().namespace()
return this.attr({ xmlns: svg, version: '1.1' }).attr(
'xmlns:xlink',
xlink,
xmlns
)
}
removeNamespace() {
return this.attr({ xmlns: null, version: null })
.attr('xmlns:xlink', null, xmlns)
.attr('xmlns:svgjs', null, xmlns)
}
// Check if this is a root svg
// If not, call root() from this element
root() {
if (this.isRoot()) return this
return super.root()
}
}
registerMethods({
Container: {
// Create nested svg document
nested: wrapWithAttrCheck(function () {
return this.put(new Svg())
})
}
})
register(Svg, 'Svg', true)

View File

@@ -0,0 +1,20 @@
import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js'
import { registerMethods } from '../utils/methods.js'
import Container from './Container.js'
export default class Symbol extends Container {
// Initialize node
constructor(node, attrs = node) {
super(nodeOrNew('symbol', node), attrs)
}
}
registerMethods({
Container: {
symbol: wrapWithAttrCheck(function () {
return this.put(new Symbol())
})
}
})
register(Symbol, 'Symbol')

View File

@@ -0,0 +1,158 @@
import {
adopt,
extend,
nodeOrNew,
register,
wrapWithAttrCheck
} from '../utils/adopter.js'
import { registerMethods } from '../utils/methods.js'
import SVGNumber from '../types/SVGNumber.js'
import Shape from './Shape.js'
import { globals } from '../utils/window.js'
import * as textable from '../modules/core/textable.js'
import { isDescriptive, writeDataToDom } from '../utils/utils.js'
export default class Text extends Shape {
// Initialize node
constructor(node, attrs = node) {
super(nodeOrNew('text', node), attrs)
this.dom.leading = this.dom.leading ?? new SVGNumber(1.3) // store leading value for rebuilding
this._rebuild = true // enable automatic updating of dy values
this._build = false // disable build mode for adding multiple lines
}
// Set / get leading
leading(value) {
// act as getter
if (value == null) {
return this.dom.leading
}
// act as setter
this.dom.leading = new SVGNumber(value)
return this.rebuild()
}
// Rebuild appearance type
rebuild(rebuild) {
// store new rebuild flag if given
if (typeof rebuild === 'boolean') {
this._rebuild = rebuild
}
// define position of all lines
if (this._rebuild) {
const self = this
let blankLineOffset = 0
const leading = this.dom.leading
this.each(function (i) {
if (isDescriptive(this.node)) return
const fontSize = globals.window
.getComputedStyle(this.node)
.getPropertyValue('font-size')
const dy = leading * new SVGNumber(fontSize)
if (this.dom.newLined) {
this.attr('x', self.attr('x'))
if (this.text() === '\n') {
blankLineOffset += dy
} else {
this.attr('dy', i ? dy + blankLineOffset : 0)
blankLineOffset = 0
}
}
})
this.fire('rebuild')
}
return this
}
// overwrite method from parent to set data properly
setData(o) {
this.dom = o
this.dom.leading = new SVGNumber(o.leading || 1.3)
return this
}
writeDataToDom() {
writeDataToDom(this, this.dom, { leading: 1.3 })
return this
}
// Set the text content
text(text) {
// act as getter
if (text === undefined) {
const children = this.node.childNodes
let firstLine = 0
text = ''
for (let i = 0, len = children.length; i < len; ++i) {
// skip textPaths - they are no lines
if (children[i].nodeName === 'textPath' || isDescriptive(children[i])) {
if (i === 0) firstLine = i + 1
continue
}
// add newline if its not the first child and newLined is set to true
if (
i !== firstLine &&
children[i].nodeType !== 3 &&
adopt(children[i]).dom.newLined === true
) {
text += '\n'
}
// add content of this node
text += children[i].textContent
}
return text
}
// remove existing content
this.clear().build(true)
if (typeof text === 'function') {
// call block
text.call(this, this)
} else {
// store text and make sure text is not blank
text = (text + '').split('\n')
// build new lines
for (let j = 0, jl = text.length; j < jl; j++) {
this.newLine(text[j])
}
}
// disable build mode and rebuild lines
return this.build(false).rebuild()
}
}
extend(Text, textable)
registerMethods({
Container: {
// Create text element
text: wrapWithAttrCheck(function (text = '') {
return this.put(new Text()).text(text)
}),
// Create plain text element
plain: wrapWithAttrCheck(function (text = '') {
return this.put(new Text()).plain(text)
})
}
})
register(Text, 'Text')

View File

@@ -0,0 +1,106 @@
import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js'
import { registerMethods } from '../utils/methods.js'
import { xlink } from '../modules/core/namespaces.js'
import Path from './Path.js'
import PathArray from '../types/PathArray.js'
import Text from './Text.js'
import baseFind from '../modules/core/selector.js'
export default class TextPath extends Text {
// Initialize node
constructor(node, attrs = node) {
super(nodeOrNew('textPath', node), attrs)
}
// return the array of the path track element
array() {
const track = this.track()
return track ? track.array() : null
}
// Plot path if any
plot(d) {
const track = this.track()
let pathArray = null
if (track) {
pathArray = track.plot(d)
}
return d == null ? pathArray : this
}
// Get the path element
track() {
return this.reference('href')
}
}
registerMethods({
Container: {
textPath: wrapWithAttrCheck(function (text, path) {
// Convert text to instance if needed
if (!(text instanceof Text)) {
text = this.text(text)
}
return text.path(path)
})
},
Text: {
// Create path for text to run on
path: wrapWithAttrCheck(function (track, importNodes = true) {
const textPath = new TextPath()
// if track is a path, reuse it
if (!(track instanceof Path)) {
// create path element
track = this.defs().path(track)
}
// link textPath to path and add content
textPath.attr('href', '#' + track, xlink)
// Transplant all nodes from text to textPath
let node
if (importNodes) {
while ((node = this.node.firstChild)) {
textPath.node.appendChild(node)
}
}
// add textPath element as child node and return textPath
return this.put(textPath)
}),
// Get the textPath children
textPath() {
return this.findOne('textPath')
}
},
Path: {
// creates a textPath from this path
text: wrapWithAttrCheck(function (text) {
// Convert text to instance if needed
if (!(text instanceof Text)) {
text = new Text().addTo(this.parent()).text(text)
}
// Create textPath from text and path and return
return text.path(this)
}),
targets() {
return baseFind('svg textPath').filter((node) => {
return (node.attr('href') || '').includes(this.id())
})
// Does not work in IE11. Use when IE support is dropped
// return baseFind('svg textPath[*|href*=' + this.id() + ']')
}
}
})
TextPath.prototype.MorphArray = PathArray
register(TextPath, 'TextPath')

View File

@@ -0,0 +1,95 @@
import {
extend,
nodeOrNew,
register,
wrapWithAttrCheck
} from '../utils/adopter.js'
import { globals } from '../utils/window.js'
import { registerMethods } from '../utils/methods.js'
import SVGNumber from '../types/SVGNumber.js'
import Shape from './Shape.js'
import Text from './Text.js'
import * as textable from '../modules/core/textable.js'
export default class Tspan extends Shape {
// Initialize node
constructor(node, attrs = node) {
super(nodeOrNew('tspan', node), attrs)
this._build = false // disable build mode for adding multiple lines
}
// Shortcut dx
dx(dx) {
return this.attr('dx', dx)
}
// Shortcut dy
dy(dy) {
return this.attr('dy', dy)
}
// Create new line
newLine() {
// mark new line
this.dom.newLined = true
// fetch parent
const text = this.parent()
// early return in case we are not in a text element
if (!(text instanceof Text)) {
return this
}
const i = text.index(this)
const fontSize = globals.window
.getComputedStyle(this.node)
.getPropertyValue('font-size')
const dy = text.dom.leading * new SVGNumber(fontSize)
// apply new position
return this.dy(i ? dy : 0).attr('x', text.x())
}
// Set text content
text(text) {
if (text == null)
return this.node.textContent + (this.dom.newLined ? '\n' : '')
if (typeof text === 'function') {
this.clear().build(true)
text.call(this, this)
this.build(false)
} else {
this.plain(text)
}
return this
}
}
extend(Tspan, textable)
registerMethods({
Tspan: {
tspan: wrapWithAttrCheck(function (text = '') {
const tspan = new Tspan()
// clear if build mode is disabled
if (!this._build) {
this.clear()
}
// add new tspan
return this.put(tspan).text(text)
})
},
Text: {
newLine: function (text = '') {
return this.tspan(text).newLine()
}
}
})
register(Tspan, 'Tspan')

View File

@@ -0,0 +1,27 @@
import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js'
import { registerMethods } from '../utils/methods.js'
import { xlink } from '../modules/core/namespaces.js'
import Shape from './Shape.js'
export default class Use extends Shape {
constructor(node, attrs = node) {
super(nodeOrNew('use', node), attrs)
}
// Use element as a reference
use(element, file) {
// Set lined element
return this.attr('href', (file || '') + '#' + element, xlink)
}
}
registerMethods({
Container: {
// Create a use element
use: wrapWithAttrCheck(function (element, file) {
return this.put(new Use()).use(element, file)
})
}
})
register(Use, 'Use')

170
frontend/node_modules/@svgdotjs/svg.js/src/main.js generated vendored Normal file
View File

@@ -0,0 +1,170 @@
/* Optional Modules */
import './modules/optional/arrange.js'
import './modules/optional/class.js'
import './modules/optional/css.js'
import './modules/optional/data.js'
import './modules/optional/memory.js'
import './modules/optional/sugar.js'
import './modules/optional/transform.js'
import { extend, makeInstance } from './utils/adopter.js'
import { getMethodNames, getMethodsFor } from './utils/methods.js'
import Box from './types/Box.js'
import Color from './types/Color.js'
import Container from './elements/Container.js'
import Defs from './elements/Defs.js'
import Dom from './elements/Dom.js'
import Element from './elements/Element.js'
import Ellipse from './elements/Ellipse.js'
import EventTarget from './types/EventTarget.js'
import Fragment from './elements/Fragment.js'
import Gradient from './elements/Gradient.js'
import Image from './elements/Image.js'
import Line from './elements/Line.js'
import List from './types/List.js'
import Marker from './elements/Marker.js'
import Matrix from './types/Matrix.js'
import Morphable, {
NonMorphable,
ObjectBag,
TransformBag,
makeMorphable,
registerMorphableType
} from './animation/Morphable.js'
import Path from './elements/Path.js'
import PathArray from './types/PathArray.js'
import Pattern from './elements/Pattern.js'
import PointArray from './types/PointArray.js'
import Point from './types/Point.js'
import Polygon from './elements/Polygon.js'
import Polyline from './elements/Polyline.js'
import Rect from './elements/Rect.js'
import Runner from './animation/Runner.js'
import SVGArray from './types/SVGArray.js'
import SVGNumber from './types/SVGNumber.js'
import Shape from './elements/Shape.js'
import Svg from './elements/Svg.js'
import Symbol from './elements/Symbol.js'
import Text from './elements/Text.js'
import Tspan from './elements/Tspan.js'
import * as defaults from './modules/core/defaults.js'
import * as utils from './utils/utils.js'
import * as namespaces from './modules/core/namespaces.js'
import * as regex from './modules/core/regex.js'
export {
Morphable,
registerMorphableType,
makeMorphable,
TransformBag,
ObjectBag,
NonMorphable
}
export { defaults, utils, namespaces, regex }
export const SVG = makeInstance
export { default as parser } from './modules/core/parser.js'
export { default as find } from './modules/core/selector.js'
export * from './modules/core/event.js'
export * from './utils/adopter.js'
export {
getWindow,
registerWindow,
restoreWindow,
saveWindow,
withWindow
} from './utils/window.js'
/* Animation Modules */
export { default as Animator } from './animation/Animator.js'
export {
Controller,
Ease,
PID,
Spring,
easing
} from './animation/Controller.js'
export { default as Queue } from './animation/Queue.js'
export { default as Runner } from './animation/Runner.js'
export { default as Timeline } from './animation/Timeline.js'
/* Types */
export { default as Array } from './types/SVGArray.js'
export { default as Box } from './types/Box.js'
export { default as Color } from './types/Color.js'
export { default as EventTarget } from './types/EventTarget.js'
export { default as Matrix } from './types/Matrix.js'
export { default as Number } from './types/SVGNumber.js'
export { default as PathArray } from './types/PathArray.js'
export { default as Point } from './types/Point.js'
export { default as PointArray } from './types/PointArray.js'
export { default as List } from './types/List.js'
/* Elements */
export { default as Circle } from './elements/Circle.js'
export { default as ClipPath } from './elements/ClipPath.js'
export { default as Container } from './elements/Container.js'
export { default as Defs } from './elements/Defs.js'
export { default as Dom } from './elements/Dom.js'
export { default as Element } from './elements/Element.js'
export { default as Ellipse } from './elements/Ellipse.js'
export { default as ForeignObject } from './elements/ForeignObject.js'
export { default as Fragment } from './elements/Fragment.js'
export { default as Gradient } from './elements/Gradient.js'
export { default as G } from './elements/G.js'
export { default as A } from './elements/A.js'
export { default as Image } from './elements/Image.js'
export { default as Line } from './elements/Line.js'
export { default as Marker } from './elements/Marker.js'
export { default as Mask } from './elements/Mask.js'
export { default as Path } from './elements/Path.js'
export { default as Pattern } from './elements/Pattern.js'
export { default as Polygon } from './elements/Polygon.js'
export { default as Polyline } from './elements/Polyline.js'
export { default as Rect } from './elements/Rect.js'
export { default as Shape } from './elements/Shape.js'
export { default as Stop } from './elements/Stop.js'
export { default as Style } from './elements/Style.js'
export { default as Svg } from './elements/Svg.js'
export { default as Symbol } from './elements/Symbol.js'
export { default as Text } from './elements/Text.js'
export { default as TextPath } from './elements/TextPath.js'
export { default as Tspan } from './elements/Tspan.js'
export { default as Use } from './elements/Use.js'
extend([Svg, Symbol, Image, Pattern, Marker], getMethodsFor('viewbox'))
extend([Line, Polyline, Polygon, Path], getMethodsFor('marker'))
extend(Text, getMethodsFor('Text'))
extend(Path, getMethodsFor('Path'))
extend(Defs, getMethodsFor('Defs'))
extend([Text, Tspan], getMethodsFor('Tspan'))
extend([Rect, Ellipse, Gradient, Runner], getMethodsFor('radius'))
extend(EventTarget, getMethodsFor('EventTarget'))
extend(Dom, getMethodsFor('Dom'))
extend(Element, getMethodsFor('Element'))
extend(Shape, getMethodsFor('Shape'))
extend([Container, Fragment], getMethodsFor('Container'))
extend(Gradient, getMethodsFor('Gradient'))
extend(Runner, getMethodsFor('Runner'))
List.extend(getMethodNames())
registerMorphableType([
SVGNumber,
Color,
Box,
Matrix,
SVGArray,
PointArray,
PathArray,
Point
])
makeMorphable()

View File

@@ -0,0 +1,94 @@
import { attrs as defaults } from './defaults.js'
import { isNumber } from './regex.js'
import Color from '../../types/Color.js'
import SVGArray from '../../types/SVGArray.js'
import SVGNumber from '../../types/SVGNumber.js'
const colorAttributes = new Set([
'fill',
'stroke',
'color',
'bgcolor',
'stop-color',
'flood-color',
'lighting-color'
])
const hooks = []
export function registerAttrHook(fn) {
hooks.push(fn)
}
// Set svg element attribute
export default function attr(attr, val, ns) {
// act as full getter
if (attr == null) {
// get an object of attributes
attr = {}
val = this.node.attributes
for (const node of val) {
attr[node.nodeName] = isNumber.test(node.nodeValue)
? parseFloat(node.nodeValue)
: node.nodeValue
}
return attr
} else if (attr instanceof Array) {
// loop through array and get all values
return attr.reduce((last, curr) => {
last[curr] = this.attr(curr)
return last
}, {})
} else if (typeof attr === 'object' && attr.constructor === Object) {
// apply every attribute individually if an object is passed
for (val in attr) this.attr(val, attr[val])
} else if (val === null) {
// remove value
this.node.removeAttribute(attr)
} else if (val == null) {
// act as a getter if the first and only argument is not an object
val = this.node.getAttribute(attr)
return val == null
? defaults[attr]
: isNumber.test(val)
? parseFloat(val)
: val
} else {
// Loop through hooks and execute them to convert value
val = hooks.reduce((_val, hook) => {
return hook(attr, _val, this)
}, val)
// ensure correct numeric values (also accepts NaN and Infinity)
if (typeof val === 'number') {
val = new SVGNumber(val)
} else if (colorAttributes.has(attr) && Color.isColor(val)) {
// ensure full hex color
val = new Color(val)
} else if (val.constructor === Array) {
// Check for plain arrays and parse array values
val = new SVGArray(val)
}
// if the passed attribute is leading...
if (attr === 'leading') {
// ... call the leading method instead
if (this.leading) {
this.leading(val)
}
} else {
// set given attribute on node
typeof ns === 'string'
? this.node.setAttributeNS(ns, attr, val.toString())
: this.node.setAttribute(attr, val.toString())
}
// rebuild if required
if (this.rebuild && (attr === 'font-size' || attr === 'x')) {
this.rebuild()
}
}
return this
}

View File

@@ -0,0 +1,43 @@
import SVGNumber from '../../types/SVGNumber.js'
// Radius x value
export function rx(rx) {
return this.attr('rx', rx)
}
// Radius y value
export function ry(ry) {
return this.attr('ry', ry)
}
// Move over x-axis
export function x(x) {
return x == null ? this.cx() - this.rx() : this.cx(x + this.rx())
}
// Move over y-axis
export function y(y) {
return y == null ? this.cy() - this.ry() : this.cy(y + this.ry())
}
// Move by center over x-axis
export function cx(x) {
return this.attr('cx', x)
}
// Move by center over y-axis
export function cy(y) {
return this.attr('cy', y)
}
// Set width of element
export function width(width) {
return width == null ? this.rx() * 2 : this.rx(new SVGNumber(width).divide(2))
}
// Set height of element
export function height(height) {
return height == null
? this.ry() * 2
: this.ry(new SVGNumber(height).divide(2))
}

View File

@@ -0,0 +1,88 @@
import Matrix from '../../types/Matrix.js'
import Point from '../../types/Point.js'
import Box from '../../types/Box.js'
import { proportionalSize } from '../../utils/utils.js'
import { getWindow } from '../../utils/window.js'
export function dmove(dx, dy) {
this.children().forEach((child) => {
let bbox
// We have to wrap this for elements that dont have a bbox
// e.g. title and other descriptive elements
try {
// Get the childs bbox
// Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1905039
// Because bbox for nested svgs returns the contents bbox in the coordinate space of the svg itself (weird!), we cant use bbox for svgs
// Therefore we have to use getBoundingClientRect. But THAT is broken (as explained in the bug).
// Funnily enough the broken behavior would work for us but that breaks it in chrome
// So we have to replicate the broken behavior of FF by just reading the attributes of the svg itself
bbox =
child.node instanceof getWindow().SVGSVGElement
? new Box(child.attr(['x', 'y', 'width', 'height']))
: child.bbox()
} catch (e) {
return
}
// Get childs matrix
const m = new Matrix(child)
// Translate childs matrix by amount and
// transform it back into parents space
const matrix = m.translate(dx, dy).transform(m.inverse())
// Calculate new x and y from old box
const p = new Point(bbox.x, bbox.y).transform(matrix)
// Move element
child.move(p.x, p.y)
})
return this
}
export function dx(dx) {
return this.dmove(dx, 0)
}
export function dy(dy) {
return this.dmove(0, dy)
}
export function height(height, box = this.bbox()) {
if (height == null) return box.height
return this.size(box.width, height, box)
}
export function move(x = 0, y = 0, box = this.bbox()) {
const dx = x - box.x
const dy = y - box.y
return this.dmove(dx, dy)
}
export function size(width, height, box = this.bbox()) {
const p = proportionalSize(this, width, height, box)
const scaleX = p.width / box.width
const scaleY = p.height / box.height
this.children().forEach((child) => {
const o = new Point(box).transform(new Matrix(child).inverse())
child.scale(scaleX, scaleY, o.x, o.y)
})
return this
}
export function width(width, box = this.bbox()) {
if (width == null) return box.width
return this.size(width, box.height, box)
}
export function x(x, box = this.bbox()) {
if (x == null) return box.x
return this.move(x, box.y, box)
}
export function y(y, box = this.bbox()) {
if (y == null) return box.y
return this.move(box.x, y, box)
}

View File

@@ -0,0 +1,44 @@
export function noop() {}
// Default animation values
export const timeline = {
duration: 400,
ease: '>',
delay: 0
}
// Default attribute values
export const attrs = {
// fill and stroke
'fill-opacity': 1,
'stroke-opacity': 1,
'stroke-width': 0,
'stroke-linejoin': 'miter',
'stroke-linecap': 'butt',
fill: '#000000',
stroke: '#000000',
opacity: 1,
// position
x: 0,
y: 0,
cx: 0,
cy: 0,
// size
width: 0,
height: 0,
// radius
r: 0,
rx: 0,
ry: 0,
// gradient
offset: 0,
'stop-opacity': 1,
'stop-color': '#000000',
// text
'text-anchor': 'start'
}

View File

@@ -0,0 +1,143 @@
import { delimiter } from './regex.js'
import { makeInstance } from '../../utils/adopter.js'
import { globals } from '../../utils/window.js'
let listenerId = 0
export const windowEvents = {}
export function getEvents(instance) {
let n = instance.getEventHolder()
// We dont want to save events in global space
if (n === globals.window) n = windowEvents
if (!n.events) n.events = {}
return n.events
}
export function getEventTarget(instance) {
return instance.getEventTarget()
}
export function clearEvents(instance) {
let n = instance.getEventHolder()
if (n === globals.window) n = windowEvents
if (n.events) n.events = {}
}
// Add event binder in the SVG namespace
export function on(node, events, listener, binding, options) {
const l = listener.bind(binding || node)
const instance = makeInstance(node)
const bag = getEvents(instance)
const n = getEventTarget(instance)
// events can be an array of events or a string of events
events = Array.isArray(events) ? events : events.split(delimiter)
// add id to listener
if (!listener._svgjsListenerId) {
listener._svgjsListenerId = ++listenerId
}
events.forEach(function (event) {
const ev = event.split('.')[0]
const ns = event.split('.')[1] || '*'
// ensure valid object
bag[ev] = bag[ev] || {}
bag[ev][ns] = bag[ev][ns] || {}
// reference listener
bag[ev][ns][listener._svgjsListenerId] = l
// add listener
n.addEventListener(ev, l, options || false)
})
}
// Add event unbinder in the SVG namespace
export function off(node, events, listener, options) {
const instance = makeInstance(node)
const bag = getEvents(instance)
const n = getEventTarget(instance)
// listener can be a function or a number
if (typeof listener === 'function') {
listener = listener._svgjsListenerId
if (!listener) return
}
// events can be an array of events or a string or undefined
events = Array.isArray(events) ? events : (events || '').split(delimiter)
events.forEach(function (event) {
const ev = event && event.split('.')[0]
const ns = event && event.split('.')[1]
let namespace, l
if (listener) {
// remove listener reference
if (bag[ev] && bag[ev][ns || '*']) {
// removeListener
n.removeEventListener(
ev,
bag[ev][ns || '*'][listener],
options || false
)
delete bag[ev][ns || '*'][listener]
}
} else if (ev && ns) {
// remove all listeners for a namespaced event
if (bag[ev] && bag[ev][ns]) {
for (l in bag[ev][ns]) {
off(n, [ev, ns].join('.'), l)
}
delete bag[ev][ns]
}
} else if (ns) {
// remove all listeners for a specific namespace
for (event in bag) {
for (namespace in bag[event]) {
if (ns === namespace) {
off(n, [event, ns].join('.'))
}
}
}
} else if (ev) {
// remove all listeners for the event
if (bag[ev]) {
for (namespace in bag[ev]) {
off(n, [ev, namespace].join('.'))
}
delete bag[ev]
}
} else {
// remove all listeners on a given node
for (event in bag) {
off(n, event)
}
clearEvents(instance)
}
})
}
export function dispatch(node, event, data, options) {
const n = getEventTarget(node)
// Dispatch event
if (event instanceof globals.window.Event) {
n.dispatchEvent(event)
} else {
event = new globals.window.CustomEvent(event, {
detail: data,
cancelable: true,
...options
})
n.dispatchEvent(event)
}
return event
}

View File

@@ -0,0 +1,13 @@
import SVGNumber from '../../types/SVGNumber.js'
export function from(x, y) {
return (this._element || this).type === 'radialGradient'
? this.attr({ fx: new SVGNumber(x), fy: new SVGNumber(y) })
: this.attr({ x1: new SVGNumber(x), y1: new SVGNumber(y) })
}
export function to(x, y) {
return (this._element || this).type === 'radialGradient'
? this.attr({ cx: new SVGNumber(x), cy: new SVGNumber(y) })
: this.attr({ x2: new SVGNumber(x), y2: new SVGNumber(y) })
}

View File

@@ -0,0 +1,5 @@
// Default namespaces
export const svg = 'http://www.w3.org/2000/svg'
export const html = 'http://www.w3.org/1999/xhtml'
export const xmlns = 'http://www.w3.org/2000/xmlns/'
export const xlink = 'http://www.w3.org/1999/xlink'

View File

@@ -0,0 +1,30 @@
import { globals } from '../../utils/window.js'
import { makeInstance } from '../../utils/adopter.js'
export default function parser() {
// Reuse cached element if possible
if (!parser.nodes) {
const svg = makeInstance().size(2, 0)
svg.node.style.cssText = [
'opacity: 0',
'position: absolute',
'left: -100%',
'top: -100%',
'overflow: hidden'
].join(';')
svg.attr('focusable', 'false')
svg.attr('aria-hidden', 'true')
const path = svg.path().node
parser.nodes = { svg, path }
}
if (!parser.nodes.svg.node.parentNode) {
const b = globals.document.body || globals.document.documentElement
parser.nodes.svg.addTo(b)
}
return parser.nodes
}

View File

@@ -0,0 +1,25 @@
import PointArray from '../../types/PointArray.js'
export const MorphArray = PointArray
// Move by left top corner over x-axis
export function x(x) {
return x == null ? this.bbox().x : this.move(x, this.bbox().y)
}
// Move by left top corner over y-axis
export function y(y) {
return y == null ? this.bbox().y : this.move(this.bbox().x, y)
}
// Set width of element
export function width(width) {
const b = this.bbox()
return width == null ? b.width : this.size(width, b.height)
}
// Set height of element
export function height(height) {
const b = this.bbox()
return height == null ? b.height : this.size(b.width, height)
}

View File

@@ -0,0 +1,34 @@
import { proportionalSize } from '../../utils/utils.js'
import PointArray from '../../types/PointArray.js'
// Get array
export function array() {
return this._array || (this._array = new PointArray(this.attr('points')))
}
// Clear array cache
export function clear() {
delete this._array
return this
}
// Move by left top corner
export function move(x, y) {
return this.attr('points', this.array().move(x, y))
}
// Plot new path
export function plot(p) {
return p == null
? this.array()
: this.clear().attr(
'points',
typeof p === 'string' ? p : (this._array = new PointArray(p))
)
}
// Set element size to given width and height
export function size(width, height) {
const p = proportionalSize(this, width, height)
return this.attr('points', this.array().size(p.width, p.height))
}

View File

@@ -0,0 +1,39 @@
// Parse unit value
export const numberAndUnit =
/^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i
// Parse hex value
export const hex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i
// Parse rgb value
export const rgb = /rgb\((\d+),(\d+),(\d+)\)/
// Parse reference id
export const reference = /(#[a-z_][a-z0-9\-_]*)/i
// splits a transformation chain
export const transforms = /\)\s*,?\s*/
// Whitespace
export const whitespace = /\s/g
// Test hex value
export const isHex = /^#[a-f0-9]{3}$|^#[a-f0-9]{6}$/i
// Test rgb value
export const isRgb = /^rgb\(/
// Test for blank string
export const isBlank = /^(\s+)?$/
// Test for numeric string
export const isNumber = /^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i
// Test for image url
export const isImage = /\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i
// split at whitespace and comma
export const delimiter = /[\s,]+/
// Test for path letter
export const isPathLetter = /[MLHVCSQTAZ]/i

View File

@@ -0,0 +1,21 @@
import { adopt } from '../../utils/adopter.js'
import { globals } from '../../utils/window.js'
import { map } from '../../utils/utils.js'
import List from '../../types/List.js'
export default function baseFind(query, parent) {
return new List(
map((parent || globals.document).querySelectorAll(query), function (node) {
return adopt(node)
})
)
}
// Scoped find method
export function find(query) {
return baseFind(query, this.node)
}
export function findOne(query) {
return adopt(this.node.querySelector(query))
}

View File

@@ -0,0 +1,83 @@
import { globals } from '../../utils/window.js'
// Create plain text node
export function plain(text) {
// clear if build mode is disabled
if (this._build === false) {
this.clear()
}
// create text node
this.node.appendChild(globals.document.createTextNode(text))
return this
}
// Get length of text element
export function length() {
return this.node.getComputedTextLength()
}
// Move over x-axis
// Text is moved by its bounding box
// text-anchor does NOT matter
export function x(x, box = this.bbox()) {
if (x == null) {
return box.x
}
return this.attr('x', this.attr('x') + x - box.x)
}
// Move over y-axis
export function y(y, box = this.bbox()) {
if (y == null) {
return box.y
}
return this.attr('y', this.attr('y') + y - box.y)
}
export function move(x, y, box = this.bbox()) {
return this.x(x, box).y(y, box)
}
// Move center over x-axis
export function cx(x, box = this.bbox()) {
if (x == null) {
return box.cx
}
return this.attr('x', this.attr('x') + x - box.cx)
}
// Move center over y-axis
export function cy(y, box = this.bbox()) {
if (y == null) {
return box.cy
}
return this.attr('y', this.attr('y') + y - box.cy)
}
export function center(x, y, box = this.bbox()) {
return this.cx(x, box).cy(y, box)
}
export function ax(x) {
return this.attr('x', x)
}
export function ay(y) {
return this.attr('y', y)
}
export function amove(x, y) {
return this.ax(x).ay(y)
}
// Enable / disable build mode
export function build(build) {
this._build = !!build
return this
}

View File

@@ -0,0 +1,114 @@
import { makeInstance } from '../../utils/adopter.js'
import { registerMethods } from '../../utils/methods.js'
// Get all siblings, including myself
export function siblings() {
return this.parent().children()
}
// Get the current position siblings
export function position() {
return this.parent().index(this)
}
// Get the next element (will return null if there is none)
export function next() {
return this.siblings()[this.position() + 1]
}
// Get the next element (will return null if there is none)
export function prev() {
return this.siblings()[this.position() - 1]
}
// Send given element one step forward
export function forward() {
const i = this.position()
const p = this.parent()
// move node one step forward
p.add(this.remove(), i + 1)
return this
}
// Send given element one step backward
export function backward() {
const i = this.position()
const p = this.parent()
p.add(this.remove(), i ? i - 1 : 0)
return this
}
// Send given element all the way to the front
export function front() {
const p = this.parent()
// Move node forward
p.add(this.remove())
return this
}
// Send given element all the way to the back
export function back() {
const p = this.parent()
// Move node back
p.add(this.remove(), 0)
return this
}
// Inserts a given element before the targeted element
export function before(element) {
element = makeInstance(element)
element.remove()
const i = this.position()
this.parent().add(element, i)
return this
}
// Inserts a given element after the targeted element
export function after(element) {
element = makeInstance(element)
element.remove()
const i = this.position()
this.parent().add(element, i + 1)
return this
}
export function insertBefore(element) {
element = makeInstance(element)
element.before(this)
return this
}
export function insertAfter(element) {
element = makeInstance(element)
element.after(this)
return this
}
registerMethods('Dom', {
siblings,
position,
next,
prev,
forward,
backward,
front,
back,
before,
after,
insertBefore,
insertAfter
})

View File

@@ -0,0 +1,53 @@
import { delimiter } from '../core/regex.js'
import { registerMethods } from '../../utils/methods.js'
// Return array of classes on the node
export function classes() {
const attr = this.attr('class')
return attr == null ? [] : attr.trim().split(delimiter)
}
// Return true if class exists on the node, false otherwise
export function hasClass(name) {
return this.classes().indexOf(name) !== -1
}
// Add class to the node
export function addClass(name) {
if (!this.hasClass(name)) {
const array = this.classes()
array.push(name)
this.attr('class', array.join(' '))
}
return this
}
// Remove class from the node
export function removeClass(name) {
if (this.hasClass(name)) {
this.attr(
'class',
this.classes()
.filter(function (c) {
return c !== name
})
.join(' ')
)
}
return this
}
// Toggle the presence of a class on the node
export function toggleClass(name) {
return this.hasClass(name) ? this.removeClass(name) : this.addClass(name)
}
registerMethods('Dom', {
classes,
hasClass,
addClass,
removeClass,
toggleClass
})

View File

@@ -0,0 +1,79 @@
import { isBlank } from '../core/regex.js'
import { registerMethods } from '../../utils/methods.js'
// Dynamic style generator
export function css(style, val) {
const ret = {}
if (arguments.length === 0) {
// get full style as object
this.node.style.cssText
.split(/\s*;\s*/)
.filter(function (el) {
return !!el.length
})
.forEach(function (el) {
const t = el.split(/\s*:\s*/)
ret[t[0]] = t[1]
})
return ret
}
if (arguments.length < 2) {
// get style properties as array
if (Array.isArray(style)) {
for (const name of style) {
const cased = name
ret[name] = this.node.style.getPropertyValue(cased)
}
return ret
}
// get style for property
if (typeof style === 'string') {
return this.node.style.getPropertyValue(style)
}
// set styles in object
if (typeof style === 'object') {
for (const name in style) {
// set empty string if null/undefined/'' was given
this.node.style.setProperty(
name,
style[name] == null || isBlank.test(style[name]) ? '' : style[name]
)
}
}
}
// set style for property
if (arguments.length === 2) {
this.node.style.setProperty(
style,
val == null || isBlank.test(val) ? '' : val
)
}
return this
}
// Show element
export function show() {
return this.css('display', '')
}
// Hide element
export function hide() {
return this.css('display', 'none')
}
// Is element visible?
export function visible() {
return this.css('display') !== 'none'
}
registerMethods('Dom', {
css,
show,
hide,
visible
})

View File

@@ -0,0 +1,47 @@
import { registerMethods } from '../../utils/methods.js'
import { filter, map } from '../../utils/utils.js'
// Store data values on svg nodes
export function data(a, v, r) {
if (a == null) {
// get an object of attributes
return this.data(
map(
filter(
this.node.attributes,
(el) => el.nodeName.indexOf('data-') === 0
),
(el) => el.nodeName.slice(5)
)
)
} else if (a instanceof Array) {
const data = {}
for (const key of a) {
data[key] = this.data(key)
}
return data
} else if (typeof a === 'object') {
for (v in a) {
this.data(v, a[v])
}
} else if (arguments.length < 2) {
try {
return JSON.parse(this.attr('data-' + a))
} catch (e) {
return this.attr('data-' + a)
}
} else {
this.attr(
'data-' + a,
v === null
? null
: r === true || typeof v === 'string' || typeof v === 'number'
? v
: JSON.stringify(v)
)
}
return this
}
registerMethods('Dom', { data })

View File

@@ -0,0 +1,40 @@
import { registerMethods } from '../../utils/methods.js'
// Remember arbitrary data
export function remember(k, v) {
// remember every item in an object individually
if (typeof arguments[0] === 'object') {
for (const key in k) {
this.remember(key, k[key])
}
} else if (arguments.length === 1) {
// retrieve memory
return this.memory()[k]
} else {
// store memory
this.memory()[k] = v
}
return this
}
// Erase a given memory
export function forget() {
if (arguments.length === 0) {
this._memory = {}
} else {
for (let i = arguments.length - 1; i >= 0; i--) {
delete this.memory()[arguments[i]]
}
}
return this
}
// This triggers creation of a new hidden class which is not performant
// However, this function is not rarely used so it will not happen frequently
// Return local memory object
export function memory() {
return (this._memory = this._memory || {})
}
registerMethods('Dom', { remember, forget, memory })

View File

@@ -0,0 +1,200 @@
import { registerMethods } from '../../utils/methods.js'
import Color from '../../types/Color.js'
import Element from '../../elements/Element.js'
import Matrix from '../../types/Matrix.js'
import Point from '../../types/Point.js'
import SVGNumber from '../../types/SVGNumber.js'
// Define list of available attributes for stroke and fill
const sugar = {
stroke: [
'color',
'width',
'opacity',
'linecap',
'linejoin',
'miterlimit',
'dasharray',
'dashoffset'
],
fill: ['color', 'opacity', 'rule'],
prefix: function (t, a) {
return a === 'color' ? t : t + '-' + a
}
}
// Add sugar for fill and stroke
;['fill', 'stroke'].forEach(function (m) {
const extension = {}
let i
extension[m] = function (o) {
if (typeof o === 'undefined') {
return this.attr(m)
}
if (
typeof o === 'string' ||
o instanceof Color ||
Color.isRgb(o) ||
o instanceof Element
) {
this.attr(m, o)
} else {
// set all attributes from sugar.fill and sugar.stroke list
for (i = sugar[m].length - 1; i >= 0; i--) {
if (o[sugar[m][i]] != null) {
this.attr(sugar.prefix(m, sugar[m][i]), o[sugar[m][i]])
}
}
}
return this
}
registerMethods(['Element', 'Runner'], extension)
})
registerMethods(['Element', 'Runner'], {
// Let the user set the matrix directly
matrix: function (mat, b, c, d, e, f) {
// Act as a getter
if (mat == null) {
return new Matrix(this)
}
// Act as a setter, the user can pass a matrix or a set of numbers
return this.attr('transform', new Matrix(mat, b, c, d, e, f))
},
// Map rotation to transform
rotate: function (angle, cx, cy) {
return this.transform({ rotate: angle, ox: cx, oy: cy }, true)
},
// Map skew to transform
skew: function (x, y, cx, cy) {
return arguments.length === 1 || arguments.length === 3
? this.transform({ skew: x, ox: y, oy: cx }, true)
: this.transform({ skew: [x, y], ox: cx, oy: cy }, true)
},
shear: function (lam, cx, cy) {
return this.transform({ shear: lam, ox: cx, oy: cy }, true)
},
// Map scale to transform
scale: function (x, y, cx, cy) {
return arguments.length === 1 || arguments.length === 3
? this.transform({ scale: x, ox: y, oy: cx }, true)
: this.transform({ scale: [x, y], ox: cx, oy: cy }, true)
},
// Map translate to transform
translate: function (x, y) {
return this.transform({ translate: [x, y] }, true)
},
// Map relative translations to transform
relative: function (x, y) {
return this.transform({ relative: [x, y] }, true)
},
// Map flip to transform
flip: function (direction = 'both', origin = 'center') {
if ('xybothtrue'.indexOf(direction) === -1) {
origin = direction
direction = 'both'
}
return this.transform({ flip: direction, origin: origin }, true)
},
// Opacity
opacity: function (value) {
return this.attr('opacity', value)
}
})
registerMethods('radius', {
// Add x and y radius
radius: function (x, y = x) {
const type = (this._element || this).type
return type === 'radialGradient'
? this.attr('r', new SVGNumber(x))
: this.rx(x).ry(y)
}
})
registerMethods('Path', {
// Get path length
length: function () {
return this.node.getTotalLength()
},
// Get point at length
pointAt: function (length) {
return new Point(this.node.getPointAtLength(length))
}
})
registerMethods(['Element', 'Runner'], {
// Set font
font: function (a, v) {
if (typeof a === 'object') {
for (v in a) this.font(v, a[v])
return this
}
return a === 'leading'
? this.leading(v)
: a === 'anchor'
? this.attr('text-anchor', v)
: a === 'size' ||
a === 'family' ||
a === 'weight' ||
a === 'stretch' ||
a === 'variant' ||
a === 'style'
? this.attr('font-' + a, v)
: this.attr(a, v)
}
})
// Add events to elements
const methods = [
'click',
'dblclick',
'mousedown',
'mouseup',
'mouseover',
'mouseout',
'mousemove',
'mouseenter',
'mouseleave',
'touchstart',
'touchmove',
'touchleave',
'touchend',
'touchcancel',
'contextmenu',
'wheel',
'pointerdown',
'pointermove',
'pointerup',
'pointerleave',
'pointercancel'
].reduce(function (last, event) {
// add event to Element
const fn = function (f) {
if (f === null) {
this.off(event)
} else {
this.on(event, f)
}
return this
}
last[event] = fn
return last
}, {})
registerMethods('Element', methods)

View File

@@ -0,0 +1,83 @@
import { getOrigin, isDescriptive } from '../../utils/utils.js'
import { delimiter, transforms } from '../core/regex.js'
import { registerMethods } from '../../utils/methods.js'
import Matrix from '../../types/Matrix.js'
// Reset all transformations
export function untransform() {
return this.attr('transform', null)
}
// merge the whole transformation chain into one matrix and returns it
export function matrixify() {
const matrix = (this.attr('transform') || '')
// split transformations
.split(transforms)
.slice(0, -1)
.map(function (str) {
// generate key => value pairs
const kv = str.trim().split('(')
return [
kv[0],
kv[1].split(delimiter).map(function (str) {
return parseFloat(str)
})
]
})
.reverse()
// merge every transformation into one matrix
.reduce(function (matrix, transform) {
if (transform[0] === 'matrix') {
return matrix.lmultiply(Matrix.fromArray(transform[1]))
}
return matrix[transform[0]].apply(matrix, transform[1])
}, new Matrix())
return matrix
}
// add an element to another parent without changing the visual representation on the screen
export function toParent(parent, i) {
if (this === parent) return this
if (isDescriptive(this.node)) return this.addTo(parent, i)
const ctm = this.screenCTM()
const pCtm = parent.screenCTM().inverse()
this.addTo(parent, i).untransform().transform(pCtm.multiply(ctm))
return this
}
// same as above with parent equals root-svg
export function toRoot(i) {
return this.toParent(this.root(), i)
}
// Add transformations
export function transform(o, relative) {
// Act as a getter if no object was passed
if (o == null || typeof o === 'string') {
const decomposed = new Matrix(this).decompose()
return o == null ? decomposed : decomposed[o]
}
if (!Matrix.isMatrixLike(o)) {
// Set the origin according to the defined transform
o = { ...o, origin: getOrigin(o, this) }
}
// The user can pass a boolean, an Element or an Matrix or nothing
const cleanRelative = relative === true ? this : relative || false
const result = new Matrix(cleanRelative).transform(o)
return this.attr('transform', result)
}
registerMethods('Element', {
untransform,
matrixify,
toParent,
toRoot,
transform
})

View File

@@ -0,0 +1,8 @@
import { filter } from '../utils/utils.js'
// IE11: children does not work for svg nodes
export default function children(node) {
return filter(node.childNodes, function (child) {
return child.nodeType === 1
})
}

View File

@@ -0,0 +1,115 @@
;(function () {
try {
if (SVGElement.prototype.innerHTML) return
} catch (e) {
return
}
const serializeXML = function (node, output) {
const nodeType = node.nodeType
if (nodeType === 3) {
output.push(
node.textContent
.replace(/&/, '&amp;')
.replace(/</, '&lt;')
.replace('>', '&gt;')
)
} else if (nodeType === 1) {
output.push('<', node.tagName)
if (node.hasAttributes()) {
;[].forEach.call(node.attributes, function (attrNode) {
output.push(' ', attrNode.name, '="', attrNode.value, '"')
})
}
output.push('>')
if (node.hasChildNodes()) {
;[].forEach.call(node.childNodes, function (childNode) {
serializeXML(childNode, output)
})
} else {
// output.push('/>')
}
output.push('</', node.tagName, '>')
} else if (nodeType === 8) {
output.push('<!--', node.nodeValue, '-->')
}
}
Object.defineProperty(SVGElement.prototype, 'innerHTML', {
get: function () {
const output = []
let childNode = this.firstChild
while (childNode) {
serializeXML(childNode, output)
childNode = childNode.nextSibling
}
return output.join('')
},
set: function (markupText) {
while (this.firstChild) {
this.removeChild(this.firstChild)
}
try {
const dXML = new DOMParser()
dXML.async = false
const sXML =
"<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>" +
markupText +
'</svg>'
const svgDocElement = dXML.parseFromString(
sXML,
'text/xml'
).documentElement
let childNode = svgDocElement.firstChild
while (childNode) {
this.appendChild(this.ownerDocument.importNode(childNode, true))
childNode = childNode.nextSibling
}
} catch (e) {
throw new Error('Can not set innerHTML on node')
}
}
})
Object.defineProperty(SVGElement.prototype, 'outerHTML', {
get: function () {
const output = []
serializeXML(this, output)
return output.join('')
},
set: function (markupText) {
while (this.firstChild) {
this.removeChild(this.firstChild)
}
try {
const dXML = new DOMParser()
dXML.async = false
const sXML =
"<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>" +
markupText +
'</svg>'
const svgDocElement = dXML.parseFromString(
sXML,
'text/xml'
).documentElement
let childNode = svgDocElement.firstChild
while (childNode) {
this.parentNode.insertBefore(
this.ownerDocument.importNode(childNode, true),
this
)
// this.appendChild(this.ownerDocument.importNode(childNode, true));
childNode = childNode.nextSibling
}
} catch (e) {
throw new Error('Can not set outerHTML on node')
}
}
})
})()

9
frontend/node_modules/@svgdotjs/svg.js/src/svg.js generated vendored Normal file
View File

@@ -0,0 +1,9 @@
import * as svgMembers from './main.js'
import { makeInstance } from './utils/adopter.js'
// The main wrapping element
export default function SVG(element, isHTML) {
return makeInstance(element, isHTML)
}
Object.assign(SVG, svgMembers)

View File

@@ -0,0 +1,10 @@
export default class Base {
// constructor (node/*, {extensions = []} */) {
// // this.tags = []
// //
// // for (let extension of extensions) {
// // extension.setup.call(this, node)
// // this.tags.push(extension.name)
// // }
// }
}

270
frontend/node_modules/@svgdotjs/svg.js/src/types/Box.js generated vendored Normal file
View File

@@ -0,0 +1,270 @@
import { delimiter } from '../modules/core/regex.js'
import { globals } from '../utils/window.js'
import { register } from '../utils/adopter.js'
import { registerMethods } from '../utils/methods.js'
import Matrix from './Matrix.js'
import Point from './Point.js'
import parser from '../modules/core/parser.js'
export function isNulledBox(box) {
return !box.width && !box.height && !box.x && !box.y
}
export function domContains(node) {
return (
node === globals.document ||
(
globals.document.documentElement.contains ||
function (node) {
// This is IE - it does not support contains() for top-level SVGs
while (node.parentNode) {
node = node.parentNode
}
return node === globals.document
}
).call(globals.document.documentElement, node)
)
}
export default class Box {
constructor(...args) {
this.init(...args)
}
addOffset() {
// offset by window scroll position, because getBoundingClientRect changes when window is scrolled
this.x += globals.window.pageXOffset
this.y += globals.window.pageYOffset
return new Box(this)
}
init(source) {
const base = [0, 0, 0, 0]
source =
typeof source === 'string'
? source.split(delimiter).map(parseFloat)
: Array.isArray(source)
? source
: typeof source === 'object'
? [
source.left != null ? source.left : source.x,
source.top != null ? source.top : source.y,
source.width,
source.height
]
: arguments.length === 4
? [].slice.call(arguments)
: base
this.x = source[0] || 0
this.y = source[1] || 0
this.width = this.w = source[2] || 0
this.height = this.h = source[3] || 0
// Add more bounding box properties
this.x2 = this.x + this.w
this.y2 = this.y + this.h
this.cx = this.x + this.w / 2
this.cy = this.y + this.h / 2
return this
}
isNulled() {
return isNulledBox(this)
}
// Merge rect box with another, return a new instance
merge(box) {
const x = Math.min(this.x, box.x)
const y = Math.min(this.y, box.y)
const width = Math.max(this.x + this.width, box.x + box.width) - x
const height = Math.max(this.y + this.height, box.y + box.height) - y
return new Box(x, y, width, height)
}
toArray() {
return [this.x, this.y, this.width, this.height]
}
toString() {
return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height
}
transform(m) {
if (!(m instanceof Matrix)) {
m = new Matrix(m)
}
let xMin = Infinity
let xMax = -Infinity
let yMin = Infinity
let yMax = -Infinity
const pts = [
new Point(this.x, this.y),
new Point(this.x2, this.y),
new Point(this.x, this.y2),
new Point(this.x2, this.y2)
]
pts.forEach(function (p) {
p = p.transform(m)
xMin = Math.min(xMin, p.x)
xMax = Math.max(xMax, p.x)
yMin = Math.min(yMin, p.y)
yMax = Math.max(yMax, p.y)
})
return new Box(xMin, yMin, xMax - xMin, yMax - yMin)
}
}
function getBox(el, getBBoxFn, retry) {
let box
try {
// Try to get the box with the provided function
box = getBBoxFn(el.node)
// If the box is worthless and not even in the dom, retry
// by throwing an error here...
if (isNulledBox(box) && !domContains(el.node)) {
throw new Error('Element not in the dom')
}
} catch (e) {
// ... and calling the retry handler here
box = retry(el)
}
return box
}
export function bbox() {
// Function to get bbox is getBBox()
const getBBox = (node) => node.getBBox()
// Take all measures so that a stupid browser renders the element
// so we can get the bbox from it when we try again
const retry = (el) => {
try {
const clone = el.clone().addTo(parser().svg).show()
const box = clone.node.getBBox()
clone.remove()
return box
} catch (e) {
// We give up...
throw new Error(
`Getting bbox of element "${
el.node.nodeName
}" is not possible: ${e.toString()}`
)
}
}
const box = getBox(this, getBBox, retry)
const bbox = new Box(box)
return bbox
}
export function rbox(el) {
const getRBox = (node) => node.getBoundingClientRect()
const retry = (el) => {
// There is no point in trying tricks here because if we insert the element into the dom ourselves
// it obviously will be at the wrong position
throw new Error(
`Getting rbox of element "${el.node.nodeName}" is not possible`
)
}
const box = getBox(this, getRBox, retry)
const rbox = new Box(box)
// If an element was passed, we want the bbox in the coordinate system of that element
if (el) {
return rbox.transform(el.screenCTM().inverseO())
}
// Else we want it in absolute screen coordinates
// Therefore we need to add the scrollOffset
return rbox.addOffset()
}
// Checks whether the given point is inside the bounding box
export function inside(x, y) {
const box = this.bbox()
return (
x > box.x && y > box.y && x < box.x + box.width && y < box.y + box.height
)
}
registerMethods({
viewbox: {
viewbox(x, y, width, height) {
// act as getter
if (x == null) return new Box(this.attr('viewBox'))
// act as setter
return this.attr('viewBox', new Box(x, y, width, height))
},
zoom(level, point) {
// Its best to rely on the attributes here and here is why:
// clientXYZ: Doesn't work on non-root svgs because they dont have a CSSBox (silly!)
// getBoundingClientRect: Doesn't work because Chrome just ignores width and height of nested svgs completely
// that means, their clientRect is always as big as the content.
// Furthermore this size is incorrect if the element is further transformed by its parents
// computedStyle: Only returns meaningful values if css was used with px. We dont go this route here!
// getBBox: returns the bounding box of its content - that doesn't help!
let { width, height } = this.attr(['width', 'height'])
// Width and height is a string when a number with a unit is present which we can't use
// So we try clientXYZ
if (
(!width && !height) ||
typeof width === 'string' ||
typeof height === 'string'
) {
width = this.node.clientWidth
height = this.node.clientHeight
}
// Giving up...
if (!width || !height) {
throw new Error(
'Impossible to get absolute width and height. Please provide an absolute width and height attribute on the zooming element'
)
}
const v = this.viewbox()
const zoomX = width / v.width
const zoomY = height / v.height
const zoom = Math.min(zoomX, zoomY)
if (level == null) {
return zoom
}
let zoomAmount = zoom / level
// Set the zoomAmount to the highest value which is safe to process and recover from
// The * 100 is a bit of wiggle room for the matrix transformation
if (zoomAmount === Infinity) zoomAmount = Number.MAX_SAFE_INTEGER / 100
point =
point || new Point(width / 2 / zoomX + v.x, height / 2 / zoomY + v.y)
const box = new Box(v).transform(
new Matrix({ scale: zoomAmount, origin: point })
)
return this.viewbox(box)
}
}
})
register(Box, 'Box')

View File

@@ -0,0 +1,450 @@
import { hex, isHex, isRgb, rgb, whitespace } from '../modules/core/regex.js'
function sixDigitHex(hex) {
return hex.length === 4
? [
'#',
hex.substring(1, 2),
hex.substring(1, 2),
hex.substring(2, 3),
hex.substring(2, 3),
hex.substring(3, 4),
hex.substring(3, 4)
].join('')
: hex
}
function componentHex(component) {
const integer = Math.round(component)
const bounded = Math.max(0, Math.min(255, integer))
const hex = bounded.toString(16)
return hex.length === 1 ? '0' + hex : hex
}
function is(object, space) {
for (let i = space.length; i--; ) {
if (object[space[i]] == null) {
return false
}
}
return true
}
function getParameters(a, b) {
const params = is(a, 'rgb')
? { _a: a.r, _b: a.g, _c: a.b, _d: 0, space: 'rgb' }
: is(a, 'xyz')
? { _a: a.x, _b: a.y, _c: a.z, _d: 0, space: 'xyz' }
: is(a, 'hsl')
? { _a: a.h, _b: a.s, _c: a.l, _d: 0, space: 'hsl' }
: is(a, 'lab')
? { _a: a.l, _b: a.a, _c: a.b, _d: 0, space: 'lab' }
: is(a, 'lch')
? { _a: a.l, _b: a.c, _c: a.h, _d: 0, space: 'lch' }
: is(a, 'cmyk')
? { _a: a.c, _b: a.m, _c: a.y, _d: a.k, space: 'cmyk' }
: { _a: 0, _b: 0, _c: 0, space: 'rgb' }
params.space = b || params.space
return params
}
function cieSpace(space) {
if (space === 'lab' || space === 'xyz' || space === 'lch') {
return true
} else {
return false
}
}
function hueToRgb(p, q, t) {
if (t < 0) t += 1
if (t > 1) t -= 1
if (t < 1 / 6) return p + (q - p) * 6 * t
if (t < 1 / 2) return q
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6
return p
}
export default class Color {
constructor(...inputs) {
this.init(...inputs)
}
// Test if given value is a color
static isColor(color) {
return (
color && (color instanceof Color || this.isRgb(color) || this.test(color))
)
}
// Test if given value is an rgb object
static isRgb(color) {
return (
color &&
typeof color.r === 'number' &&
typeof color.g === 'number' &&
typeof color.b === 'number'
)
}
/*
Generating random colors
*/
static random(mode = 'vibrant', t) {
// Get the math modules
const { random, round, sin, PI: pi } = Math
// Run the correct generator
if (mode === 'vibrant') {
const l = (81 - 57) * random() + 57
const c = (83 - 45) * random() + 45
const h = 360 * random()
const color = new Color(l, c, h, 'lch')
return color
} else if (mode === 'sine') {
t = t == null ? random() : t
const r = round(80 * sin((2 * pi * t) / 0.5 + 0.01) + 150)
const g = round(50 * sin((2 * pi * t) / 0.5 + 4.6) + 200)
const b = round(100 * sin((2 * pi * t) / 0.5 + 2.3) + 150)
const color = new Color(r, g, b)
return color
} else if (mode === 'pastel') {
const l = (94 - 86) * random() + 86
const c = (26 - 9) * random() + 9
const h = 360 * random()
const color = new Color(l, c, h, 'lch')
return color
} else if (mode === 'dark') {
const l = 10 + 10 * random()
const c = (125 - 75) * random() + 86
const h = 360 * random()
const color = new Color(l, c, h, 'lch')
return color
} else if (mode === 'rgb') {
const r = 255 * random()
const g = 255 * random()
const b = 255 * random()
const color = new Color(r, g, b)
return color
} else if (mode === 'lab') {
const l = 100 * random()
const a = 256 * random() - 128
const b = 256 * random() - 128
const color = new Color(l, a, b, 'lab')
return color
} else if (mode === 'grey') {
const grey = 255 * random()
const color = new Color(grey, grey, grey)
return color
} else {
throw new Error('Unsupported random color mode')
}
}
// Test if given value is a color string
static test(color) {
return typeof color === 'string' && (isHex.test(color) || isRgb.test(color))
}
cmyk() {
// Get the rgb values for the current color
const { _a, _b, _c } = this.rgb()
const [r, g, b] = [_a, _b, _c].map((v) => v / 255)
// Get the cmyk values in an unbounded format
const k = Math.min(1 - r, 1 - g, 1 - b)
if (k === 1) {
// Catch the black case
return new Color(0, 0, 0, 1, 'cmyk')
}
const c = (1 - r - k) / (1 - k)
const m = (1 - g - k) / (1 - k)
const y = (1 - b - k) / (1 - k)
// Construct the new color
const color = new Color(c, m, y, k, 'cmyk')
return color
}
hsl() {
// Get the rgb values
const { _a, _b, _c } = this.rgb()
const [r, g, b] = [_a, _b, _c].map((v) => v / 255)
// Find the maximum and minimum values to get the lightness
const max = Math.max(r, g, b)
const min = Math.min(r, g, b)
const l = (max + min) / 2
// If the r, g, v values are identical then we are grey
const isGrey = max === min
// Calculate the hue and saturation
const delta = max - min
const s = isGrey
? 0
: l > 0.5
? delta / (2 - max - min)
: delta / (max + min)
const h = isGrey
? 0
: max === r
? ((g - b) / delta + (g < b ? 6 : 0)) / 6
: max === g
? ((b - r) / delta + 2) / 6
: max === b
? ((r - g) / delta + 4) / 6
: 0
// Construct and return the new color
const color = new Color(360 * h, 100 * s, 100 * l, 'hsl')
return color
}
init(a = 0, b = 0, c = 0, d = 0, space = 'rgb') {
// This catches the case when a falsy value is passed like ''
a = !a ? 0 : a
// Reset all values in case the init function is rerun with new color space
if (this.space) {
for (const component in this.space) {
delete this[this.space[component]]
}
}
if (typeof a === 'number') {
// Allow for the case that we don't need d...
space = typeof d === 'string' ? d : space
d = typeof d === 'string' ? 0 : d
// Assign the values straight to the color
Object.assign(this, { _a: a, _b: b, _c: c, _d: d, space })
// If the user gave us an array, make the color from it
} else if (a instanceof Array) {
this.space = b || (typeof a[3] === 'string' ? a[3] : a[4]) || 'rgb'
Object.assign(this, { _a: a[0], _b: a[1], _c: a[2], _d: a[3] || 0 })
} else if (a instanceof Object) {
// Set the object up and assign its values directly
const values = getParameters(a, b)
Object.assign(this, values)
} else if (typeof a === 'string') {
if (isRgb.test(a)) {
const noWhitespace = a.replace(whitespace, '')
const [_a, _b, _c] = rgb
.exec(noWhitespace)
.slice(1, 4)
.map((v) => parseInt(v))
Object.assign(this, { _a, _b, _c, _d: 0, space: 'rgb' })
} else if (isHex.test(a)) {
const hexParse = (v) => parseInt(v, 16)
const [, _a, _b, _c] = hex.exec(sixDigitHex(a)).map(hexParse)
Object.assign(this, { _a, _b, _c, _d: 0, space: 'rgb' })
} else throw Error("Unsupported string format, can't construct Color")
}
// Now add the components as a convenience
const { _a, _b, _c, _d } = this
const components =
this.space === 'rgb'
? { r: _a, g: _b, b: _c }
: this.space === 'xyz'
? { x: _a, y: _b, z: _c }
: this.space === 'hsl'
? { h: _a, s: _b, l: _c }
: this.space === 'lab'
? { l: _a, a: _b, b: _c }
: this.space === 'lch'
? { l: _a, c: _b, h: _c }
: this.space === 'cmyk'
? { c: _a, m: _b, y: _c, k: _d }
: {}
Object.assign(this, components)
}
lab() {
// Get the xyz color
const { x, y, z } = this.xyz()
// Get the lab components
const l = 116 * y - 16
const a = 500 * (x - y)
const b = 200 * (y - z)
// Construct and return a new color
const color = new Color(l, a, b, 'lab')
return color
}
lch() {
// Get the lab color directly
const { l, a, b } = this.lab()
// Get the chromaticity and the hue using polar coordinates
const c = Math.sqrt(a ** 2 + b ** 2)
let h = (180 * Math.atan2(b, a)) / Math.PI
if (h < 0) {
h *= -1
h = 360 - h
}
// Make a new color and return it
const color = new Color(l, c, h, 'lch')
return color
}
/*
Conversion Methods
*/
rgb() {
if (this.space === 'rgb') {
return this
} else if (cieSpace(this.space)) {
// Convert to the xyz color space
let { x, y, z } = this
if (this.space === 'lab' || this.space === 'lch') {
// Get the values in the lab space
let { l, a, b } = this
if (this.space === 'lch') {
const { c, h } = this
const dToR = Math.PI / 180
a = c * Math.cos(dToR * h)
b = c * Math.sin(dToR * h)
}
// Undo the nonlinear function
const yL = (l + 16) / 116
const xL = a / 500 + yL
const zL = yL - b / 200
// Get the xyz values
const ct = 16 / 116
const mx = 0.008856
const nm = 7.787
x = 0.95047 * (xL ** 3 > mx ? xL ** 3 : (xL - ct) / nm)
y = 1.0 * (yL ** 3 > mx ? yL ** 3 : (yL - ct) / nm)
z = 1.08883 * (zL ** 3 > mx ? zL ** 3 : (zL - ct) / nm)
}
// Convert xyz to unbounded rgb values
const rU = x * 3.2406 + y * -1.5372 + z * -0.4986
const gU = x * -0.9689 + y * 1.8758 + z * 0.0415
const bU = x * 0.0557 + y * -0.204 + z * 1.057
// Convert the values to true rgb values
const pow = Math.pow
const bd = 0.0031308
const r = rU > bd ? 1.055 * pow(rU, 1 / 2.4) - 0.055 : 12.92 * rU
const g = gU > bd ? 1.055 * pow(gU, 1 / 2.4) - 0.055 : 12.92 * gU
const b = bU > bd ? 1.055 * pow(bU, 1 / 2.4) - 0.055 : 12.92 * bU
// Make and return the color
const color = new Color(255 * r, 255 * g, 255 * b)
return color
} else if (this.space === 'hsl') {
// https://bgrins.github.io/TinyColor/docs/tinycolor.html
// Get the current hsl values
let { h, s, l } = this
h /= 360
s /= 100
l /= 100
// If we are grey, then just make the color directly
if (s === 0) {
l *= 255
const color = new Color(l, l, l)
return color
}
// TODO I have no idea what this does :D If you figure it out, tell me!
const q = l < 0.5 ? l * (1 + s) : l + s - l * s
const p = 2 * l - q
// Get the rgb values
const r = 255 * hueToRgb(p, q, h + 1 / 3)
const g = 255 * hueToRgb(p, q, h)
const b = 255 * hueToRgb(p, q, h - 1 / 3)
// Make a new color
const color = new Color(r, g, b)
return color
} else if (this.space === 'cmyk') {
// https://gist.github.com/felipesabino/5066336
// Get the normalised cmyk values
const { c, m, y, k } = this
// Get the rgb values
const r = 255 * (1 - Math.min(1, c * (1 - k) + k))
const g = 255 * (1 - Math.min(1, m * (1 - k) + k))
const b = 255 * (1 - Math.min(1, y * (1 - k) + k))
// Form the color and return it
const color = new Color(r, g, b)
return color
} else {
return this
}
}
toArray() {
const { _a, _b, _c, _d, space } = this
return [_a, _b, _c, _d, space]
}
toHex() {
const [r, g, b] = this._clamped().map(componentHex)
return `#${r}${g}${b}`
}
toRgb() {
const [rV, gV, bV] = this._clamped()
const string = `rgb(${rV},${gV},${bV})`
return string
}
toString() {
return this.toHex()
}
xyz() {
// Normalise the red, green and blue values
const { _a: r255, _b: g255, _c: b255 } = this.rgb()
const [r, g, b] = [r255, g255, b255].map((v) => v / 255)
// Convert to the lab rgb space
const rL = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92
const gL = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92
const bL = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92
// Convert to the xyz color space without bounding the values
const xU = (rL * 0.4124 + gL * 0.3576 + bL * 0.1805) / 0.95047
const yU = (rL * 0.2126 + gL * 0.7152 + bL * 0.0722) / 1.0
const zU = (rL * 0.0193 + gL * 0.1192 + bL * 0.9505) / 1.08883
// Get the proper xyz values by applying the bounding
const x = xU > 0.008856 ? Math.pow(xU, 1 / 3) : 7.787 * xU + 16 / 116
const y = yU > 0.008856 ? Math.pow(yU, 1 / 3) : 7.787 * yU + 16 / 116
const z = zU > 0.008856 ? Math.pow(zU, 1 / 3) : 7.787 * zU + 16 / 116
// Make and return the color
const color = new Color(x, y, z, 'xyz')
return color
}
/*
Input and Output methods
*/
_clamped() {
const { _a, _b, _c } = this.rgb()
const { max, min, round } = Math
const format = (v) => max(0, min(round(v), 255))
return [_a, _b, _c].map(format)
}
/*
Constructing colors
*/
}

View File

@@ -0,0 +1,56 @@
import { dispatch, off, on } from '../modules/core/event.js'
import { register } from '../utils/adopter.js'
import Base from './Base.js'
export default class EventTarget extends Base {
addEventListener() {}
dispatch(event, data, options) {
return dispatch(this, event, data, options)
}
dispatchEvent(event) {
const bag = this.getEventHolder().events
if (!bag) return true
const events = bag[event.type]
for (const i in events) {
for (const j in events[i]) {
events[i][j](event)
}
}
return !event.defaultPrevented
}
// Fire given event
fire(event, data, options) {
this.dispatch(event, data, options)
return this
}
getEventHolder() {
return this
}
getEventTarget() {
return this
}
// Unbind event from listener
off(event, listener, options) {
off(this, event, listener, options)
return this
}
// Bind given event to listener
on(event, listener, binding, options) {
on(this, event, listener, binding, options)
return this
}
removeEventListener() {}
}
register(EventTarget, 'EventTarget')

View File

@@ -0,0 +1,63 @@
import { extend } from '../utils/adopter.js'
// import { subClassArray } from './ArrayPolyfill.js'
class List extends Array {
constructor(arr = [], ...args) {
super(arr, ...args)
if (typeof arr === 'number') return this
this.length = 0
this.push(...arr)
}
}
/* = subClassArray('List', Array, function (arr = []) {
// This catches the case, that native map tries to create an array with new Array(1)
if (typeof arr === 'number') return this
this.length = 0
this.push(...arr)
}) */
export default List
extend([List], {
each(fnOrMethodName, ...args) {
if (typeof fnOrMethodName === 'function') {
return this.map((el, i, arr) => {
return fnOrMethodName.call(el, el, i, arr)
})
} else {
return this.map((el) => {
return el[fnOrMethodName](...args)
})
}
},
toArray() {
return Array.prototype.concat.apply([], this)
}
})
const reserved = ['toArray', 'constructor', 'each']
List.extend = function (methods) {
methods = methods.reduce((obj, name) => {
// Don't overwrite own methods
if (reserved.includes(name)) return obj
// Don't add private methods
if (name[0] === '_') return obj
// Allow access to original Array methods through a prefix
if (name in Array.prototype) {
obj['$' + name] = Array.prototype[name]
}
// Relay every call to each()
obj[name] = function (...attrs) {
return this.each(name, ...attrs)
}
return obj
}, {})
extend([List], methods)
}

View File

@@ -0,0 +1,543 @@
import { delimiter } from '../modules/core/regex.js'
import { radians } from '../utils/utils.js'
import { register } from '../utils/adopter.js'
import Element from '../elements/Element.js'
import Point from './Point.js'
function closeEnough(a, b, threshold) {
return Math.abs(b - a) < (threshold || 1e-6)
}
export default class Matrix {
constructor(...args) {
this.init(...args)
}
static formatTransforms(o) {
// Get all of the parameters required to form the matrix
const flipBoth = o.flip === 'both' || o.flip === true
const flipX = o.flip && (flipBoth || o.flip === 'x') ? -1 : 1
const flipY = o.flip && (flipBoth || o.flip === 'y') ? -1 : 1
const skewX =
o.skew && o.skew.length
? o.skew[0]
: isFinite(o.skew)
? o.skew
: isFinite(o.skewX)
? o.skewX
: 0
const skewY =
o.skew && o.skew.length
? o.skew[1]
: isFinite(o.skew)
? o.skew
: isFinite(o.skewY)
? o.skewY
: 0
const scaleX =
o.scale && o.scale.length
? o.scale[0] * flipX
: isFinite(o.scale)
? o.scale * flipX
: isFinite(o.scaleX)
? o.scaleX * flipX
: flipX
const scaleY =
o.scale && o.scale.length
? o.scale[1] * flipY
: isFinite(o.scale)
? o.scale * flipY
: isFinite(o.scaleY)
? o.scaleY * flipY
: flipY
const shear = o.shear || 0
const theta = o.rotate || o.theta || 0
const origin = new Point(
o.origin || o.around || o.ox || o.originX,
o.oy || o.originY
)
const ox = origin.x
const oy = origin.y
// We need Point to be invalid if nothing was passed because we cannot default to 0 here. That is why NaN
const position = new Point(
o.position || o.px || o.positionX || NaN,
o.py || o.positionY || NaN
)
const px = position.x
const py = position.y
const translate = new Point(
o.translate || o.tx || o.translateX,
o.ty || o.translateY
)
const tx = translate.x
const ty = translate.y
const relative = new Point(
o.relative || o.rx || o.relativeX,
o.ry || o.relativeY
)
const rx = relative.x
const ry = relative.y
// Populate all of the values
return {
scaleX,
scaleY,
skewX,
skewY,
shear,
theta,
rx,
ry,
tx,
ty,
ox,
oy,
px,
py
}
}
static fromArray(a) {
return { a: a[0], b: a[1], c: a[2], d: a[3], e: a[4], f: a[5] }
}
static isMatrixLike(o) {
return (
o.a != null ||
o.b != null ||
o.c != null ||
o.d != null ||
o.e != null ||
o.f != null
)
}
// left matrix, right matrix, target matrix which is overwritten
static matrixMultiply(l, r, o) {
// Work out the product directly
const a = l.a * r.a + l.c * r.b
const b = l.b * r.a + l.d * r.b
const c = l.a * r.c + l.c * r.d
const d = l.b * r.c + l.d * r.d
const e = l.e + l.a * r.e + l.c * r.f
const f = l.f + l.b * r.e + l.d * r.f
// make sure to use local variables because l/r and o could be the same
o.a = a
o.b = b
o.c = c
o.d = d
o.e = e
o.f = f
return o
}
around(cx, cy, matrix) {
return this.clone().aroundO(cx, cy, matrix)
}
// Transform around a center point
aroundO(cx, cy, matrix) {
const dx = cx || 0
const dy = cy || 0
return this.translateO(-dx, -dy).lmultiplyO(matrix).translateO(dx, dy)
}
// Clones this matrix
clone() {
return new Matrix(this)
}
// Decomposes this matrix into its affine parameters
decompose(cx = 0, cy = 0) {
// Get the parameters from the matrix
const a = this.a
const b = this.b
const c = this.c
const d = this.d
const e = this.e
const f = this.f
// Figure out if the winding direction is clockwise or counterclockwise
const determinant = a * d - b * c
const ccw = determinant > 0 ? 1 : -1
// Since we only shear in x, we can use the x basis to get the x scale
// and the rotation of the resulting matrix
const sx = ccw * Math.sqrt(a * a + b * b)
const thetaRad = Math.atan2(ccw * b, ccw * a)
const theta = (180 / Math.PI) * thetaRad
const ct = Math.cos(thetaRad)
const st = Math.sin(thetaRad)
// We can then solve the y basis vector simultaneously to get the other
// two affine parameters directly from these parameters
const lam = (a * c + b * d) / determinant
const sy = (c * sx) / (lam * a - b) || (d * sx) / (lam * b + a)
// Use the translations
const tx = e - cx + cx * ct * sx + cy * (lam * ct * sx - st * sy)
const ty = f - cy + cx * st * sx + cy * (lam * st * sx + ct * sy)
// Construct the decomposition and return it
return {
// Return the affine parameters
scaleX: sx,
scaleY: sy,
shear: lam,
rotate: theta,
translateX: tx,
translateY: ty,
originX: cx,
originY: cy,
// Return the matrix parameters
a: this.a,
b: this.b,
c: this.c,
d: this.d,
e: this.e,
f: this.f
}
}
// Check if two matrices are equal
equals(other) {
if (other === this) return true
const comp = new Matrix(other)
return (
closeEnough(this.a, comp.a) &&
closeEnough(this.b, comp.b) &&
closeEnough(this.c, comp.c) &&
closeEnough(this.d, comp.d) &&
closeEnough(this.e, comp.e) &&
closeEnough(this.f, comp.f)
)
}
// Flip matrix on x or y, at a given offset
flip(axis, around) {
return this.clone().flipO(axis, around)
}
flipO(axis, around) {
return axis === 'x'
? this.scaleO(-1, 1, around, 0)
: axis === 'y'
? this.scaleO(1, -1, 0, around)
: this.scaleO(-1, -1, axis, around || axis) // Define an x, y flip point
}
// Initialize
init(source) {
const base = Matrix.fromArray([1, 0, 0, 1, 0, 0])
// ensure source as object
source =
source instanceof Element
? source.matrixify()
: typeof source === 'string'
? Matrix.fromArray(source.split(delimiter).map(parseFloat))
: Array.isArray(source)
? Matrix.fromArray(source)
: typeof source === 'object' && Matrix.isMatrixLike(source)
? source
: typeof source === 'object'
? new Matrix().transform(source)
: arguments.length === 6
? Matrix.fromArray([].slice.call(arguments))
: base
// Merge the source matrix with the base matrix
this.a = source.a != null ? source.a : base.a
this.b = source.b != null ? source.b : base.b
this.c = source.c != null ? source.c : base.c
this.d = source.d != null ? source.d : base.d
this.e = source.e != null ? source.e : base.e
this.f = source.f != null ? source.f : base.f
return this
}
inverse() {
return this.clone().inverseO()
}
// Inverses matrix
inverseO() {
// Get the current parameters out of the matrix
const a = this.a
const b = this.b
const c = this.c
const d = this.d
const e = this.e
const f = this.f
// Invert the 2x2 matrix in the top left
const det = a * d - b * c
if (!det) throw new Error('Cannot invert ' + this)
// Calculate the top 2x2 matrix
const na = d / det
const nb = -b / det
const nc = -c / det
const nd = a / det
// Apply the inverted matrix to the top right
const ne = -(na * e + nc * f)
const nf = -(nb * e + nd * f)
// Construct the inverted matrix
this.a = na
this.b = nb
this.c = nc
this.d = nd
this.e = ne
this.f = nf
return this
}
lmultiply(matrix) {
return this.clone().lmultiplyO(matrix)
}
lmultiplyO(matrix) {
const r = this
const l = matrix instanceof Matrix ? matrix : new Matrix(matrix)
return Matrix.matrixMultiply(l, r, this)
}
// Left multiplies by the given matrix
multiply(matrix) {
return this.clone().multiplyO(matrix)
}
multiplyO(matrix) {
// Get the matrices
const l = this
const r = matrix instanceof Matrix ? matrix : new Matrix(matrix)
return Matrix.matrixMultiply(l, r, this)
}
// Rotate matrix
rotate(r, cx, cy) {
return this.clone().rotateO(r, cx, cy)
}
rotateO(r, cx = 0, cy = 0) {
// Convert degrees to radians
r = radians(r)
const cos = Math.cos(r)
const sin = Math.sin(r)
const { a, b, c, d, e, f } = this
this.a = a * cos - b * sin
this.b = b * cos + a * sin
this.c = c * cos - d * sin
this.d = d * cos + c * sin
this.e = e * cos - f * sin + cy * sin - cx * cos + cx
this.f = f * cos + e * sin - cx * sin - cy * cos + cy
return this
}
// Scale matrix
scale() {
return this.clone().scaleO(...arguments)
}
scaleO(x, y = x, cx = 0, cy = 0) {
// Support uniform scaling
if (arguments.length === 3) {
cy = cx
cx = y
y = x
}
const { a, b, c, d, e, f } = this
this.a = a * x
this.b = b * y
this.c = c * x
this.d = d * y
this.e = e * x - cx * x + cx
this.f = f * y - cy * y + cy
return this
}
// Shear matrix
shear(a, cx, cy) {
return this.clone().shearO(a, cx, cy)
}
// eslint-disable-next-line no-unused-vars
shearO(lx, cx = 0, cy = 0) {
const { a, b, c, d, e, f } = this
this.a = a + b * lx
this.c = c + d * lx
this.e = e + f * lx - cy * lx
return this
}
// Skew Matrix
skew() {
return this.clone().skewO(...arguments)
}
skewO(x, y = x, cx = 0, cy = 0) {
// support uniformal skew
if (arguments.length === 3) {
cy = cx
cx = y
y = x
}
// Convert degrees to radians
x = radians(x)
y = radians(y)
const lx = Math.tan(x)
const ly = Math.tan(y)
const { a, b, c, d, e, f } = this
this.a = a + b * lx
this.b = b + a * ly
this.c = c + d * lx
this.d = d + c * ly
this.e = e + f * lx - cy * lx
this.f = f + e * ly - cx * ly
return this
}
// SkewX
skewX(x, cx, cy) {
return this.skew(x, 0, cx, cy)
}
// SkewY
skewY(y, cx, cy) {
return this.skew(0, y, cx, cy)
}
toArray() {
return [this.a, this.b, this.c, this.d, this.e, this.f]
}
// Convert matrix to string
toString() {
return (
'matrix(' +
this.a +
',' +
this.b +
',' +
this.c +
',' +
this.d +
',' +
this.e +
',' +
this.f +
')'
)
}
// Transform a matrix into another matrix by manipulating the space
transform(o) {
// Check if o is a matrix and then left multiply it directly
if (Matrix.isMatrixLike(o)) {
const matrix = new Matrix(o)
return matrix.multiplyO(this)
}
// Get the proposed transformations and the current transformations
const t = Matrix.formatTransforms(o)
const current = this
const { x: ox, y: oy } = new Point(t.ox, t.oy).transform(current)
// Construct the resulting matrix
const transformer = new Matrix()
.translateO(t.rx, t.ry)
.lmultiplyO(current)
.translateO(-ox, -oy)
.scaleO(t.scaleX, t.scaleY)
.skewO(t.skewX, t.skewY)
.shearO(t.shear)
.rotateO(t.theta)
.translateO(ox, oy)
// If we want the origin at a particular place, we force it there
if (isFinite(t.px) || isFinite(t.py)) {
const origin = new Point(ox, oy).transform(transformer)
// TODO: Replace t.px with isFinite(t.px)
// Doesn't work because t.px is also 0 if it wasn't passed
const dx = isFinite(t.px) ? t.px - origin.x : 0
const dy = isFinite(t.py) ? t.py - origin.y : 0
transformer.translateO(dx, dy)
}
// Translate now after positioning
transformer.translateO(t.tx, t.ty)
return transformer
}
// Translate matrix
translate(x, y) {
return this.clone().translateO(x, y)
}
translateO(x, y) {
this.e += x || 0
this.f += y || 0
return this
}
valueOf() {
return {
a: this.a,
b: this.b,
c: this.c,
d: this.d,
e: this.e,
f: this.f
}
}
}
export function ctm() {
return new Matrix(this.node.getCTM())
}
export function screenCTM() {
try {
/* https://bugzilla.mozilla.org/show_bug.cgi?id=1344537
This is needed because FF does not return the transformation matrix
for the inner coordinate system when getScreenCTM() is called on nested svgs.
However all other Browsers do that */
if (typeof this.isRoot === 'function' && !this.isRoot()) {
const rect = this.rect(1, 1)
const m = rect.node.getScreenCTM()
rect.remove()
return new Matrix(m)
}
return new Matrix(this.node.getScreenCTM())
} catch (e) {
console.warn(
`Cannot get CTM from SVG node ${this.node.nodeName}. Is the element rendered?`
)
return new Matrix()
}
}
register(Matrix, 'Matrix')

View File

@@ -0,0 +1,150 @@
import SVGArray from './SVGArray.js'
import parser from '../modules/core/parser.js'
import Box from './Box.js'
import { pathParser } from '../utils/pathParser.js'
function arrayToString(a) {
let s = ''
for (let i = 0, il = a.length; i < il; i++) {
s += a[i][0]
if (a[i][1] != null) {
s += a[i][1]
if (a[i][2] != null) {
s += ' '
s += a[i][2]
if (a[i][3] != null) {
s += ' '
s += a[i][3]
s += ' '
s += a[i][4]
if (a[i][5] != null) {
s += ' '
s += a[i][5]
s += ' '
s += a[i][6]
if (a[i][7] != null) {
s += ' '
s += a[i][7]
}
}
}
}
}
}
return s + ' '
}
export default class PathArray extends SVGArray {
// Get bounding box of path
bbox() {
parser().path.setAttribute('d', this.toString())
return new Box(parser.nodes.path.getBBox())
}
// Move path string
move(x, y) {
// get bounding box of current situation
const box = this.bbox()
// get relative offset
x -= box.x
y -= box.y
if (!isNaN(x) && !isNaN(y)) {
// move every point
for (let l, i = this.length - 1; i >= 0; i--) {
l = this[i][0]
if (l === 'M' || l === 'L' || l === 'T') {
this[i][1] += x
this[i][2] += y
} else if (l === 'H') {
this[i][1] += x
} else if (l === 'V') {
this[i][1] += y
} else if (l === 'C' || l === 'S' || l === 'Q') {
this[i][1] += x
this[i][2] += y
this[i][3] += x
this[i][4] += y
if (l === 'C') {
this[i][5] += x
this[i][6] += y
}
} else if (l === 'A') {
this[i][6] += x
this[i][7] += y
}
}
}
return this
}
// Absolutize and parse path to array
parse(d = 'M0 0') {
if (Array.isArray(d)) {
d = Array.prototype.concat.apply([], d).toString()
}
return pathParser(d)
}
// Resize path string
size(width, height) {
// get bounding box of current situation
const box = this.bbox()
let i, l
// If the box width or height is 0 then we ignore
// transformations on the respective axis
box.width = box.width === 0 ? 1 : box.width
box.height = box.height === 0 ? 1 : box.height
// recalculate position of all points according to new size
for (i = this.length - 1; i >= 0; i--) {
l = this[i][0]
if (l === 'M' || l === 'L' || l === 'T') {
this[i][1] = ((this[i][1] - box.x) * width) / box.width + box.x
this[i][2] = ((this[i][2] - box.y) * height) / box.height + box.y
} else if (l === 'H') {
this[i][1] = ((this[i][1] - box.x) * width) / box.width + box.x
} else if (l === 'V') {
this[i][1] = ((this[i][1] - box.y) * height) / box.height + box.y
} else if (l === 'C' || l === 'S' || l === 'Q') {
this[i][1] = ((this[i][1] - box.x) * width) / box.width + box.x
this[i][2] = ((this[i][2] - box.y) * height) / box.height + box.y
this[i][3] = ((this[i][3] - box.x) * width) / box.width + box.x
this[i][4] = ((this[i][4] - box.y) * height) / box.height + box.y
if (l === 'C') {
this[i][5] = ((this[i][5] - box.x) * width) / box.width + box.x
this[i][6] = ((this[i][6] - box.y) * height) / box.height + box.y
}
} else if (l === 'A') {
// resize radii
this[i][1] = (this[i][1] * width) / box.width
this[i][2] = (this[i][2] * height) / box.height
// move position values
this[i][6] = ((this[i][6] - box.x) * width) / box.width + box.x
this[i][7] = ((this[i][7] - box.y) * height) / box.height + box.y
}
}
return this
}
// Convert array to string
toString() {
return arrayToString(this)
}
}

View File

@@ -0,0 +1,57 @@
import Matrix from './Matrix.js'
export default class Point {
// Initialize
constructor(...args) {
this.init(...args)
}
// Clone point
clone() {
return new Point(this)
}
init(x, y) {
const base = { x: 0, y: 0 }
// ensure source as object
const source = Array.isArray(x)
? { x: x[0], y: x[1] }
: typeof x === 'object'
? { x: x.x, y: x.y }
: { x: x, y: y }
// merge source
this.x = source.x == null ? base.x : source.x
this.y = source.y == null ? base.y : source.y
return this
}
toArray() {
return [this.x, this.y]
}
transform(m) {
return this.clone().transformO(m)
}
// Transform point with matrix
transformO(m) {
if (!Matrix.isMatrixLike(m)) {
m = new Matrix(m)
}
const { x, y } = this
// Perform the matrix multiplication
this.x = m.a * x + m.c * y + m.e
this.y = m.b * x + m.d * y + m.f
return this
}
}
export function point(x, y) {
return new Point(x, y).transformO(this.screenCTM().inverseO())
}

View File

@@ -0,0 +1,121 @@
import { delimiter } from '../modules/core/regex.js'
import SVGArray from './SVGArray.js'
import Box from './Box.js'
import Matrix from './Matrix.js'
export default class PointArray extends SVGArray {
// Get bounding box of points
bbox() {
let maxX = -Infinity
let maxY = -Infinity
let minX = Infinity
let minY = Infinity
this.forEach(function (el) {
maxX = Math.max(el[0], maxX)
maxY = Math.max(el[1], maxY)
minX = Math.min(el[0], minX)
minY = Math.min(el[1], minY)
})
return new Box(minX, minY, maxX - minX, maxY - minY)
}
// Move point string
move(x, y) {
const box = this.bbox()
// get relative offset
x -= box.x
y -= box.y
// move every point
if (!isNaN(x) && !isNaN(y)) {
for (let i = this.length - 1; i >= 0; i--) {
this[i] = [this[i][0] + x, this[i][1] + y]
}
}
return this
}
// Parse point string and flat array
parse(array = [0, 0]) {
const points = []
// if it is an array, we flatten it and therefore clone it to 1 depths
if (array instanceof Array) {
array = Array.prototype.concat.apply([], array)
} else {
// Else, it is considered as a string
// parse points
array = array.trim().split(delimiter).map(parseFloat)
}
// validate points - https://svgwg.org/svg2-draft/shapes.html#DataTypePoints
// Odd number of coordinates is an error. In such cases, drop the last odd coordinate.
if (array.length % 2 !== 0) array.pop()
// wrap points in two-tuples
for (let i = 0, len = array.length; i < len; i = i + 2) {
points.push([array[i], array[i + 1]])
}
return points
}
// Resize poly string
size(width, height) {
let i
const box = this.bbox()
// recalculate position of all points according to new size
for (i = this.length - 1; i >= 0; i--) {
if (box.width)
this[i][0] = ((this[i][0] - box.x) * width) / box.width + box.x
if (box.height)
this[i][1] = ((this[i][1] - box.y) * height) / box.height + box.y
}
return this
}
// Convert array to line object
toLine() {
return {
x1: this[0][0],
y1: this[0][1],
x2: this[1][0],
y2: this[1][1]
}
}
// Convert array to string
toString() {
const array = []
// convert to a poly point string
for (let i = 0, il = this.length; i < il; i++) {
array.push(this[i].join(','))
}
return array.join(' ')
}
transform(m) {
return this.clone().transformO(m)
}
// transform points with matrix (similar to Point.transform)
transformO(m) {
if (!Matrix.isMatrixLike(m)) {
m = new Matrix(m)
}
for (let i = this.length; i--; ) {
// Perform the matrix multiplication
const [x, y] = this[i]
this[i][0] = m.a * x + m.c * y + m.e
this[i][1] = m.b * x + m.d * y + m.f
}
return this
}
}

View File

@@ -0,0 +1,47 @@
import { delimiter } from '../modules/core/regex.js'
export default class SVGArray extends Array {
constructor(...args) {
super(...args)
this.init(...args)
}
clone() {
return new this.constructor(this)
}
init(arr) {
// This catches the case, that native map tries to create an array with new Array(1)
if (typeof arr === 'number') return this
this.length = 0
this.push(...this.parse(arr))
return this
}
// Parse whitespace separated string
parse(array = []) {
// If already is an array, no need to parse it
if (array instanceof Array) return array
return array.trim().split(delimiter).map(parseFloat)
}
toArray() {
return Array.prototype.concat.apply([], this)
}
toSet() {
return new Set(this)
}
toString() {
return this.join(' ')
}
// Flattens the array if needed
valueOf() {
const ret = []
ret.push(...this)
return ret
}
}

View File

@@ -0,0 +1,104 @@
import { numberAndUnit } from '../modules/core/regex.js'
// Module for unit conversions
export default class SVGNumber {
// Initialize
constructor(...args) {
this.init(...args)
}
convert(unit) {
return new SVGNumber(this.value, unit)
}
// Divide number
divide(number) {
number = new SVGNumber(number)
return new SVGNumber(this / number, this.unit || number.unit)
}
init(value, unit) {
unit = Array.isArray(value) ? value[1] : unit
value = Array.isArray(value) ? value[0] : value
// initialize defaults
this.value = 0
this.unit = unit || ''
// parse value
if (typeof value === 'number') {
// ensure a valid numeric value
this.value = isNaN(value)
? 0
: !isFinite(value)
? value < 0
? -3.4e38
: +3.4e38
: value
} else if (typeof value === 'string') {
unit = value.match(numberAndUnit)
if (unit) {
// make value numeric
this.value = parseFloat(unit[1])
// normalize
if (unit[5] === '%') {
this.value /= 100
} else if (unit[5] === 's') {
this.value *= 1000
}
// store unit
this.unit = unit[5]
}
} else {
if (value instanceof SVGNumber) {
this.value = value.valueOf()
this.unit = value.unit
}
}
return this
}
// Subtract number
minus(number) {
number = new SVGNumber(number)
return new SVGNumber(this - number, this.unit || number.unit)
}
// Add number
plus(number) {
number = new SVGNumber(number)
return new SVGNumber(this + number, this.unit || number.unit)
}
// Multiply number
times(number) {
number = new SVGNumber(number)
return new SVGNumber(this * number, this.unit || number.unit)
}
toArray() {
return [this.value, this.unit]
}
toJSON() {
return this.toString()
}
toString() {
return (
(this.unit === '%'
? ~~(this.value * 1e8) / 1e6
: this.unit === 's'
? this.value / 1e3
: this.value) + this.unit
)
}
valueOf() {
return this.value
}
}

View File

@@ -0,0 +1,145 @@
import { addMethodNames } from './methods.js'
import { capitalize } from './utils.js'
import { svg } from '../modules/core/namespaces.js'
import { globals } from '../utils/window.js'
import Base from '../types/Base.js'
const elements = {}
export const root = '___SYMBOL___ROOT___'
// Method for element creation
export function create(name, ns = svg) {
// create element
return globals.document.createElementNS(ns, name)
}
export function makeInstance(element, isHTML = false) {
if (element instanceof Base) return element
if (typeof element === 'object') {
return adopter(element)
}
if (element == null) {
return new elements[root]()
}
if (typeof element === 'string' && element.charAt(0) !== '<') {
return adopter(globals.document.querySelector(element))
}
// Make sure, that HTML elements are created with the correct namespace
const wrapper = isHTML ? globals.document.createElement('div') : create('svg')
wrapper.innerHTML = element
// We can use firstChild here because we know,
// that the first char is < and thus an element
element = adopter(wrapper.firstChild)
// make sure, that element doesn't have its wrapper attached
wrapper.removeChild(wrapper.firstChild)
return element
}
export function nodeOrNew(name, node) {
return node &&
(node instanceof globals.window.Node ||
(node.ownerDocument &&
node instanceof node.ownerDocument.defaultView.Node))
? node
: create(name)
}
// Adopt existing svg elements
export function adopt(node) {
// check for presence of node
if (!node) return null
// make sure a node isn't already adopted
if (node.instance instanceof Base) return node.instance
if (node.nodeName === '#document-fragment') {
return new elements.Fragment(node)
}
// initialize variables
let className = capitalize(node.nodeName || 'Dom')
// Make sure that gradients are adopted correctly
if (className === 'LinearGradient' || className === 'RadialGradient') {
className = 'Gradient'
// Fallback to Dom if element is not known
} else if (!elements[className]) {
className = 'Dom'
}
return new elements[className](node)
}
let adopter = adopt
export function mockAdopt(mock = adopt) {
adopter = mock
}
export function register(element, name = element.name, asRoot = false) {
elements[name] = element
if (asRoot) elements[root] = element
addMethodNames(Object.getOwnPropertyNames(element.prototype))
return element
}
export function getClass(name) {
return elements[name]
}
// Element id sequence
let did = 1000
// Get next named element id
export function eid(name) {
return 'Svgjs' + capitalize(name) + did++
}
// Deep new id assignment
export function assignNewId(node) {
// do the same for SVG child nodes as well
for (let i = node.children.length - 1; i >= 0; i--) {
assignNewId(node.children[i])
}
if (node.id) {
node.id = eid(node.nodeName)
return node
}
return node
}
// Method for extending objects
export function extend(modules, methods) {
let key, i
modules = Array.isArray(modules) ? modules : [modules]
for (i = modules.length - 1; i >= 0; i--) {
for (key in methods) {
modules[i].prototype[key] = methods[key]
}
}
}
export function wrapWithAttrCheck(fn) {
return function (...args) {
const o = args[args.length - 1]
if (o && o.constructor === Object && !(o instanceof Array)) {
return fn.apply(this, args.slice(0, -1)).attr(o)
} else {
return fn.apply(this, args)
}
}
}

View File

@@ -0,0 +1,33 @@
const methods = {}
const names = []
export function registerMethods(name, m) {
if (Array.isArray(name)) {
for (const _name of name) {
registerMethods(_name, m)
}
return
}
if (typeof name === 'object') {
for (const _name in name) {
registerMethods(_name, name[_name])
}
return
}
addMethodNames(Object.getOwnPropertyNames(m))
methods[name] = Object.assign(methods[name] || {}, m)
}
export function getMethodsFor(name) {
return methods[name] || {}
}
export function getMethodNames() {
return [...new Set(names)]
}
export function addMethodNames(_names) {
names.push(..._names)
}

View File

@@ -0,0 +1,250 @@
import { isPathLetter } from '../modules/core/regex.js'
import Point from '../types/Point.js'
const segmentParameters = {
M: 2,
L: 2,
H: 1,
V: 1,
C: 6,
S: 4,
Q: 4,
T: 2,
A: 7,
Z: 0
}
const pathHandlers = {
M: function (c, p, p0) {
p.x = p0.x = c[0]
p.y = p0.y = c[1]
return ['M', p.x, p.y]
},
L: function (c, p) {
p.x = c[0]
p.y = c[1]
return ['L', c[0], c[1]]
},
H: function (c, p) {
p.x = c[0]
return ['H', c[0]]
},
V: function (c, p) {
p.y = c[0]
return ['V', c[0]]
},
C: function (c, p) {
p.x = c[4]
p.y = c[5]
return ['C', c[0], c[1], c[2], c[3], c[4], c[5]]
},
S: function (c, p) {
p.x = c[2]
p.y = c[3]
return ['S', c[0], c[1], c[2], c[3]]
},
Q: function (c, p) {
p.x = c[2]
p.y = c[3]
return ['Q', c[0], c[1], c[2], c[3]]
},
T: function (c, p) {
p.x = c[0]
p.y = c[1]
return ['T', c[0], c[1]]
},
Z: function (c, p, p0) {
p.x = p0.x
p.y = p0.y
return ['Z']
},
A: function (c, p) {
p.x = c[5]
p.y = c[6]
return ['A', c[0], c[1], c[2], c[3], c[4], c[5], c[6]]
}
}
const mlhvqtcsaz = 'mlhvqtcsaz'.split('')
for (let i = 0, il = mlhvqtcsaz.length; i < il; ++i) {
pathHandlers[mlhvqtcsaz[i]] = (function (i) {
return function (c, p, p0) {
if (i === 'H') c[0] = c[0] + p.x
else if (i === 'V') c[0] = c[0] + p.y
else if (i === 'A') {
c[5] = c[5] + p.x
c[6] = c[6] + p.y
} else {
for (let j = 0, jl = c.length; j < jl; ++j) {
c[j] = c[j] + (j % 2 ? p.y : p.x)
}
}
return pathHandlers[i](c, p, p0)
}
})(mlhvqtcsaz[i].toUpperCase())
}
function makeAbsolut(parser) {
const command = parser.segment[0]
return pathHandlers[command](parser.segment.slice(1), parser.p, parser.p0)
}
function segmentComplete(parser) {
return (
parser.segment.length &&
parser.segment.length - 1 ===
segmentParameters[parser.segment[0].toUpperCase()]
)
}
function startNewSegment(parser, token) {
parser.inNumber && finalizeNumber(parser, false)
const pathLetter = isPathLetter.test(token)
if (pathLetter) {
parser.segment = [token]
} else {
const lastCommand = parser.lastCommand
const small = lastCommand.toLowerCase()
const isSmall = lastCommand === small
parser.segment = [small === 'm' ? (isSmall ? 'l' : 'L') : lastCommand]
}
parser.inSegment = true
parser.lastCommand = parser.segment[0]
return pathLetter
}
function finalizeNumber(parser, inNumber) {
if (!parser.inNumber) throw new Error('Parser Error')
parser.number && parser.segment.push(parseFloat(parser.number))
parser.inNumber = inNumber
parser.number = ''
parser.pointSeen = false
parser.hasExponent = false
if (segmentComplete(parser)) {
finalizeSegment(parser)
}
}
function finalizeSegment(parser) {
parser.inSegment = false
if (parser.absolute) {
parser.segment = makeAbsolut(parser)
}
parser.segments.push(parser.segment)
}
function isArcFlag(parser) {
if (!parser.segment.length) return false
const isArc = parser.segment[0].toUpperCase() === 'A'
const length = parser.segment.length
return isArc && (length === 4 || length === 5)
}
function isExponential(parser) {
return parser.lastToken.toUpperCase() === 'E'
}
const pathDelimiters = new Set([' ', ',', '\t', '\n', '\r', '\f'])
export function pathParser(d, toAbsolute = true) {
let index = 0
let token = ''
const parser = {
segment: [],
inNumber: false,
number: '',
lastToken: '',
inSegment: false,
segments: [],
pointSeen: false,
hasExponent: false,
absolute: toAbsolute,
p0: new Point(),
p: new Point()
}
while (((parser.lastToken = token), (token = d.charAt(index++)))) {
if (!parser.inSegment) {
if (startNewSegment(parser, token)) {
continue
}
}
if (token === '.') {
if (parser.pointSeen || parser.hasExponent) {
finalizeNumber(parser, false)
--index
continue
}
parser.inNumber = true
parser.pointSeen = true
parser.number += token
continue
}
if (!isNaN(parseInt(token))) {
if (parser.number === '0' || isArcFlag(parser)) {
parser.inNumber = true
parser.number = token
finalizeNumber(parser, true)
continue
}
parser.inNumber = true
parser.number += token
continue
}
if (pathDelimiters.has(token)) {
if (parser.inNumber) {
finalizeNumber(parser, false)
}
continue
}
if (token === '-' || token === '+') {
if (parser.inNumber && !isExponential(parser)) {
finalizeNumber(parser, false)
--index
continue
}
parser.number += token
parser.inNumber = true
continue
}
if (token.toUpperCase() === 'E') {
parser.number += token
parser.hasExponent = true
continue
}
if (isPathLetter.test(token)) {
if (parser.inNumber) {
finalizeNumber(parser, false)
} else if (!segmentComplete(parser)) {
throw new Error('parser Error')
} else {
finalizeSegment(parser)
}
--index
}
}
if (parser.inNumber) {
finalizeNumber(parser, false)
}
if (parser.inSegment && segmentComplete(parser)) {
finalizeSegment(parser)
}
return parser.segments
}

View File

@@ -0,0 +1,136 @@
// Map function
export function map(array, block) {
let i
const il = array.length
const result = []
for (i = 0; i < il; i++) {
result.push(block(array[i]))
}
return result
}
// Filter function
export function filter(array, block) {
let i
const il = array.length
const result = []
for (i = 0; i < il; i++) {
if (block(array[i])) {
result.push(array[i])
}
}
return result
}
// Degrees to radians
export function radians(d) {
return ((d % 360) * Math.PI) / 180
}
// Radians to degrees
export function degrees(r) {
return ((r * 180) / Math.PI) % 360
}
// Convert camel cased string to dash separated
export function unCamelCase(s) {
return s.replace(/([A-Z])/g, function (m, g) {
return '-' + g.toLowerCase()
})
}
// Capitalize first letter of a string
export function capitalize(s) {
return s.charAt(0).toUpperCase() + s.slice(1)
}
// Calculate proportional width and height values when necessary
export function proportionalSize(element, width, height, box) {
if (width == null || height == null) {
box = box || element.bbox()
if (width == null) {
width = (box.width / box.height) * height
} else if (height == null) {
height = (box.height / box.width) * width
}
}
return {
width: width,
height: height
}
}
/**
* This function adds support for string origins.
* It searches for an origin in o.origin o.ox and o.originX.
* This way, origin: {x: 'center', y: 50} can be passed as well as ox: 'center', oy: 50
**/
export function getOrigin(o, element) {
const origin = o.origin
// First check if origin is in ox or originX
let ox = o.ox != null ? o.ox : o.originX != null ? o.originX : 'center'
let oy = o.oy != null ? o.oy : o.originY != null ? o.originY : 'center'
// Then check if origin was used and overwrite in that case
if (origin != null) {
;[ox, oy] = Array.isArray(origin)
? origin
: typeof origin === 'object'
? [origin.x, origin.y]
: [origin, origin]
}
// Make sure to only call bbox when actually needed
const condX = typeof ox === 'string'
const condY = typeof oy === 'string'
if (condX || condY) {
const { height, width, x, y } = element.bbox()
// And only overwrite if string was passed for this specific axis
if (condX) {
ox = ox.includes('left')
? x
: ox.includes('right')
? x + width
: x + width / 2
}
if (condY) {
oy = oy.includes('top')
? y
: oy.includes('bottom')
? y + height
: y + height / 2
}
}
// Return the origin as it is if it wasn't a string
return [ox, oy]
}
const descriptiveElements = new Set(['desc', 'metadata', 'title'])
export const isDescriptive = (element) =>
descriptiveElements.has(element.nodeName)
export const writeDataToDom = (element, data, defaults = {}) => {
const cloned = { ...data }
for (const key in cloned) {
if (cloned[key].valueOf() === defaults[key]) {
delete cloned[key]
}
}
if (Object.keys(cloned).length) {
element.node.setAttribute('data-svgjs', JSON.stringify(cloned)) // see #428
} else {
element.node.removeAttribute('data-svgjs')
element.node.removeAttribute('svgjs:data')
}
}

View File

@@ -0,0 +1,32 @@
export const globals = {
window: typeof window === 'undefined' ? null : window,
document: typeof document === 'undefined' ? null : document
}
export function registerWindow(win = null, doc = null) {
globals.window = win
globals.document = doc
}
const save = {}
export function saveWindow() {
save.window = globals.window
save.document = globals.document
}
export function restoreWindow() {
globals.window = save.window
globals.document = save.document
}
export function withWindow(win, fn) {
saveWindow()
registerWindow(win, win.document)
fn(win, win.document)
restoreWindow()
}
export function getWindow() {
return globals.window
}

1982
frontend/node_modules/@svgdotjs/svg.js/svg.js.d.ts generated vendored Normal file

File diff suppressed because it is too large Load Diff