Made lines follow 45 degree angles when holding shift, made selection box fit better on zoom, updated canvg

git-svn-id: http://svg-edit.googlecode.com/svn/trunk@1531 eee81c28-f429-11dd-99c0-75d572ba1ddd
master
Alexis Deveria 2010-04-20 20:29:30 +00:00
parent afb7ef00bb
commit ff7af498a3
2 changed files with 262 additions and 49 deletions

View File

@ -159,7 +159,19 @@ if(!window.console) {
getDefinition: function() {
var name = that.value.replace(/^(url\()?#([^\)]+)\)?$/, '$2');
return svg.Definitions[name];
}
},
isUrl: function() {
return that.value.indexOf('url(') == 0
},
getGradient: function(e) {
var grad = this.getDefinition();
if (grad != null && grad.createGradient) {
return grad.createGradient(svg.ctx, e);
}
return null;
}
}
// length extensions
@ -171,7 +183,7 @@ if(!window.console) {
EM: function(viewPort) {
var em = 12;
var fontSize = new svg.Property('fontSize', svg.ctx.font.match(/[0-9][^\s\t\n\r\/]*/g)[0]);
var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);
if (fontSize.hasValue()) em = fontSize.Length.toPixels(viewPort);
return em;
@ -220,6 +232,41 @@ if(!window.console) {
}
}
// fonts
svg.Font = new (function() {
this.Styles = ['normal','italic','oblique','inherit'];
this.Variants = ['normal','small-caps','inherit'];
this.Weights = ['normal','bold','bolder','lighter','100','200','300','400','500','600','700','800','900','inherit'];
this.CreateFont = function(fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit) {
var f = inherit != null ? this.Parse(inherit) : this.CreateFont('', '', '', '', '', svg.ctx.font);
return {
fontFamily: fontFamily || f.fontFamily,
fontSize: fontSize || f.fontSize,
fontStyle: fontStyle || f.fontStyle,
fontWeight: fontWeight || f.fontWeight,
fontVariant: fontVariant || f.fontVariant,
toString: function () { return [this.fontStyle, this.fontVariant, this.fontWeight, this.fontSize, this.fontFamily].join(' ') }
}
}
var that = this;
this.Parse = function(s) {
var f = {};
var d = svg.trim(svg.compressSpaces(s || '')).split(' ');
var set = { fontSize: false, fontStyle: false, fontWeight: false, fontVariant: false }
var ff = '';
for (var i=0; i<d.length; i++) {
if (!set.fontStyle && that.Styles.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontStyle = d[i]; set.fontStyle = true; }
else if (!set.fontVariant && that.Variants.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontVariant = d[i]; set.fontStyle = set.fontVariant = true; }
else if (!set.fontWeight && that.Weights.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontWeight = d[i]; set.fontStyle = set.fontVariant = set.fontWeight = true; }
else if (!set.fontSize) { if (d[i] != 'inherit') f.fontSize = d[i].split('/')[0]; set.fontStyle = set.fontVariant = set.fontWeight = set.fontSize = true; }
else { if (d[i] != 'inherit') ff += d[i]; }
} if (ff != '') f.fontFamily = ff;
return f;
}
});
// points and paths
svg.ToNumberArray = function(s) {
var a = svg.trim(svg.compressSpaces((s || '').replace(/,/g, ' '))).split(' ');
@ -532,22 +579,22 @@ if(!window.console) {
this.setContext = function(ctx) {
// fill
if (this.style('fill').value.indexOf('url(') == 0) {
var grad = this.style('fill').Definition.getDefinition();
if (grad != null && grad.createGradient) {
ctx.fillStyle = grad.createGradient(ctx, this);
}
if (this.style('fill').Definition.isUrl()) {
var grad = this.style('fill').Definition.getGradient(this);
if (grad != null) ctx.fillStyle = grad;
}
else {
if (this.style('fill').hasValue()) {
var fillStyle = this.style('fill');
if (this.style('fill-opacity').hasValue()) fillStyle = fillStyle.Color.addOpacity(this.style('fill-opacity').value);
ctx.fillStyle = (fillStyle.value == 'none' ? 'rgba(0,0,0,0)' : fillStyle.value);
}
else if (this.style('fill').hasValue()) {
var fillStyle = this.style('fill');
if (this.style('fill-opacity').hasValue()) fillStyle = fillStyle.Color.addOpacity(this.style('fill-opacity').value);
ctx.fillStyle = (fillStyle.value == 'none' ? 'rgba(0,0,0,0)' : fillStyle.value);
}
// stroke
if (this.style('stroke').hasValue()) {
if (this.style('stroke').Definition.isUrl()) {
var grad = this.style('stroke').Definition.getGradient(this);
if (grad != null) ctx.strokeStyle = grad;
}
else if (this.style('stroke').hasValue()) {
var strokeStyle = this.style('stroke');
if (this.style('stroke-opacity').hasValue()) strokeStyle = strokeStyle.Color.addOpacity(this.style('stroke-opacity').value);
ctx.strokeStyle = (strokeStyle.value == 'none' ? 'rgba(0,0,0,0)' : strokeStyle.value);
@ -558,10 +605,14 @@ if(!window.console) {
if (this.style('stroke-miterlimit').hasValue()) ctx.miterLimit = this.style('stroke-miterlimit').value;
// font
if (this.style('font-size').hasValue()) {
var fontFamily = this.style('font-family').valueOrDefault('Arial');
var fontSize = this.style('font-size').numValueOrDefault('12') + 'px';
ctx.font = fontSize + ' ' + fontFamily;
opera.postError('typeof(ctx.font):' + ctx.font);
if (typeof(ctx.font) != 'undefined') {
ctx.font = svg.Font.CreateFont(
this.style('font-style').value,
this.style('font-variant').value,
this.style('font-weight').value,
this.style('font-size').hasValue() ? this.style('font-size').Length.toPixels() + 'px' : '',
this.style('font-family').value).toString();
}
// transform
@ -596,12 +647,29 @@ if(!window.console) {
this.renderChildren = function(ctx) {
this.path(ctx);
if (ctx.fillStyle != '') ctx.fill();
if (ctx.strokeStyle != '') ctx.stroke();
if (ctx.strokeStyle != '') ctx.stroke();
if (this.attribute('marker-end').Definition.isUrl()) {
var marker = this.attribute('marker-end').Definition.getDefinition();
var endPoint = this.getEndPoint();
var endAngle = this.getEndAngle();
if (endPoint != null && endAngle != null) {
marker.render(ctx, endPoint, endAngle);
}
}
}
this.getBoundingBox = function() {
return this.path();
}
this.getEndPoint = function() {
return null;
}
this.getEndAngle = function() {
return null;
}
}
svg.Element.PathElementBase.prototype = new svg.Element.RenderedElementBase;
@ -630,11 +698,19 @@ if(!window.console) {
if (this.attribute('width').hasValue() && this.attribute('height').hasValue()) {
width = this.attribute('width').Length.toPixels('x');
height = this.attribute('height').Length.toPixels('y');
var x = 0;
var y = 0;
if (this.attribute('refX').hasValue() && this.attribute('refY').hasValue()) {
x = -this.attribute('refX').Length.toPixels('x');
y = -this.attribute('refY').Length.toPixels('y');
}
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(width, 0);
ctx.moveTo(x, y);
ctx.lineTo(width, y);
ctx.lineTo(width, height);
ctx.lineTo(0, height);
ctx.lineTo(x, height);
ctx.closePath();
ctx.clip();
}
@ -661,24 +737,28 @@ if(!window.console) {
var scaleMax = Math.max(scaleX, scaleY);
if (meetOrSlice == 'meet') { width *= scaleMin; height *= scaleMin; }
if (meetOrSlice == 'slice') { width *= scaleMax; height *= scaleMax; }
// align
if (align.match(/^xMid/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(svg.ViewPort.width() / 2.0 - width / 2.0, 0);
if (align.match(/YMid$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, svg.ViewPort.height() / 2.0 - height / 2.0);
if (align.match(/^xMax/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(svg.ViewPort.width() - width, 0);
if (align.match(/YMax$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, svg.ViewPort.height() - height);
if (this.attribute('refX').hasValue() && this.attribute('refY').hasValue()) {
ctx.translate(-scaleMin * this.attribute('refX').Length.toPixels('x'), -scaleMin * this.attribute('refY').Length.toPixels('y'));
}
else {
// align
if (align.match(/^xMid/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(svg.ViewPort.width() / 2.0 - width / 2.0, 0);
if (align.match(/YMid$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, svg.ViewPort.height() / 2.0 - height / 2.0);
if (align.match(/^xMax/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(svg.ViewPort.width() - width, 0);
if (align.match(/YMax$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, svg.ViewPort.height() - height);
}
// scale
if (meetOrSlice == 'meet') ctx.scale(scaleMin, scaleMin);
if (meetOrSlice == 'slice') ctx.scale(scaleMax, scaleMax);
ctx.translate(-minX, -minY);
ctx.translate(-minX, -minY);
svg.ViewPort.RemoveCurrent();
svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);
}
// initial values
ctx.fillStyle = '#000000';
ctx.strokeStyle = 'rgba(0,0,0,0)';
ctx.lineCap = 'butt';
ctx.lineJoin = 'miter';
@ -788,6 +868,20 @@ if(!window.console) {
return new svg.BoundingBox(x1, y1, x2, y2);
}
this.getEndPoint = function() {
var x2 = this.attribute('x2').Length.toPixels('x');
var y2 = this.attribute('y2').Length.toPixels('y');
return new svg.Point(x2, y2);
}
this.getEndAngle = function() {
var x1 = this.attribute('x1').Length.toPixels('x');
var y1 = this.attribute('y1').Length.toPixels('y');
var x2 = this.attribute('x2').Length.toPixels('x');
var y2 = this.attribute('y2').Length.toPixels('y');
return Math.atan((y2-y1)/(x2-x1));
}
}
svg.Element.line.prototype = new svg.Element.PathElementBase;
@ -843,6 +937,7 @@ if(!window.console) {
d = d.replace(/([^\s])([A-Za-z])/gm,'$1 $2'); // separate commands from points
d = d.replace(/([0-9])([+\-])/gm,'$1 $2'); // separate digits when no comma
d = d.replace(/(\.[0-9]*)(\.)/gm,'$1 $2'); // separate digits when no comma
d = d.replace(/([Aa](\s+[0-9]+){3})\s+([01])\s*([01])/gm,'$1 $3 $4 '); // shorthand elliptical arc path syntax
d = svg.compressSpaces(d); // compress multiple spaces
d = svg.trim(d);
this.PathParser = new (function(d) {
@ -852,9 +947,19 @@ if(!window.console) {
this.i = -1;
this.command = '';
this.control = new svg.Point(0, 0);
this.last = new svg.Point(0, 0);
this.current = new svg.Point(0, 0);
}
this.angle = function() {
return Math.atan((this.current.y - this.last.y) / (this.current.x - this.last.x));
}
this.setCurrent = function(p) {
this.last = this.current;
this.current = p;
}
this.isEnd = function() {
return this.i == this.tokens.length - 1;
}
@ -894,7 +999,7 @@ if(!window.console) {
this.getAsCurrentPoint = function() {
var p = this.getPoint();
this.current = p;
this.setCurrent(p);
return p;
}
@ -912,7 +1017,7 @@ if(!window.console) {
}
})(d);
this.path = function(ctx) {
this.path = function(ctx) {
var pp = this.PathParser;
pp.reset();
@ -940,6 +1045,7 @@ if(!window.console) {
else if (pp.command.toUpperCase() == 'H') {
while (!pp.isCommandOrEnd()) {
pp.current.x = (pp.isRelativeCommand() ? pp.current.x : 0) + pp.getScalar();
pp.setCurrent(pp.current);
bb.addPoint(pp.current.x, pp.current.y);
if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);
}
@ -947,6 +1053,7 @@ if(!window.console) {
else if (pp.command.toUpperCase() == 'V') {
while (!pp.isCommandOrEnd()) {
pp.current.y = (pp.isRelativeCommand() ? pp.current.y : 0) + pp.getScalar();
pp.setCurrent(pp.current);
bb.addPoint(pp.current.x, pp.current.y);
if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);
}
@ -1050,16 +1157,59 @@ if(!window.console) {
if (ctx != null) ctx.closePath();
}
}
return bb;
}
this.getEndPoint = function() {
return this.PathParser.current;
}
this.getEndAngle = function() {
return this.PathParser.angle();
}
}
svg.Element.path.prototype = new svg.Element.PathElementBase;
svg.Element.marker = function(node) {
this.base = svg.Element.ElementBase;
this.base(node);
this.baseRender = this.render;
this.render = function(ctx, endPoint, endAngle) {
ctx.translate(endPoint.x, endPoint.y);
if (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(endAngle);
if (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(ctx.lineWidth, ctx.lineWidth);
ctx.save();
// render me using a temporary svg element
var tempSvg = new svg.Element.svg();
tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);
tempSvg.attributes['refX'] = new svg.Property('refX', this.attribute('refX').value);
tempSvg.attributes['refY'] = new svg.Property('refY', this.attribute('refY').value);
tempSvg.attributes['width'] = new svg.Property('width', this.attribute('markerWidth').value);
tempSvg.attributes['height'] = new svg.Property('height', this.attribute('markerHeight').value);
tempSvg.attributes['fill'] = new svg.Property('fill', this.attribute('fill').valueOrDefault('black'));
tempSvg.attributes['stroke'] = new svg.Property('stroke', this.attribute('stroke').valueOrDefault('none'));
tempSvg.children = this.children;
tempSvg.render(ctx);
ctx.restore();
if (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(1/ctx.lineWidth, 1/ctx.lineWidth);
if (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(-endAngle);
ctx.translate(-endPoint.x, -endPoint.y);
}
}
svg.Element.marker.prototype = new svg.Element.ElementBase;
// definitions element
svg.Element.defs = function(node) {
this.base = svg.Element.ElementBase;
this.base(node);
this.render = function(ctx) {
// NOOP
}
}
svg.Element.defs.prototype = new svg.Element.ElementBase;
@ -1275,7 +1425,7 @@ if(!window.console) {
// text element
svg.Element.text = function(node) {
this.base = svg.Element.ElementBase;
this.base = svg.Element.RenderedElementBase;
this.base(node);
// accumulate all the child text nodes, then trim them
@ -1287,13 +1437,23 @@ if(!window.console) {
}
this.text = svg.trim(this.text);
this.render = function(ctx) {
this.baseSetContext = this.setContext;
this.setContext = function(ctx) {
this.baseSetContext(ctx);
if (this.attribute('text-anchor').hasValue()) {
var textAnchor = this.attribute('text-anchor').value;
ctx.textAlign = textAnchor == 'middle' ? 'center' : textAnchor;
}
if (this.attribute('alignment-baseline').hasValue()) ctx.textBaseline = this.attribute('alignment-baseline').value;
}
this.renderChildren = function(ctx) {
var x = this.attribute('x').Length.toPixels('x');
var y = this.attribute('y').Length.toPixels('y');
if (ctx.fillText) ctx.fillText(this.text, x, y);
}
}
svg.Element.text.prototype = new svg.Element.ElementBase;
svg.Element.text.prototype = new svg.Element.RenderedElementBase;
// group element
svg.Element.g = function(node) {

View File

@ -522,13 +522,13 @@ function BatchCommand(text) {
var selectedBox = this.selectorRect,
selectedGrips = this.selectorGrips,
selected = this.selectedElement,
sw = round(selected.getAttribute("stroke-width"));
var offset = 1/canvas.getZoom();
sw = selected.getAttribute("stroke-width");
var offset = 1/current_zoom;
if (selected.getAttribute("stroke") != "none" && !isNaN(sw)) {
offset += sw/2;
offset += (sw/2);
}
if (selected.tagName == "text") {
offset += 2/canvas.getZoom();
offset += 2/current_zoom;
}
var bbox = canvas.getBBox(selected);
if(selected.tagName == 'g') {
@ -550,7 +550,7 @@ function BatchCommand(text) {
m.f *= current_zoom;
// apply the transforms
var l=bbox.x-offset, t=bbox.y-offset, w=bbox.width+(offset<<1), h=bbox.height+(offset<<1),
var l=bbox.x-offset, t=bbox.y-offset, w=bbox.width+(offset*2), h=bbox.height+(offset*2),
bbox = {x:l, y:t, width:w, height:h};
// we need to handle temporary transforms too
@ -3421,8 +3421,58 @@ function BatchCommand(text) {
// Opera has a problem with suspendRedraw() apparently
var handle = null;
if (!window.opera) svgroot.suspendRedraw(1000);
shape.setAttributeNS(null, "x2", x);
shape.setAttributeNS(null, "y2", y);
var x2 = x;
var y2 = y;
if(evt.shiftKey) {
var diff_x = x - start_x;
var diff_y = y - start_y;
var angle = ((Math.atan2(start_y-y,start_x-x) * (180/Math.PI))-90) % 360;
angle = angle<0?(360+angle):angle
// TODO: Write all this more efficiently
var dist = Math.sqrt(diff_x * diff_x + diff_y * diff_y);
var val = Math.sqrt((dist*dist)/2);
var diagonal = false;
if(angle >= 360-22.5 || angle < 22.5) {
x2 = start_x;
} else if(angle >= 22.5 && angle < 22.5 + 45) {
diagonal = true;
} else if(angle >= 22.5 + 45 && angle < 22.5 + 90) {
y2 = start_y;
} else if(angle >= 22.5 + 90 && angle < 22.5 + 135) {
diagonal = true;
} else if(angle >= 22.5 + 135 && angle < 22.5 + 180) {
x2 = start_x;
} else if(angle >= 22.5 + 180 && angle < 22.5 + 225) {
diagonal = true;
} else if(angle >= 22.5 + 225 && angle < 22.5 + 270) {
y2 = start_y;
} else if(angle >= 22.5 + 270 && angle < 22.5 + 315) {
diagonal = true;
}
if(diagonal) {
if(y2 < start_y) {
y2 = start_y - val;
} else {
y2 = start_y + val;
}
if(x2 < start_x) {
x2 = start_x - val;
} else {
x2 = start_x + val;
}
}
}
shape.setAttributeNS(null, "x2", x2);
shape.setAttributeNS(null, "y2", y2);
if (!window.opera) svgroot.unsuspendRedraw(handle);
break;
case "foreignObject":
@ -6203,15 +6253,18 @@ function BatchCommand(text) {
// Selector and notice
var issue_list = {
'feGaussianBlur': 'Blurred elements will appear as un-blurred',
'text': 'Text may not appear as expected',
'image': 'Image elements will not appear',
'foreignObject': 'foreignObject elements will not appear',
'marker': 'Marker elements (arrows, etc) will not appear',
'[stroke-dasharray]': 'Strokes will appear filled',
'[stroke^=url]': 'Strokes with gradients will not appear'
'marker': 'Marker elements (arrows, etc) may not appear as expected', // waiting for marker-mid and marker-end support
'[stroke-dasharray]': 'Strokes will appear filled'
};
var content = $(svgcontent);
// Add font/text check if Canvas Text API is not implemented
if(!("font" in $('<canvas>')[0].getContext('2d'))) {
issue_list['text'] = 'Text may not appear as expected';
}
$.each(issue_list, function(sel, descr) {
if(content.find(sel).length) {
issues.push(descr);