Reputation: 89
I want to extract pixel size of the text object (svgwrite.Drawing.Text) as it would appear in file after formatting with given style. My font is fixed-width (Courier New).
The reason why I need it is that I want to print to SVG file a string and then map on the resulting text some information: e.g. I have a string "ABCDEF" and external wise man told me that "BCD" portion should be marked. Then I need to know how many pixels (units?) are covered by symbol "A" and symbols "BCD" in both dimensions, and then draw a colored rectangle, or transparent frame, or whatever strictly under the "BCD" portion.
So I have the following code and I would expect to use something like "w = text1.width" to extract width, but it doesn't work this way. Thank you in advance for trying to answer my question.
import svgwrite
my_svg = svgwrite.Drawing(filename = "dasha.svg", size = ("800px", "600px"))
text_style = "font-size:%ipx; font-family:%s" % (12, "Courier New")
text1 = my_svg.text("HELLO WORLD", insert=(0, 0), fill="black", style=text_style)
my_svg.add(text1)
my_svg.save()
UPD1 [22.06.2014]: Intermediate solution which I use at the moment is to measure height and width of the letter with particular font and size manually in Inkscape. I tried my best, but I am not sure such values are perfect, and now I can't change font size in the program.
Upvotes: 8
Views: 7095
Reputation: 298
I wanted to determine the width of the text in svgwrite in a specific font. I ended up using the following solution from http://blog.mathieu-leplatre.info/text-extents-with-python-cairo.html:
def textwidth(text, fontsize=14):
try:
import cairo
except:
return len(text) * fontsize
surface = cairo.SVGSurface('undefined.svg', 1280, 200)
cr = cairo.Context(surface)
cr.select_font_face('Arial', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
cr.set_font_size(fontsize)
xbearing, ybearing, width, height, xadvance, yadvance = cr.text_extents(text)
return width
It brings in cairo as dependency but it is the most clean an platform-independent solution I've found so far.
Upvotes: 4
Reputation: 626
I ended up using Tkinter:
try:
# for Python2
import Tkinter
except ImportError:
# for Python3
import tkinter as Tkinter
try:
import tkFont
except ImportError:
import tkinter.font as tkFont
def get_text_metrics(family, size, text):
# initialize Tk so that font metrics will work
tk_root = Tkinter.Tk()
font = None
font = tkFont.Font(family=family, size=size)
assert font is not None
(w, h) = (font.measure(text), font.metrics('linespace'))
return (w, h)
Upvotes: 0
Reputation: 739
I had this exact same problem, but I had a variable width font. I solved it by taking the text element (correct font and content) I wanted, wrote it to a svg file, and I used Inkscape installed on my PC to render the drawing to a temporary png file. I then read back the dimensions of the png file (extracted from the header), removed the temp svg and png files and used the result to place the text where I wanted and elements around it.
I found that rendering to a drawing, using a DPI of 90 seemed to give me the exact numbers I needed, or the native numbers used in svgwrite as a whole. -D is the flag to use so that only the drawable element, i.e. the text, is rendered.
os.cmd(/cygdrive/c/Program\ Files\ \(x86\)/Inkscape/inkscape.exe -f work_temp.svg -e work_temp.png -d 90 -D)
I used these functions to extract the png numbers, found at this link, note mine is corrected slightly for python3 (still working in python2)
def is_png(data):
return (data[:8] == b'\x89PNG\r\n\x1a\n'and (data[12:16] == b'IHDR'))
def get_image_info(data):
if is_png(data):
w, h = struct.unpack('>LL', data[16:24])
width = int(w)
height = int(h)
else:
raise Exception('not a png image')
return width, height
if __name__ == '__main__':
with open('foo.png', 'rb') as f:
data = f.read()
print is_png(data)
print get_image_info(data)
It's clunky, but it worked
Upvotes: 1
Reputation: 429
I am not an expert in svg but it is my understanding that this is difficult to determine character and text width in svg. That means svgwrite cannot do it either. What I have done is picked a fixed width for the characters and create each character separately a fixed width from the start of the previous character. You may also consider centering string, that is each character, by using text_anchor='middle'. With a character centered on point a box could be created to be centered on that point also.
see also http://www.w3.org/TR/SVG/text.html which says "In many situations, the algorithms for mapping from characters to glyphs are system-dependent, resulting in the possibility that the rendering of text might be (usually slightly) different when viewed in different user environments. If the author of SVG content requires precise selection of fonts and glyphs, then the recommendation is that the necessary fonts (potentially subsetted to include only the glyphs needed for the given document) be available either as SVG fonts embedded within the SVG content or as WebFonts ([CSS2], section 15.1) posted at the same Web location as the SVG content."
Upvotes: -1