Text and textArea height

Bonjour,

When converting svg ‘textArea’ to ‘text’, or creating the two types of objects from the same text, the two objects display different heights. Some fonts produce areas of the same, or close enough, height. Other fonts are way off.

I’d like to make the ‘text’ object the same height as the ‘textArea’ object. Does anyone know of any font and/or ‘textArea’ properties that could be used to adjust the ‘linespacing’ attribute when creating a ‘text’?

Merci - Keith

[]19141177;“Sample”[/]

Hi Keith,
from http://stackoverflow.com/questions/29829277/svg-how-to-set-text-line-heightthis> and http://stackoverflow.com/questions/4991171/auto-line-wrapping-in-svg-textthat>, svg seems poor. I don’t know if the engine that draws the textarea is based on some font characteristics to get line spacing. I remember when I had to convert textarea in N lines that I used a factor, something like (1.2*text height) - i.e. y values in your example 22, 34, 46, 58 would become 22, 36, 50, 64. With very small fonts, 1.2 may be increased for easier reading.

Hi Arnaud,

1.2 worked for the font I was using the most, but other fonts really misbehave;

Getting the “occupied visible” height of the ‘textArea’ is tricky.
But, with that and the height of a ‘text’ line:
Create one line of the ‘text’ at 0,0 in a new svg document.
Convert that to a picture and get its height.

There would be enough information to space ‘tspan’ elements…

I think you can try to use http://doc.4d.com/4Dv16/4D/16/SVG-New-tspan.301-3202317.en.htmlsvg_new_tspan>.

The line spacing to calculate ‘y’ for each svg_new_tspan could be gotten using the height of the visible textArea in the svg document. The problem is retrieving that height, as shown in the illustration only the textArea object’s height is available.

OBJECT GET BEST SIZE seemed like a solution, but it only works on form objects.

TEXT TO ARRAY provides the right line breaks for each tspan.

[]19144868;“SVG Picture showing a textArea”[/]

This seems like a lot of processing time is taken, but the results are close enough.
I could not get TRANSFORM PICTURE’s ‘Translate’ to produce any changes to the pic, but ‘Crop’ worked.

It compares the cropped parts of a blank picture and a picture of the text (binary search style) to figure out where the blank part of the textArea begins.

<code 4D>
// ----------------------------------------------------
// Method: textArea_VisibleHeight
// - Returns the height of the visible text in a textArea. Excludes text below the object height.
// INPUT1: ref - svg textArea ref
// OUTPUT: Longint - Approx height of visible text
// ----------------------------------------------------
C_TEXT($inRef;$1)
C_PICTURE($pic;$picBlank;$pic2;$p1;$p2)
C_LONGINT($from;$0;$h;$height;$width)

$inRef:=$1

$svg:=SVG_New
$newRef:=SVG_Add_object ($svg;$inRef)
SVG_SET_ATTRIBUTES ($newRef;“x”;“0”;“y”;“0”)
$width:=Num(get_Attribute ($newRef;“width”))
$height:=Num(get_Attribute ($newRef;“height”))
$pic:=SVG_Export_to_picture ($svg)
SVG_CLEAR ($svg)

$svg:=SVG_New
$newRef:=SVG_New_textArea ($svg;"";0;0;$width;$height)
$picBlank:=SVG_Export_to_picture ($svg)
SVG_CLEAR ($svg)

$h:=$height/2
$from:=$h
While ($h>1)
$p1:=$pic
$p2:=$picBlank
TRANSFORM PICTURE($p1;Crop;0;$from;$width;$height)
TRANSFORM PICTURE($p2;Crop;0;$from;$width;$height)
$h:=$h/2
If (Equal pictures($p1;$p2;$pic2))
$from:=$from-$h
Else
$from:=$from+$h
End if

End while

$0:=$from+1
</code 4D>

This is what I do in the “label editor” component.

https://github.com/miyako/4d-component-label-editor

first, I have a “set text” method

Editor_TEXT_EDIT_SET_VALUE ($dom;$text)

<code 4D>
C_TEXT($1;$2)

$textArea:=$1
$textValue:=$2

C_LONGINT($i;$j;$count)
$count:=DOM Count XML elements($textArea;“tbreak”)

ARRAY TEXT($tbreaks;0)
$tbreak:=DOM Find XML element($textArea;“textArea/tbreak”;$tbreaks)

For ($i;1;Size of array($tbreaks))
DOM REMOVE XML ELEMENT($tbreaks{$i})
End for

$i:=1

ARRAY LONGINT($len;0)
ARRAY LONGINT($pos;0)

While (Match regex("(.+)";$textValue;$i;$pos;$len))
$line:=Substring($textValue;$pos{1};$len{1})
If ($i=1)
DOM SET XML ELEMENT VALUE($textArea;$line)
Else
$breaks:=Substring($textValue;$i;$pos{1}-$i)
$count:=Length(Replace string($breaks;"\r\n";"\n";*))
For ($j;1;$count)
$tbreak:=DOM Create XML element($textArea;“tbreak”)
End for
$append:=DOM Append XML child node($textArea;XML DATA;$line)
End if
$i:=$pos{1}+$len{1}
End while
</code 4D>

I call it like this:

<code 4D>
C_TEXT($1;$2)
C_REAL($3)
C_TEXT($4;$5;$6;$7)
C_POINTER($8;$9)

$text:=$1
$fontFamily:=$2
$fontSize:=$3
$fontStyle:=$4
$fontWeight:=$5
$textAlign:=$6
$textDecoration:=$7

$svg:=DOM Create XML Ref(“svg”;“http://www.w3.org/2000/svg";“xmlns:svg”;“http://www.w3.org/2000/svg”;“xmlns:xlink”;"http://www.w3.org/1999/xlink”)

$dom:=DOM Create XML element($svg;“text”;“x”;0;“y”;0;“fill”;“red”;“fill-opacity”;0.4;“font-family”;$fontFamily;“font-size”;$fontSize;“font-style”;$fontStyle;“font-weight”;$fontWeight;“text-anchor”;$textAlign;“text-decoration”;$textDecoration;“text-rendering”;“auto”)

//to compute the handing height of the last line

ARRAY LONGINT($pos;0)
ARRAY LONGINT($len;0)

$line:=$text

If (Match regex(".*$";$text;1;$pos;$len))
$line:=Substring($text;$pos{0};$len{0})
End if

DOM SET XML ELEMENT VALUE($dom;$line)

SVG EXPORT TO PICTURE($svg;$image)
C_REAL($width;$height;$height2)
PICTURE PROPERTIES($image;$width;$height)

DOM SET XML ATTRIBUTE($dom;“fill-opacity”;0)

$dom:=DOM Create XML element($svg;“textArea”;“x”;0;“y”;0;“fill”;“red”;“fill-opacity”;0.4;“font-family”;$fontFamily;“font-size”;$fontSize;“font-style”;$fontStyle;“font-weight”;$fontWeight;“text-align”;$textAlign;“text-decoration”;$textDecoration;“text-rendering”;“auto”)

Editor_TEXT_EDIT_SET_VALUE ($dom;$text)

SVG EXPORT TO PICTURE($svg;$image)
PICTURE PROPERTIES($image;$width;$height2)

DOM SET XML ATTRIBUTE($dom;“height”;$height2+$height)

$dom:=DOM Create XML element($svg;“rect”;“x”;0;“y”;0;“width”;$width;“height”;$height2+$height-4;“fill-opacity”;0.1;“shape-rendering”;“geometricPrecision”)

SVG EXPORT TO PICTURE($svg;$image)

PICTURE PROPERTIES($image;$width;$height)

DOM CLOSE XML($svg)

$8->:=$width
$9->:=$height
</code 4D>

and it will return a picture with the “best” width and height.

the point to is to call SVG EXPORT TO PICTURE several times;

by using a text element with the height undefined,
you find the height below the baseline (a.k.a. descent, hanging).

by using a textArea element with the height undefined,
you find the height above the last baseline.

by adding the two you get the “best” width and height.
(the part that inserts a background rect in the code above maybe unnecessary for your needs.)

Here are some measurements of the two methods of getting the visible text height for two fonts.
[]19152853;“Results”[/]

However, getting a ‘y’ value so a ‘textArea’ can be reproduced in a ‘text’ with a number of tspans was not as complicated as I thought. This code gives a good lineHeight number of pixels. Pass the textArea ref so the attributes are the same, make two lines of text, and take their ‘average’.

<code 4D>
// ----------------------------------------------------
// Method: get_LineHeight
// -
// INPUT1: Text - textArea ref
// OUTPUT: Longint - line character height
// ----------------------------------------------------
C_TEXT($ref;$1;$t)
C_LONGINT($w;$h;$0;$averageOf)

$ref:=$1

$averageOf:=2 // 1 is not good, higher numbers make no difference
For ($i;1;$averageOf)
$t:=$t+“M”+Char(13)
End for

$svg:=SVG_New
$newRef:=SVG_Add_object ($svg;$ref)
SVG_SET_TEXTAREA_TEXT ($newRef;$t)
SVG_SET_ATTRIBUTES ($newRef;“x”;“0”;“y”;“0”;“width”;“0”;“height”;“0”)
SVG_SET_TRANSFORM_ROTATE ($newRef;0)
$pic:=SVG_Export_to_picture ($svg)
SVG_CLEAR ($svg)

PICTURE PROPERTIES($pic;$w;$h)
$0:=$h/$averageOf
</code 4D>

This can use that number:
<code 4D>
$spacing:=Choose($h=0;$fontSize*1.21;$h)

ARRAY TEXT($aText;0)
TEXT TO ARRAY($text;$aText;$width;$family;$fontSize;$fontStyle)
$newT:=SVG_New_text ($svgArea;$aText{1};$x;$y;$family;$fontSize;$fontStyle;$alignN;$fill)

$size:=Size of array($aText)
For ($i;2;$size)
$y:=$y+$spacing
$ref:=SVG_New_tspan ($newT;$aText{$i};$x;$y;$family;$fontSize;$fontStyle;$alignN;$fill)
End for
</code 4D>

Thanks

Correction: Sorry the $averageOf number should be at least 3

$averageOf:=3 //1 & 2 are not good, higher numbers make no difference

Relating to the original question: Depending on the font family, a ‘text’ placed at x,y usually appears one or more pixels below a ‘textArea’ placed at x,y. On the other hand an Arial ‘text’ appears one pixel above and Helvetica appears even. So, just for completeness:

<code 4D>
// ----------------------------------------------------
// Method: get_TextVOffset
// - Measures the vertical offset between a ‘textArea’ and a ‘text’
// - created at the same y coordinate. (assumes lineSpacing is default)
// INPUT1: Text - font family
// INPUT1: Longint - font size
// INPUT1: Longint - font style
// OUTPUT: Longint - offset
// ----------------------------------------------------

C_TEXT($fontName;$1;$text)
C_LONGINT($w;$h1;$h2;$0;$fontSize;$2;$fontStyle;$3)
C_PICTURE($pic1;$pic2)

$fontName:=$1
$fontSize:=$2
$fontStyle:=$3
$text:=“Mj”

$svg:=SVG_New
SVG_New_text ($svg;$text;0;0;$fontName;$fontSize;$fontStyle)
$pic1:=SVG_Export_to_picture ($svg)
SVG_CLEAR ($svg)
PICTURE PROPERTIES($pic1;$w;$h1)

$svg:=SVG_New
SVG_New_textArea ($svg;$text;0;0;0;0;$fontName;$fontSize;$fontStyle)
$pic2:=SVG_Export_to_picture ($svg)
SVG_CLEAR ($svg)
PICTURE PROPERTIES($pic2;$w;$h2)

$0:=$h1-$h2
</code 4D>