316 lines
8.7 KiB
JavaScript
316 lines
8.7 KiB
JavaScript
|
var Class = require('../../core/class');
|
||
|
var Transform = require('../../core/transform');
|
||
|
var Color = require('../../core/color');
|
||
|
var Node = require('./node');
|
||
|
var DOM = require('./dom');
|
||
|
|
||
|
var precision = 100;
|
||
|
|
||
|
var defaultBox = { left: 0, top: 0, width: 500, height: 500 };
|
||
|
|
||
|
module.exports = Class(Node, {
|
||
|
|
||
|
element_initialize: Node.prototype.initialize,
|
||
|
|
||
|
initialize: function(tag){
|
||
|
this.element_initialize(tag);
|
||
|
var element = this.element;
|
||
|
|
||
|
var skew = this.skewElement = DOM.createElement('skew');
|
||
|
skew.on = true;
|
||
|
element.appendChild(skew);
|
||
|
|
||
|
var fill = this.fillElement = DOM.createElement('fill');
|
||
|
fill.on = false;
|
||
|
element.appendChild(fill);
|
||
|
|
||
|
var stroke = this.strokeElement = DOM.createElement('stroke');
|
||
|
stroke.on = false;
|
||
|
element.appendChild(stroke);
|
||
|
},
|
||
|
|
||
|
/* transform */
|
||
|
|
||
|
_transform: function(){
|
||
|
var container = this.parentNode;
|
||
|
|
||
|
// Active Transformation Matrix
|
||
|
var m = container ? new Transform(container._activeTransform).transform(this) : this;
|
||
|
|
||
|
// Box in shape user space
|
||
|
|
||
|
var box = this._boxCoords || this._size || defaultBox;
|
||
|
|
||
|
var originX = box.left || 0,
|
||
|
originY = box.top || 0,
|
||
|
width = box.width || 1,
|
||
|
height = box.height || 1;
|
||
|
|
||
|
// Flipped
|
||
|
var flip = m.yx / m.xx > m.yy / m.xy;
|
||
|
if (m.xx < 0 ? m.xy >= 0 : m.xy < 0) flip = !flip;
|
||
|
flip = flip ? -1 : 1;
|
||
|
|
||
|
m = new Transform().scale(flip, 1).transform(m);
|
||
|
|
||
|
// Rotation is approximated based on the transform
|
||
|
var rotation = Math.atan2(-m.xy, m.yy) * 180 / Math.PI;
|
||
|
|
||
|
// Reverse the rotation, leaving the final transform in box space
|
||
|
var rad = rotation * Math.PI / 180, sin = Math.sin(rad), cos = Math.cos(rad);
|
||
|
|
||
|
var transform = new Transform(
|
||
|
(m.xx * cos - m.xy * sin),
|
||
|
(m.yx * cos - m.yy * sin) * flip,
|
||
|
(m.xy * cos + m.xx * sin) * flip,
|
||
|
(m.yy * cos + m.yx * sin)
|
||
|
);
|
||
|
|
||
|
var rotationTransform = new Transform().rotate(rotation, 0, 0);
|
||
|
|
||
|
var shapeToBox = new Transform().rotate(-rotation, 0, 0).transform(m).moveTo(0,0);
|
||
|
|
||
|
// Scale box after reversing rotation
|
||
|
width *= Math.abs(shapeToBox.xx);
|
||
|
height *= Math.abs(shapeToBox.yy);
|
||
|
|
||
|
// Place box
|
||
|
var left = m.x, top = m.y;
|
||
|
|
||
|
// Compensate for offset by center origin rotation
|
||
|
var vx = -width / 2, vy = -height / 2;
|
||
|
var point = rotationTransform.point(vx, vy);
|
||
|
left -= point.x - vx;
|
||
|
top -= point.y - vy;
|
||
|
|
||
|
// Adjust box position based on offset
|
||
|
var rsm = new Transform(m).moveTo(0,0);
|
||
|
point = rsm.point(originX, originY);
|
||
|
left += point.x;
|
||
|
top += point.y;
|
||
|
|
||
|
if (flip < 0) left = -left - width;
|
||
|
|
||
|
// Place transformation origin
|
||
|
var point0 = rsm.point(-originX, -originY);
|
||
|
var point1 = rotationTransform.point(width, height);
|
||
|
var point2 = rotationTransform.point(width, 0);
|
||
|
var point3 = rotationTransform.point(0, height);
|
||
|
|
||
|
var minX = Math.min(0, point1.x, point2.x, point3.x),
|
||
|
maxX = Math.max(0, point1.x, point2.x, point3.x),
|
||
|
minY = Math.min(0, point1.y, point2.y, point3.y),
|
||
|
maxY = Math.max(0, point1.y, point2.y, point3.y);
|
||
|
|
||
|
var transformOriginX = (point0.x - point1.x / 2) / (maxX - minX) * flip,
|
||
|
transformOriginY = (point0.y - point1.y / 2) / (maxY - minY);
|
||
|
|
||
|
// Adjust the origin
|
||
|
point = shapeToBox.point(originX, originY);
|
||
|
originX = point.x;
|
||
|
originY = point.y;
|
||
|
|
||
|
// Scale stroke
|
||
|
var strokeWidth = this._strokeWidth;
|
||
|
if (strokeWidth){
|
||
|
// Scale is the hypothenus between the two vectors
|
||
|
// TODO: Use area calculation instead
|
||
|
var vx = m.xx + m.xy, vy = m.yy + m.yx;
|
||
|
strokeWidth *= Math.sqrt(vx * vx + vy * vy) / Math.sqrt(2);
|
||
|
}
|
||
|
|
||
|
// convert to multiplied precision space
|
||
|
originX *= precision;
|
||
|
originY *= precision;
|
||
|
left *= precision;
|
||
|
top *= precision;
|
||
|
width *= precision;
|
||
|
height *= precision;
|
||
|
|
||
|
// Set box
|
||
|
var element = this.element;
|
||
|
element.coordorigin = originX + ',' + originY;
|
||
|
element.coordsize = width + ',' + height;
|
||
|
element.style.left = left + 'px';
|
||
|
element.style.top = top + 'px';
|
||
|
element.style.width = width;
|
||
|
element.style.height = height;
|
||
|
element.style.rotation = rotation.toFixed(8);
|
||
|
element.style.flip = flip < 0 ? 'x' : '';
|
||
|
|
||
|
// Set transform
|
||
|
var skew = this.skewElement;
|
||
|
skew.matrix = [transform.xx.toFixed(4), transform.xy.toFixed(4), transform.yx.toFixed(4), transform.yy.toFixed(4), 0, 0];
|
||
|
skew.origin = transformOriginX + ',' + transformOriginY;
|
||
|
|
||
|
// Set stroke
|
||
|
this.strokeElement.weight = strokeWidth + 'px';
|
||
|
},
|
||
|
|
||
|
/* styles */
|
||
|
|
||
|
_createGradient: function(style, stops){
|
||
|
var fill = this.fillElement;
|
||
|
|
||
|
// Temporarily eject the fill from the DOM
|
||
|
this.element.removeChild(fill);
|
||
|
|
||
|
fill.type = style;
|
||
|
fill.method = 'none';
|
||
|
fill.rotate = true;
|
||
|
|
||
|
var colors = [], color1, color2;
|
||
|
|
||
|
var addColor = function(offset, color){
|
||
|
color = Color.detach(color);
|
||
|
if (color1 == null) color1 = color2 = color;
|
||
|
else color2 = color;
|
||
|
colors.push(offset + ' ' + color[0]);
|
||
|
};
|
||
|
|
||
|
// Enumerate stops, assumes offsets are enumerated in order
|
||
|
if ('length' in stops) for (var i = 0, l = stops.length - 1; i <= l; i++) addColor(i / l, stops[i]);
|
||
|
else for (var offset in stops) addColor(offset, stops[offset]);
|
||
|
|
||
|
fill.color = color1[0];
|
||
|
fill.color2 = color2[0];
|
||
|
|
||
|
//if (fill.colors) fill.colors.value = colors; else
|
||
|
fill.colors = colors;
|
||
|
|
||
|
// Opacity order gets flipped when color stops are specified
|
||
|
fill.opacity = color2[1];
|
||
|
fill['ao:opacity2'] = color1[1];
|
||
|
|
||
|
fill.on = true;
|
||
|
this.element.appendChild(fill);
|
||
|
return fill;
|
||
|
},
|
||
|
|
||
|
_setColor: function(type, color){
|
||
|
var element = type == 'fill' ? this.fillElement : this.strokeElement;
|
||
|
if (color == null){
|
||
|
element.on = false;
|
||
|
} else {
|
||
|
color = Color.detach(color);
|
||
|
element.color = color[0];
|
||
|
element.opacity = color[1];
|
||
|
element.on = true;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
fill: function(color){
|
||
|
if (arguments.length > 1){
|
||
|
this.fillLinear(arguments);
|
||
|
} else {
|
||
|
this._boxCoords = defaultBox;
|
||
|
var fill = this.fillElement;
|
||
|
fill.type = 'solid';
|
||
|
fill.color2 = '';
|
||
|
fill['ao:opacity2'] = '';
|
||
|
if (fill.colors) fill.colors.value = '';
|
||
|
this._setColor('fill', color);
|
||
|
}
|
||
|
return this;
|
||
|
},
|
||
|
|
||
|
fillRadial: function(stops, focusX, focusY, radiusX, radiusY, centerX, centerY){
|
||
|
var fill = this._createGradient('gradientradial', stops);
|
||
|
if (focusX == null) focusX = this.left + this.width * 0.5;
|
||
|
if (focusY == null) focusY = this.top + this.height * 0.5;
|
||
|
if (radiusY == null) radiusY = radiusX || (this.height * 0.5);
|
||
|
if (radiusX == null) radiusX = this.width * 0.5;
|
||
|
if (centerX == null) centerX = focusX;
|
||
|
if (centerY == null) centerY = focusY;
|
||
|
|
||
|
centerX += centerX - focusX;
|
||
|
centerY += centerY - focusY;
|
||
|
|
||
|
var box = this._boxCoords = {
|
||
|
left: centerX - radiusX * 2,
|
||
|
top: centerY - radiusY * 2,
|
||
|
width: radiusX * 4,
|
||
|
height: radiusY * 4
|
||
|
};
|
||
|
focusX -= box.left;
|
||
|
focusY -= box.top;
|
||
|
focusX /= box.width;
|
||
|
focusY /= box.height;
|
||
|
|
||
|
fill.focussize = '0 0';
|
||
|
fill.focusposition = focusX + ',' + focusY;
|
||
|
fill.focus = '50%';
|
||
|
|
||
|
this._transform();
|
||
|
|
||
|
return this;
|
||
|
},
|
||
|
|
||
|
fillLinear: function(stops, x1, y1, x2, y2){
|
||
|
var fill = this._createGradient('gradient', stops);
|
||
|
fill.focus = '100%';
|
||
|
if (arguments.length == 5){
|
||
|
var w = Math.abs(x2 - x1), h = Math.abs(y2 - y1);
|
||
|
this._boxCoords = {
|
||
|
left: Math.min(x1, x2),
|
||
|
top: Math.min(y1, y2),
|
||
|
width: w < 1 ? h : w,
|
||
|
height: h < 1 ? w : h
|
||
|
};
|
||
|
fill.angle = (360 + Math.atan2((x2 - x1) / h, (y2 - y1) / w) * 180 / Math.PI) % 360;
|
||
|
} else {
|
||
|
this._boxCoords = null;
|
||
|
fill.angle = (x1 == null) ? 0 : (90 + x1) % 360;
|
||
|
}
|
||
|
this._transform();
|
||
|
return this;
|
||
|
},
|
||
|
|
||
|
fillImage: function(url, width, height, left, top, color1, color2){
|
||
|
var fill = this.fillElement;
|
||
|
if (color1 != null){
|
||
|
color1 = Color.detach(color1);
|
||
|
if (color2 != null) color2 = Color.detach(color2);
|
||
|
fill.type = 'pattern';
|
||
|
fill.color = color1[0];
|
||
|
fill.color2 = color2 == null ? color1[0] : color2[0];
|
||
|
fill.opacity = color2 == null ? 0 : color2[1];
|
||
|
fill['ao:opacity2'] = color1[1];
|
||
|
} else {
|
||
|
fill.type = 'tile';
|
||
|
fill.color = '';
|
||
|
fill.color2 = '';
|
||
|
fill.opacity = 1;
|
||
|
fill['ao:opacity2'] = 1;
|
||
|
}
|
||
|
if (fill.colors) fill.colors.value = '';
|
||
|
fill.rotate = true;
|
||
|
fill.src = url;
|
||
|
|
||
|
fill.size = '1,1';
|
||
|
fill.position = '0,0';
|
||
|
fill.origin = '0,0';
|
||
|
fill.aspect = 'ignore'; // ignore, atleast, atmost
|
||
|
fill.on = true;
|
||
|
|
||
|
if (!left) left = 0;
|
||
|
if (!top) top = 0;
|
||
|
this._boxCoords = width ? { left: left + 0.5, top: top + 0.5, width: width, height: height } : null;
|
||
|
this._transform();
|
||
|
return this;
|
||
|
},
|
||
|
|
||
|
/* stroke */
|
||
|
|
||
|
stroke: function(color, width, cap, join){
|
||
|
var stroke = this.strokeElement;
|
||
|
this._strokeWidth = (width != null) ? width : 1;
|
||
|
stroke.weight = (width != null) ? width + 'px' : 1;
|
||
|
stroke.endcap = (cap != null) ? ((cap == 'butt') ? 'flat' : cap) : 'round';
|
||
|
stroke.joinstyle = (join != null) ? join : 'round';
|
||
|
|
||
|
this._setColor('stroke', color);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
});
|