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,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')