Proper way to calculate the height and width of a SVG text - text

I need to calculate the height and width of a svg text element. Is there a way to do so without actually adding it to the DOM of my page? I need only the measures not the actual element.
I am using neither d3 nor Raphael, but only plain JavaScript. (Maybe I should use one of the former for my calculations?)
What I am after is just a function like imagettfbbox in PHP, but in plain JavaScript. Is there such a thing? Or is it easy to write?
Since I am not actually using the text elements it seems strange to me to add them and hide them (I also have read somewhere that Firefox has problems with calculating the bbox of hidden elements, Calculating vertical height of a SVG text). But maybe this is the only way to go? Will I have to work with opacity in that case? Do I destroy the element somehow afterwards?

Maybe there is no good way to achieve exactly what I was after. However, giving up the "without actually adding it to the DOM of my page" part, the following function seems to achieve my goal.
function bboxText( svgDocument, string ) {
var data = svgDocument.createTextNode( string );
var svgElement = svgDocument.createElementNS( svgns, "text" );
svgElement.appendChild(data);
svgDocument.documentElement.appendChild( svgElement );
var bbox = svgElement.getBBox();
svgElement.parentNode.removeChild(svgElement);
return bbox;
}
Edit:
A note on the calculation of the height: the height of the bbox returned, i.e. bbox.height, is always the full height of the glyph, i.e. a will have the same height as A. And I could not find a way to calculate them more exactly.
However, one can calculate the height of uppercase accented characters, e.g. Ä. This will be just the negative of the y coordinate of the bbox, i.e. -bbox.y.
Using this one can calculate, for example, some coordinates for vertical alignment. For example to emulate the dominantBaseline attribute set to text-before-edge, text-after-edge, and central.
text-before-edge: dy = -bbox.y
text-after-edge: dy = -bbox.height -bbox.y
central: dy = -bbox.y -bbox.height/2
Where dy is the vertical translation. This can be used to get around limitations of some applications that do not support these alignments set by attributes.

I have encountered a similar problem in VB.Net !
I written a VB.Net program that generate a SVG file and that needs to compute width of text to compute next vertical bar position as you can see in following image
The B line vertical bar is positionned in computing max text width of A line elements.
To do that in Console VB.Net application, I execute following lines of code
Private Sub WriteTextInfo(sInfo As String)
If sInfo <> "" Then
'display Text in SVG file
sw.WriteLine("<text x ='" & (xPos + 10) & "' y='" & yPos & "' fill='black' font-family='Arial' font-size='20'>" & sInfo & "</text>")
'Create Font object as defined in <text> SVG tag
Dim font As New Font("Arial", 20.0F)
'Compute width of text in pixels
Dim xSize = TextRenderer.MeasureText(sInfo, font)
'Divide pixels witdh by a factor 1.4 to obtain SVG width !
Dim iWidth As Decimal = Math.Truncate(CDec(xSize.Width) / 1.4)
'If new vertical position is greater than old vertical position
If xPos + iWidth > xPosMax Then
xPosMax = xPos + iWidth
End If
End If
End Sub
In resume, I compute text's width in pixels using TextRenderer.MeasureText() function and I divide this number by 1.4 to obtain SVG width.
1.4 value is obtained by experiment relatively to my case !
To use TextRendered and Font objects, I have added reference to
System.Drawing
System.Windows.Forms
As you can see, I don't use any DOM method to compute text's width because I compute it in VB.Net program.

Related

How to set relative position (oCoords) in FabricJs?

I have a Text in fabricJs. I set top and left.
This sets the aCoords properly to those values.
However the oCoords dont match. And the Text is not displayed at the right position.
I suspect that I need to set to oCoords somehow. So that the Text is displayed at the right pixel coordinates (top & left) on the canvas.
aCoords and oCoords are two different things and should not be in sync.
In your comment you speak about scaled canvas.
Top and Left are 2 absolute values that represent the position of the object on the canvas. This position match with the canvas pixels when the canvas has a identity transform matrix.
If you apply a zoom, this coordinates diverge.
To get the position of pixel 300,100 of the scaled canvas on the unscaled canvas, you need to apply some basic math.
1) get the transform applied to the canvas
canvas.viewportTransform
2) invert it
var iM = fabric.util.invertTransform(canvas.viewportTransform)
3) multiply the wanted point by this matrix
var point = new fabric.Point(myX, myY);
var transformedPoint = fabric.util.transformPoint(point, iM)
4) set the object at that point.

How to add centered text with gm for node (graphicsmagick/imagemagick)?

This relates to the "gm" extension for node, http://aheckmann.github.io/gm/docs.html
I need to add some text centered around a bounding box (horizontally is enough). The function drawText() requires x,y coordinates, but there is no way to draw centered text.
I would otherwise need a function which can return the width of a text string in the font/size given, so I can calculate my starting x position in javascript, before calling drawText().
You can use the region and gravity functions this way:
gm(filePath)
.region(WIDTH, HEIGHT, X, Y)
.gravity('Center')
.fill(color)
.fontSize(textFontSize)
.font(font)
.drawText(0, 0, 'This text will be centered inside the region')

d3.js : getting the bars width or X position right?

I have a weird issue in my bar graph realized using d3.js: the 1 px padding between each rectangle appears irregular. I gather either or both the width or x position are the culprit but i don't understand what i'm doing wrong: the width is a fraction of the svg area and the X position is obtained via a D3 scale.
I've put a demo here: http://jsfiddle.net/pixeline/j679N/4/
The code ( a scale) controling the x position:
var xScale = d3.time.scale().domain([minDate, maxDate]).rangeRound([padding, w - padding]);
The code controlling the width:
var barWidth = Math.floor((w/dataset.length))-barPadding;
Thank you for your insight.
It's irregular because you are rounding your output range (rangeRound). In some cases, the distance between two bars is 3 pixels and sometimes only 2. This is because the actual x position is a fractional value and ends up being rounded one way in some cases and the other way on other cases.
You can mitigate the effect but changing rangeRound to range, but that won't eliminate it entirely as you'll still get fractional pixel values for positions. The best thing to do is probably to simply increase the padding so that the differences aren't as obvious.

Create native SVG <circle> elements from Illustrator/Gephi

I'm trying to add interactivity to a network graph I made in Gephi, which outputs SVG. I'm using a Raphael template that likes my nodes to be inputted as SVG circles. The problem is that my SVG circles from Gephi are actually bezier curves. For example:
<path fill="#D52A2A" d="M663.395,426.958c0-2.869-2.324-5.194-5.193-5.194s-5.191,2.325-5.191,5.194
c0,2.867,2.322,5.189,5.191,5.189C661.069,432.148,663.395,429.826,663.395,426.958"/>
Is there any way to convert a path like this to a standard SVG circle element with an x, y, and radius?
You could analyze the paths, but it's easier if you know which paths are circles to begin with, then you could do a quick approximation like this:
var p = document.querySelector("path");
var c = document.createElementNS("http://www.w3.org/2000/svg", "circle");
var b = p.getBBox();
c.cx.baseVal.value = b.x + b.width/2;
c.cy.baseVal.value = b.y + b.height/2;
c.r.baseVal.value = b.width/2; // assuming width and height are the same
p.parentNode.appendChild(c);
Hints that your path is a circle might be that the bbox width and height are very close to equal, or that the path 'd' attribute gephi outputs uses the same form always for circles (not sure if that's the case).
Gephi is opensource, so another option is to look at making it output what you want to begin with.
Update: here's a jsfiddle showing this.

Line width in raphaeljs

Is it real to make line with 1px weight in SVG or raphaeljs?
The follow code
var p = Paper.path("M1 1 L50 1");
p.attr("stroke", "#D7D7D7");
p.attr("stroke-width", "1");
p.attr("opacity", 0.5);
draw line which looks like 2px or 3px. Any alternative?
When SVG lines lie at their apparently correct coordinates they actually lie inbetween pixels, so when you state M1 1 L50 1 it paints half a pixel on the top and the other half in the bottom of the pixel, making it look like a thick, semitransparent line.
To solve this problem you need to either paint at half pixels, or translate your elements half a pixel, ie. element.translate(0.5, 0.5)
You can see the blurry and sharp lines here:
http://jsfiddle.net/k8AKy/
You should also use the Paper.renderfix() function since you do not know which browser your users will be using.
From the documentation
Fixes the issue of Firefox and IE9 regarding subpixel rendering. If
paper is dependant on other elements after reflow it could shift half
pixel which cause for lines to lost their crispness. This method fixes
the issue.
This links take you point what's going wrong with integer coordinates and why +0.5 was fixed edge blurring (with nice pictures!!):
http://diveintohtml5.info/canvas.html#pixel-madness
http://kilianvalkhof.com/2010/design/the-problem-with-svg-and-canvas/
Compare:
with +0.5:
You can avoid +0.5 by:
SVG_Area.setAttribute("viewBox", "0.5 0.5 " + width + " " + height);
or by wrapper:
function fiXY(x) { return parseInt(x) + 0.5; }
var rect = document.createElementNS(SVGobj.svgNS, "rect");
rect.setAttribute("x", fiXY(x));
rect.setAttribute("y", fiXY(y));
or by:
SVG_Area.setAttribute("shape-rendering", "crispEdges");
which effect on all shapes in you SVG image....

Resources