8000 First commit of code to embed fonts as paths in SVG files. · matplotlib/matplotlib@d871588 · GitHub
[go: up one dir, main page]

Skip to content

Commit d871588

Browse files
committed
First commit of code to embed fonts as paths in SVG files.
svn path=/trunk/matplotlib/; revision=3498
1 parent 815cbd8 commit d871588

File tree

3 files changed

+138
-37
lines changed

3 files changed

+138
-37
lines changed

lib/matplotlib/backends/backend_svg.py

Lines changed: 136 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
from __future__ import division
22

3-
import os, codecs, base64, tempfile
3+
import os, codecs, base64, tempfile, urllib
44

55
from matplotlib import verbose, __version__, rcParams
66
from matplotlib.backend_bases import RendererBase, GraphicsContextBase,\
77
FigureManagerBase, FigureCanvasBase
88
from matplotlib.colors import rgb2hex
99
from matplotlib.figure import Figure
10-
from matplotlib.font_manager import fontManager
11-
from matplotlib.ft2font import FT2Font
10+
from matplotlib.font_manager import fontManager, FontProperties
11+
from matplotlib.ft2font import FT2Font, KERNING_UNFITTED, KERNING_DEFAULT, KERNING_UNSCALED
1212
from matplotlib.mathtext import math_parse_s_ft2font_svg
1313

1414
backend_version = __version__
@@ -24,6 +24,8 @@ def new_figure_manager(num, *args, **kwargs):
2424
_fontd = {}
2525
_capstyle_d = {'projecting' : 'square', 'butt' : 'butt', 'round': 'round',}
2626
class RendererSVG(RendererBase):
27+
FONT_SCALE = 1200.0
28+
2729
def __init__(self, width, height, svgwriter, basename=None):
2830
self.width=width
2931
self.height=height
@@ -35,6 +37,7 @@ def __init__(self, width, height, svgwriter, basename=None):
3537
self.basename = basename
3638
self._imaged = {}
3739
self._clipd = {}
40+
self._char_defs = {}
3841
svgwriter.write(svgProlog%(width,height,width,height))
3942

4043
def _draw_svg_element(self, element, details, gc, rgbFace):
@@ -237,68 +240,159 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath):
237240
font = self._get_font(prop)
238241

239242
thetext = '%s' % s
240-
fontfamily=font.family_name
241-
fontstyle=font.style_name
243+
fontfamily = font.family_name
244+
fontstyle = font.style_name
242245
fontsize = prop.get_size_in_points()
243246
color = rgb2hex(gc.get_rgb())
244247

245-
style = 'font-size: %f; font-family: %s; font-style: %s; fill: %s;'%(fontsize, fontfamily,fontstyle, color)
246-
if angle!=0:
247-
transform = 'transform="translate(%f,%f) rotate(%1.1f) translate(%f,%f)"' % (x,y,-angle,-x,-y) # Inkscape doesn't support rotate(angle x y)
248-
else: transform = ''
248+
if rcParams['svg.embed_char_paths']:
249+
svg = '<g transform="'
250+
if angle!=0:
251+
svg += 'translate(%f,%f) rotate(%1.1f) ' % (x,y,-angle) # Inkscape doesn't support rotate(angle x y)
252+
else:
253+
svg += 'translate(%f,%f)' % (x,y)
254+
svg += ' scale(%f)">\n' % (fontsize / self.FONT_SCALE)
255+
256+
cmap = font.get_charmap()
257+
lastgind = None
258+
lines = []
259+
currx = 0
260+
for c in s:
261+
charid = self._add_char_def(prop, c)
262+
ccode = ord(c)
263+
gind = cmap.get(ccode)
264+
if gind is None:
265+
ccode = ord('?')
266+
name = '.notdef'
267+
gind = 0
268+
else:
269+
name = font.get_glyph_name(gind)
270+
glyph = font.load_char(ccode)
271+
272+
if lastgind is not None:
273+
kern = font.get_kerning(lastgind, gind, KERNING_UNFITTED)
274+
else:
275+
kern = 0
276+
lastgind = gind
277+
currx += kern/64.0
278+
279+
svg += ('<use xlink:href="#%s" transform="translate(%s)"/>\n'
280+
% (charid, currx / (fontsize / self.FONT_SCALE)))
281+
282+
currx += glyph.linearHoriAdvance / 65536.0
283+
svg += '</g>\n'
284+
else:
285+
style = 'font-size: %f; font-family: %s; font-style: %s; fill: %s;'%(fontsize, fontfamily,fontstyle, color)
286+
if angle!=0:
287+
transform = 'transform="translate(%f,%f) rotate(%1.1f) translate(%f,%f)"' % (x,y,-angle,-x,-y) # Inkscape doesn't support rotate(angle x y)
288+
else: transform = ''
249289

250-
svg = """\
290+
svg = """\
251291
<text style="%(style)s" x="%(x)f" y="%(y)f" %(transform)s>%(thetext)s</text>
252292
""" % locals()
253293
self._svgwriter.write (svg)
254294

295+
def _add_char_def(self, prop, char):
296+
newprop = prop.copy()
297+
newprop.set_size(self.FONT_SCALE)
298+
font = self._get_font(newprop)
299+
ps_name = font.get_sfnt()[(1,0,0,6)]
300+
id = urllib.quote('%s-%d' % (ps_name, ord(char)))
301+
if id in self._char_defs:
302+
return id
303+
304+
path_element = '<path id="%s" ' % (id)
305+
path_data = []
306+
glyph = font.load_char(ord(char))
307+
currx, curry = 0.0, 0.0
308+
for step in glyph.path:
309+
if step[0] == 0: # MOVE_TO
310+
path_data.append("m%s %s" %
311+
(step[1] - currx, -step[2] - curry))
312+
elif step[0] == 1: # LINE_TO
313+
path_data.append("l%s %s" %
314+
(step[1] - currx, -step[2] - curry))
315+
elif step[0] == 2: # CURVE3
316+
path_data.append("q%s %s %s %s" %
317+
(step[1] - currx, -step[2] - curry,
318+
step[3] - currx, -step[4] - curry))
319+
elif step[0] == 3: # CURVE4
320+
path_data.append("c%s %s %s %s %s %s" %
321+
(step[1] - currx, -step[2] - curry,
322+
step[3] - currx, -step[4] - curry,
323+
step[5] - currx, -step[6] - curry))
324+
elif step[0] == 4: # ENDPOLY
325+
path_data.append("Z")
326+
327+
if step[0] != 4:
328+
currx, curry = step[-2], -step[-1]
329+
path_element += 'd="%s"/>\n' % " ".join(path_data)
330+
331+
self._char_defs[id] = path_element
332+
return id
333+
255334
def _draw_mathtext(self, gc, x, y, s, prop, angle):
256335
"""
257336
Draw math text using matplotlib.mathtext
258337
"""
259338
fontsize = prop.get_size_in_points()
260-
width, height, svg_elements = math_parse_s_ft2font_svg(s,
261-
72, fontsize)
339+
width, height, svg_elements = math_parse_s_ft2font_svg(s, 72, fontsize)
262340
svg_glyphs = svg_elements.svg_glyphs
263341
svg_lines = svg_elements.svg_lines
264342
color = rgb2hex(gc.get_rgb())
265343

266344
self.open_group("mathtext")
267345

268-
svg = '<text style="fill: %s" x="%f" y="%f"' % (color,x,y)
346+
if rcParams['svg.embed_char_paths']:
347+
svg = '<g style="fill: %s" transform="' % color
348+
if angle != 0:
349+
svg += ( 'translate(%f,%f) rotate(%1.1f)'
350+
% (x,y,-angle) )
351+
else:
352+
svg += 'translate(%f,%f)' % (x, y)
353+
svg += '">\n'
269354

270-
if angle != 0:
271-
svg += ( ' transform="translate(%f,%f) rotate(%1.1f) translate(%f,%f)"'
272-
% (x,y,-angle,-x,-y) ) # Inkscape doesn't support rotate(angle x y)
273-
svg += '>\n'
355+
for fontname, fontsize, thetext, new_x, new_y_mtc, metrics in svg_glyphs:
356+
prop = FontProperties(family=fontname, size=fontsize)
357+
charid = self._add_char_def(prop, thetext)
274358

275-
curr_x,curr_y = 0.0,0.0
359+
svg += '<use xlink:href="#%s" transform="translate(%s, %s) scale(%s)"/>\n' % (charid, new_x, -new_y_mtc, fontsize / self.FONT_SCALE)
360+
svg += '</g>\n'
361+
else: # not rcParams['svg.embed_char_paths']
362+
svg = '<text style="fill: %s" x="%f" y="%f"' % (color,x,y)
276363

277-
for fontname, fontsize, thetext, new_x, new_y_mtc, metrics in svg_glyphs:
278-
if rcParams["mathtext.mathtext2"]:
279-
new_y = new_y_mtc - height
280-
else:
281-
new_y = - new_y_mtc
364+
if angle != 0:
365+
svg += ( ' transform="translate(%f,%f) rotate(%1.1f) translate(%f,%f)"'
366+
% (x,y,-angle,-x,-y) ) # Inkscape doesn't support rotate(angle x y)
367+
svg += '>\n'
368+
369+
curr_x,curr_y = 0.0,0.0
370+
371+
for fontname, fontsize, thetext, new_x, new_y_mtc, metrics in svg_glyphs:
372+
if rcParams["mathtext.mathtext2"]:
373+
new_y = new_y_mtc - height
374+
else:
375+
new_y = - new_y_mtc
282376

283-
svg += '<tspan'
284-
svg += ' style="font-size: %f; font-family: %s"'%(fontsize, fontname)
285-
xadvance = metrics.advance
286-
svg += ' textLength="%f"' % xadvance
377+
svg += '<tspan'
378+
svg += ' style="font-size: %f; font-family: %s"'%(fontsize, fontname)
379+
xadvance = metrics.advance
380+
svg += ' textLength="%f"' % xadvance
287381

288-
dx = new_x - curr_x
289-
if dx != 0.0:
290-
svg += ' dx="%f"' % dx
382+
dx = new_x - curr_x
383+
if dx != 0.0:
384+
svg += ' dx="%f"' % dx
291385

292-
dy = new_y - curr_y
293-
if dy != 0.0:
294-
svg += ' dy="%f"' % dy
386+
dy = new_y - curr_y
387+
if dy != 0.0:
388+
svg += ' dy="%f"' % dy
295389

296-
svg += '>%s</tspan>\n' % thetext
390+
svg += '>%s</tspan>\n' % thetext
297391

298-
curr_x = new_x + xadvance
299-
curr_y = new_y
392+
curr_x = new_x + xadvance
393+
curr_y = new_y
300394

301-
svg += '</text>\n'
395+
svg += '</text>\n'
302396

303397
self._svgwriter.write (svg)
304398
rgbFace = gc.get_rgb()
@@ -310,6 +404,11 @@ def _draw_mathtext(self, gc, x, y, s, prop, angle):
310404
self.close_group("mathtext")
311405

312406
def finish(self):
407+
if len(self._char_defs):
408+
self._svgwriter.write('<defs id="fontpaths">\n')
409+
for path in self._char_defs.values():
410+
self._svgwriter.write(path)
411+
self._svgwriter.write('</defs>\n')
313412
self._svgwriter.write('</svg>\n')
314413

315414
def flipy(self):

lib/matplotlib/rcsetup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@ def __call__(self, s):
432432
'pdf.fonttype' : [3, validate_fonttype], # 3 (Type3) or 42 (Truetype)
433433
'svg.image_inline' : [True, validate_bool], # write raster image data directly into the svg file
434434
'svg.image_noscale' : [False, validate_bool], # suppress scaling of raster data embedded in SVG
435+
'svg.embed_char_paths' : [False, validate_bool], # True to save all characters as paths in the SVG
435436
'plugins.directory' : ['.matplotlib_plugins', str], # where plugin directory is locate
436437

437438
# mathtext settings

matplotlibrc.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ numerix : %(numerix)s # numpy, Numeric or numarray
268268
# svg backend params
269269
#svg.image_inline : True # write raster image data directly into the svg file
270270
#svg.image_noscale : False # suppress scaling of raster data embedded in SVG
271+
#svg.embed_chars : False # embed character outlines in the SVG file
271272

272273
# Set the verbose flags. This controls how much information
273274
# matplotlib gives you at runtime and where it goes. The verbosity

0 commit comments

Comments
 (0)
0