Is it possible to more accurately measure SVG text height? - javascript

I'm trying to measure the exact height used to render a given string with a given font with an SVG text tag.
I've tried using getBBox and getExtentOfChar, but the height returned by both of these includes some extra space above (and sometimes below) the actual text rendered.
http://upload.wikimedia.org/wikipedia/commons/3/39/Typography_Line_Terms.svg
Using the terms in this image, I'm trying to get the either the cap height + descender height of the text being rendered. Or, if that's not possible, just the cap height. Is there a good way to calculate these values?
Here's a quick codepen showing the extra space I'm talking about:
http://codepen.io/pcorey/pen/amkGl
HTML:
<div>
<svg><text>Hello</text></svg>
<svg><text>Age</text></svg>
</div>
JS:
$(function() {
$('svg').each(function() {
var svg = $(this);
var text = svg.find('text');
var bbox = text.get(0).getBBox();
svg.get(0).setAttribute('viewBox',
[bbox.x,
bbox.y,
bbox.width,
bbox.height].join(' '));
});
});
I understand that this is a fairly font-specific thing, so this might be totally impossible...

No. All the SVG DOM methods (getBBox(), getExtentOfChar()) are defined to return the full glyph cell height. That extra space above the cap height is allowance for taller glyphs - such as accented capitals. I think this is true for HTML DOM methods as well.
There are, however, JS libraries around which may be of use. For example:
https://github.com/Pomax/fontmetrics.js
I have not used this library myself, so I can't tell you how reliable or accurate it is.

Related

retrieving the aspect ratio of an svg <image>

TLDR;
given this svg element:
<image width="30" height="48" x="3.75" y="6" href="http://some/image.jpg">
How can I retrieve the image's actual height and width (seeing as it is defined in part by the image's aspect ratio).
I have a d3js script that draws a bunch of <rect>s and a bunch of <image>s.
Now stuff is laid out so that the images fall inside the rects, as though the rects were borders. There is other stuff inside the rects too.
Now each of these images has it's own unique and special aspect ratio and this bothers me because it means each of the rects then has a different amount of blank space. This is untidy.
To avoid this I want to load the images then get the actual image dimensions and then adjust the positions and sizes of the surrounding goodies. The crux of the matter is getting the actual image sizes. How can I do this?
I've googled around a bit and spent some quality time with the debugging console to no avail. Just nothing comes up. I'll keep hunting but an answer would be really nice.
First, set the width attribute only, keep height unspecified.
Then, call the getBBox for the image element.
Note that image box is available after it's properly rendered by the SVG
const image = parent.append('image').attr('xlink:href', url).attr('width', 100);
setTimeout(() => {
const box = image.node().getBBox();
const ratio = box.width / box.height;
}, 0);
This is the best I can come up with. I would be surprised and horrified if there isn't an easier way. Anyway:
add a normal html <img> element with a suitable src.
use js to fetch the image height and width
remove the extra html
Ugly but it works...
Here's some code:
var oImage = document.createElement("img");
oImage.setAttribute("src",sUrl);
document.body.appendChild(oImage);
var iWidth = oImage.width;
var iHeight = oImage.height;
oImage.remove();

Text paragraph with fixed ratio within svg

I generate dynamic SVG graphics on the fly using JavaScript. For this purpose a paragraph of text should be added into a box with a fixed aspect ratio inside the svg image. The text length may differ between short and also very long text length. As the actual font size is not important for my purpose I use the viewBox attribute to show the whole paragraph within the box.
As far as I researched and tested until now, svg does not provide any automatic line breaking functionality, therefore I might use a standard HTML div within a foreignObject to make use of HTML line breaking.
Are there any possibilities to get a div with fixed aspect ratio based on its content length?
I already managed to get such a div by an ittertive decreasing of width until the ratio more or less fits the purpose. But this solution is rather imprecise and needs to add the div to the DOM before actually inserting it into the svg. Are there any CSS solutions?
As unfortunately nobody could help to solve this problem, I implemented the following (more or less working) solution:
for(var i = 0; i < 200; i++){
if($('#wrapper').width()/$('#wrapper').height() <= 5){
console.log($('#wrapper').width()/$('#wrapper').height())
break;
}
$('#wrapper').width($('#wrapper').width()*0.8);
}
for(var y = 0; y < 200; y++){
if($('#wrapper').width()/$('#wrapper').height() >= 4.9){
break;
}
$('#wrapper').width($('#wrapper').width()*1.02);
}
This approach tries to itteratively converge the aspect ratio towards an approximately correct ratio.
This is by far not an optimal solution, but at least a working one.

wrap text to fit into a rectangle : raphael

Anyone know of function that can break text at word boundaries to fit into rectangle
Following is code for rectangle and text
window.onload = function () {
var outsideRectX1=30, outsideRectY1=30,outsideRectX2=220, outsideRectY2=480, outsideRectR=10;
var group = paper.set();
var rect1=paper.rect(outsideRectX1+40, outsideRectY1+70, 80, 40,10);
var text3=paper.text(outsideRectX1+75, outsideRectY1+85,"Test code for wrap text").attr({fill: '#000000', 'font-family':'calibri', 'font-size':'14px'});
group.push(rect1);
group.push(text3);
};
When text is greater than rectangle width it automatically wrap so that it always display into rectangle boundaries.
I'm not sure whether there is any direct way to wrap the text according to the size of the rectangle. May be you can specify line breaks or a "\n". Or you can try to resize the rectangle as and when the text length increases.
Here is a sample code where the rectangle resize as the text length increases.
var recttext = paper.set();
el = paper.rect(0, 0, 300, 200);
text = paper.text(0,10, "Hi... This is a test to check whether the rectangle dynamically changes its size.").attr({"text-anchor":"start",fill:'#ff0000',"font-size": 14});
text1=paper.text(0,30,"hi").attr({"text-anchor":"start",fill: '#ff0000',"font-size": 14});
//el.setSize(495,200);
recttext.push(el);
recttext.push(text);
recttext.push(text1);
alert(recttext.getBBox().width);
alert(recttext.getBBox().height);
var att = {width:recttext.getBBox().width,height:recttext.getBBox().height};
el.attr(att);
recttext.translate(700,400);
I know it's a little belated now, but you might be interested in my [Raphael-paragraph][1] project.
It's a small library that allows you to create auto-wrapped multiline text with maximum width and height constraints, line height and text style configuration. It's still quite beta-ish and requires a lot of optimization, but it should work for your purposes.
Usage examples and documentation are provided on the GitHub page.

Getting selected text position

Currently I'm getting the selected text in the browser doing this:
window.getSelection();
Now I need to show a tooltip above that text when pressing a custom key(note that the mouse could not be over the text anymore), so in order to do that I need the absolute position of that selected text.
Is there a way to do that, maybe wrapping that text inside a tag and then getting the offsets? It just has to work in Chrome, not all browsers.
s = window.getSelection();
Returns a Selection. So try
s = window.getSelection();
oRange = s.getRangeAt(0); //get the text range
oRect = oRange.getBoundingClientRect();
oRect will be the bounding rectangle in client (fixed) coordinates.
The easiest way is to insert a temporary marker element at the start or end of the selection and get its position. I've demonstrated how to do this before on Stack Overflow: How can I position an element next to user text selection?
Before using getBoundingClientRect, you need to know this note:
CSSOM working draft specifies that it returns a ClientRect for each border box
And by this 'standard':
For an inline element, the two definitions are the same. But for a block element, Mozilla will return only a single rectangle.
So if anyone reading this post wants a general solution for more precise positions and layouts of selected texts, I suggest the following approaches:
Option 1: Find exact starting and and ending point of texts by inserting invisible elements. Then calculate selected line rectangles with extracted computed line height and container width. APIs in use: window.getComputedStyle.
Pro: the result would be most precise for each line of text.
Con: 1) If the selection is across several nodes with different line heights and widths, the algorithm becomes complex. 2) And you need to implement the computation algorithm, which is too time consuming when implementing a simple feature.
Option 2: Wrap each text with a carefully styled inline element, extracting
layout of each box, and merge results into lines.
Pro: Works for all continuous selections (that basically means all cases in current mainstream browser implementations.). Good enough precision for each line of texts.
Con: 1) Its result is a little inaccurate in some cases, as it adds error widths of kerning. 2) It's slow on very large selection of texts.
For option 2, rangeblock is an existing implementation with an easy API which gives you the absolution layout of each line of text:
let block = rangeblock.extractSelectedBlock(window, document);
console.info("Text layout: " + JSON.stringify(block.dimensions));
// output: Text layout: {Left: 100, Top: 90, Width: 200, Height: 50}

Convert HTML element non-px dimensions to px in (IE issue)

I'm trying to get border width of a particular element.
Getting border width style setting is pretty easy by simply reading if from current calculated style of an element:
var styles = (
document.defaultView && document.defaultView.getComputedStyle ?
document.defaultView.getComputedStyle(de, null) :
de.currentStyle
);
Reading a particular border value is then rather simple by:
var top = styles.borderTopWidth;
var value = parseFloat(top);
This is all fine and dandy (as long as you don't use IE) and I can get top border width in the value variable. But this number relates to pixels only when border width was set in pixels. If it wasn't (was em for instance) than value has the number of that particular dimension.
I have to get an answer to any of these two questions:
How do I always get border width in pixels?
How do I calculate different units into pixels?
Example
I've prepared a jsFiddle example where you can see various dimensions reported by DOM and jQuery. Run it in different browsers and you'll see the difference in IE. All dimansions in Crome are in integer values while Firefox calculates margin and padding in floats while border in integers.
BTW: Margin, border and padding are all set to 2mm.
Most libraries solve this problem for you, as does YUI3 for example.
If you don't want to use those libraries, then at least you can peak at how they do it ;)
http://developer.yahoo.com/yui/3/api/dom-style-ie.js.html
Awnser contained therein.
You can generally get computed pixel sizes using element.offsetWidth and element.offsetHeight. This is somewhat sensitive if you want to support a range of browsers. In that case, use a library. For example, using jQuery you can get guaranteed pixel dimensions with something like this: jQuery("#theID").width().

Categories

Resources