svg-kit
Version:
A SVG toolkit, with its own Vector Graphics structure, multiple renderers (svg text, DOM svg, canvas), and featuring Flowing Text.
1,752 lines (1,267 loc) • 1.55 MB
JavaScript
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.svgKit = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
/*
SVG Kit
Copyright (c) 2017 - 2024 Cédric Ronvel
The MIT License (MIT)
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.
*/
"use strict" ;
function BoundingBox( xmin = 0 , ymin = 0 , xmax = xmin , ymax = ymin ) {
this.xmin = 0 ;
this.ymin = 0 ;
this.xmax = 0 ;
this.ymax = 0 ;
if ( xmin && typeof xmin === 'object' ) {
this.set( xmin ) ;
}
else if ( xmin === null ) {
this.xmin = Infinity ;
this.xmax = - Infinity ;
this.ymin = Infinity ;
this.ymax = - Infinity ;
}
else {
this.xmin = xmin ;
this.ymin = ymin ;
this.xmax = xmax ;
this.ymax = ymax ;
}
}
module.exports = BoundingBox ;
BoundingBox.prototype.set = function( params ) {
if ( params instanceof BoundingBox ) {
this.xmin = params.xmin ;
this.ymin = params.ymin ;
this.xmax = params.xmax ;
this.ymax = params.ymax ;
return ;
}
if ( params.xmin !== undefined ) { this.xmin = params.xmin ; }
if ( params.ymin !== undefined ) { this.ymin = params.ymin ; }
if ( params.xmax !== undefined ) { this.xmax = params.xmax ; }
if ( params.ymax !== undefined ) { this.ymax = params.ymax ; }
if ( params.x !== undefined ) { this.x = params.x ; }
if ( params.y !== undefined ) { this.y = params.y ; }
if ( params.width !== undefined ) { this.width = params.width ; }
if ( params.height !== undefined ) { this.height = params.height ; }
} ;
// Shorter update lines
BoundingBox.prototype.setMinMax = function( xmin = 0 , ymin = 0 , xmax = xmin , ymax = ymin ) {
this.xmin = xmin ;
this.ymin = ymin ;
this.xmax = xmax ;
this.ymax = ymax ;
} ;
// Prepare a bounding box for a sequence of .merge()/.ensurePoint(), making it negatively infinitely large
BoundingBox.prototype.nullify = function() {
this.xmin = Infinity ;
this.xmax = - Infinity ;
this.ymin = Infinity ;
this.ymax = - Infinity ;
} ;
// A null bounding box is a neutral bounding box ready for any .merge()/.ensurePoint() operation
BoundingBox.prototype.isNull = function() {
return this.xmin === Infinity && this.xmax === - Infinity && this.ymin === Infinity && this.ymax === - Infinity ;
} ;
// A zero bounding box is has no area (or a negative one)
BoundingBox.prototype.isZero = function() {
return this.xmin >= this.xmax || this.ymin >= this.ymax ;
} ;
Object.defineProperties( BoundingBox.prototype , {
x: {
get: function() { return this.xmin ; } ,
set: function( x ) {
// Preserve width
var width = this.xmax - this.xmin ;
this.xmin = x ;
this.xmax = this.xmin + width ;
}
} ,
y: {
get: function() { return this.ymin ; } ,
set: function( y ) {
// Preserve height
var height = this.ymax - this.ymin ;
this.ymin = y ;
this.ymax = this.ymin + height ;
}
} ,
width: {
get: function() { return this.xmax - this.xmin ; } ,
set: function( width ) { this.xmax = this.xmin + width ; }
} ,
height: {
get: function() { return this.ymax - this.ymin ; } ,
set: function( height ) { this.ymax = this.ymin + height ; }
}
} ) ;
BoundingBox.prototype.export = function() {
return {
xmin: this.xmin ,
ymin: this.ymin ,
xmax: this.xmax ,
ymax: this.ymax
} ;
} ;
BoundingBox.prototype.dup =
BoundingBox.prototype.clone = function() {
return new BoundingBox( this.xmin , this.ymin , this.xmax , this.ymax ) ;
} ;
// Floor min and ceil max
BoundingBox.prototype.round = function( bbox ) {
this.xmin = Math.floor( this.xmin ) ;
this.ymin = Math.floor( this.ymin ) ;
this.xmax = Math.ceil( this.xmax ) ;
this.ymax = Math.ceil( this.ymax ) ;
return this ;
} ;
// Merge two bounding box
BoundingBox.prototype.merge = function( bbox ) {
this.xmin = Math.min( this.xmin , bbox.xmin ) ;
this.ymin = Math.min( this.ymin , bbox.ymin ) ;
this.xmax = Math.max( this.xmax , bbox.xmax ) ;
this.ymax = Math.max( this.ymax , bbox.ymax ) ;
return this ;
} ;
BoundingBox.prototype.mergeArray = function( bboxList ) {
for ( let bbox of bboxList ) {
this.xmin = Math.min( this.xmin , bbox.xmin ) ;
this.ymin = Math.min( this.ymin , bbox.ymin ) ;
this.xmax = Math.max( this.xmax , bbox.xmax ) ;
this.ymax = Math.max( this.ymax , bbox.ymax ) ;
}
return this ;
} ;
// Ensure that the bounding box includes a point, like .merge() but for singularity
BoundingBox.prototype.ensurePoint = function( point ) {
this.xmin = Math.min( this.xmin , point.x ) ;
this.ymin = Math.min( this.ymin , point.y ) ;
this.xmax = Math.max( this.xmax , point.x ) ;
this.ymax = Math.max( this.ymax , point.y ) ;
return this ;
} ;
BoundingBox.prototype.ensurePointArray = function( pointList ) {
for ( let point of pointList ) {
this.xmin = Math.min( this.xmin , point.x ) ;
this.ymin = Math.min( this.ymin , point.y ) ;
this.xmax = Math.max( this.xmax , point.x ) ;
this.ymax = Math.max( this.ymax , point.y ) ;
}
return this ;
} ;
BoundingBox.prototype.isEqualTo = function( bbox ) {
return bbox.xmin === this.xmin && bbox.xmax === this.xmax && bbox.ymin === this.ymin && bbox.ymax === this.ymax ;
} ;
// Is equal to a foreign object, support both the xmin/xmax/ymin/ymax and the x/y/width/height format
BoundingBox.prototype.isEqualToObject = function( object ) {
if ( object.width !== undefined ) {
return object.x === this.xmin && object.width === this.width && object.y === this.ymin && object.height === this.height ;
}
if ( object.xmin !== undefined ) {
return object.xmin === this.xmin && object.xmax === this.xmax && object.ymin === this.ymin && object.ymax === this.ymax ;
}
return false ;
} ;
BoundingBox.prototype.isInside = function( coords ) {
return coords.x >= this.xmin && coords.x <= this.xmax && coords.y >= this.ymin && coords.y <= this.ymax ;
} ;
BoundingBox.prototype.enlarge = function( value ) {
value = + value || 0 ;
if ( value < 0 ) { return this.shrink( - value ) ; }
this.xmin -= value ;
this.xmax += value ;
this.ymin -= value ;
this.ymax += value ;
return this ;
} ;
BoundingBox.prototype.shrink = function( value , min = 0 ) {
value = + value || 0 ;
if ( value < 0 ) { return this.enlarge( - value ) ; }
var width = this.xmax - this.xmin ,
height = this.ymax - this.ymin ;
if ( width < 2 * value ) {
this.xmin += width / 2 - min / 2 ;
this.xmax -= width / 2 - min / 2 ;
}
else {
this.xmin += value ;
this.xmax -= value ;
}
if ( height < 2 * value ) {
this.ymin += height / 2 - min / 2 ;
this.ymax -= height / 2 - min / 2 ;
}
else {
this.ymin += value ;
this.ymax -= value ;
}
return this ;
} ;
},{}],2:[function(require,module,exports){
/*
SVG Kit
Copyright (c) 2017 - 2024 Cédric Ronvel
The MIT License (MIT)
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.
*/
"use strict" ;
const Polygon = require( './Polygon.js' ) ;
const BoundingBox = require( './BoundingBox.js' ) ;
function ConvexPolygon( params ) {
this.points = [] ;
this.sides = [] ;
this.boundingBox = new BoundingBox( null ) ;
this.badConvexPolygon = false ;
if ( params ) { this.set( params ) ; }
}
module.exports = ConvexPolygon ;
ConvexPolygon.prototype = Object.create( Polygon.prototype ) ;
ConvexPolygon.prototype.constructor = ConvexPolygon ;
ConvexPolygon.prototype.__prototypeUID__ = 'svg-kit/ConvexPolygon' ;
ConvexPolygon.prototype.__prototypeVersion__ = require( '../package.json' ).version ;
ConvexPolygon.prototype.set = function( params ) {
Polygon.prototype.set.call( this , params ) ;
} ;
ConvexPolygon.prototype.setPoints = function( points ) {
Polygon.prototype.setPoints.call( this , points ) ;
} ;
ConvexPolygon.prototype.isInside = function( coords ) {
if ( ! this.sides.length ) { return false ; }
return this.totalAngle > 0 ?
this.sides.every( side => Polygon.testSideLineEquation( side , coords ) >= 0 ) :
this.sides.every( side => Polygon.testSideLineEquation( side , coords ) <= 0 ) ;
} ;
ConvexPolygon.prototype.checkConvex = function() {
// Checking convexity is easy : just check that each point lie on the correct side of its line equation
this.badConvexPolygon = false ;
if ( ! this.sides.length ) { return true ; }
for ( let i = 0 ; i < this.sides.length ; i ++ ) {
let side = this.sides[ i ] ;
for ( let j = i + 2 ; j < this.points.length ; j ++ ) {
// We don't check points of the current side
if ( j === i || j === ( i + 1 ) % this.sides.length ) { continue ; }
let point = this.points[ j ] ;
let ok = this.totalAngle > 0 ?
Polygon.testSideLineEquation( side , point ) > 0 :
Polygon.testSideLineEquation( side , point ) < 0 ;
if ( ! ok ) {
console.warn( "Bad convex polygon, probably not simple/convex" ) ;
this.badConvexPolygon = true ;
return false ;
}
}
}
return true ;
} ;
},{"../package.json":110,"./BoundingBox.js":1,"./Polygon.js":14}],3:[function(require,module,exports){
/*
SVG Kit
Copyright (c) 2017 - 2024 Cédric Ronvel
The MIT License (MIT)
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.
*/
"use strict" ;
//const svgKit = require( './svg-kit.js' ) ;
const BoundingBox = require( './BoundingBox.js' ) ;
function DynamicArea( entity , params ) {
// A status change will trigger the redraw.
// Can be things like on/off, or a tick number for animation.
this.availableStatus = new Set() ; // The list of status that this dynamic area can have
this.status = 'base' ;
this.everyTick = 1 ; // Divide the DynamicManager's tick by this value before setting up the DynamicArea's tick
this.tick = 0 ; // A tick change will trigger the redraw
// Should be a Path2D, from time to time, we will have to check if some events are inside the area or not.
// The boundingBox act as a fast early out test.
this.area = null ;
this.boundingBox = new BoundingBox( entity.boundingBox ) ;
this.statusData = {} ;
this.morph = null ;
this.toEmit = [] ; // Pending events to be emited
this.noRedraw = params.noRedraw || false ; // If set, this dynamic area is not used to trigger redraw (probably just to send event back)
this.outdated = false ; // If set, redraw is needed
this.backgroundImageData = null ; // Image data stored for the redraw
this.useEntityBackgroundImageData = true ; // Use the entity's image data instead of the dynamic area one
// Non-enumerable properties (better for displaying the data)
Object.defineProperties( this , {
entity: { value: entity } // The entity where to apply things
} ) ;
this.set( params ) ;
// Force .outdated back to false after the initial call to .set()
this.outdated = false ;
if ( this.useEntityBackgroundImageData && ! this.noRedraw ) {
this.entity.needFullDraw = true ;
if ( this.entity.root ) { this.entity.root.needFullDraw = true ; }
}
}
module.exports = DynamicArea ;
DynamicArea.prototype.__prototypeUID__ = 'svg-kit/DynamicArea' ;
DynamicArea.prototype.__prototypeVersion__ = require( '../package.json' ).version ;
DynamicArea.prototype.set = function( params ) {
if ( params.everyTick ) { this.everyTick = + params.everyTick || 1 ; }
if ( params.statusData && typeof params.statusData === 'object' ) {
for ( let statusName in params.statusData ) {
this.setStatusMorph( statusName , params.statusData[ statusName ] ) ;
}
}
if ( params.status ) { this.setStatus( params.status ) ; }
if ( params.boundingBox && typeof params.boundingBox === 'object' ) {
this.boundingBox.set( params.boundingBox ) ;
if ( this.useEntityBackgroundImageData && ! this.boundingBox.isEqualTo( this.entity.boundingBox ) ) {
this.useEntityBackgroundImageData = false ;
}
}
} ;
DynamicArea.prototype.setStatusMorph = function( statusName , data ) {
if ( ! this.availableStatus.has( statusName ) ) { this.availableStatus.add( statusName ) ; }
if ( data.frames ) { data.modulo = data.frames.reduce( ( v , e ) => v + ( e.ticks || 1 ) , 0 ) ; }
this.statusData[ statusName ] = data ;
if ( ! this.noRedraw ) { this.outdated = true ; }
} ;
DynamicArea.prototype.setStatus = function( status ) {
if ( status && status !== this.status && this.availableStatus.has( status ) ) {
//console.warn( "DynamicArea#setStatus() for:" , this.entity.__prototypeUID__ ) ;
this.status = status ;
this.updateMorph() ;
let data = this.statusData[ this.status ] ;
if ( data.emit ) {
let event = {
name: data.emit.name ,
data: Object.assign( {} , data.emit.data )
} ;
event.data.boundingBox = this.boundingBox.export() ;
event.data.entityUid = this.entity._id ;
this.toEmit.push( event ) ;
}
}
} ;
DynamicArea.prototype.setTick = function( tick ) {
tick = Math.floor( tick / this.everyTick ) ;
if ( tick === this.tick ) { return ; }
this.tick = tick ;
this.updateMorph() ;
} ;
DynamicArea.prototype.updateMorph = function() {
//console.warn( "> DynamicArea#updateMorph() for:" , this.entity.__prototypeUID__ ) ;
var data = this.statusData[ this.status ] ;
if ( ! data ) { return ; }
var morph = null ;
if ( data.morph ) {
morph = data.morph ;
}
else if ( data.frames ) {
let tickModulo = this.tick % data.modulo ;
for ( let frame of data.frames ) {
if ( tickModulo < frame.ticks ) {
morph = frame.morph ;
break ;
}
tickModulo -= frame.ticks ;
}
}
else if ( data.eachFrame ) {
morph = data.eachFrame( this ) ;
}
else {
return ;
}
if ( morph === true ) {
// Special case: force a redraw without changing anything, mostly for special FX, or random translations like tremors
if ( ! this.noRedraw ) { this.outdated = true ; }
return ;
}
if ( ! morph || morph === this.morph ) { return ; }
this.morph = morph ;
this.entity.set( this.morph ) ;
if ( ! this.noRedraw ) { this.outdated = true ; }
//console.log( "==> " , this.noRedraw , this.outdated ) ;
} ;
// Get the data that can be emitted as event
DynamicArea.prototype.getEmittableEvent = function( eventName ) {
for ( let status in this.statusData ) {
let data = this.statusData[ status ] ;
if ( data.emit?.name === eventName ) {
let eventData = Object.assign( {} , data.emit.data ) ;
eventData.boundingBox = this.boundingBox.export() ;
eventData.entityUid = this.entity._id ;
return eventData ;
}
}
return null ;
} ;
DynamicArea.prototype.save = function( canvasCtx ) {
if ( this.noRedraw ) { return ; }
if ( this.useEntityBackgroundImageData ) {
if ( this.entity.backgroundImageUsed ) { return ; }
//console.warn( "::: Saving backgroundImageData for:" , this.entity.__prototypeUID__.slice( 8 ) + ' ' + this.entity._id ) ;
this.entity.backgroundImageData = canvasCtx.getImageData(
this.boundingBox.x , this.boundingBox.y ,
this.boundingBox.width , this.boundingBox.height
) ;
this.entity.backgroundImageUsed = true ;
}
else {
this.backgroundImageData = canvasCtx.getImageData(
this.boundingBox.x , this.boundingBox.y ,
this.boundingBox.width , this.boundingBox.height
) ;
}
} ;
DynamicArea.prototype.restore = function( canvasCtx ) {
if ( this.noRedraw ) { return ; }
if ( this.useEntityBackgroundImageData ) {
if ( this.entity.backgroundImageUsed ) { return ; }
if ( ! this.entity.backgroundImageData ) {
//console.warn( "!!! Missing backgroundImageData:" , this.entity.__prototypeUID__.slice( 8 ) + ' ' + this.entity._id ) ;
}
canvasCtx.putImageData( this.entity.backgroundImageData , this.boundingBox.x , this.boundingBox.y ) ;
this.entity.backgroundImageUsed = true ;
}
else {
canvasCtx.putImageData( this.backgroundImageData , this.boundingBox.x , this.boundingBox.y ) ;
}
} ;
},{"../package.json":110,"./BoundingBox.js":1}],4:[function(require,module,exports){
/*
SVG Kit
Copyright (c) 2017 - 2024 Cédric Ronvel
The MIT License (MIT)
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.
*/
"use strict" ;
const canvasUtilities = require( './canvas-utilities.js' ) ;
const LeanEvents = require( 'nextgen-events/lib/LeanEvents.js' ) ;
const Promise = require( 'seventh' ) ;
function DynamicManager( ctx , vg , tickTime ) {
this.ctx = ctx ;
this.vg = vg ;
this.tickTime = tickTime || 100 ;
this.tick = 0 ;
this.timer = null ;
this.running = false ;
this.canvasListeners = [] ;
this.babylonControl = null ;
this.babylonControlListeners = [] ;
this.toEmit = [] ; // Pending events to be emited
// A debounced redraw
this.redraw = Promise.debounceUpdate( { delay: this.tickTime / 2 } , async () => {
//console.warn( "###! redraw()" ) ;
await this.vg.redrawCanvas( this.ctx ) ;
this.emit( 'redraw' ) ;
/*
try {
await this.vg.redrawCanvas( this.ctx ) ;
this.emit( 'redraw' ) ;
} catch ( error ) {
console.error( "Error:" , error ) ;
this.destroy() ;
return ;
}
//*/
//console.warn( "###! redrawn()" ) ;
} ) ;
}
module.exports = DynamicManager ;
DynamicManager.prototype = Object.create( LeanEvents.prototype ) ;
DynamicManager.prototype.constructor = DynamicManager ;
DynamicManager.prototype.__prototypeUID__ = 'svg-kit/DynamicManager' ;
DynamicManager.prototype.__prototypeVersion__ = require( '../package.json' ).version ;
DynamicManager.prototype.destroy = function() {
this.reset() ;
} ;
DynamicManager.prototype.reset = function() {
if ( this.timer ) {
clearInterval( this.timer ) ;
this.timer = null ;
}
this.clearCanvasEventListener() ;
this.clearBabylonControlEventListener() ;
this.babylonControl = null ;
this.running = false ;
} ;
DynamicManager.prototype.emitPendingEvents = function() {
for ( let event of this.toEmit ) {
this.emit( event.name , event.data ) ;
}
this.toEmit.length = 0 ;
} ;
DynamicManager.prototype.onTick = function() {
//console.warn( "###! onTick()" ) ;
this.tick ++ ;
let outdated = false ;
for ( let dynamic of this.vg.dynamicAreaIterator() ) {
dynamic.setTick( this.tick ) ;
if ( dynamic.outdated ) {
outdated = true ;
}
}
if ( outdated ) { this.redraw() ; }
} ;
DynamicManager.prototype.onPointerMove = function( canvasCoords , convertBackCoords ) {
let outdated = false ;
let contextCoords = canvasUtilities.canvasToContextCoords( this.ctx , canvasCoords ) ;
for ( let dynamic of this.vg.dynamicAreaIterator() ) {
if ( dynamic.boundingBox.isInside( contextCoords ) ) {
if ( dynamic.status !== 'press' ) {
dynamic.setStatus( 'hover' ) ;
}
}
else {
dynamic.setStatus( 'base' ) ;
}
if ( dynamic.outdated ) {
outdated = true ;
}
if ( dynamic.toEmit ) {
for ( let event of dynamic.toEmit ) {
this.convertEventCoords( event.data , convertBackCoords ) ;
this.toEmit.push( event ) ;
}
dynamic.toEmit.length = 0 ;
}
}
if ( outdated ) { this.redraw() ; }
if ( this.toEmit.length ) { this.emitPendingEvents() ; }
} ;
DynamicManager.prototype.onPointerPress = function( canvasCoords , convertBackCoords ) {
let outdated = false ;
let contextCoords = canvasUtilities.canvasToContextCoords( this.ctx , canvasCoords ) ;
for ( let dynamic of this.vg.dynamicAreaIterator() ) {
if ( dynamic.boundingBox.isInside( contextCoords ) ) {
dynamic.setStatus( 'press' ) ;
}
else {
dynamic.setStatus( 'base' ) ;
}
if ( dynamic.outdated ) {
outdated = true ;
}
if ( dynamic.toEmit ) {
for ( let event of dynamic.toEmit ) {
this.convertEventCoords( event.data , convertBackCoords ) ;
this.toEmit.push( event ) ;
}
dynamic.toEmit.length = 0 ;
}
}
if ( outdated ) { this.redraw() ; }
if ( this.toEmit.length ) { this.emitPendingEvents() ; }
} ;
DynamicManager.prototype.onPointerRelease = function( canvasCoords , convertBackCoords ) {
let outdated = false ;
let contextCoords = canvasUtilities.canvasToContextCoords( this.ctx , canvasCoords ) ;
for ( let dynamic of this.vg.dynamicAreaIterator() ) {
if ( dynamic.boundingBox.isInside( contextCoords ) ) {
dynamic.setStatus( 'release' ) ;
}
else {
dynamic.setStatus( 'base' ) ;
}
if ( dynamic.outdated ) {
outdated = true ;
}
if ( dynamic.toEmit ) {
for ( let event of dynamic.toEmit ) {
this.convertEventCoords( event.data , convertBackCoords ) ;
this.toEmit.push( event ) ;
}
dynamic.toEmit.length = 0 ;
}
}
if ( outdated ) { this.redraw() ; }
if ( this.toEmit.length ) { this.emitPendingEvents() ; }
} ;
DynamicManager.prototype.convertEventCoords = function( eventData , convertBackCoords ) {
let min = canvasUtilities.contextToCanvasCoords( this.ctx , { x: eventData.boundingBox.xmin , y: eventData.boundingBox.ymin } ) ,
max = canvasUtilities.contextToCanvasCoords( this.ctx , { x: eventData.boundingBox.xmax , y: eventData.boundingBox.ymax } ) ;
min = convertBackCoords( min ) ;
max = convertBackCoords( max ) ;
eventData.foreignBoundingBox = {
xmin: min.x ,
ymin: min.y ,
xmax: max.x ,
ymax: max.y
} ;
} ;
// Misc
DynamicManager.prototype.getAllEmittableEvents = function( eventName , convertBackCoords = null ) {
var list = [] ;
for ( let dynamic of this.vg.dynamicAreaIterator() ) {
let eventData = dynamic.getEmittableEvent( eventName ) ;
if ( eventData ) {
if ( convertBackCoords ) { this.convertEventCoords( eventData , convertBackCoords ) ; }
list.push( eventData ) ;
}
}
return list ;
} ;
// Browser specifics
DynamicManager.prototype.manageBrowserCanvas = function() {
if ( this.running ) { throw new Error( "Manager is already running!" ) ; }
this.running = true ;
if ( this.timer ) {
clearInterval( this.timer ) ;
this.timer = null ;
}
this.timer = setInterval( () => this.onTick() , this.tickTime ) ;
const convertCoords = event => canvasUtilities.screenToCanvasCoords( this.ctx.canvas , { x: event.clientX , y: event.clientY } ) ;
const convertBackCoords = coords => canvasUtilities.canvasToScreenCoords( this.ctx.canvas , coords ) ;
this.addCanvasEventListener( 'mousemove' , event => this.onPointerMove( convertCoords( event ) , convertBackCoords ) ) ;
this.addCanvasEventListener( 'mousedown' , event => this.onPointerPress( convertCoords( event ) , convertBackCoords ) ) ;
this.addCanvasEventListener( 'mouseup' , event => this.onPointerRelease( convertCoords( event ) , convertBackCoords ) ) ;
} ;
DynamicManager.prototype.addCanvasEventListener = function( eventName , listener ) {
this.canvasListeners.push( [ eventName , listener ] ) ;
this.ctx.canvas.addEventListener( eventName , listener ) ;
} ;
DynamicManager.prototype.clearCanvasEventListener = function() {
for ( let [ eventName , listener ] of this.canvasListeners ) {
this.ctx.canvas.removeEventListener( eventName , listener ) ;
}
this.canvasListeners.length = 0 ;
} ;
DynamicManager.prototype.getAllBrowserEmittableEvents = function( eventName ) {
return this.getAllEmittableEvents(
eventName ,
coords => canvasUtilities.canvasToScreenCoords( this.ctx.canvas , coords )
) ;
} ;
// BabylonJS specifics
DynamicManager.prototype.manageBabylonControl = function( control ) {
if ( this.running ) { throw new Error( "Manager is already running!" ) ; }
this.running = true ;
if ( ! control ) { throw new Error( "No Babylon Control was provided" ) ; }
this.babylonControl = control ;
if ( this.timer ) {
clearInterval( this.timer ) ;
this.timer = null ;
}
this.timer = setInterval( () => this.onTick() , this.tickTime ) ;
const convertCoords = coords => {
// This is the same than: this.babylonControl.getLocalCoordinates( coords ), except we avoid instanciation
return {
x: coords.x - this.babylonControl._currentMeasure.left ,
y: coords.y - this.babylonControl._currentMeasure.top
} ;
} ;
const convertBackCoords = coords => {
return {
x: coords.x + this.babylonControl._currentMeasure.left ,
y: coords.y + this.babylonControl._currentMeasure.top
} ;
} ;
this.addBabylonControlEventListener( 'onPointerMoveObservable' , coords => this.onPointerMove( convertCoords( coords ) , convertBackCoords ) ) ;
this.addBabylonControlEventListener( 'onPointerDownObservable' , coords => this.onPointerPress( convertCoords( coords ) , convertBackCoords ) ) ;
this.addBabylonControlEventListener( 'onPointerUpObservable' , coords => this.onPointerRelease( convertCoords( coords ) , convertBackCoords ) ) ;
// Special case, acts as if the pointer was moved to the negative region
this.addBabylonControlEventListener( 'onPointerOutObservable' , coords => this.onPointerMove( { x: - 1 , y: - 1 } , convertBackCoords ) ) ;
} ;
DynamicManager.prototype.addBabylonControlEventListener = function( observable , listener ) {
this.babylonControlListeners.push( [ observable , listener ] ) ;
this.babylonControl[ observable ].add( listener ) ;
} ;
DynamicManager.prototype.clearBabylonControlEventListener = function() {
for ( let [ observable , listener ] of this.babylonControlListeners ) {
this.babylonControl[ observable ].removeCallback( listener ) ;
}
this.babylonControlListeners.length = 0 ;
} ;
DynamicManager.prototype.getAllBabylonControlEmittableEvents = function( eventName ) {
return this.getAllEmittableEvents(
eventName ,
coords => {
return {
x: coords.x + this.babylonControl._currentMeasure.left ,
y: coords.y + this.babylonControl._currentMeasure.top
} ;
}
) ;
} ;
},{"../package.json":110,"./canvas-utilities.js":40,"nextgen-events/lib/LeanEvents.js":82,"seventh":98}],5:[function(require,module,exports){
/*
SVG Kit
Copyright (c) 2017 - 2024 Cédric Ronvel
The MIT License (MIT)
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.
*/
"use strict" ;
/*
A mesure value, either unit-less, or relative, eventually px.
*/
function Metric( value , unit ) {
this.value = 0 ;
this.unit = '' ;
this.set( value , unit ) ;
}
module.exports = Metric ;
const INPUT_UNITS = {
'': '' ,
'px': '' ,
'em': 'em' ,
'rel': 'em' ,
'%': {
value: v => v * 0.01 ,
unit: 'em'
}
} ;
const UNITS_CALC = {
'': v => v ,
'em': ( v , relativeTo ) => v * relativeTo
} ;
const REGEXP = /^([0-9]+(?:\.[0-9]+)?) *([a-z%]+)?$/ ;
Metric.prototype.set = function( value , unit = '' ) {
if ( value && typeof value === 'object' ) {
unit = value.unit ;
value = value.value ;
}
if ( typeof value === 'string' ) {
let match = value.match( REGEXP ) ;
if ( ! match ) { return false ; }
value = parseFloat( match[ 1 ] ) ;
unit = match[ 2 ] || '' ;
}
unit = INPUT_UNITS[ unit ] ;
if ( unit === undefined ) { return false ; }
if ( typeof unit === 'object' ) {
value = unit.value( value ) ;
unit = unit.unit ;
}
this.value = value ;
this.unit = unit ;
return true ;
} ;
Metric.prototype.get = function( relativeTo , relativeTo2 , ... relativeToN ) {
if ( relativeTo instanceof Metric ) { relativeTo = relativeTo.get( relativeTo2 , ... relativeToN ) ; }
return UNITS_CALC[ this.unit ]( this.value , relativeTo ) ;
} ;
Metric.chainedGet = function( ... metrics ) {
metrics = metrics.filter( m => ( m instanceof Metric ) || typeof m === 'number' ) ;
if ( ! metrics.length ) { return ; }
if ( typeof metrics[ 0 ] === 'number' ) { return metrics[ 0 ] ; }
var metric = metrics.shift() ;
return metric.get( ... metrics ) ;
} ;
Metric.isEqual = function( a , b ) {
if ( ! a && ! b ) { return true ; }
if ( ! a || ! b ) { return false ; }
return (
a.value === b.value
&& a.unit === b.unit
) ;
} ;
},{}],6:[function(require,module,exports){
/*
SVG Kit
Copyright (c) 2017 - 2024 Cédric Ronvel
The MIT License (MIT)
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.
*/
"use strict" ;
/*
Mostly derived from svg-path-properties by RogerVecianaAbzu:
https://github.com/rveciana/svg-path-properties/blob/master/src/bezier.ts
*/
const BoundingBox = require( '../BoundingBox.js' ) ;
function Arc( startPoint , radius , xAxisRotate , largeArcFlag , sweepFlag , endPoint ) {
this.startPoint = startPoint ;
this.radius = radius ;
this.xAxisRotate = xAxisRotate ;
this.largeArcFlag = largeArcFlag ;
this.sweepFlag = sweepFlag ;
this.endPoint = endPoint ;
this.boundingBox = new BoundingBox( null ) ;
this.length = this.getLength( true ) ; // <-- this will update the BBox
}
module.exports = Arc ;
Arc.prototype.getLength = function( updateBBox ) {
if ( updateBBox ) { this.boundingBox.nullify() ; }
const lengthProperties = this.approximateArcLengthOfCurve(
300 ,
t => this.pointOnEllipticalArc(
this.startPoint ,
this.radius ,
this.xAxisRotate ,
this.largeArcFlag ,
this.sweepFlag ,
this.endPoint ,
t
) ,
updateBBox ? this.boundingBox : null
) ;
return lengthProperties.arcLength ;
} ;
Arc.prototype.getPointAtLength = function( length ) {
if ( length === 0 ) { return { x: this.startPoint.x , y: this.startPoint.y } ; }
if ( length === this.length ) { return { x: this.endPoint.x , y: this.endPoint.y } ; }
const position = this.pointOnEllipticalArc(
this.startPoint ,
this.radius ,
this.xAxisRotate ,
this.largeArcFlag ,
this.sweepFlag ,
this.endPoint ,
length / this.length
) ;
return { x: position.x , y: position.y } ;
} ;
Arc.prototype.getTangentAtLength = function( length ) {
const delta = 0.001 * ( Math.min( this.radius.x , this.radius.y ) || this.length ) ; // should manage degenerate case
const p1 = this.getPointAtLength( length - delta ) ;
const p2 = this.getPointAtLength( length + delta ) ;
const dx = p2.x - p1.x ;
const dy = p2.y - p1.y ;
const dist = Math.sqrt( dx * dx + dy * dy ) ;
return {
x: dx / dist ,
y: dy / dist ,
angle: Math.atan2( dy , dx )
} ;
} ;
// Position + Tangent + Angle (radians)
Arc.prototype.getPropertiesAtLength = function( length ) {
const point = this.getPointAtLength( length ) ;
const tangent = this.getTangentAtLength( length ) ;
return {
x: point.x ,
y: point.y ,
dx: tangent.x ,
dy: tangent.y ,
angle: tangent.angle
} ;
} ;
Arc.pointOnEllipticalArc =
Arc.prototype.pointOnEllipticalArc = function( p0 , radius , xAxisRotation , largeArcFlag , sweepFlag , p1 , t ) {
// In accordance to: http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
let rx = Math.abs( radius.x ) ,
ry = Math.abs( radius.y ) ,
xAxisRotationRadians = toRadians( mod( xAxisRotation , 360 ) ) ;
// If the endpoints are identical, then this is equivalent to omitting the elliptical arc segment entirely.
if ( p0.x === p1.x && p0.y === p1.y ) {
return { x: p0.x , y: p0.y , ellipticalArcAngle: 0 } ; // Check if angle is correct
}
// If rx = 0 or ry = 0 then this arc is treated as a straight line segment joining the endpoints.
if ( rx === 0 || ry === 0 ) {
//return this.pointOnLine(p0, p1, t);
return { x: 0 , y: 0 , ellipticalArcAngle: 0 } ; // Check if angle is correct
}
// Following "Conversion from endpoint to center parameterization"
// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
// Step #1: Compute transformedPoint
const dx = ( p0.x - p1.x ) / 2 ;
const dy = ( p0.y - p1.y ) / 2 ;
const transformedPoint = {
x: Math.cos( xAxisRotationRadians ) * dx + Math.sin( xAxisRotationRadians ) * dy ,
y: - Math.sin( xAxisRotationRadians ) * dx + Math.cos( xAxisRotationRadians ) * dy
} ;
// Ensure radii are large enough
const radiiCheck =
Math.pow( transformedPoint.x , 2 ) / Math.pow( rx , 2 ) +
Math.pow( transformedPoint.y , 2 ) / Math.pow( ry , 2 ) ;
if ( radiiCheck > 1 ) {
rx = Math.sqrt( radiiCheck ) * rx ;
ry = Math.sqrt( radiiCheck ) * ry ;
}
// Step #2: Compute transformedCenter
const cSquareNumerator =
Math.pow( rx , 2 ) * Math.pow( ry , 2 ) -
Math.pow( rx , 2 ) * Math.pow( transformedPoint.y , 2 ) -
Math.pow( ry , 2 ) * Math.pow( transformedPoint.x , 2 ) ;
const cSquareRootDenom =
Math.pow( rx , 2 ) * Math.pow( transformedPoint.y , 2 ) +
Math.pow( ry , 2 ) * Math.pow( transformedPoint.x , 2 ) ;
let cRadicand = cSquareNumerator / cSquareRootDenom ;
// Make sure this never drops below zero because of precision
cRadicand = cRadicand < 0 ? 0 : cRadicand ;
const cCoef = ( largeArcFlag !== sweepFlag ? 1 : - 1 ) * Math.sqrt( cRadicand ) ;
const transformedCenter = {
x: cCoef * ( ( rx * transformedPoint.y ) / ry ) ,
y: cCoef * ( - ( ry * transformedPoint.x ) / rx )
} ;
// Step #3: Compute center
const center = {
x:
Math.cos( xAxisRotationRadians ) * transformedCenter.x -
Math.sin( xAxisRotationRadians ) * transformedCenter.y +
( p0.x + p1.x ) / 2 ,
y:
Math.sin( xAxisRotationRadians ) * transformedCenter.x +
Math.cos( xAxisRotationRadians ) * transformedCenter.y +
( p0.y + p1.y ) / 2
} ;
// Step #4: Compute start/sweep angles
// Start angle of the elliptical arc prior to the stretch and rotate operations.
// Difference between the start and end angles
const startVector = {
x: ( transformedPoint.x - transformedCenter.x ) / rx ,
y: ( transformedPoint.y - transformedCenter.y ) / ry
} ;
const startAngle = angleBetween(
{
x: 1 ,
y: 0
} ,
startVector
) ;
const endVector = {
x: ( - transformedPoint.x - transformedCenter.x ) / rx ,
y: ( - transformedPoint.y - transformedCenter.y ) / ry
} ;
let sweepAngle = angleBetween( startVector , endVector ) ;
if ( ! sweepFlag && sweepAngle > 0 ) {
sweepAngle -= 2 * Math.PI ;
}
else if ( sweepFlag && sweepAngle < 0 ) {
sweepAngle += 2 * Math.PI ;
}
// We use % instead of `mod(..)` because we want it to be -360deg to 360deg(but actually in radians)
sweepAngle %= 2 * Math.PI ;
// From http://www.w3.org/TR/SVG/implnote.html#ArcParameterizationAlternatives
const angle = startAngle + sweepAngle * t ;
const ellipseComponentX = rx * Math.cos( angle ) ;
const ellipseComponentY = ry * Math.sin( angle ) ;
const point = {
x:
Math.cos( xAxisRotationRadians ) * ellipseComponentX -
Math.sin( xAxisRotationRadians ) * ellipseComponentY +
center.x ,
y:
Math.sin( xAxisRotationRadians ) * ellipseComponentX +
Math.cos( xAxisRotationRadians ) * ellipseComponentY +
center.y ,
ellipticalArcStartAngle: startAngle ,
ellipticalArcEndAngle: startAngle + sweepAngle ,
ellipticalArcAngle: angle ,
ellipticalArcCenter: center ,
resultantRx: rx ,
resultantRy: ry
} ;
return point ;
} ;
Arc.approximateArcLengthOfCurve =
Arc.prototype.approximateArcLengthOfCurve = function( resolution , pointOnCurveFunc , boundingBox = null ) {
// Resolution is the number of segments we use
resolution = resolution ? resolution : 500 ;
let resultantArcLength = 0 ;
const arcLengthMap = [] ;
const approximationLines = [] ;
let nextPoint ,
prevPoint = pointOnCurveFunc( 0 ) ;
if ( boundingBox ) { boundingBox.ensurePoint( prevPoint ) ; }
for ( let i = 0 ; i < resolution ; i ++ ) {
const t = clamp( i * ( 1 / resolution ) , 0 , 1 ) ;
nextPoint = pointOnCurveFunc( t ) ;
if ( boundingBox ) { boundingBox.ensurePoint( nextPoint ) ; }
resultantArcLength += distance( prevPoint , nextPoint ) ;
approximationLines.push( [ prevPoint , nextPoint ] ) ;
arcLengthMap.push( {
t: t ,
arcLength: resultantArcLength
} ) ;
prevPoint = nextPoint ;
}
// Last stretch to the endpoint
nextPoint = pointOnCurveFunc( 1 ) ;
if ( boundingBox ) { boundingBox.ensurePoint( nextPoint ) ; }
approximationLines.push( [ prevPoint , nextPoint ] ) ;
resultantArcLength += distance( prevPoint , nextPoint ) ;
arcLengthMap.push( {
t: 1 ,
arcLength: resultantArcLength
} ) ;
return {
arcLength: resultantArcLength ,
arcLengthMap: arcLengthMap ,
approximationLines: approximationLines
} ;
} ;
const mod = ( x , m ) => {
return ( ( x % m ) + m ) % m ;
} ;
const toRadians = ( angle ) => {
return angle * ( Math.PI / 180 ) ;
} ;
const distance = ( p0 , p1 ) => {
return Math.sqrt( Math.pow( p1.x - p0.x , 2 ) + Math.pow( p1.y - p0.y , 2 ) ) ;
} ;
const clamp = ( val , min , max ) => {
return Math.min( Math.max( val , min ) , max ) ;
} ;
const angleBetween = ( v0 , v1 ) => {
const p = v0.x * v1.x + v0.y * v1.y ;
const n = Math.sqrt(
( Math.pow( v0.x , 2 ) + Math.pow( v0.y , 2 ) ) * ( Math.pow( v1.x , 2 ) + Math.pow( v1.y , 2 ) )
) ;
const sign = v0.x * v1.y - v0.y * v1.x < 0 ? - 1 : 1 ;
const angle = sign * Math.acos( p / n ) ;
return angle ;
} ;
},{"../BoundingBox.js":1}],7:[function(require,module,exports){
/*
SVG Kit
Copyright (c) 2017 - 2024 Cédric Ronvel
The MIT License (MIT)
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.
*/
"use strict" ;
/*
Mostly derived from svg-path-properties by RogerVecianaAbzu:
https://github.com/rveciana/svg-path-properties/blob/master/src/bezier.ts
*/
function Bezier( ... args ) {
// Cache those values for faster execution
this.xs = args.map( v => v.x ) ;
this.ys = args.map( v => v.y ) ;
this.length = this.getLength( this.xs , this.ys , 1 ) ;
this.boundingBox = this.getBoundingBox( this.xs , this.ys ) ;
}
module.exports = Bezier ;
Bezier.prototype.getPointAtLength = function( length ) {
if ( length === 0 ) { return { x: this.startPoint.x , y: this.startPoint.y } ; }
if ( length === this.length ) { return { x: this.endPoint.x , y: this.endPoint.y } ; }
const t = this.getT( this.xs , this.ys , length ) ;
return this.getPoint( this.xs , this.ys , t ) ;
} ;
Bezier.prototype.getTangentAtLength = function( length ) {
const t = this.getT( this.xs , this.ys , length ) ;
const derivative = this.getDerivative( this.xs , this.ys , t ) ;
const mdl = Math.sqrt( derivative.x * derivative.x + derivative.y * derivative.y ) ;
let tangent ;
if ( mdl > 0 ) {
tangent = { x: derivative.x / mdl , y: derivative.y / mdl } ;
}
else {
tangent = { x: 0 , y: 0 } ;
}
tangent.angle = Math.atan2( tangent.y , tangent.x ) ;
return tangent ;
} ;
// Position + Tangent + Angle (radians)
Bezier.prototype.getPropertiesAtLength = function( length ) {
const t = this.getT( this.xs , this.ys , length ) ;
let point , tangent ;
if ( length === 0 ) { point = { x: this.startPoint.x , y: this.startPoint.y } ; }
else if ( length === this.length ) { point = { x: this.endPoint.x , y: this.endPoint.y } ; }
else { point = this.getPoint( this.xs , this.ys , t ) ; }
const derivative = this.getDerivative( this.xs , this.ys , t ) ;
const mdl = Math.sqrt( derivative.x * derivative.x + derivative.y * derivative.y ) ;
if ( mdl > 0 ) {
tangent = { x: derivative.x / mdl , y: derivative.y / mdl } ;
}
else {
tangent = { x: 0 , y: 0 } ;
}
return {
x: point.x ,
y: point.y ,
dx: tangent.x ,
dy: tangent.y ,
angle: Math.atan2( tangent.y , tangent.x )
} ;
} ;
// Get the t value for a given length
Bezier.prototype.getT = function( xs , ys , length ) {
let error = 1 ;
let t = length / this.length ;
let step = ( length - this.getLength( xs , ys , t ) ) / this.length ;
let numIterations = 0 ;
while ( error > 0.001 ) {
const increasedTLength = this.getLength( xs , ys , t + step ) ;
const increasedTError = Math.abs( length - increasedTLength ) / this.length ;
if ( increasedTError < error ) {
error = increasedTError ;
t += step ;
}
else {
const decreasedTLength = this.getLength( xs , ys , t - step ) ;
const decreasedTError = Math.abs( length - decreasedTLength ) / this.length ;
if ( decreasedTError < error ) {
error = decreasedTError ;
t -= step ;
}
else {
step /= 2 ;
}
}
numIterations ++ ;
if ( numIterations > 500 ) {
break ;
}
}
return t ;
} ;
},{}],8:[function(require,module,exports){
/*
SVG Kit
Copyright (c) 2017 - 2024 Cédric Ronvel
The MIT License (MIT)
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.
*/
"use strict" ;
/*
Mostly derived from svg-path-properties by RogerVecianaAbzu:
https://github.com/rveciana/svg-path-properties/blob/master/src/bezier.ts
*/
const Bezier = require( './Bezier.js' ) ;
const QuadraticBezier = require( './QuadraticBezier.js' ) ;
const BoundingBox = require( '../BoundingBox.js' ) ;
const { tValues , cValues , binomialCoefficients } = require( './bezier-values.json' ) ;
function CubicBezier( startPoint , startControl , endControl , endPoint ) {
this.startPoint = startPoint ;
this.startControl = startControl ;
this.endControl = endControl ;
this.endPoint = endPoint ;
Bezier.call( this , startPoint , startControl , endControl , endPoint ) ;
}
module.exports = CubicBezier ;
CubicBezier.prototype = Object.create( Bezier.prototype ) ;
CubicBezier.prototype.constructor = CubicBezier ;
// The parametric formula for a given axis
function parametric( ps , t ) {
const mt = 1 - t ;
return ( mt * mt * mt * ps[0] ) + ( 3 * mt * mt * t * ps[1] ) + ( 3 * mt * t * t * ps[2] ) + ( t * t * t * ps[3] ) ;
}
CubicBezier.getPoint =
CubicBezier.prototype.getPoint = ( xs , ys , t ) => {
return {
x: parametric( xs , t ) ,
y: parametric( ys , t )
} ;
} ;
CubicBezier.getDerivative =
CubicBezier.prototype.getDerivative = ( xs , ys , t ) => {
const derivative = QuadraticBezier.getPoint(
[ 3 * ( xs[1] - xs[0] ) , 3 * ( xs[2] - xs[1] ) , 3 * ( xs[3] - xs[2] ) ] ,
[ 3 * ( ys[1] - ys[0] ) , 3 * ( ys[2] - ys[1] ) , 3 * ( ys[3] - ys[2] ) ] ,
t
) ;
return derivative ;
} ;
CubicBezier.getLength =
CubicBezier.prototype.getLength = ( xs , ys , t = 1 ) => {
let z ;
let sum ;
let correctedT ;
/*if (xs.length >= tValues.length) {
throw new Error('too high n bezier');
}*/
const n = 20 ;
z = t / 2 ;
sum = 0 ;
for ( let i = 0 ; i < n ; i ++ ) {
correctedT = z * tValues[n][i] + z ;
sum += cValues[n][i] * cubicIteration( xs , ys , correctedT ) ;
}
return z * sum ;
} ;
function cubicIteration( x