Metadata-Version: 2.1
Name: mplcairo
Version: 0.3
Summary: A (new) cairo backend for Matplotlib.
Home-page: https://github.com/matplotlib/mplcairo
Author: Antony Lee
License: MIT
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: Matplotlib
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.6
Requires-Dist: matplotlib (>=2.2)
Requires-Dist: pillow
Requires-Dist: pycairo (>=1.16.0) ; os_name == "posix"

====================================
A (new) cairo backend for Matplotlib
====================================

|PyPI| |Fedora Rawhide|

|Azure Pipelines|

.. |PyPI|
   image:: https://img.shields.io/pypi/v/mplcairo.svg
   :target: https://pypi.python.org/pypi/mplcairo
.. |Fedora Rawhide|
   image:: https://repology.org/badge/version-for-repo/fedora_rawhide/python:mplcairo.svg
   :target: fedora-package_
.. |Azure Pipelines|
   image:: https://dev.azure.com/matplotlib/mplcairo/_apis/build/status/matplotlib.mplcairo
   :target: https://dev.azure.com/matplotlib/mplcairo/_build/latest?definitionId=1

.. _fedora-package: https://apps.fedoraproject.org/packages/python-mplcairo

.. contents:: :local:

This is a new, essentially complete implementation of a cairo_ backend for
Matplotlib_.  It can be used in combination with a Qt5, GTK3, Tk, wx, or macOS
UI, or non-interactively (i.e., to save figure to various file formats).

Noteworthy points include:

.. ... sadly, currently not true.

   - Speed (the backend can be up to ~10× faster than Agg, e.g., when stamping
     circular markers of variable colors).

- Improved accuracy (e.g., with marker positioning, quad meshes, and text
  kerning; floating point surfaces are supported with cairo≥1.17.2).
- Support for a wider variety of font formats, such as otf and pfb, for vector
  (PDF, PS, SVG) backends (Matplotlib's Agg backend also supports such fonts).
- Optional support for complex text layout (right-to-left languages, etc.) and
  OpenType font features (see `examples/opentype_features.py`_) using Raqm_.
  **Note** that Raqm depends on Fribidi,
  which is licensed under the LGPLv2.1+.
- Support for embedding URLs in PDF (but not SVG) output (requires
  cairo≥1.15.4).
- Support for multi-page output both for PDF and PS (Matplotlib only supports
  multi-page PDF).
- Support for custom blend modes (see `examples/operators.py`_).

.. _cairo: https://www.cairographics.org/
.. _Matplotlib: http://matplotlib.org/
.. _Raqm: https://github.com/HOST-Oman/libraqm
.. _examples/opentype_features.py: examples/opentype_features.py
.. _examples/operators.py: examples/operators.py

Installation
============

mplcairo requires

- Python≥3.6,
- Matplotlib≥2.2 (declared as ``install_requires``),
- on Linux and macOS, pycairo≥1.16.0 [#]_ (declared as ``install_requires``),
- on Windows, cairo≥1.11.4 [#]_ (shipped with the wheel).

Additionally, building mplcairo from source requires

- pybind11≥2.5.0 [#]_ (declared as ``setup_requires``),
- on Linux and macOS, pycairo≥1.16.0 (declared as ``setup_requires`` on macOS,
  but not on Linux).

As usual, install using pip:

.. code-block:: sh

   $ pip install mplcairo  # from PyPI
   $ pip install git+https://github.com/matplotlib/mplcairo  # from Github

Note that wheels are not available for macOS<10.13, because the libc++ included
with these versions is too old and vendoring of libc++ appears to be fragile.
Help for packaging would be welcome.

mplcairo can use Raqm_ (≥0.2) for complex text layout and handling of
OpenType font features.  Refer to the instructions on that project's
website for installation on Linux and macOS.  You may want to look at
https://github.com/HOST-Oman/libraqm-cmake for Windows build scripts.

.. [#] pycairo 1.16.0 added ``get_include()``.

   We do not actually rely on pycairo's Python bindings.  Rather, specifying a
   dependency on pycairo is a convenient way to specify a dependency on cairo
   (≥1.13.1, for pycairo≥1.14.0) itself, and allows us to load cairo at
   runtime instead of linking to it (simplifying the build of self-contained
   wheels).

   On Windows, this strategy is (AFAIK) not possible, so we explicitly link
   against the cairo DLL.

   pycairo is not declared as a ``setup_requires`` on Linux because the
   manylinux wheel builder needs to work around it.

.. [#] cairo 1.11.4 added mesh gradient support (used by ``draw_quad_mesh()``).

   cairo 1.15.4 added support for PDF metadata and links; the presence of this
   feature is detected at runtime.

   cairo 1.17.2 added support for floating point surfaces, usable with
   ``mplcairo.set_options(float_surface=True)``; the presence of this feature
   is detected at runtime.

   Note that cairo 1.17.2 (and only that version) has a bug that causes (in
   particular) polar gridlines to be incorrectly cropped.  This bug was fixed
   in 2d1a137.  However, if you are already using a non-tagged, >1.17.2 version
   of cairo, it is suggested to use a commit ≥dfe3aa6, as the latter further
   fixes another bug that can cause crashes in mplcairo.

.. [#] pybind11 2.5.0 is the earliest version that supports being added as
   ``setup_requires`` (and read-only buffers).

On Fedora, the package is available as `python-mplcairo <fedora-package_>`_.

Building/packaging
==================

This section is only relevant if you wish to build mplcairo yourself, or
package it for redistribution.  Otherwise, proceed to the Use_ section.

In all cases, once the dependencies described below are installed, mplcairo
can be built and installed using any of the standard commands (``pip wheel
--no-deps .``, ``pip install .``, ``pip install -e .`` and ``python setup.py
build_ext -i`` being the most relevant ones).

Unix
----

The following additional dependencies are required:

- a C++ compiler with C++17 support, e.g. GCC≥7.2 or Clang≥5.0.

- cairo and FreeType headers, and pkg-config information to locate them.

  If using conda, they can be installed using ::

     conda install -y -c conda-forge pycairo pkg-config

  as pycairo (also a dependency) depends on cairo, which depends on freetype.
  Note that cairo and pkg-config from the ``anaconda`` channel will *not* work.

  On Linux, they can also be installed with your distribution's package manager
  (Arch: ``cairo``, Debian/Ubuntu: ``libcairo2-dev``, Fedora: ``cairo-devel``).

Raqm (≥0.2) headers are also needed, but will be automatically downloaded if
not found.

Linux
`````

conda's compilers (``gxx_linux-64`` on the ``anaconda`` channel) `currently
interact poorly with installing cairo and pkg-config from conda-forge
<conda-build-2523_>`_, so you are on your own to install a recent compiler
(e.g., using your distribution's package manager).  You may want to set the
``CC`` and ``CXX`` environment variables to point to your C++ compiler if it is
nonstandard [#]_.  In that case, be careful to set them to e.g. ``g++-7`` and
**not** ``gcc-7``, otherwise the compilation will succeed but the shared object
will be mis-linked and fail to load.

The manylinux wheel is built using `tools/build-manylinux-wheel.sh`_.

.. _tools/build-manylinux-wheel.sh: tools/build-manylinux-wheel.sh

**NOTE**: On Arch Linux, the python-pillow (Arch) package includes an invalid
version of ``raqm.h`` (https://bugs.archlinux.org/task/57492) and must not be
installed while building mplcairo with the system Python, even in a virtualenv
(it can be installed when *using* mplcairo without causing any problems).  One
solution is to temporarily uninstall the package; another one is to package it
yourself using e.g. pypi2pkgbuild_.

.. [#] ``distutils`` uses ``CC`` for *compiling* C++ sources but ``CXX`` for
   linking them (don't ask).  You may run into additional issues if ``CC`` or
   ``CXX`` has multiple words; e.g., if ``CC`` is set to ``ccache g++``, you
   also need to set ``CXX`` to ``ccache gcc``.

.. _conda-build-2523: https://github.com/conda/conda-build/issues/2523
.. _pypi2pkgbuild: https://github.com/anntzer/pypi2pkgbuild

macOS
`````

Clang≥5.0 can be installed from ``conda``'s ``anaconda`` channel (``conda
install -c anaconda clangxx_osx-64``), or can also be installed with Homebrew
(``brew install llvm``).  Note that Homebrew's llvm formula is keg-only, i.e.
it requires manual modifications to the PATH and LDFLAGS (as documented by
``brew info llvm``).

On macOS<10.14, it is additionally necessary to use clang<8.0 (e.g. with ``brew
install llvm@7``) as clang 8.0 appears to believe that code relying on C++17
can only be run on macOS≥10.14+.

The macOS wheel is built using ``tools/build-macos-wheel.sh``, which relies on
delocate-wheel_ (to vendor a recent version of libc++).  Currently, it can only
be built from a Homebrew-clang wheel, not a conda-clang wheel (due to some path
intricacies...).

As I can personally only test the macOS build on CI, any help with the build
and the packaging on that platform would be welcome.

.. _delocate-wheel: https://github.com/matthew-brett/delocate

Windows
-------

The following additional dependencies are required:

- VS2019 (The exact minimum version is unknown, but it is known that mplcairo
  fails to build on the Azure ``vs2017-win2016`` agent and requires the
  ``windows-2019`` agent.)

- cairo headers and import and dynamic libraries (``cairo.lib`` and
  ``cairo.dll``) *with FreeType support*.  Note that this excludes, in
  particular, the Anaconda and conda-forge builds: they do not include
  FreeType support.

  I am in fact not aware of any such build available online, with the exception
  of https://github.com/preshing/cairo-windows/releases; however, this specific
  build appears to `misrender pdfs`_.  Instead, a solution is to get the
  headers e.g. from a Linux distribution package, the DLL from Christoph
  Gohlke's cairocffi_ build, and generate the import library oneself using
  ``dumpbin`` and ``lib``.

- FreeType headers and import and dynamic libraries (``freetype.lib`` and
  ``freetype.dll``), which can be retrieved from
  https://github.com/ubawurinna/freetype-windows-binaries, or alternatively
  using conda::

     conda install -y freetype

.. _misrender pdfs: https://preshing.com/20170529/heres-a-standalone-cairo-dll-for-windows/#IDComment1047546463
.. _cairocffi: https://www.lfd.uci.edu/~gohlke/pythonlibs/#cairocffi

The (standard) |CL|_ and |LINK|_ environment variables (which always get
prepended respectively to the invocations of the compiler and the linker)
should be set as follows::

   set CL=/IC:\path\to\dir\containing\cairo.h /IC:\same\for\ft2build.h
   set LINK=/LIBPATH:C:\path\to\dir\containing\cairo.lib /LIBPATH:C:\same\for\freetype.lib

Moreover, we also need to find ``cairo.dll`` and ``freetype.dll`` and copy
them next to ``mplcairo``'s extension module.  As the dynamic libraries are
typically found next to import libraries, we search the ``/LIBPATH:`` entries
in the ``LINK`` environment variable and copy the first ``cairo.dll`` and
``freetype.dll`` found there.

The script ``tools/build-windows-wheel.py`` automates the retrieval of the
cairo (assuming that a Gohlke cairocffi is already installed) and FreeType and
the wheel build.

.. |CL| replace:: ``CL``
.. _CL: https://docs.microsoft.com/en-us/cpp/build/reference/cl-environment-variables
.. |LINK| replace:: ``LINK``
.. _LINK: https://docs.microsoft.com/en-us/cpp/build/reference/link-environment-variables

Use
===

On Linux and Windows, mplcairo can be used as any normal Matplotlib backend:
call e.g. ``matplotlib.use("module://mplcairo.qt")`` before importing pyplot,
add a ``backend: module://mplcairo.qt`` line in your ``matplotlibrc``, or set
the ``MPLBACKEND`` environment variable to ``module://mplcairo.qt``.  More
specifically, the following backends are provided:

- ``module://mplcairo.base`` (No GUI, but can output to EPS, PDF, PS, SVG, and
  SVGZ using cairo's implementation, rather than Matplotlib's),
- ``module://mplcairo.gtk`` (GTK3 widget, copying data from a cairo image
  surface),
- ``module://mplcairo.gtk_native`` (GTK3 widget, directly drawn onto as a
  native surface; does not and cannot support blitting),
- ``module://mplcairo.qt`` (Qt4/5 widget, copying data from a cairo image
  surface — select the binding to use by importing it before mplcairo, or by
  setting the ``QT_API`` environment variable),
- ``module://mplcairo.tk`` (Tk widget, copying data from a cairo image
  surface),
- ``module://mplcairo.wx`` (wx widget, copying data from a cairo image
  surface),
- ``module://mplcairo.macosx`` (macOS widget, copying data from a cairo image
  surface).

On macOS, **it is necessary to explicitly import mplcairo before importing
Matplotlib** due to incompatibilities associated with the use of a recent
libc++.  As such, the most practical option is to import mplcairo, then call
e.g. ``matplotlib.use("module://mplcairo.macosx")``.

To use cairo rendering in Jupyter's ``inline`` mode, patch, in your
``ipython_config.py``:

.. code-block:: python

   import mplcairo.base
   import ipykernel.pylab.backend_inline
   ipykernel.pylab.backend_inline.new_figure_manager = \
       mplcairo.base.new_figure_manager

Alternatively, set the ``MPLCAIRO_PATCH_AGG`` environment variable to a
non-empty value to fully replace the Agg renderer by the cairo renderer
throughout Matplotlib.  However, this approach is inefficient (due to the need
of copies and conversions between premultiplied ARGB32 and straight RGBA8888
buffers); additionally, it does not work with the wx and macosx backends due
to peculiarities of the corresponding canvas classes.  On the other hand, this
is currently the only way in which the webagg-based backends (e.g., Jupyter's
interactive widgets) are supported.

At import-time, mplcairo will attempt to load Raqm_.  The use of that library
can be controlled and checked using the ``set_options`` and ``get_options``
functions.

The examples_ directory contains a few cases where the output of this renderer
is arguably more accurate than the one of the default renderer, Agg:

- circle_markers.py_ and square_markers.py_: more accurate and faster marker
  stamping.
- marker_stamping.py_: more accurate marker stamping.
- quadmesh.py_: better antialiasing of quad meshes, fewer artefacts with
  masked data.
- text_kerning.py_: improved text kerning.

.. _examples: examples/
.. _circle_markers.py: examples/circle_markers.py
.. _square_markers.py: examples/square_markers.py
.. _marker_stamping.py: examples/marker_stamping.py
.. _quadmesh.py: examples/quadmesh.py
.. _text_kerning.py: examples/text_kerning.py

Benchmarks
==========

Install (in the virtualenv) ``pytest>=3.1.0`` and ``pytest-benchmark``, then
call (e.g.):

.. code-block:: sh

   pytest --benchmark-group-by=fullfunc --benchmark-timer=time.process_time

Keep in mind that conda-forge's cairo is (on my setup) ~2× slower than a
"native" build of cairo.

Test suite
==========

Run ``run-mpl-test-suite.py`` (which depends on ``pytest>=3.2.2``) to run the
Matplotlib test suite with the Agg backend patched by the mplcairo backend.
Note that Matplotlib must be installed with its test data, which is not the
case when it is installed from conda or from most Linux distributions; instead,
it should be installed from PyPI or from source.

Nearly all image comparison tests "fail" as the renderers are fundamentally
different; currently, the intent is to manually check the diff images.  Passing
``--tolerance=inf`` marks these tests as "passed" (while still textually
reporting the image differences) so that one can spot issues not related to
rendering differences.  In practice, ``--tolerance=50`` appears to be enough.

Some other (non-image-comparison) tests are also known to fail (they are listed
in ``ISSUES.rst``, with the relevant explanations), and automatically skipped.

Run ``run-examples.py`` to run some examples that exercise some more aspects of
mplcairo.

Notes
=====

Antialiasing
------------

The artist antialiasing property can be set to any of the ``cairo_antialias_t``
enum values, or ``True`` (the default) or ``False`` (which is synonym to
``NONE``).

Setting antialiasing to ``True`` uses ``FAST`` antialiasing for lines thicker
than 1/3px and ``BEST`` for lines thinner than that: for lines thinner
than 1/3px, the former leads to artefacts such as lines disappearing in
certain sections (see e.g. ``test_cycles.test_property_collision_plot`` after
forcing the antialiasing to ``FAST``).  The threshold of 1/3px was determined
empirically, see `examples/thin_line_antialiasing.py`_.

.. _examples/thin_line_antialiasing.py: examples/thin_line_antialiasing.py

Note that in order to set the ``lines.antialiased`` or ``patch.antialiased``
rcparams to a ``cairo_antialias_t`` enum value, it is necessary to bypass
rcparam validation, using, e.g.

.. code-block:: python

   dict.__setitem__(plt.rcParams, "lines.antialiased", antialias_t.FAST)

The ``text.antialiased`` rcparam can likewise be set to any
``cairo_antialias_t`` enum value, or ``True`` (the default, which maps to
``SUBPIXEL`` — ``GRAY`` is not sufficient to benefit from Raqm_'s subpixel
positioning; see also `cairo issue #152 <cairo-152_>`_) or ``False`` (which
maps to ``NONE``).

.. _cairo-152: https://gitlab.freedesktop.org/cairo/cairo/issues/152

Note that in rare cases, ``FAST`` antialiasing can trigger a "double free or
corruption" bug in cairo (`#44 <cairo-44_>`_).  If you hit this problem,
consider using ``BEST`` or ``NONE`` antialiasing (depending on your quality and
speed requirements).

.. _cairo-44: https://gitlab.freedesktop.org/cairo/cairo/issues/44

Fast drawing
------------

For fast drawing of path with many segments, the ``agg.path.chunksize`` rcparam
should be set to e.g. 1000 (see `examples/time_drawing_per_element.py`_ for the
determination of this value); this causes longer paths to be split into
individually rendered sections of 1000 segments each (directly rendering longer
paths appears to have slightly superlinear complexity).

.. _examples/time_drawing_per_element.py: examples/time_drawing_per_element.py

Simplification threshold
------------------------

The ``path.simplify_threshold`` rcparam is used to control the accuracy of
marker stamping, down to an arbitrarily chosen threshold of 1/16px.  If the
threshold is set to a lower value, the exact (slower) marker drawing path will
be used.  Marker stamping is also implemented for scatter plots (which can have
multiple colors).  Likewise, markers of different sizes get mapped into markers
of discretized sizes, with an error bounded by the threshold.

**NOTE**: ``pcolor`` and mplot3d's ``plot_surface`` display some artifacts
where the facets join each other.  This is because these functions internally
use a ``PathCollection``; this triggers the approximate stamping, and
even without it (by setting ``path.simplify_threshold`` to zero), cairo's
rasterization of the edge between the facets is poor.  ``pcolormesh`` (which
internally uses a ``QuadMesh``) should generally be preferred over ``pcolor``
anyways.  ``plot_surface`` could likewise instead represent the surface using
``QuadMesh``, which is drawn without such artefacts.

Font formats and features
-------------------------

In order to use a specific font that Matplotlib may be unable to use, pass a
filename directly:

.. code-block:: python

   from matplotlib.font_manager import FontProperties
   fig.text(.5, .5, "hello, world",
            fontproperties=FontProperties(fname="/path/to/font.ttf"))

or more simply, with Matplotlib≥3.3:

.. code-block:: python

   from pathlib import Path
   fig.text(.5, .5, "hello, world", font=Path("/path/to/font.ttf"))

mplcairo still relies on Matplotlib's font cache, so fonts unsupported by
Matplotlib remain unavailable by other means.

For TTC fonts (and, more generally, font formats that include multiple font
faces in a single file), the *n*\th font (*n*\≥0) can be selected by appending
``#n`` to the filename (e.g., ``"/path/to/font.ttc#1"``).

OpenType font features can be selected by appending ``|feature,...``
to the filename, followed by a `HarfBuzz feature string`_ (e.g.,
``"/path/to/font.otf|frac,onum"``); see `examples/opentype_features.py`_.

.. _HarfBuzz feature string: https://harfbuzz.github.io/harfbuzz-hb-common.html#hb-feature-from-string

The syntaxes for selecting TTC subfonts and OpenType font features are
**experimental** and may change, especially if such features are implemented in
Matplotlib itself.

Note that Matplotlib's (default) Agg backend will handle most (single-face)
fonts equally well (ultimately, both backends relies on FreeType for
rasterization).  It is Matplotlib's vector backends (PS, PDF, and, for pfb
fonts, SVG) that do not support these fonts, whereas mplcairo support these
fonts in all output formats.

Multi-page output
-----------------

Matplotlib's ``PdfPages`` class is deeply tied with the builtin ``backend_pdf``
(in fact, it cannot even be used with Matplotlib's own cairo backend).
Instead, use ``mplcairo.multipage.MultiPage`` for multi-page PDF and PS output.
The API is similar:

.. code-block:: python

   from mplcairo.multipage import MultiPage

   fig1 = ...
   fig2 = ...
   with MultiPage(path_or_stream, metadata=...) as mp:
       mp.savefig(fig1)
       mp.savefig(fig2)

See the class' docstring for additional information.

``cairo-script`` output
-----------------------

Setting the ``MPLCAIRO_SCRIPT_SURFACE`` environment variable *before mplcairo
is imported* to ``vector`` or ``raster`` allows one to save figures (with
``savefig``) in the ``.cairoscript`` format, which is a "native script that
matches the cairo drawing model".  The value of the variable determines the
rendering path used (e.g., whether marker stamping is used at all).  This may
be helpful for troubleshooting purposes.

Note that this may crash the process after the file is written, due to `cairo
issue #277 <cairo-277_>`_.

.. _cairo-277: https://gitlab.freedesktop.org/cairo/cairo/issues/277

Markers at Bézier control points
--------------------------------

``draw_markers`` draws a marker at each control point of the given path, which
is the documented behavior, even though all builtin renderers only draw markers
at straight or Bézier segment ends.

Known issues
============

Missing support from cairo
--------------------------

- SVG output does not set URLs or ids on any element, as cairo provides no
  support to do so.
- PS output does not respect SOURCE_DATE_EPOCH.
- PS output does not support the ``Creator`` metadata key; however it supports
  the ``Title`` key.
- The following rcparams have no effect:

  - ``pdf.fonttype`` (font type is selected by cairo internally),
  - ``pdf.inheritcolor`` (effectively always ``False``),
  - ``pdf.use14corefonts`` (effectively always ``False``),
  - ``ps.fonttype`` (font type is selected by cairo internally),
  - ``ps.useafm`` (effectively always ``False``),
  - ``svg.fonttype`` (effectively always ``"path"``, see `cairo issue #253
    <cairo-253_>`_),
  - ``svg.hashsalt``.

.. _cairo-253: https://gitlab.freedesktop.org/cairo/cairo/issues/253

Possible optimizations
======================

- Cache eviction policy and persistent cache for ``draw_path_collection``.
- Path simplification (although cairo appears to use vertex reduction and
  Douglas-Peucker internally?).
- Use QtOpenGLWidget and the cairo-gl backend.

What about the already existing cairo (gtk3/qt4/qt5/wx/tk/...cairo) backends?
=============================================================================

They are very slow (try running `examples/mplot3d/wire3d_animation.py`_) and
render math poorly (try ``title(r"$\sqrt{2}$")``).

.. _examples/mplot3d/wire3d_animation.py: examples/mplot3d/wire3d_animation.py


