initial commit
This commit is contained in:
commit
7b26ee23c9
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/bower_components/
|
||||
/node_modules/
|
||||
|
2
.npmignore
Normal file
2
.npmignore
Normal file
@ -0,0 +1,2 @@
|
||||
dist/
|
||||
|
46
Gruntfile.coffee
Normal file
46
Gruntfile.coffee
Normal file
@ -0,0 +1,46 @@
|
||||
module.exports = (grunt) ->
|
||||
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks)
|
||||
|
||||
grunt.initConfig {
|
||||
watch:
|
||||
coffee:
|
||||
files: ['src/**/*.coffee']
|
||||
tasks: ['browserify', 'coffee', 'uglify']
|
||||
browserify:
|
||||
dist:
|
||||
files:
|
||||
'dist/bismarck.js': 'src/bismarck.coffee'
|
||||
options:
|
||||
transform: ['coffeeify']
|
||||
browserifyOptions:
|
||||
debug: true
|
||||
extensions: ['.coffee', '.litcoffee']
|
||||
coffee:
|
||||
lib:
|
||||
expand: true
|
||||
cwd: 'src'
|
||||
src: ['**/*.coffee']
|
||||
dest: 'lib'
|
||||
ext: '.js'
|
||||
options:
|
||||
bare: true
|
||||
karma:
|
||||
unit:
|
||||
configFile: 'karma.conf.js'
|
||||
autoWatch: true
|
||||
uglify:
|
||||
options:
|
||||
mangle: false
|
||||
dist:
|
||||
files:
|
||||
'dist/bismarck.min.js': 'dist/bismarck.js'
|
||||
concurrent:
|
||||
default:
|
||||
tasks: ['karma', 'watch']
|
||||
options:
|
||||
logConcurrentOutput: true
|
||||
limit: 2
|
||||
}
|
||||
|
||||
grunt.registerTask 'default', ['browserify', 'uglify', 'coffee', 'concurrent:default']
|
||||
|
16
bower.json
Normal file
16
bower.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "bismarck",
|
||||
"version": "0.0.0",
|
||||
"main": "dist/bismarck.js",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"dependencies": {
|
||||
"snap.svg": "~0.3.0",
|
||||
"jquery": "~2.1.1"
|
||||
}
|
||||
}
|
73
karma.conf.js
Normal file
73
karma.conf.js
Normal file
@ -0,0 +1,73 @@
|
||||
// Karma configuration
|
||||
// Generated on Tue Sep 23 2014 07:24:39 GMT-0400 (EDT)
|
||||
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
|
||||
// base path that will be used to resolve all patterns (eg. files, exclude)
|
||||
basePath: '',
|
||||
|
||||
preprocessors: {
|
||||
'test/index.coffee': 'browserify',
|
||||
'src/coffee/app.coffee': 'browserify',
|
||||
},
|
||||
|
||||
// frameworks to use
|
||||
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
||||
frameworks: ['browserify', 'jasmine'],
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
'bower_components/jquery/dist/jquery.js',
|
||||
'bower_components/snap.svg/dist/snap.svg.js',
|
||||
'test/index.coffee'
|
||||
],
|
||||
|
||||
|
||||
// list of files to exclude
|
||||
exclude: [
|
||||
],
|
||||
|
||||
|
||||
// preprocess matching files before serving them to the browser
|
||||
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
||||
browserify: {
|
||||
extensions: ['.coffee', '.litcoffee'],
|
||||
transform: ['coffeeify'],
|
||||
watch: true,
|
||||
debug: true
|
||||
},
|
||||
|
||||
// test results reporter to use
|
||||
// possible values: 'dots', 'progress'
|
||||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
||||
reporters: ['progress'],
|
||||
|
||||
|
||||
// web server port
|
||||
port: 9877,
|
||||
|
||||
|
||||
// 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: true,
|
||||
|
||||
|
||||
// start these browsers
|
||||
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
|
||||
browsers: ['PhantomJS'],
|
||||
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, Karma captures browsers, runs the tests and exits
|
||||
singleRun: false
|
||||
});
|
||||
};
|
36
package.json
Normal file
36
package.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "bismarck",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"main": "lib/bismarck.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"browserify": "^6.1.0",
|
||||
"coffeeify": "^0.7.0",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-browserify": "^3.1.0",
|
||||
"grunt-concurrent": "^1.0.0",
|
||||
"grunt-contrib-coffee": "^0.11.1",
|
||||
"grunt-contrib-concat": "^0.5.0",
|
||||
"grunt-contrib-uglify": "^0.6.0",
|
||||
"grunt-contrib-watch": "^0.6.1",
|
||||
"grunt-karma": "^0.9.0",
|
||||
"karma": "^0.12.23",
|
||||
"karma-bro": "^0.8.0",
|
||||
"karma-chrome-launcher": "^0.1.4",
|
||||
"karma-coffee-preprocessor": "^0.2.1",
|
||||
"karma-firefox-launcher": "^0.1.3",
|
||||
"karma-jasmine": "~0.2.2",
|
||||
"karma-phantomjs-launcher": "^0.1.4",
|
||||
"matchdep": "^0.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"q": "^1.0.1",
|
||||
"underscore": "^1.7.0",
|
||||
"xml2js": "^0.4.4"
|
||||
}
|
||||
}
|
15
src/bismarck.coffee
Normal file
15
src/bismarck.coffee
Normal file
@ -0,0 +1,15 @@
|
||||
Canvas = require('./bismarck/canvas')
|
||||
|
||||
Bismarck = (element, options) ->
|
||||
if typeof element == 'string'
|
||||
new Canvas(document.getElementById(element), options)
|
||||
else
|
||||
new Canvas(element, options)
|
||||
|
||||
Bismarck.debug = false
|
||||
|
||||
module.exports = Bismarck
|
||||
|
||||
if window?
|
||||
window.Bismarck = Bismarck
|
||||
|
77
src/bismarck/animate.coffee
Normal file
77
src/bismarck/animate.coffee
Normal file
@ -0,0 +1,77 @@
|
||||
Animate =
|
||||
_animationsEnabled: true
|
||||
currentTime: -> +new Date
|
||||
moveFromTo: (object, from, to, distance) ->
|
||||
dx = to.x - from.x
|
||||
dy = to.y - from.y
|
||||
x = from.x + (dx * distance)
|
||||
y = from.y + (dy * distance)
|
||||
|
||||
object.moveTo(x, y)
|
||||
|
||||
class Animate.Synchronizer
|
||||
constructor: ->
|
||||
@animations = []
|
||||
@recalcs = []
|
||||
@removers = []
|
||||
@isRunning = false
|
||||
@fpsLimit = 30
|
||||
|
||||
addAnimation: (recalc, animation) ->
|
||||
@animations.push(animation)
|
||||
@recalcs.push(recalc)
|
||||
@removers.push => @removeAnimation(animation)
|
||||
@run()
|
||||
|
||||
=> @removeAnimation(animation)
|
||||
|
||||
removeAnimation: (animation) ->
|
||||
index = @animations.indexOf(animation)
|
||||
@animations.splice(index, 1)
|
||||
@recalcs.splice(index, 1)
|
||||
@removers.splice(index, 1)
|
||||
if @animations.length == 0
|
||||
@isRunning = false
|
||||
|
||||
run: =>
|
||||
if not @isRunning
|
||||
@isRunning = true
|
||||
|
||||
window.requestAnimationFrame =>
|
||||
if @animations.length > 0
|
||||
startTime = Animate.currentTime()
|
||||
Animate._animationsDisabled = false
|
||||
for i in [0..@animations.length - 1]
|
||||
if @animations[i]
|
||||
@animations[i](startTime, @removers[i])
|
||||
Animate._animationsDisabled = true
|
||||
r.recalc() for r in @recalcs
|
||||
endTime = Animate.currentTime()
|
||||
if @isRunning
|
||||
calc = Math.max(0, (1 / @fpsLimit - ((endTime - startTime) / 1000)) * 1000)
|
||||
setTimeout(@run, calc)
|
||||
else
|
||||
@isRunning = false
|
||||
|
||||
Animate.synchronizer = new Animate.Synchronizer()
|
||||
|
||||
Animate.once = (recalc, options, code) ->
|
||||
startTime = Animate.currentTime()
|
||||
endTime = startTime + options.duration
|
||||
|
||||
Animate.synchronizer.addAnimation recalc, (currentTime, stop) ->
|
||||
index = Math.min(1, (currentTime - startTime) / options.duration)
|
||||
result = code(index)
|
||||
|
||||
stop() if not result or currentTime >= endTime
|
||||
|
||||
Animate.loop = (recalc, options, code) ->
|
||||
startTime = Animate.currentTime()
|
||||
|
||||
Animate.synchronizer.addAnimation recalc, (currentTime, stop) ->
|
||||
index = Math.min(1, (currentTime - startTime) % options.duration / options.duration)
|
||||
result = code(index)
|
||||
|
||||
stop() if not result
|
||||
|
||||
module.exports = Animate
|
32
src/bismarck/canvas.coffee
Normal file
32
src/bismarck/canvas.coffee
Normal file
@ -0,0 +1,32 @@
|
||||
Drawable = require('./drawable')
|
||||
Resources = require('./resources')
|
||||
Layout = require('./layout')
|
||||
|
||||
class Canvas
|
||||
constructor: (@element, options = {}) ->
|
||||
@snapElement = Snap(@element)
|
||||
if not @snapElement.attr('bismarck')
|
||||
if options.id
|
||||
@snapElement.attr(id: options.id, bismarck: 'canvas')
|
||||
|
||||
fromLayout: (xml, callback) ->
|
||||
Layout(xml, this, callback)
|
||||
|
||||
create: (options) ->
|
||||
drawable = new Drawable(this, options)
|
||||
drawable.attachTo(@snapElement)
|
||||
drawable
|
||||
|
||||
find: (id) ->
|
||||
drawable = new Drawable(this)
|
||||
drawable.useExisting(@snapElement.select("##{id}"))
|
||||
drawable
|
||||
|
||||
resources: ->
|
||||
return @_resources if @_resources
|
||||
|
||||
@_resources = new Resources(this)
|
||||
@_resources.attachTo(@snapElement)
|
||||
@_resources
|
||||
|
||||
module.exports = Canvas
|
327
src/bismarck/drawable.coffee
Normal file
327
src/bismarck/drawable.coffee
Normal file
@ -0,0 +1,327 @@
|
||||
Animate = require('./animate')
|
||||
|
||||
class Drawable
|
||||
constructor: (@canvas, options = {}) ->
|
||||
defaults =
|
||||
centerPoint: 'northwest'
|
||||
position: {x: 0, y: 0}
|
||||
angle: 0
|
||||
scale: 1
|
||||
|
||||
for own key, value of defaults
|
||||
switch key
|
||||
when 'centerPoint'
|
||||
@setCenterPoint(value || defaults[key])
|
||||
else
|
||||
this[key] = value || defaults[key]
|
||||
|
||||
for own key, value of options
|
||||
switch key
|
||||
when 'centerPoint'
|
||||
@setCenterPoint(value || defaults[key])
|
||||
else
|
||||
this[key] = value || defaults[key]
|
||||
|
||||
@_resources = @canvas.resources()
|
||||
@_children = []
|
||||
|
||||
resources: -> @_resources
|
||||
snapBBox: -> @_scale.getBBox(true)
|
||||
|
||||
create: (options) ->
|
||||
drawable = new Drawable(@canvas, options)
|
||||
drawable.attachTo(@_scale)
|
||||
drawable.parentDrawable = this
|
||||
@_children.push(drawable)
|
||||
drawable
|
||||
|
||||
remove: ->
|
||||
@_translateRotate.remove()
|
||||
|
||||
attachTo: (snapElement) ->
|
||||
@_translateRotate = snapElement.group().addClass('-translate')
|
||||
@_scale = @_translateRotate.group().addClass('-scale')
|
||||
|
||||
@_translateRotate.attr(id: @id, bismarck: 'drawable')
|
||||
@_currentBBox = @snapBBox()
|
||||
@snapElement = @_translateRotate
|
||||
|
||||
forceSize: (w, h) ->
|
||||
@_forcedSize.remove() if @_forcedSize
|
||||
|
||||
@_forcedDims = { w: w, h: h }
|
||||
@_forcedSize = @_translateRotate.rect(0, 0, w, h).attr(fill: 'rgba(0,0,0,0)')
|
||||
|
||||
me = @_forcedSize.node
|
||||
@_translateRotate.node.insertBefore(me, @_translateRotate.node.childNodes[0])
|
||||
|
||||
useExisting: (@_translateRotate) ->
|
||||
throw "Not a Bismarck element!" if not @_translateRotate.attr('bismarck')
|
||||
childNodes = @_translateRotate.node.childNodes
|
||||
|
||||
@_scale = Snap(childNodes[0])
|
||||
if childNodes[1]
|
||||
@centerPointCross = Snap(childNodes[1])
|
||||
|
||||
currentNode = @_translateRotate.node.parentNode
|
||||
while currentNode
|
||||
switch currentNode.getAttribute('bismarck')
|
||||
when 'drawable'
|
||||
@parentDrawable = new Drawable(@canvas, {})
|
||||
@parentDrawable.useExisting(Snap(currentNode))
|
||||
currentNode = null
|
||||
when 'canvas'
|
||||
currentNode = null
|
||||
else
|
||||
# stop!
|
||||
if currentNode.nodeName == 'svg'
|
||||
currentNode = null
|
||||
else
|
||||
currentNode = currentNode.parentNode
|
||||
|
||||
angular.extend(this, @_translateRotate.data('settings'))
|
||||
|
||||
showCenterPoint: ->
|
||||
@centerPointCross = @_translateRotate.group()
|
||||
@centerPointCross.line(0, -5, 0, 5).attr(stroke: 'black')
|
||||
@centerPointCross.line(-5, 0, 5, 0).attr(stroke: 'black')
|
||||
|
||||
@recalc()
|
||||
|
||||
moveTo: (x, y) ->
|
||||
@position = {x: x, y: y}
|
||||
@recalc()
|
||||
|
||||
rotateTo: (angle) ->
|
||||
@angle = angle
|
||||
@recalc()
|
||||
|
||||
scaleTo: (scale) ->
|
||||
@scale = scale
|
||||
@recalc()
|
||||
|
||||
alignTo: (object, how = 'center', args...) ->
|
||||
switch how
|
||||
when 'center'
|
||||
@moveTo(object.position.x, object.position.y, args...)
|
||||
when 'horizontal'
|
||||
@moveTo(@position.x, object.position.y, args...)
|
||||
when 'vertical'
|
||||
@moveTo(object.position.x, @position.y, args...)
|
||||
|
||||
centerInParent: ->
|
||||
@_centerInParent = true
|
||||
@setCenterPoint('center')
|
||||
@recalc(true)
|
||||
|
||||
recalc: (down = false) ->
|
||||
if Animate._animationsEnabled
|
||||
if @_centerInParent
|
||||
parent = @parentDrawable.getDimensions()
|
||||
|
||||
@position = { x: parent.w / 2, y: parent.h / 2}
|
||||
|
||||
if @oldScale != @scale
|
||||
matrix = new Snap.Matrix()
|
||||
matrix.scale(@scale)
|
||||
@_scale.transform(matrix)
|
||||
@oldScale = @scale
|
||||
|
||||
offset = @getOffset()
|
||||
upperLeft = @upperLeft()
|
||||
|
||||
if @centerPointCross?
|
||||
matrix = new Snap.Matrix()
|
||||
matrix.translate(offset.x + upperLeft.x, offset.y + upperLeft.y)
|
||||
@centerPointCross.transform(matrix)
|
||||
|
||||
matrix = new Snap.Matrix()
|
||||
|
||||
matrix.translate(@position.x, @position.y)
|
||||
matrix.rotate(@angle, 0, 0)
|
||||
|
||||
if not @_forcedSize
|
||||
matrix.translate(-offset.x - upperLeft.x, -offset.y - upperLeft.y)
|
||||
|
||||
@_translateRotate.transform(matrix)
|
||||
|
||||
if false
|
||||
@_translateRotate.data('settings', {
|
||||
centerPoint: @centerPoint
|
||||
position: @position
|
||||
angle: @angle
|
||||
scale: @scale
|
||||
_currentBBox: @_currentBBox
|
||||
_centerInParent: @_centerInParent
|
||||
_forcedDims: @_forcedDims
|
||||
_children: @_children
|
||||
})
|
||||
|
||||
if down
|
||||
child.recalc(true) for child in @_children
|
||||
else
|
||||
@parentDrawable.recalc() if @parentDrawable?
|
||||
|
||||
moveForward: ->
|
||||
me = @_translateRotate.node
|
||||
parent = me.parentNode
|
||||
|
||||
l = parent.childNodes.length - 1
|
||||
|
||||
for i in [0..l]
|
||||
if parent.childNodes[i] == me and i < l
|
||||
parent.insertBefore(parent.childNodes[i + 1], me)
|
||||
|
||||
moveBackward: ->
|
||||
me = @_translateRotate.node
|
||||
parent = me.parentNode
|
||||
|
||||
for i in [0..parent.childNodes.length]
|
||||
if parent.childNodes[i] == me and i > 0
|
||||
parent.insertBefore(me, parent.childNodes[i - 1])
|
||||
|
||||
moveToFront: ->
|
||||
me = @_translateRotate.node
|
||||
me.parent.appendChild(me)
|
||||
|
||||
moveToBack: ->
|
||||
me = @_translateRotate.node
|
||||
me.parent.insertBefore(me, me.parent.childNodes[0])
|
||||
|
||||
on: (event, args...) ->
|
||||
code = args.pop()
|
||||
targetSelector = args.shift()
|
||||
|
||||
target = @_translateRotate
|
||||
if targetSelector?
|
||||
target = target.select(targetSelector)
|
||||
|
||||
target[event](code)
|
||||
|
||||
draw: (code) ->
|
||||
code(@_scale)
|
||||
@recalc()
|
||||
|
||||
@_currentBBox = @snapBBox()
|
||||
|
||||
scaleAndRotateCoords: (coords) ->
|
||||
pairs = if coords.w? then ['w', 'h'] else ['x', 'y']
|
||||
|
||||
coords[pairs[0]] *= @scale
|
||||
coords[pairs[1]] *= @scale
|
||||
|
||||
rad2ang = Math.PI / 180
|
||||
|
||||
radianAngle = rad2ang * @angle
|
||||
cos = Math.cos(radianAngle)
|
||||
sin = Math.sin(radianAngle)
|
||||
|
||||
newW = coords[pairs[0]] * cos + coords[pairs[1]] * sin
|
||||
newH = coords[pairs[1]] * cos + coords[pairs[0]] * sin
|
||||
|
||||
coords[pairs[0]] = newW
|
||||
coords[pairs[1]] = newh
|
||||
|
||||
coords
|
||||
|
||||
getOffset: ->
|
||||
output = {}
|
||||
|
||||
source = if @_children.length > 0
|
||||
@getDimensions()
|
||||
else
|
||||
@_currentBBox
|
||||
|
||||
for coordPart in @centerPoint
|
||||
output[coordPart.which] = coordPart.convert(source[coordPart.dim])
|
||||
|
||||
output
|
||||
|
||||
upperLeft: ->
|
||||
if @_children.length == 0
|
||||
{ x: 0, y: 0 }
|
||||
else
|
||||
furthestUpperLeft = { x: null, y: null }
|
||||
for child in @_children
|
||||
offset = child.getOffset()
|
||||
offset.x = -offset.x + child.position.x
|
||||
offset.y = -offset.y + child.position.y
|
||||
|
||||
if !furthestUpperLeft.x
|
||||
furthestUpperLeft.x = offset.x
|
||||
furthestUpperLeft.y = offset.y
|
||||
else
|
||||
furthestUpperLeft.x = Math.min(furthestUpperLeft.x, offset.x)
|
||||
furthestUpperLeft.y = Math.min(furthestUpperLeft.y, offset.y)
|
||||
|
||||
furthestUpperLeft
|
||||
|
||||
setCenterPoint: (args...) ->
|
||||
if args.length == 1
|
||||
args = switch args[0]
|
||||
when 'center'
|
||||
['50%', '50%']
|
||||
when 'northwest'
|
||||
[0,0]
|
||||
|
||||
@coordinates = {}
|
||||
|
||||
data = [
|
||||
{ index: 0, which: 'x', dim: 'w' },
|
||||
{ index: 1, which: 'y', dim: 'h' }
|
||||
]
|
||||
|
||||
coordMap = (datum) ->
|
||||
datum.value = args[datum.index]
|
||||
if datum.value.substr? and datum.value.substr(-1) == '%'
|
||||
percent = Number(datum.value.slice(0, -1)) / 100.0
|
||||
|
||||
datum.convert = (value) -> value * percent
|
||||
else
|
||||
datum.convert = (value) -> datum.value
|
||||
|
||||
datum
|
||||
|
||||
@centerPoint = data.map(coordMap)
|
||||
|
||||
getDimensions: ->
|
||||
if @_forcedDims
|
||||
@_forcedDims
|
||||
else
|
||||
if @_children.length > 0
|
||||
@getDimensionsWithChildren()
|
||||
else
|
||||
@_currentBBox
|
||||
|
||||
getDimensionsWithChildren: ->
|
||||
bbox =
|
||||
sx: null
|
||||
sy: null
|
||||
ex: null
|
||||
ey: null
|
||||
|
||||
for child in @_children
|
||||
childBBox = child.getDimensions()
|
||||
childOffset = child.getOffset()
|
||||
|
||||
childBBox.x = child.position.x - childOffset.x
|
||||
childBBox.y = child.position.y - childOffset.y
|
||||
|
||||
if bbox.sx == null
|
||||
bbox.sx = childBBox.x
|
||||
bbox.sy = childBBox.y
|
||||
bbox.ex = childBBox.x + childBBox.w
|
||||
bbox.ey = childBBox.y + childBBox.h
|
||||
else
|
||||
bbox.sx = Math.min(bbox.sx, childBBox.x)
|
||||
bbox.sy = Math.min(bbox.sy, childBBox.y)
|
||||
bbox.ex = Math.max(bbox.ex, childBBox.x + childBBox.w)
|
||||
bbox.ey = Math.max(bbox.ey, childBBox.y + childBBox.h)
|
||||
|
||||
{ w: bbox.ex - bbox.sx, h: bbox.ey - bbox.sy }
|
||||
|
||||
append: (drawable) ->
|
||||
@_translateRotate.append(drawable._scaleRotate)
|
||||
|
||||
module.exports = Drawable
|
||||
|
46
src/bismarck/layout.coffee
Normal file
46
src/bismarck/layout.coffee
Normal file
@ -0,0 +1,46 @@
|
||||
xml2js = require('xml2js')
|
||||
parseString = require('xml2js').parseString
|
||||
_ = require('underscore')
|
||||
|
||||
svgObjs =
|
||||
rect: (options, canvas) ->
|
||||
rect = canvas.rect(options.x, options.y, options.width, options.height)
|
||||
rect.attr(_.omit(options, 'x', 'y', 'width', 'height'))
|
||||
|
||||
rect
|
||||
|
||||
oneOrMore = (value, code) ->
|
||||
if value.push?
|
||||
for node in value
|
||||
code(node)
|
||||
else
|
||||
code(value)
|
||||
|
||||
dive = (node, drawable, topLevel = null) ->
|
||||
for own key, value of node
|
||||
switch key
|
||||
when 'drawable'
|
||||
oneOrMore value, (v) ->
|
||||
childDrawable = drawable.create(v.$)
|
||||
topLevel ||= childDrawable
|
||||
dive(v, childDrawable, topLevel)
|
||||
when '$'
|
||||
# nothing
|
||||
else
|
||||
oneOrMore value, (v) ->
|
||||
drawable.draw (svg) ->
|
||||
svgObjs[key](v.$, svg)
|
||||
dive(v, drawable, topLevel)
|
||||
|
||||
topLevel
|
||||
|
||||
module.exports = (xml, canvas, callback = null) ->
|
||||
parseString xml, (err, result) ->
|
||||
if err?
|
||||
throw err
|
||||
else
|
||||
diveResult = dive(result, canvas)
|
||||
|
||||
if callback
|
||||
callback(diveResult)
|
||||
|
26
src/bismarck/resources.coffee
Normal file
26
src/bismarck/resources.coffee
Normal file
@ -0,0 +1,26 @@
|
||||
class Resources
|
||||
constructor: (@canvas) ->
|
||||
@resources = {}
|
||||
@bboxes = {}
|
||||
|
||||
attachTo: (snapElement) ->
|
||||
@resourceGroup = snapElement.group().attr(display: 'none')
|
||||
|
||||
copyIDsFrom: (snapElement, ids...) ->
|
||||
for id in ids
|
||||
node = snapElement.select("##{id}")
|
||||
node.transform('')
|
||||
@resourceGroup.append(node)
|
||||
@resources[id] = node
|
||||
|
||||
clone: (id) ->
|
||||
@resources[id].use()
|
||||
|
||||
copy: (id) ->
|
||||
@resources[id].clone()
|
||||
|
||||
bbox: (id) ->
|
||||
@bboxes[id] ||= @resources[id].getBBox()
|
||||
|
||||
module.exports = Resources
|
||||
|
30
test/bismarck/animateSpec.coffee
Normal file
30
test/bismarck/animateSpec.coffee
Normal file
@ -0,0 +1,30 @@
|
||||
Canvas = require('../../src/bismarck/canvas')
|
||||
Animate = require('../../src/bismarck/animate')
|
||||
|
||||
describe 'Animate', ->
|
||||
svg = null
|
||||
canvas = null
|
||||
|
||||
beforeEach ->
|
||||
$('body').append('<svg id="svg" width="300" height="300"></svg>')
|
||||
canvas = new Canvas(document.getElementById('svg'), id: 'canvas')
|
||||
|
||||
afterEach ->
|
||||
$('svg').remove()
|
||||
|
||||
describe '.moveFromTo', ->
|
||||
it 'should move an object from one point to another', ->
|
||||
drawable = canvas.create(id: 'drawable', centerPoint: 'northwest')
|
||||
|
||||
Animate.moveFromTo(drawable, {x: 0, y: 0}, {x: 100, y: 50}, 0)
|
||||
expect(drawable.position).toEqual({x: 0, y: 0})
|
||||
expect(drawable.snapElement.getBBox().x).toEqual(0)
|
||||
|
||||
Animate.moveFromTo(drawable, {x: 0, y: 0}, {x: 100, y: 50}, 0.5)
|
||||
expect(drawable.position).toEqual({x: 50, y: 25})
|
||||
expect(drawable.snapElement.getBBox().x).toEqual(50)
|
||||
|
||||
Animate.moveFromTo(drawable, {x: 0, y: 0}, {x: 100, y: 50}, 1.0)
|
||||
expect(drawable.position).toEqual({x: 100, y: 50})
|
||||
expect(drawable.snapElement.getBBox().x).toEqual(100)
|
||||
|
189
test/bismarck/drawableSpec.coffee
Normal file
189
test/bismarck/drawableSpec.coffee
Normal file
@ -0,0 +1,189 @@
|
||||
Canvas = require('../../src/bismarck/canvas')
|
||||
|
||||
describe 'Drawable', ->
|
||||
svg = null
|
||||
canvas = null
|
||||
drawable = null
|
||||
|
||||
beforeEach ->
|
||||
$('body').append('<svg id="svg" width="300" height="300"></svg>')
|
||||
canvas = new Canvas(document.getElementById('svg'), id: 'canvas')
|
||||
drawable = canvas.create(id: 'drawable', centerPoint: 'northwest')
|
||||
|
||||
afterEach ->
|
||||
$('svg').remove()
|
||||
|
||||
describe 'offset', ->
|
||||
describe 'with children', ->
|
||||
it 'should calculate the offset correctly', ->
|
||||
childOne = drawable.create(id: 'one', centerPoint: 'northwest')
|
||||
|
||||
childOne.draw (surface) ->
|
||||
surface.rect(0, 0, 10, 1).attr(fill: '#000000')
|
||||
|
||||
childTwo = drawable.create(id: 'two', centerPoint: 'northwest')
|
||||
|
||||
childTwo.draw (surface) ->
|
||||
surface.rect(0, 0, 1, 10).attr(fill: '#000000')
|
||||
|
||||
expect(drawable.getOffset().x).toEqual(0)
|
||||
expect(drawable.getOffset().y).toEqual(0)
|
||||
|
||||
drawable.setCenterPoint('50%', '50%')
|
||||
|
||||
expect(drawable.getOffset().x).toEqual(5)
|
||||
expect(drawable.getOffset().y).toEqual(5)
|
||||
|
||||
childTwo.moveTo(20, 0)
|
||||
|
||||
expect(drawable.getOffset().x).toEqual(10.5)
|
||||
expect(drawable.getOffset().y).toEqual(5)
|
||||
|
||||
it 'should calculate the offset correctly', ->
|
||||
drawable.setCenterPoint('center')
|
||||
childOne = drawable.create(id: 'one', centerPoint: 'center')
|
||||
|
||||
childOne.draw (surface) ->
|
||||
surface.rect(0, 0, 20, 30).attr(fill: '#000000')
|
||||
|
||||
childOne.moveTo(0, 0)
|
||||
|
||||
childTwo = drawable.create(id: 'two', centerPoint: 'center')
|
||||
|
||||
childTwo.draw (surface) ->
|
||||
surface.rect(0, 0, 20, 30).attr(fill: '#000000')
|
||||
|
||||
childTwo.moveTo(0, 30)
|
||||
|
||||
expect(drawable.getOffset().x).toEqual(10)
|
||||
expect(drawable.getOffset().y).toEqual(30)
|
||||
|
||||
it 'should calculate the offset correctly', ->
|
||||
spyOn(drawable, 'snapBBox').and.callThrough()
|
||||
|
||||
orect = null
|
||||
|
||||
drawable.setCenterPoint(0, 0)
|
||||
|
||||
drawable.draw (surface) ->
|
||||
orect = surface.rect(0, 0, 20, 10).attr(fill: '#000000')
|
||||
|
||||
expect(drawable.getOffset().x).toEqual(0)
|
||||
expect(drawable.getOffset().y).toEqual(0)
|
||||
|
||||
drawable.setCenterPoint('50%', '50%')
|
||||
|
||||
expect(drawable.getOffset().x).toEqual(10)
|
||||
expect(drawable.getOffset().y).toEqual(5)
|
||||
|
||||
drawable.setCenterPoint('-100%', '0%')
|
||||
|
||||
expect(drawable.getOffset().x).toEqual(-20)
|
||||
expect(drawable.getOffset().y).toEqual(0)
|
||||
|
||||
drawable.draw (surface) ->
|
||||
orect = surface.rect(0, 0, 20, 10).attr(fill: 'red')
|
||||
|
||||
expect(drawable.getOffset().x).toEqual(-20)
|
||||
expect(drawable.getOffset().y).toEqual(0)
|
||||
|
||||
drawable.rotateTo(90)
|
||||
|
||||
expect(drawable.getOffset().x).toEqual(-20)
|
||||
expect(drawable.getOffset().y).toEqual(0)
|
||||
|
||||
drawable.setCenterPoint('center')
|
||||
|
||||
expect(drawable.getOffset().x).toEqual(10)
|
||||
expect(drawable.getOffset().y).toEqual(5)
|
||||
|
||||
describe 'dimensions', ->
|
||||
describe 'after drawing', ->
|
||||
it 'should position the bbox correctly', ->
|
||||
spyOn(drawable, 'snapBBox').and.callThrough()
|
||||
|
||||
orect = null
|
||||
|
||||
drawable.draw (surface) ->
|
||||
orect = surface.rect(0, 0, 20, 10).attr(fill: '#000000')
|
||||
|
||||
expect(drawable.getDimensions().w).toEqual(20)
|
||||
expect(drawable.getDimensions().h).toEqual(10)
|
||||
|
||||
drawable.draw (surface) ->
|
||||
orect.remove()
|
||||
surface.rect(10, 10, 20, 10).attr(fill: '#000000')
|
||||
|
||||
expect(drawable.getDimensions().w).toEqual(20)
|
||||
expect(drawable.getDimensions().h).toEqual(10)
|
||||
|
||||
expect(drawable.snapBBox.calls.count()).toEqual(2)
|
||||
|
||||
drawable.scaleTo(2)
|
||||
|
||||
expect(drawable.getDimensions().w).toEqual(20)
|
||||
expect(drawable.getDimensions().h).toEqual(10)
|
||||
|
||||
drawable.rotateTo(90)
|
||||
|
||||
expect(drawable.getDimensions().w).toBeCloseTo(20, 2)
|
||||
expect(drawable.getDimensions().h).toBeCloseTo(10, 2)
|
||||
|
||||
describe 'with children', ->
|
||||
describe 'northwest', ->
|
||||
it 'should composite together all the drawables', ->
|
||||
childOne = drawable.create(id: 'one', centerPoint: 'northwest')
|
||||
|
||||
childOne.draw (surface) ->
|
||||
surface.rect(0, 0, 10, 1).attr(fill: '#000000')
|
||||
|
||||
childTwo = drawable.create(id: 'two', centerPoint: 'northwest')
|
||||
|
||||
childTwo.draw (surface) ->
|
||||
surface.rect(0, 0, 1, 10).attr(fill: '#000000')
|
||||
|
||||
expect(drawable.getDimensions().w).toEqual(10)
|
||||
expect(drawable.getDimensions().h).toEqual(10)
|
||||
|
||||
expect(drawable.upperLeft().x).toBeCloseTo(0, 1)
|
||||
expect(drawable.upperLeft().y).toBeCloseTo(0, 1)
|
||||
|
||||
expect(childTwo.upperLeft().x).toBeCloseTo(0, 1)
|
||||
expect(childTwo.upperLeft().y).toBeCloseTo(0, 1)
|
||||
|
||||
childTwo.moveTo(-10, 0)
|
||||
|
||||
expect(drawable.getDimensions().w).toEqual(20)
|
||||
expect(drawable.getDimensions().h).toEqual(10)
|
||||
expect(drawable.upperLeft().x).toBeCloseTo(-10, 1)
|
||||
expect(drawable.upperLeft().y).toBeCloseTo(0, 1)
|
||||
expect(childTwo.upperLeft().x).toBeCloseTo(0, 1)
|
||||
expect(childTwo.upperLeft().y).toBeCloseTo(0, 1)
|
||||
|
||||
describe 'center', ->
|
||||
it 'should composite together all the drawables', ->
|
||||
drawable.setCenterPoint('center')
|
||||
childOne = drawable.create(id: 'one', centerPoint: 'center')
|
||||
|
||||
childOne.draw (surface) ->
|
||||
surface.rect(0, 0, 10, 20).attr(fill: '#000000')
|
||||
|
||||
childTwo = drawable.create(id: 'two', centerPoint: 'center')
|
||||
|
||||
childTwo.draw (surface) ->
|
||||
surface.rect(0, 0, 10, 20).attr(fill: '#000000')
|
||||
|
||||
expect(drawable.getDimensions().w).toEqual(10)
|
||||
expect(drawable.getDimensions().h).toEqual(20)
|
||||
|
||||
expect(childTwo.upperLeft().x).toBeCloseTo(0, 1)
|
||||
expect(childTwo.upperLeft().y).toBeCloseTo(0, 1)
|
||||
|
||||
expect(drawable.upperLeft().x).toBeCloseTo(-5, 1)
|
||||
expect(drawable.upperLeft().y).toBeCloseTo(-10, 1)
|
||||
|
||||
childTwo.rotateTo(270)
|
||||
|
||||
expect(drawable.getDimensions().w).toBeCloseTo(10, 2)
|
||||
expect(drawable.getDimensions().h).toBeCloseTo(20, 2)
|
||||
|
28
test/bismarck/layoutSpec.coffee
Normal file
28
test/bismarck/layoutSpec.coffee
Normal file
@ -0,0 +1,28 @@
|
||||
Canvas = require('../../src/bismarck/canvas')
|
||||
|
||||
describe 'layoutSpec', ->
|
||||
svg = null
|
||||
canvas = null
|
||||
drawable = null
|
||||
|
||||
beforeEach ->
|
||||
$('body').append('<svg id="svg" width="300" height="300"></svg>')
|
||||
canvas = new Canvas(document.getElementById('svg'), id: 'canvas')
|
||||
drawable = canvas.create(id: 'drawable', centerPoint: 'northwest')
|
||||
svg = canvas.snapElement
|
||||
|
||||
afterEach ->
|
||||
$('svg').remove()
|
||||
|
||||
describe 'from canvas', ->
|
||||
it 'should create a group of objects with SVG bits', ->
|
||||
layout = canvas.fromLayout("""
|
||||
<drawable id="cat">
|
||||
<rect id="dog" x="10" y="10" width="30" height="20" style="fill: red" />
|
||||
</drawable>
|
||||
""")
|
||||
|
||||
console.log svg.outerSVG()
|
||||
|
||||
expect(svg).toContainSVG('#cat')
|
||||
expect(svg).toContainSVG('rect#dog[style="fill: red"]')
|
0
test/bismarckSpec.coffee
Normal file
0
test/bismarckSpec.coffee
Normal file
17
test/index.coffee
Normal file
17
test/index.coffee
Normal file
@ -0,0 +1,17 @@
|
||||
beforeEach ->
|
||||
jasmine.addMatchers {
|
||||
toContainSVG: ->
|
||||
compare: (svg, select) ->
|
||||
result = { pass: svg.select(select)? }
|
||||
|
||||
result.message = if !result.pass
|
||||
"Expect #{svg.outerSVG()} to contain #{select}"
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
require('./bismarckSpec')
|
||||
require('./bismarck/animateSpec')
|
||||
require('./bismarck/drawableSpec')
|
||||
require('./bismarck/layoutSpec')
|
||||
|
Loading…
Reference in New Issue
Block a user