diff --git a/doc/_static/pgf_fonts.pdf b/doc/_static/pgf_fonts.pdf
new file mode 100644
index 000000000000..7c5784f6200a
Binary files /dev/null and b/doc/_static/pgf_fonts.pdf differ
diff --git a/doc/_static/pgf_fonts.png b/doc/_static/pgf_fonts.png
new file mode 100644
index 000000000000..6aa4e360b566
Binary files /dev/null and b/doc/_static/pgf_fonts.png differ
diff --git a/doc/_static/pgf_preamble.pdf b/doc/_static/pgf_preamble.pdf
new file mode 100644
index 000000000000..e8e085043219
Binary files /dev/null and b/doc/_static/pgf_preamble.pdf differ
diff --git a/doc/_static/pgf_preamble.png b/doc/_static/pgf_preamble.png
new file mode 100644
index 000000000000..6841e3b9bdd4
Binary files /dev/null and b/doc/_static/pgf_preamble.png differ
diff --git a/doc/_static/pgf_texsystem.pdf b/doc/_static/pgf_texsystem.pdf
new file mode 100644
index 000000000000..fbae0ea766ff
Binary files /dev/null and b/doc/_static/pgf_texsystem.pdf differ
diff --git a/doc/_static/pgf_texsystem.png b/doc/_static/pgf_texsystem.png
new file mode 100644
index 000000000000..6075e7b764dd
Binary files /dev/null and b/doc/_static/pgf_texsystem.png differ
diff --git a/doc/users/index_text.rst b/doc/users/index_text.rst
index bc4459803f43..bba1f330299f 100644
--- a/doc/users/index_text.rst
+++ b/doc/users/index_text.rst
@@ -8,6 +8,7 @@ Working with text
text_intro.rst
text_props.rst
mathtext.rst
+ pgf.rst
usetex.rst
annotations_intro.rst
diff --git a/doc/users/pgf.rst b/doc/users/pgf.rst
new file mode 100644
index 000000000000..6411a9f9c6e3
--- /dev/null
+++ b/doc/users/pgf.rst
@@ -0,0 +1,128 @@
+.. _pgf-tutorial:
+
+*********************************
+Typesetting With XeLaTeX/LuaLaTeX
+*********************************
+
+Using the ``pgf`` backend, matplotlib can export figures as pgf drawing commands
+that can be processed with pdflatex, xelatex or lualatex. XeLaTeX and LuaLaTeX
+have full unicode support and can use any fonts installed in the operating
+system, making use of advanced typographic features of OpenType, AAT and
+Graphite. Pgf pictures created by ``plt.savefig('figure.pgf')`` can be
+embedded as raw commands in LaTeX documents. Figures can also be directly
+compiled and saved to PDF with ``plt.savefig('figure.pdf')``.
+
+Matplotlib's pgf support requires a working LaTeX_ installation (such as
+TeXLive_), preferably including XeLaTeX or LuaLaTeX. If pdftocairo or
+ghostscript is installed, figures can optionally be saved to PNG images.
+The executables for all applications must be located on your :envvar:`PATH`.
+
+Rc parameters that control the behavior of the pgf backend:
+
+ ================= =====================================================
+ Parameter Documentation
+ ================= =====================================================
+ pgf.preamble Lines to be included in the LaTeX preamble
+ pgf.rcfonts Setup fonts from rc params using the fontspec package
+ pgf.texsystem Either "xelatex", "lualatex" or "pdflatex"
+ ================= =====================================================
+
+.. note::
+
+ TeX defines a set of secial characters, such as::
+
+ # $ % & ~ _ ^ \ { }
+
+ Generally, these characters must be escaped correctly. For convenience,
+ some characters (_,^,%) are automatically escaped outside of math
+ environments.
+
+.. _pgf-rcfonts:
+
+Font specification
+==================
+
+The fonts used for obtaining the size of text elements or when compiling
+figures to PDF are usually defined in the matplotlib rc parameters. You can
+also use the LaTeX default Computer Modern fonts by clearing the lists for
+``font.serif``, ``font.sans-serif`` or ``font.monospace``. Please note that
+the glyph coverage of these fonts is very limited. For extended unicode support
+the `Computer Modern Unicode `_
+fonts "CMU Serif", "CMU Sans Serif" are recommended.
+
+.. literalinclude:: plotting/examples/pgf_fonts.py
+ :end-before: plt.savefig
+
+.. image:: /_static/pgf_fonts.*
+
+
+.. _pgf-preamble:
+
+Custom preamble
+===============
+
+Full customization is possible by adding your own commands to the preamble.
+Use the ``pgf.preamble`` parameter if you want to configure the math fonts or
+for loading additional packages. Also, if you want to do the font configuration
+yourself instead of using the fonts specified in the rc parameters, make sure
+to disable ``pgf.rcfonts``.
+
+.. htmlonly::
+
+ .. literalinclude:: plotting/examples/pgf_preamble.py
+ :end-before: plt.savefig
+
+.. latexonly::
+
+ .. literalinclude:: plotting/examples/pgf_preamble.py
+ :end-before: import matplotlib.pyplot as plt
+
+.. image:: /_static/pgf_preamble.*
+
+
+.. _pgf-texsystem:
+
+Choosing the TeX system
+=======================
+
+The TeX system to be used by matplotlib is chosen by the ``pgf.texsystem``
+parameter. Possible values are ``'xelatex'`` (default), ``'lualatex'`` and
+``'pdflatex'``. Please note that when selecting pdflatex the fonts and
+unicode handling must be configured in the preamble.
+
+.. literalinclude:: plotting/examples/pgf_texsystem.py
+ :end-before: plt.savefig
+
+.. image:: /_static/pgf_texsystem.*
+
+.. _pgf-hangups:
+
+Possible hangups
+================
+
+* On Windows, the :envvar:`PATH` environment variable may need to be modified
+ to include the directories containing the latex, dvipng and ghostscript
+ executables. See :ref:`environment-variables` and
+ :ref:`setting-windows-environment-variables` for details.
+
+* Sometimes the font rendering in figures that are saved to png images is
+ very bad. This happens when the pdftocairo tool is not available and
+ ghostscript is used for the pdf to png conversion.
+
+.. _pgf-troubleshooting:
+
+Troubleshooting
+===============
+
+* Make sure what you are trying to do is possible in a LaTeX document,
+ that your LaTeX syntax is valid and that you are using raw strings
+ if necessary to avoid unintended escape sequences.
+
+* The ``pgf.preamble`` rc setting provides lots of flexibility, and lots of
+ ways to cause problems. When experiencing problems, try to minimalize or
+ disable the custom preamble before reporting problems.
+
+* If you still need help, please see :ref:`reporting-problems`
+
+.. _LaTeX: http://www.tug.org
+.. _TeXLive: http://www.tug.org/texlive/
diff --git a/doc/users/plotting/examples/pgf_fonts.py b/doc/users/plotting/examples/pgf_fonts.py
new file mode 100644
index 000000000000..ae260b151406
--- /dev/null
+++ b/doc/users/plotting/examples/pgf_fonts.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+import matplotlib as mpl
+mpl.use("pgf")
+pgf_with_rc_fonts = {
+ "font.family": "serif",
+ "font.serif": [], # use latex default serif font
+ "font.sans-serif": ["DejaVu Sans"], # use a specific sans-serif font
+}
+mpl.rcParams.update(pgf_with_rc_fonts)
+
+import matplotlib.pyplot as plt
+plt.figure(figsize=(4.5,2.5))
+plt.plot(range(5))
+plt.text(0.5, 3., "serif")
+plt.text(0.5, 2., "monospace", family="monospace")
+plt.text(2.5, 2., "sans-serif", family="sans-serif")
+plt.text(2.5, 1., "comic sans", family="Comic Sans MS")
+plt.xlabel(u"µ is not $\\mu$")
+plt.tight_layout(.5)
+
+plt.savefig("pgf_fonts.pdf")
+plt.savefig("pgf_fonts.png")
diff --git a/doc/users/plotting/examples/pgf_preamble.py b/doc/users/plotting/examples/pgf_preamble.py
new file mode 100644
index 000000000000..67c6283f6324
--- /dev/null
+++ b/doc/users/plotting/examples/pgf_preamble.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+import matplotlib as mpl
+mpl.use("pgf")
+pgf_with_custom_preamble = {
+ "font.family": "serif", # use serif/main font for text elements
+ "text.usetex": True, # use inline math for ticks
+ "pgf.rcfonts": False, # don't setup fonts from rc parameters
+ "pgf.preamble": [
+ r"\usepackage{units}", # load additional packages
+ r"\usepackage{metalogo}", # load additional packages
+ r"\usepackage{unicode-math}", # unicode math setup
+ r"\setmathfont{XITS Math}",
+ r"\setmainfont{DejaVu Serif}", # font setup via preamble
+ ]
+}
+mpl.rcParams.update(pgf_with_custom_preamble)
+
+import matplotlib.pyplot as plt
+plt.figure(figsize=(4.5,2.5))
+plt.plot(range(5))
+plt.xlabel(u"unicode text: я, ψ, €, ü, \\unitfrac[10]{°}{µm}")
+plt.ylabel(u"\\XeLaTeX")
+plt.legend([u"unicode math: $λ=∑_i^∞ μ_i^2$"])
+plt.tight_layout(.5)
+
+plt.savefig("pgf_preamble.pdf")
+plt.savefig("pgf_preamble.png")
diff --git a/doc/users/plotting/examples/pgf_texsystem.py b/doc/users/plotting/examples/pgf_texsystem.py
new file mode 100644
index 000000000000..88231348b5e2
--- /dev/null
+++ b/doc/users/plotting/examples/pgf_texsystem.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+import matplotlib as mpl
+mpl.use("pgf")
+pgf_with_pdflatex = {
+ "pgf.texsystem": "pdflatex",
+ "pgf.preamble": [
+ r"\usepackage[utf8x]{inputenc}",
+ r"\usepackage[T1]{fontenc}",
+ r"\usepackage{cmbright}",
+ ]
+}
+mpl.rcParams.update(pgf_with_pdflatex)
+
+import matplotlib.pyplot as plt
+plt.figure(figsize=(4.5,2.5))
+plt.plot(range(5))
+plt.text(0.5, 3., "serif", family="serif")
+plt.text(0.5, 2., "monospace", family="monospace")
+plt.text(2.5, 2., "sans-serif", family="sans-serif")
+plt.xlabel(u"µ is not $\\mu$")
+plt.tight_layout(.5)
+
+plt.savefig("pgf_texsystem.pdf")
+plt.savefig("pgf_texsystem.png")
diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst
index b7a2f890662d..9ae7645f3a21 100644
--- a/doc/users/whats_new.rst
+++ b/doc/users/whats_new.rst
@@ -17,6 +17,15 @@ This page just covers the highlights -- for the full story, see the
new in matplotlib-1.2
=====================
+PGF/TikZ backend
+----------------
+Peter Würtz wrote a backend that allows matplotlib to export figures as
+drawing commands for LaTeX that can be processed by PdfLaTeX, XeLaTeX or
+LuaLaTeX using the PGF/TikZ package. Usage examples and documentation are
+found in :ref:`pgf-tutorial`.
+
+.. image:: /_static/pgf_preamble.*
+
Locator interface
-----------------
diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py
index 232ac22618cf..a1a170766be8 100644
--- a/lib/matplotlib/__init__.py
+++ b/lib/matplotlib/__init__.py
@@ -1056,6 +1056,7 @@ def tk_window_focus():
'matplotlib.tests.test_agg',
'matplotlib.tests.test_axes',
'matplotlib.tests.test_backend_svg',
+ 'matplotlib.tests.test_backend_pgf',
'matplotlib.tests.test_basic',
'matplotlib.tests.test_cbook',
'matplotlib.tests.test_colorbar',
diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py
index 3beffdcc90e8..c0eeb0543db6 100644
--- a/lib/matplotlib/backend_bases.py
+++ b/lib/matplotlib/backend_bases.py
@@ -1807,6 +1807,7 @@ def get_width_height(self):
'emf': 'Enhanced Metafile',
'eps': 'Encapsulated Postscript',
'pdf': 'Portable Document Format',
+ 'pgf': 'LaTeX PGF Figure',
'png': 'Portable Network Graphics',
'ps' : 'Postscript',
'raw': 'Raw RGBA bitmap',
@@ -1841,6 +1842,11 @@ def print_pdf(self, *args, **kwargs):
pdf = self.switch_backends(FigureCanvasPdf)
return pdf.print_pdf(*args, **kwargs)
+ def print_pgf(self, *args, **kwargs):
+ from backends.backend_pgf import FigureCanvasPgf # lazy import
+ pgf = self.switch_backends(FigureCanvasPgf)
+ return pgf.print_pgf(*args, **kwargs)
+
def print_png(self, *args, **kwargs):
from backends.backend_agg import FigureCanvasAgg # lazy import
agg = self.switch_backends(FigureCanvasAgg)
diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py
new file mode 100644
index 000000000000..5684522efdc6
--- /dev/null
+++ b/lib/matplotlib/backends/backend_pgf.py
@@ -0,0 +1,803 @@
+from __future__ import division
+
+import math
+import os
+import sys
+import re
+import shutil
+import tempfile
+import codecs
+import subprocess
+
+import matplotlib as mpl
+from matplotlib.backend_bases import RendererBase, GraphicsContextBase,\
+ FigureManagerBase, FigureCanvasBase
+from matplotlib.figure import Figure
+from matplotlib.text import Text
+from matplotlib.path import Path
+from matplotlib import _png, rcParams
+from matplotlib import font_manager
+from matplotlib.ft2font import FT2Font
+
+###############################################################################
+
+# create a list of system fonts, all of these should work with xe/lua-latex
+system_fonts = []
+for f in font_manager.findSystemFonts():
+ try:
+ system_fonts.append(FT2Font(str(f).family_name))
+ except RuntimeError:
+ pass # some fonts on osx are known to fail, print?
+ except:
+ pass # unknown error, skip this font
+
+# get chosen TeX system from rc
+def get_texcommand():
+ texsystem_options = ["xelatex", "lualatex", "pdflatex"]
+ texsystem = rcParams.get("pgf.texsystem", "xelatex")
+ return texsystem if texsystem in texsystem_options else "xelatex"
+
+# build fontspec preamble from rc
+def get_fontspec():
+ latex_fontspec = []
+ texcommand = get_texcommand()
+
+ if texcommand is not "pdflatex":
+ latex_fontspec.append(r"\usepackage{fontspec}")
+
+ if texcommand is not "pdflatex" and rcParams.get("pgf.rcfonts", True):
+ # try to find fonts from rc parameters
+ families = ["serif", "sans-serif", "monospace"]
+ fontspecs = [r"\setmainfont{%s}", r"\setsansfont{%s}", r"\setmonofont{%s}"]
+ for family, fontspec in zip(families, fontspecs):
+ matches = [f for f in rcParams["font."+family] if f in system_fonts]
+ if matches:
+ latex_fontspec.append(fontspec % matches[0])
+ else:
+ pass # no fonts found, fallback to LaTeX defaule
+
+ return "\n".join(latex_fontspec)
+
+# get LaTeX preamble from rc
+def get_preamble():
+ latex_preamble = rcParams.get("pgf.preamble", "")
+ if type(latex_preamble) == list:
+ latex_preamble = "\n".join(latex_preamble)
+ return latex_preamble
+
+###############################################################################
+
+# This almost made me cry!!!
+# In the end, it's better to use only one unit for all coordinates, since the
+# arithmetic in latex seems to produce inaccurate conversions.
+latex_pt_to_in = 1./72.27
+latex_in_to_pt = 1./latex_pt_to_in
+mpl_pt_to_in = 1./72.
+mpl_in_to_pt = 1./mpl_pt_to_in
+
+###############################################################################
+# helper functions
+
+NO_ESCAPE = r"(? 3) and (rgbFace[3] != 1.0)
+ if has_fill:
+ writeln(self.fh, r"\definecolor{currentfill}{rgb}{%f,%f,%f}" % tuple(rgbFace[:3]))
+ writeln(self.fh, r"\pgfsetfillcolor{currentfill}")
+ if has_fill and (path_is_transparent or fill_is_transparent):
+ opacity = gc.get_alpha() * 1.0 if not fill_is_transparent else rgbFace[3]
+ writeln(self.fh, r"\pgfsetfillopacity{%f}" % opacity)
+
+ # linewidth and color
+ lw = gc.get_linewidth() * mpl_pt_to_in * latex_in_to_pt
+ stroke_rgba = gc.get_rgb()
+ writeln(self.fh, r"\pgfsetlinewidth{%fpt}" % lw)
+ writeln(self.fh, r"\definecolor{currentstroke}{rgb}{%f,%f,%f}" % stroke_rgba[:3])
+ writeln(self.fh, r"\pgfsetstrokecolor{currentstroke}")
+ if gc.get_alpha() != 1.0:
+ writeln(self.fh, r"\pgfsetstrokeopacity{%f}" % gc.get_alpha())
+
+ # line style
+ ls = gc.get_linestyle(None)
+ if ls == "solid":
+ writeln(self.fh, r"\pgfsetdash{}{0pt}")
+ elif ls == "dashed":
+ writeln(self.fh, r"\pgfsetdash{{%fpt}{%fpt}}{0pt}" % (2.5*lw, 2.5*lw))
+ elif ls == "dashdot":
+ writeln(self.fh, r"\pgfsetdash{{%fpt}{%fpt}{%fpt}{%fpt}}{0pt}" % (3*lw, 3*lw, 1*lw, 3*lw))
+ elif "dotted":
+ writeln(self.fh, r"\pgfsetdash{{%fpt}{%fpt}}{0pt}" % (lw, 3*lw))
+
+ def _print_pgf_path(self, path, transform):
+ f = 1./self.dpi
+ # build path
+ for points, code in path.iter_segments(transform):
+ if code == Path.MOVETO:
+ x, y = tuple(points)
+ writeln(self.fh, r"\pgfpathmoveto{\pgfqpoint{%fin}{%fin}}" % (f*x,f*y))
+ elif code == Path.CLOSEPOLY:
+ writeln(self.fh, r"\pgfpathclose")
+ elif code == Path.LINETO:
+ x, y = tuple(points)
+ writeln(self.fh, r"\pgfpathlineto{\pgfqpoint{%fin}{%fin}}" % (f*x,f*y))
+ elif code == Path.CURVE3:
+ cx, cy, px, py = tuple(points)
+ coords = cx*f, cy*f, px*f, py*f
+ writeln(self.fh, r"\pgfpathquadraticcurveto{\pgfqpoint{%fin}{%fin}}{\pgfqpoint{%fin}{%fin}}" % coords)
+ elif code == Path.CURVE4:
+ c1x, c1y, c2x, c2y, px, py = tuple(points)
+ coords = c1x*f, c1y*f, c2x*f, c2y*f, px*f, py*f
+ writeln(self.fh, r"\pgfpathcurveto{\pgfqpoint{%fin}{%fin}}{\pgfqpoint{%fin}{%fin}}{\pgfqpoint{%fin}{%fin}}" % coords)
+
+ def _pgf_path_draw(self, stroke=True, fill=False):
+ actions = []
+ if stroke: actions.append("stroke")
+ if fill: actions.append("fill")
+ writeln(self.fh, r"\pgfusepath{%s}" % ",".join(actions))
+
+ def draw_image(self, gc, x, y, im):
+ # TODO: Almost no documentation for the behavior of this function.
+ # Something missing?
+
+ # save the images to png files
+ path = os.path.dirname(self.fh.name)
+ fname = os.path.splitext(os.path.basename(self.fh.name))[0]
+ fname_img = "%s-img%d.png" % (fname, self.image_counter)
+ self.image_counter += 1
+ im.flipud_out()
+ rows, cols, buf = im.as_rgba_str()
+ _png.write_png(buf, cols, rows, os.path.join(path, fname_img))
+
+ # reference the image in the pgf picture
+ writeln(self.fh, r"\begin{pgfscope}")
+ self._print_pgf_clip(gc)
+ h, w = im.get_size_out()
+ f = 1./self.dpi # from display coords to inch
+ writeln(self.fh, r"\pgftext[at=\pgfqpoint{%fin}{%fin},left,bottom]{\pgfimage[interpolate=true,width=%fin,height=%fin]{%s}}" % (x*f, y*f, w*f, h*f, fname_img))
+ writeln(self.fh, r"\end{pgfscope}")
+
+ def draw_tex(self, gc, x, y, s, prop, angle, ismath="TeX!"):
+ self.draw_text(gc, x, y, s, prop, angle, ismath)
+
+ def draw_text(self, gc, x, y, s, prop, angle, ismath=False):
+ s = common_texification(s)
+
+ # apply font properties
+ prop_cmds = _font_properties_str(prop)
+ s = ur"{%s %s}" % (prop_cmds, s)
+
+ # draw text at given coordinates
+ x = x * 1./self.dpi
+ y = y * 1./self.dpi
+ writeln(self.fh, r"\begin{pgfscope}")
+ alpha = gc.get_alpha()
+ if alpha != 1.0:
+ writeln(self.fh, r"\pgfsetfillopacity{%f}" % alpha)
+ writeln(self.fh, r"\pgfsetstrokeopacity{%f}" % alpha)
+ stroke_rgb = tuple(gc.get_rgb())[:3]
+ if stroke_rgb != (0, 0, 0):
+ writeln(self.fh, r"\definecolor{textcolor}{rgb}{%f,%f,%f}" % stroke_rgb)
+ writeln(self.fh, r"\pgfsetstrokecolor{textcolor}")
+ writeln(self.fh, r"\pgfsetfillcolor{textcolor}")
+ writeln(self.fh, "\\pgftext[left,bottom,x=%fin,y=%fin,rotate=%f]{%s}\n" % (x,y,angle,s))
+ writeln(self.fh, r"\end{pgfscope}")
+
+ def get_text_width_height_descent(self, s, prop, ismath):
+ # check if the math is supposed to be displaystyled
+ s = common_texification(s)
+
+ # get text metrics in units of latex pt, convert to display units
+ w, h, d = self.latexManager.get_width_height_descent(s, prop)
+ # TODO: this should be latex_pt_to_in instead of mpl_pt_to_in
+ # but having a little bit more space around the text looks better,
+ # plus the bounding box reported by LaTeX is VERY narrow
+ f = mpl_pt_to_in * self.dpi
+ return w*f, h*f, d*f
+
+ def flipy(self):
+ return False
+
+ def get_canvas_width_height(self):
+ return self.figure.get_figwidth(), self.figure.get_figheight()
+
+ def points_to_pixels(self, points):
+ return points * mpl_pt_to_in * self.dpi
+
+ def new_gc(self):
+ return GraphicsContextPgf()
+
+
+class GraphicsContextPgf(GraphicsContextBase):
+ pass
+
+########################################################################
+
+def draw_if_interactive():
+ pass
+
+def new_figure_manager(num, *args, **kwargs):
+ """
+ Create a new figure manager instance
+ """
+ # if a main-level app must be created, this is the usual place to
+ # do it -- see backend_wx, backend_wxagg and backend_tkagg for
+ # examples. Not all GUIs require explicit instantiation of a
+ # main-level app (egg backend_gtk, backend_gtkagg) for pylab
+ FigureClass = kwargs.pop('FigureClass', Figure)
+ thisFig = FigureClass(*args, **kwargs)
+ canvas = FigureCanvasPgf(thisFig)
+ manager = FigureManagerPgf(canvas, num)
+ return manager
+
+
+class FigureCanvasPgf(FigureCanvasBase):
+ filetypes = {"pgf": "LaTeX PGF picture",
+ "pdf": "LaTeX compiled PGF picture",
+ "png": "Portable Network Graphics",}
+
+ def __init__(self, *args):
+ FigureCanvasBase.__init__(self, *args)
+
+ def get_default_filetype(self):
+ return 'pdf'
+
+ def print_pgf(self, filename, *args, **kwargs):
+ """
+ Output pgf commands for drawing the figure so it can be included and
+ rendered in latex documents.
+ """
+
+ header_text = r"""%% Creator: Matplotlib, PGF backend
+%%
+%% To include the figure in your LaTeX document, write
+%% \input{.pgf}
+%%
+%% Make sure the required packages are loaded in your preamble
+%% \usepackage{pgf}
+%%
+%% Figures using additional raster images can only be included by \input if
+%% they are in the same directory as the main LaTeX file. For loading figures
+%% from other directories you can use the `import` package
+%% \usepackage{import}
+%% and then include the figures with
+%% \import{}{.pgf}
+%%
+"""
+
+ # append the preamble used by the backend as a comment for debugging
+ header_info_preamble = ["%% Matplotlib used the following preamble"]
+ for line in get_preamble().splitlines():
+ header_info_preamble.append("%% " + line)
+ for line in get_fontspec().splitlines():
+ header_info_preamble.append("%% " + line)
+ header_info_preamble.append("%%")
+ header_info_preamble = "\n".join(header_info_preamble)
+
+ # get figure size in inch
+ w, h = self.figure.get_figwidth(), self.figure.get_figheight()
+
+ # start a pgfpicture environment and set a bounding box
+ with codecs.open(filename, "w", encoding="utf-8") as fh:
+ fh.write(header_text)
+ fh.write(header_info_preamble)
+ fh.write("\n")
+ writeln(fh, r"\begingroup")
+ writeln(fh, r"\makeatletter")
+ writeln(fh, r"\begin{pgfpicture}")
+ writeln(fh, r"\pgfpathrectangle{\pgfpointorigin}{\pgfqpoint{%fin}{%fin}}" % (w,h))
+ writeln(fh, r"\pgfusepath{use as bounding box}")
+
+ renderer = RendererPgf(self.figure, fh)
+ self.figure.draw(renderer)
+
+ # end the pgfpicture environment
+ writeln(fh, r"\end{pgfpicture}")
+ writeln(fh, r"\makeatother")
+ writeln(fh, r"\endgroup")
+
+ def print_pdf(self, filename, *args, **kwargs):
+ """
+ Use LaTeX to compile a Pgf generated figure to PDF.
+ """
+ w, h = self.figure.get_figwidth(), self.figure.get_figheight()
+
+ target = os.path.abspath(filename)
+
+ try:
+ tmpdir = tempfile.mkdtemp()
+ cwd = os.getcwd()
+ os.chdir(tmpdir)
+ self.print_pgf("figure.pgf")
+
+ latex_preamble = get_preamble()
+ latex_fontspec = get_fontspec()
+ latexcode = r"""
+\documentclass[12pt]{minimal}
+\usepackage[paperwidth=%fin, paperheight=%fin, margin=0in]{geometry}
+%s
+%s
+\usepackage{pgf}
+
+\begin{document}
+\centering
+\input{figure.pgf}
+\end{document}""" % (w, h, latex_preamble, latex_fontspec)
+ with codecs.open("figure.tex", "w", "utf-8") as fh:
+ fh.write(latexcode)
+
+ texcommand = get_texcommand()
+ cmdargs = [texcommand, "-interaction=nonstopmode", "-halt-on-error", "figure.tex"]
+ try:
+ stdout = subprocess.check_output(cmdargs, universal_newlines=True, stderr=subprocess.STDOUT)
+ except:
+ raise RuntimeError("%s was not able to process your file.\n\nFull log:\n%s" % (texcommand, stdout))
+ shutil.copyfile("figure.pdf", target)
+ finally:
+ os.chdir(cwd)
+ try:
+ shutil.rmtree(tmpdir)
+ except:
+ sys.stderr.write("could not delete tmp directory %s\n" % tmpdir)
+
+ def print_png(self, filename, *args, **kwargs):
+ """
+ Use LaTeX to compile a pgf figure to pdf and convert it to png.
+ """
+
+ converter = make_pdf_to_png_converter()
+
+ target = os.path.abspath(filename)
+ try:
+ tmpdir = tempfile.mkdtemp()
+ cwd = os.getcwd()
+ os.chdir(tmpdir)
+ self.print_pdf("figure.pdf")
+ converter("figure.pdf", "figure.png", dpi=self.figure.dpi)
+ shutil.copyfile("figure.png", target)
+ finally:
+ os.chdir(cwd)
+ try:
+ shutil.rmtree(tmpdir)
+ except:
+ sys.stderr.write("could not delete tmp directory %s\n" % tmpdir)
+
+ def _render_texts_pgf(self, fh):
+ # TODO: currently unused code path
+
+ # alignment anchors
+ valign = {"top": "top", "bottom": "bottom", "baseline": "base", "center": ""}
+ halign = {"left": "left", "right": "right", "center": ""}
+ # alignment anchors for 90deg. rotated labels
+ rvalign = {"top": "left", "bottom": "right", "baseline": "right", "center": ""}
+ rhalign = {"left": "top", "right": "bottom", "center": ""}
+
+ # TODO: matplotlib does not hide unused tick labels yet, workaround
+ for tick in self.figure.findobj(mpl.axis.Tick):
+ tick.label1.set_visible(tick.label1On)
+ tick.label2.set_visible(tick.label2On)
+ # TODO: strange, first legend label is always "None", workaround
+ for legend in self.figure.findobj(mpl.legend.Legend):
+ labels = legend.findobj(mpl.text.Text)
+ labels[0].set_visible(False)
+ # TODO: strange, legend child labels are duplicated,
+ # find a list of unique text objects as workaround
+ texts = self.figure.findobj(match=Text, include_self=False)
+ texts = list(set(texts))
+
+ # draw text elements
+ for text in texts:
+ s = text.get_text()
+ if not s or not text.get_visible(): continue
+
+ s = common_texification(s)
+
+ fontsize = text.get_fontsize()
+ angle = text.get_rotation()
+ transform = text.get_transform()
+ x, y = transform.transform_point(text.get_position())
+ x = x * 1.0 / self.figure.dpi
+ y = y * 1.0 / self.figure.dpi
+ # TODO: positioning behavior unknown for rotated elements
+ # right now only the alignment for 90deg rotations is correct
+ if angle == 90.:
+ align = rvalign[text.get_va()] + "," + rhalign[text.get_ha()]
+ else:
+ align = valign[text.get_va()] + "," + halign[text.get_ha()]
+
+ s = ur"{\fontsize{%f}{%f}\selectfont %s}" % (fontsize, fontsize*1.2, s)
+ writeln(fh, ur"\pgftext[%s,x=%fin,y=%fin,rotate=%f]{%s}" % (align,x,y,angle,s))
+
+ def get_renderer(self):
+ return RendererPgf(self.figure, None)
+
+class FigureManagerPgf(FigureManagerBase):
+ def __init__(self, *args):
+ FigureManagerBase.__init__(self, *args)
+
+########################################################################
+
+FigureManager = FigureManagerPgf
diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py
index ec9c3e7136ca..224708d2dc00 100644
--- a/lib/matplotlib/rcsetup.py
+++ b/lib/matplotlib/rcsetup.py
@@ -30,7 +30,7 @@
non_interactive_bk = ['agg', 'cairo', 'emf', 'gdk',
- 'pdf', 'ps', 'svg', 'template']
+ 'pdf', 'pgf', 'ps', 'svg', 'template']
all_backends = interactive_bk + non_interactive_bk
@@ -319,6 +319,9 @@ def validate_hinting(s):
return s.lower()
raise ValueError("hinting should be 'auto', 'native', 'either' or 'none'")
+validate_pgf_texsystem = ValidateInStrings('pgf.texsystem',
+ ['xelatex', 'lualatex', 'pdflatex'])
+
validate_movie_writer = ValidateInStrings('animation.writer',
['ffmpeg', 'ffmpeg_file', 'mencoder', 'mencoder_file'])
@@ -574,6 +577,12 @@ def __call__(self, s):
'pdf.use14corefonts' : [False, validate_bool], # use only the 14 PDF core fonts
# embedded in every PDF viewing application
'pdf.fonttype' : [3, validate_fonttype], # 3 (Type3) or 42 (Truetype)
+
+ 'pgf.debug' : [False, validate_bool], # output debug information
+ 'pgf.texsystem' : ['xelatex', validate_pgf_texsystem], # choose latex application for creating pdf files (xelatex/lualatex)
+ 'pgf.rcfonts' : [True, validate_bool], # use matplotlib rc settings for font configuration
+ 'pgf.preamble' : [[''], validate_stringlist], # provide a custom preamble for the latex process
+
'svg.image_inline' : [True, validate_bool], # write raster image data directly into the svg file
'svg.image_noscale' : [False, validate_bool], # suppress scaling of raster data embedded in SVG
'svg.embed_char_paths' : [True, deprecate_svg_embed_char_paths], # True to save all characters as paths in the SVG
diff --git a/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_pdflatex.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_pdflatex.pdf
new file mode 100644
index 000000000000..4ffa77797d1d
Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_pdflatex.pdf differ
diff --git a/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_rcupdate1.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_rcupdate1.pdf
new file mode 100644
index 000000000000..955aad2fefd2
Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_rcupdate1.pdf differ
diff --git a/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_rcupdate2.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_rcupdate2.pdf
new file mode 100644
index 000000000000..e41ec043584c
Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_rcupdate2.pdf differ
diff --git a/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_xelatex.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_xelatex.pdf
new file mode 100644
index 000000000000..96a6d5217929
Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_xelatex.pdf differ
diff --git a/lib/matplotlib/tests/test_backend_pgf.py b/lib/matplotlib/tests/test_backend_pgf.py
new file mode 100644
index 000000000000..10993927fe5b
--- /dev/null
+++ b/lib/matplotlib/tests/test_backend_pgf.py
@@ -0,0 +1,110 @@
+# -*- encoding: utf-8 -*-
+
+import os
+import shutil
+import subprocess
+import numpy as np
+import matplotlib as mpl
+import matplotlib.pyplot as plt
+from matplotlib.testing.compare import compare_images, ImageComparisonFailure
+from matplotlib.testing.decorators import _image_directories, knownfailureif
+
+baseline_dir, result_dir = _image_directories(lambda: 'dummy func')
+
+def run(*args):
+ try:
+ subprocess.check_output(args)
+ return True
+ except:
+ return False
+
+def switch_backend(backend):
+ import nose
+
+ def switch_backend_decorator(func):
+ def backend_switcher(*args, **kwargs):
+ try:
+ prev_backend = mpl.get_backend()
+ mpl.rcdefaults()
+ plt.switch_backend(backend)
+ result = func(*args, **kwargs)
+ finally:
+ plt.switch_backend(prev_backend)
+ return result
+
+ return nose.tools.make_decorator(func)(backend_switcher)
+ return switch_backend_decorator
+
+def compare_figure(fname):
+ actual = os.path.join(result_dir, fname)
+ plt.savefig(actual)
+
+ expected = os.path.join(result_dir, "expected_%s" % fname)
+ shutil.copyfile(os.path.join(baseline_dir, fname), expected)
+ err = compare_images(expected, actual, tol=1e-4)
+ if err:
+ raise ImageComparisonFailure('images not close: %s vs. %s' % (actual, expected))
+
+###############################################################################
+
+def create_figure():
+ plt.figure()
+ x = np.linspace(0, 1, 15)
+ plt.plot(x, x**2, "b-")
+ plt.plot(x, 1-x**2, "g>")
+ plt.plot([0.9], [0.5], "ro", markersize=3)
+ plt.text(0.9, 0.5, u'unicode (ü, °, µ) and math ($\\mu_i = x_i^2$)', ha='right', fontsize=20)
+ plt.ylabel(u'sans-serif with math $\\frac{\\sqrt{x}}{y^2}$..', family='sans-serif')
+
+
+# test compiling a figure to pdf with xelatex
+@knownfailureif(not run('xelatex', '-v'), msg="xelatex is required for this test")
+@switch_backend('pgf')
+def test_xelatex():
+ rc_xelatex = {'font.family': 'serif',
+ 'pgf.rcfonts': False,}
+ mpl.rcParams.update(rc_xelatex)
+ create_figure()
+ compare_figure('pgf_xelatex.pdf')
+
+
+# test compiling a figure to pdf with pdflatex
+@knownfailureif(not run('pdflatex', '-v'), msg="pdflatex is required for this test")
+@switch_backend('pgf')
+def test_pdflatex():
+ rc_pdflatex = {'font.family': 'serif',
+ 'pgf.rcfonts': False,
+ 'pgf.texsystem': 'pdflatex',
+ 'pgf.preamble': [r'\usepackage[utf8x]{inputenc}',
+ r'\usepackage[T1]{fontenc}']}
+ mpl.rcParams.update(rc_pdflatex)
+ create_figure()
+ compare_figure('pgf_pdflatex.pdf')
+
+
+# test updating the rc parameters for each figure
+@knownfailureif(not run('pdflatex', '-v') or not run('xelatex', '-v'),
+ msg="xelatex and pdflatex are required for this test")
+@switch_backend('pgf')
+def test_rcupdate():
+ rc_sets = []
+ rc_sets.append({'font.family': 'sans-serif',
+ 'font.size': 30,
+ 'figure.subplot.left': .2,
+ 'lines.markersize': 10,
+ 'pgf.rcfonts': False,
+ 'pgf.texsystem': 'xelatex'})
+ rc_sets.append({'font.family': 'monospace',
+ 'font.size': 10,
+ 'figure.subplot.left': .1,
+ 'lines.markersize': 20,
+ 'pgf.rcfonts': False,
+ 'pgf.texsystem': 'pdflatex',
+ 'pgf.preamble': [r'\usepackage[utf8x]{inputenc}',
+ r'\usepackage[T1]{fontenc}',
+ r'\usepackage{sfmath}']})
+
+ for i, rc_set in enumerate(rc_sets):
+ mpl.rcParams.update(rc_set)
+ create_figure()
+ compare_figure('pgf_rcupdate%d.pdf' % (i+1))