Supporting Text in VML Renderer

72 views
Skip to first unread message

taf2

unread,
Oct 13, 2007, 7:26:30 PM10/13/07
to prototype-graphic
Hi,

I spent today working on adding support to render text using the VML
renderer. I used the VML textpath and for the most part it works
really nicely. I think with a little work it can be enhanced to match
the SVG renderer. Currently, the main issue in the text size in VML
isn't matching the SVG rendering... The main changes in this patch
are in vml.js Here's the svn diff inline and to get the original
patch file: http://severna.homeip.net/vml-render-text.patch


Index: samples/js/test.js
===================================================================
--- samples/js/test.js (revision 77)
+++ samples/js/test.js (working copy)
@@ -257,7 +257,8 @@
shape.setTextValue("Hello World!");
shape.setFill(getFill(index, nbShapes));
shape.setStroke(getStroke(index, nbShapes, 1));
- shape.setBounds(0, 30, 15, 10);
+ shape.setBounds(0, 30, 20, 20);
+ shape.setFont("20px","Arial","normal");
shape.translate(130, 600);

shape.rotate(angle, 0, 0);
@@ -295,4 +296,4 @@
renderer.draw();
var end = new Date().getTime();
return end - start;
-}
\ No newline at end of file
+}
Index: src/shape/text.js
===================================================================
--- src/shape/text.js (revision 77)
+++ src/shape/text.js (working copy)
@@ -24,11 +24,11 @@
},

getSize: function() {
- return {fontSize: this.attributes['font-size'], fontWeight:
this.attributes['font-weight']};
+ return {width: this.attributes.width, height:
this.attributes.height};
},

- setSize: function(fontSize, fontWeight) {
- this._setAttributes({'font-size': fontSize, 'font-weight':
fontWeight});
+ setSize: function(width, height) {
+ this._setAttributes({'width': width, 'height': height});
return this;
},

@@ -42,7 +42,9 @@
},

getFont: function() {
- return {fontSize: this.attributes['font-size'], fontFamily:
this.attributes['font-family'], fontWeight: this.attributes['font-
weight']};
+ return {fontSize: this.attributes['font-size'],
+ fontFamily: this.attributes['font-family'],
+ fontWeight: this.attributes['font-weight']};
},

setFont: function(size, family, weight) {
Index: src/renderer/vml.js
===================================================================
--- src/renderer/vml.js (revision 77)
+++ src/renderer/vml.js (working copy)
@@ -12,7 +12,7 @@
*/

//
-// Graphic.VMLRenderer render class.
+// Graphic.VMLRenderer render class.
//
Graphic.VMLRenderer = Class.create();

@@ -21,13 +21,13 @@
//
Object.extend(Graphic.VMLRenderer, {
init: false,
-
+
createNode: function(nodeName) {
return document.createElement(nodeName);;
},
-
- initBrowser: function () {
- if (!Graphic.VMLRenderer.init && document.readyState ==
"complete") {
+
+ initBrowser: function () {
+ if (!Graphic.VMLRenderer.init && document.readyState ==
"complete") {
// create xmlns
if (!document.namespaces["v"]) {
document.namespaces.add("v", "urn:schemas-microsoft-
com:vml");
@@ -42,26 +42,29 @@
})

Object.extend(Graphic.VMLRenderer.prototype,
Graphic.AbstractRender.prototype);
-Graphic.VMLRenderer.prototype._parentInitialize =
Graphic.AbstractRender.prototype.initialize;
-Graphic.VMLRenderer.prototype._parentSetSize =
Graphic.AbstractRender.prototype.setSize;
+Graphic.VMLRenderer.prototype._parentInitialize =
Graphic.AbstractRender.prototype.initialize;
+Graphic.VMLRenderer.prototype._parentSetSize =
Graphic.AbstractRender.prototype.setSize;

Object.extend(Graphic.VMLRenderer.prototype, {
initialize: function(element) {
Graphic.VMLRenderer.initBrowser();
-
+
this._parentInitialize(element);
-
+
this.element = Graphic.VMLRenderer.createNode("v:group");
- this.element.style.height = this.bounds.h + "px";
- this.element.style.width = this.bounds.w + "px";
+ this.element.style.height = this.bounds.h + "px";
+ this.element.style.width = this.bounds.w + "px";
+ this.element.style.position = "relative";
+ this.element.style.top = "0px";
+ this.element.style.left = "0px";

this._setViewing();

this.element.graphicNode = this;
- $(element).appendChild(this.element);
+ $(element).appendChild(this.element);
},
-
- destroy: function() {
+
+ destroy: function() {
$A(this.element.childNodes).each(function(node) {
if (node.shape) {
node.shape.destroy();
@@ -71,39 +74,47 @@
})
this.element.parentNode.removeChild(this.element);
},
-
- setSize: function(width, height) {
+
+ setSize: function(width, height) {
this._parentSetSize(width, height);
- this.element.style.height = this.bounds.h + "px";
- this.element.style.width = this.bounds.w + "px";
+ this.element.style.height = this.bounds.h + "px";
+ this.element.style.width = this.bounds.w + "px";
this.zoom(this.viewing.sx, this.viewing.sy)
},

- createShape: function(shape){
- var node = null;
+ createShape: function(shape){
+ var node = null;
switch (shape.nodeName) {
case "rect":
- node = Graphic.VMLRenderer.createNode("v:roundrect");
+ node = Graphic.VMLRenderer.createNode("v:roundrect");
node.arcsize = 0;
- break;
+ break;
case "ellipse":
case "circle":
node = Graphic.VMLRenderer.createNode("v:oval");
- break;
+ break;
case "polygon":
case "polyline":
- node = Graphic.VMLRenderer.createNode("v:shape");
- node.style.height = this.bounds.h + "px";
- node.style.width = this.bounds.w + "px";
+ node = Graphic.VMLRenderer.createNode("v:shape");
+ node.style.height = this.bounds.h + "px";
+ node.style.width = this.bounds.w + "px";
node.coordsize = this.bounds.w + ", " + this.bounds.h;
- node.coordorigin = "0, 0";
- break;
+ node.coordorigin = "0, 0";
+ break;
case "line":
- node = Graphic.VMLRenderer.createNode("v:line");
- break;
- // case "text":
- // node = Graphic.VMLRenderer.createNode("v:text");
- // break;
+ node = Graphic.VMLRenderer.createNode("v:line");
+ break;
+ case "text":
+ node = Graphic.VMLRenderer.createNode("v:shape");
+ node.style.height = this.bounds.h + "px";
+ node.style.width = this.bounds.w + "px";
+
+ var path = document.createElement("v:path");
+
+ path.textpathok=true;
+
+ node.appendChild(path);
+ break;
// case "image":
// node = document.createElement("div");
// node.style.position = "relative";
@@ -112,7 +123,7 @@
// node.style.filter =
"progid:DXImageTransform.Microsoft.Matrix(M11=1, M12=0, M21=0, M22=1,
Dx=100, Dy=0)";
// var img = document.createElement("img");
// node.appendChild(img);
- // break;
+ // break;
// case "image":
// node = document.createElement("div");
// node.style.position = "relative";
@@ -121,16 +132,16 @@
// node.style.filter =
"progid:DXImageTransform.Microsoft.Matrix(M11=1, M12=0, M21=0, M22=1,
Dx=100, Dy=0)";
// var img = document.createElement("img");
// node.appendChild(img);
- // break;
+ // break;
case "g":
- node = Graphic.VMLRenderer.createNode("v:group");
+ node = Graphic.VMLRenderer.createNode("v:group");
node.stroked = "false";
node.filled = "false";
- node.style.height = this.bounds.w + "px";
- node.style.width = this.bounds.h + "px";
+ node.style.height = this.bounds.w + "px";
+ node.style.width = this.bounds.h + "px";
node.coordsize = this.bounds.w + ", " + this.bounds.h;
- node.coordorigin = "0, 0";
- break;
+ node.coordorigin = "0, 0";
+ break;
default:
// console.log("shape " + shape.nodeName + " not yet
implemented for VML renderer");
break;
@@ -139,29 +150,29 @@
return null;
if (shape.nodeName != "g" && shape.nodeName != "image") {
// Create a fill node
- var fill = Graphic.VMLRenderer.createNode("v:fill");
+ var fill = Graphic.VMLRenderer.createNode("v:fill");
fill.on = "false";
node.appendChild(fill);
node.fill = fill;
-
+
// Create a stroke node
- var stroke = Graphic.VMLRenderer.createNode("v:stroke");
+ var stroke = Graphic.VMLRenderer.createNode("v:stroke");
stroke.on = "false";
node.appendChild(stroke);
- node.stroke = stroke;
+ node.stroke = stroke;

// Create a skew node
- var skew = Graphic.VMLRenderer.createNode("v:skew");
+ var skew = Graphic.VMLRenderer.createNode("v:skew");
skew.on = "true";
node.appendChild(skew);
- node.skew = skew;
+ node.skew = skew;
}
-
+
return node;
},
-
+
add: function(shape) {
- if (shape.parent) {
+ if (shape.parent) {

shape.parent.getRendererObject().appendChild(shape.getRendererObject());
// Why IE lost node attributes!!!
this.updateAttributes(shape, shape.attributes);
@@ -171,9 +182,9 @@
this.element.appendChild(shape.getRendererObject());
}
},
-
+
remove:function(shape) {
- if (shape.parent)
+ if (shape.parent)

shape.parent.getRendererObject().removeChild(shape.getRendererObject());
else
this.element.removeChild(shape.getRendererObject());
@@ -183,11 +194,11 @@
var element = $(id)
return element && element.shape ? element.shape : null;
},
-
+
shapes: function() {
return $A(this.element.childNodes).collect(function(element)
{return element.shape});
},
-
+
clear: function() {
$A(this.element.childNodes).each(function(element) {
element.shape.destroy();
@@ -198,47 +209,50 @@
var element = shape.element;
if (!element)
return;
-
- $H(attributes).keys().each(function(key) {
+
+ $H(attributes).keys().each(function(key) {
//console.log(element.nodeName + " " + key + " "+
attributes[key])
switch (key) {
case "width":
case "height":
element.style[key] = attributes[key] + "px";
- break;
+ this._updateTextPath(shape);
+ break;
case "cx":
case "cy":
case "rx":
- case "ry":
- case "r":
+ case "ry":
+ case "r":
if (element.nodeName != "roundrect") {
var rx = typeof shape.attributes.rx != "undefined" ?
shape.attributes.rx : shape.attributes.r;
var ry = typeof shape.attributes.ry != "undefined" ?
shape.attributes.ry : shape.attributes.r;
- element.style.left = (shape.attributes.cx -
rx).toFixed();
+ element.style.left = (shape.attributes.cx -
rx).toFixed();
element.style.top = (shape.attributes.cy -
ry).toFixed();
element.style.width = (rx * 2).toFixed();
element.style.height = (ry * 2).toFixed();
}
- break;
+ break;
case "x":
- element.style["left"] = attributes[key] + "px";
+ element.style["left"] = attributes[key] + "px";
+ this._updateTextPath(shape);
break;
case "y":
element.style["top"] = attributes[key] + "px";
+ this._updateTextPath(shape);
break;
case "fill":
if (element.fill) {
element.fill.color = attributes[key].parseColor();
element.fill.on = "true";
}
- break;
+ break;
case "fill-opacity":
if (element.fill) {
if (attributes[key] == "none") {
- element.fill.on = "false";
+ element.fill.on = "false";
}
- else {
- element.fill.opacity = attributes[key];
+ else {
+ element.fill.opacity = attributes[key];
element.fill.on = "true";
}
}
@@ -246,28 +260,28 @@
case "stroke":
if (element.stroke) {
if (attributes[key] == "none") {
- element.stroke.on = "false";
+ element.stroke.on = "false";
}
else {
element.stroke.color = attributes[key].parseColor();
element.stroke.on = "true";
}
}
- break;
+ break;
case "stroke-opacity":
if (element.stroke) {
- element.stroke.opacity = attributes[key];
+ element.stroke.opacity = attributes[key];
element.stroke.on = "true";
}
break;
case "stroke-width":
if (element.stroke) {
- element.stroke.weight = attributes[key] + "px";
+ element.stroke.weight = attributes[key] + "px";
element.stroke.on = "true";
}
- break;
- case "points":
- var p = shape.getPoints();
+ break;
+ case "points":
+ var p = shape.getPoints();
var attr = [];
if (p.length > 0) {
attr.push("m");
@@ -280,22 +294,29 @@
attr.push(p[i][1].toFixed());
}
}
- }
- if (shape.getType() == "polygon")
- attr.push("x");
- else
- attr.push("e");
- element.path = attr.join(" ");
- break;
- case "x1": //x2, y1, y2
+ }
+ if (shape.getType() == "polygon")
+ attr.push("x");
+ else
+ attr.push("e");
+ element.path = attr.join(" ");
+ break;
+ case "x1": //x2, y1, y2
element.from = shape.attributes.x1.toFixed() + " " +
shape.attributes.y1.toFixed();
element.to = shape.attributes.x2.toFixed() + " " +
shape.attributes.y2.toFixed();
break
+ case 'font-family':
+ case 'size':
+ case 'weight':
+ if( element.textpath ){
+ element.textpath.style.fontFamily = attributes[key];
+ }
+ break;
case "id":
element.id = attributes[key]
break;
case "class":
- // Nothing to do
+ // Nothing to do
element.id = attributes[key]
break;
case "href":
@@ -305,20 +326,20 @@
//console.log("updateAttributes: key " + key + "/" +
attributes[key] + " not supported")
break;
}
- });
+ }.bind(this));
},
-
+
updateTransform:function(shape) {
- if (!shape.element)
+ if (!shape.element)
return;
- var matrix = shape.getMatrix();
+ var matrix = shape.getMatrix();
if (shape instanceof Graphic.Group && shape.children) {
shape.children.each(function(s) {
var m = s.getMatrix()
this._updateSkew(s, Matrix.multiply(matrix, m))
}.bind(this))
}
- else if (shape instanceof Graphic.Image) {
+ else if (shape instanceof Graphic.Image) {
// if (shape.element.filters.length >0)
// with
(shape.element.filters["DXImageTransform.Microsoft.Matrix"]) {
// M11 = matrix.xx;
@@ -327,79 +348,107 @@
// M22 = matrix.yy;
// Dx = matrix.dx;
// Dy = matrix.dy;
- // }
+ // }
}
else {
this._updateSkew(shape, matrix);
}
},
-
- _updateSkew:function(shape, matrix) {
+
+ _updateSkew:function(shape, matrix) {
if (!shape.element.skew)
return;
-
+
var bounds = shape.getBounds();
shape.element.skew.on = "false";
- shape.element.skew.matrix = matrix.xx.toFixed(8) + " " +
matrix.xy.toFixed(8) + " " + matrix.yx.toFixed(8) + " " +
matrix.yy.toFixed(8) + " 0 0";
+ shape.element.skew.matrix = matrix.xx.toFixed(8) + " " +
matrix.xy.toFixed(8) + " " + matrix.yx.toFixed(8) + " " +
matrix.yy.toFixed(8) + " 0 0";
// TODO: Fix it with renderer viewing
//var pt =
Matrix.invert(this.viewingMatrix).multiplyPoint(matrix.dx, matrix.dy)
pt = {x:matrix.dx, y:matrix.dy}
- shape.element.skew.offset = Math.floor(pt.x).toFixed() + "px " +
Math.floor(pt.y).toFixed() + "px";
- shape.element.skew.origin = ((bounds.w != 0 ? -bounds.x /
bounds.w : 1) - 0.5).toFixed(8) + " " + ((bounds.h != 0 ? -bounds.y /
bounds.h : 1) - 0.5).toFixed(8);
- shape.element.skew.on = "true";
+ shape.element.skew.offset = Math.floor(pt.x).toFixed() + "px " +
Math.floor(pt.y).toFixed() + "px";
+ shape.element.skew.origin = ((bounds.w != 0 ? -bounds.x /
bounds.w : 1) - 0.5).toFixed(8) + " " + ((bounds.h != 0 ? -bounds.y /
bounds.h : 1) - 0.5).toFixed(8);
+ shape.element.skew.on = "true";
},
-
+
nbShapes: function() {
return this.element.childNodes.length;
},
-
+
moveToFront: function(node) {
if (this.nbShapes() > 0) {
//this.element.removeChild(node.element);
this.element.appendChild(node.element);
}
},
-
+
moveToBack: function(node) {
if (this.nbShapes() > 0) {
- this.element.insertBefore(node.element,
this.element.firstChild);
+ this.element.insertBefore(node.element,
this.element.firstChild);
}
},

show:function(shape) {
shape.element.style.display = "block";
},
-
+
hide:function(shape) {
- shape.element.style.display = "none";
+ shape.element.style.display = "none";
},
-
+
draw: function() {
// Nothing to do in VML
},
-
+
pick: function(event) {
- var element = Event.element(event);
+ var element = Event.element(event);
return element == this.element.parent ? null : element.shape;
},
-
+
position: function() {
if (this.offset == null)
this.offset =
Position.cumulativeOffset(this.element.parentNode);
return this.offset;
},
-
+
addComment: function(shape, text) {
shape.element.appendChild(document.createComment(text));
- },
-
+ },
+
addText: function(shape, text) {
-// shape.element.appendChild(document.createTextNode(text));
- },
-
- _setViewing: function() {
+ // try to find the textpath first
+ var childNodes = shape.element.childNodes;
+ var textpath = null;
+ for( var i = 0, len = childNodes.length; i < len; ++i ){
+ if( childNodes[i].tagName == "textpath" ){
+ textpath = childNodes[i];
+ break;
+ }
+ }
+ if( !textpath ){
+ var textpath = document.createElement("v:textpath");
+ textpath.on=true;
+ textpath.fitpath=false
+ textpath.fitshape=false
+ textpath.style.fontFamily = shape.getFont().fontFamily;;
+ textpath.style.fontSize = shape.getFont().fontSize;
+ textpath.style.fontWeight = shape.getFont().fontWeight;
+ shape.element.appendChild(textpath);
+ }
+ textpath.string = text;
+ this._updateTextPath(shape);
+ },
+ _updateTextPath: function(shape)
+ {
+ var location = shape.getLocation();
+ var size = shape.getSize();
+ if( location.x != undefined && location.y != undefined &&
size.width ){
+ shape.element.path = "m " + location.x + "," + location.y +
+ " l " + size.width + "," + location.y;
+ }
+ },
+ _setViewing: function() {
var bounds = this.viewingMatrix.multiplyBounds(this.bounds);
this.element.coordsize = bounds.w + ", " + bounds.h;
this.element.coordorigin = bounds.x + ", " + bounds.y;
}
-});
\ No newline at end of file
+});

taf2

unread,
Oct 13, 2007, 8:08:31 PM10/13/07
to prototype-graphic
Just updated the patch to include a fix to make the font rendering
more consistent with the SVG renderer.

In the samples/shape.html now the only difference is text doesn't work
with drag.

-Todd

taf2

unread,
Oct 13, 2007, 8:42:56 PM10/13/07
to prototype-graphic
Hi All,

Not sure where the best place to carry on this thought... but in
trying to further understand the issues with font differences between
SVG and VML. I think the main issue I've run into now is the
coordinate system between the two renderers appears to be different.
VML appears to set the zero zero position at the top left corner of a
group or container, while SVG appears to set the zero zero position in
the center.

My first thought to fix this was position:relative the containers with
a position absolute child... works in DHTML world... but I don't think
it carries over so well in the SVG/VML world...

-Todd

> ...
>
> read more »

taf2

unread,
Oct 13, 2007, 9:31:31 PM10/13/07
to prototype-graphic
Hi All,
Okay, the latest update to the patch here => http://severna.homeip.net/vml-render-text.patch
Fixes the issues with text positioning and text size.
-Todd

> ...
>
> read more »

taf2

unread,
Oct 16, 2007, 12:40:06 AM10/16/07
to prototype-graphic
Just wanted to send an update. I have updated the VML text patch to
correctly render the text position. My patches can more easily be
found here:

http://updates.simosoftware.com/pkg/heap-sort/prototype-graphic/vml-render-text.patch

as well my test app is here:

http://updates.simosoftware.com/pkg/heap-sort/heap-sort.html

You'll notice that on Firefox Mac svg text doesn't work however :-(

Sébastien Gruhier

unread,
Oct 16, 2007, 3:45:35 AM10/16/07
to prototyp...@googlegroups.com
Thanks a lot Tod, I will check what you did
I really appreciate help on VML :)
Seb
Reply all
Reply to author
Forward
0 new messages