From a9b069fb3e057474cf093bcdfc14cf93f4898d93 Mon Sep 17 00:00:00 2001 From: Corran Webster Date: Fri, 18 Mar 2022 10:27:57 +0000 Subject: [PATCH] Updates and improvements to Kiva documentation (#914) * Updates and improvements to documentation. In particular, this: - adds many lins to the API documentation - fixes #869 - adds a stub document discussing writing new Kiva backends * Add more info about backends. * Fixes from review. --- docs/source/index.rst | 5 +- docs/source/kiva/drawing_details.rst | 184 ++++++++++++++++--------- docs/source/kiva/font_manager.rst | 86 ++++++++---- docs/source/kiva/overview.rst | 142 +++++++++++-------- docs/source/kiva/writing_a_backend.rst | 40 ++++++ kiva/fonttools/font_manager.py | 11 +- 6 files changed, 316 insertions(+), 152 deletions(-) create mode 100644 docs/source/kiva/writing_a_backend.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index b9193619b..924fb08bf 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -16,8 +16,8 @@ Kiva :maxdepth: 2 kiva/overview - kiva/quickref kiva/drawing_details + kiva/quickref Enable ------ @@ -48,9 +48,10 @@ Internal Documentation ---------------------- .. toctree:: - :maxdepth: 1 + :maxdepth: 2 kiva/font_manager + kiva/writing_a_backend ---- diff --git a/docs/source/kiva/drawing_details.rst b/docs/source/kiva/drawing_details.rst index 9b59fae62..16bb8decb 100644 --- a/docs/source/kiva/drawing_details.rst +++ b/docs/source/kiva/drawing_details.rst @@ -4,6 +4,7 @@ Kiva Drawing In-depth Kiva State ========== + Kiva is a "stateful" drawing API. What this means is that the graphics context has a collection of state which affects the results of its drawing actions. Furthermore, Kiva enables this state to be managed with a stack such that state @@ -13,36 +14,52 @@ includes those changes. State Components ---------------- + Here is a list of all the pieces of state tracked by a Kiva graphics context, along with the methods which operate on them: -* Affine transformation (:py:meth:`translate_ctm`, :py:meth:`rotate_ctm`, - :py:meth:`scale_ctm`, :py:meth:`concat_ctm`, :py:meth:`set_ctm`, - :py:meth:`get_ctm`) -* Clipping (:py:meth:`clip_to_rect`, :py:meth:`clip_to_rects`, :py:meth:`clip`, - :py:meth:`even_odd_clip`) -* Fill color (:py:meth:`set_fill_color`, :py:meth:`get_fill_color`, - :py:meth:`linear_gradient`, :py:meth:`radial_gradient`) -* Stroke color (:py:meth:`set_stroke_color`, :py:meth:`get_stroke_color`) -* Line width (:py:meth:`set_line_width`) -* Line join style (:py:meth:`set_line_join`) -* Line cap style (:py:meth:`set_line_cap`) -* Line dashing (:py:meth:`set_line_dash`) -* Global transparency (:py:meth:`set_alpha`, :py:meth:`get_alpha`) -* Anti-aliasing (:py:meth:`set_antialias`, :py:meth:`get_antialias`) -* Miter limit (:py:meth:`set_miter_limit`) -* Flatness (:py:meth:`set_flatness`) -* Image interpolation (:py:meth:`set_image_interpolation`, :py:meth:`get_image_interpolation`) -* Text drawing mode (:py:meth:`set_text_drawing_mode`) +* Affine transformation: :py:meth:`~.AbstractGraphicsContext.translate_ctm`, + :py:meth:`~.AbstractGraphicsContext.rotate_ctm`, + :py:meth:`~.AbstractGraphicsContext.scale_ctm`, + :py:meth:`~.AbstractGraphicsContext.concat_ctm`, + :py:meth:`~.AbstractGraphicsContext.set_ctm`, + :py:meth:`~.AbstractGraphicsContext.get_ctm` +* Clipping: :py:meth:`~.AbstractGraphicsContext.clip_to_rect`, + :py:meth:`~.AbstractGraphicsContext.clip_to_rects`, + :py:meth:`~.AbstractGraphicsContext.clip`, + :py:meth:`~.AbstractGraphicsContext.even_odd_clip` +* Fill color: :py:meth:`~.AbstractGraphicsContext.set_fill_color`, + :py:meth:`~.AbstractGraphicsContext.get_fill_color`, + :py:meth:`~.AbstractGraphicsContext.linear_gradient`, + :py:meth:`~.AbstractGraphicsContext.radial_gradient` +* Stroke color: :py:meth:`~.AbstractGraphicsContext.set_stroke_color`, + :py:meth:`~.AbstractGraphicsContext.get_stroke_color` +* Line width: :py:meth:`~.AbstractGraphicsContext.set_line_width` +* Line join style: :py:meth:`~.AbstractGraphicsContext.set_line_join` +* Line cap style: :py:meth:`~.AbstractGraphicsContext.set_line_cap` +* Line dashing: :py:meth:`~.AbstractGraphicsContext.set_line_dash` +* Global transparency: :py:meth:`~.AbstractGraphicsContext.set_alpha`, + :py:meth:`~.AbstractGraphicsContext.get_alpha` +* Anti-aliasing: :py:meth:`~.AbstractGraphicsContext.set_antialias`, + :py:meth:`~.AbstractGraphicsContext.get_antialias` +* Miter limit: :py:meth:`~.AbstractGraphicsContext.set_miter_limit` +* Flatness: :py:meth:`~.AbstractGraphicsContext.set_flatness` +* Image interpolation: + :py:meth:`~.AbstractGraphicsContext.set_image_interpolation`, + :py:meth:`~.AbstractGraphicsContext.get_image_interpolation`) +* Text drawing mode: :py:meth:`~.AbstractGraphicsContext.set_text_drawing_mode` Color ----- + Kiva has two colors in its graphics state: stroke color and fill color. Stroke color is used for the lines in paths when the drawing mode is ``STROKE``, ``FILL_STROKE`` or ``EOF_FILL_STROKE``. Fill color is used for text and for the enclosed sections of paths when the drawing mode is ``FILL``, ``EOF_FILL``, ``FILL_STROKE``, or ``EOF_FILL_STROKE``. Additionally, the fill color can be -set by the :py:meth:`linear_gradient` and :py:meth:`radial_gradient` methods. +set by the :py:meth:`~.AbstractGraphicsContext.linear_gradient` and +:py:meth:`~.AbstractGraphicsContext.radial_gradient` methods where they are +available. .. note:: Even though text uses the fill color, text will not be filled with a @@ -56,15 +73,20 @@ blending, it's still OK to pass a 4 component color value when setting state. State Stack Management ---------------------- + Graphics context instances have two methods for saving and restoring the state, -:py:meth:`save_state` ("push") and :py:meth:`restore_state` ("pop"). That said, -it isn't recommended practice to call the methods directly. Instead, you can -treat the graphics context object as a -`context manager `_ -and use the ``with`` keyword to create a block of code where the graphics state -is temporarily modified. Using the context manager approach provides safety from -"temporary" modifications becoming permanent if an uncaught exception is raised -while drawing. +:py:meth:`~.AbstractGraphicsContext.save_state` ("push") and +:py:meth:`~.AbstractGraphicsContext.restore_state` ("pop"). For robust drawing +every "push" should be matched by a corresponding "pop" at some later point, +even if there is an error or other exception. + +For this reason all graphics contexts are +`context managers `_ +and can use the ``with`` keyword to create a block of code where the graphics +state is temporarily modified: it is "pushed" at the start of the ``with`` +block and "popped" at the end. Using the context manager approach provides +safety from "temporary" modifications becoming permanent if an uncaught +exception is raised while drawing. In Enable and Chaco, it is frequently the case that a graphics context instance will be passed into a method for the purpose of some drawing. Because it is not @@ -72,11 +94,13 @@ reasonable to push the responsibility of state management "up" the call stack, the onus is on the code making state modifications to do them safely so that other changes don't leak into other code. -**Well behaved code should take care to only modify graphics state inside a** -``with`` **block**. +.. note:: + Well-behaved code should take care to only modify graphics state inside a + ``with`` block. Example ------- + .. image:: images/state_ex.png :width: 300 :height: 300 @@ -96,10 +120,11 @@ join, and cap: :linenos: :lineno-match: -Then in a loop, we draw twice (the two :py:meth:`stroke_path` calls). The first -draw uses a ``with`` block to temporarily modify the drawing state. It adds more -affine transformations: a rotate and a translate. It also changes some line -properties: stroke color, width, and cap. A rectangle is then added to the +Then in a loop, we draw twice (the two +:py:meth:`~.AbstractGraphicsContext.stroke_path` calls). The first draw uses a +``with`` block to temporarily modify the drawing state. It adds more affine +transformations: a rotate and a translate. It also changes some line +properties: stroke color, width, and cap. A rectangle is then added to the current path and stroked. .. literalinclude:: state_ex.py @@ -129,22 +154,24 @@ basic unit of drawing in a graphics context. Every graphics context instance has a current path which can be manipulated by the :ref:`kiva_path_functions`. However, some drawing operations are easier to implement with an independent path instance -(specifically :py:meth:`draw_path_at_points`). +(specifically :py:meth:`~.AbstractGraphicsContext.draw_path_at_points`). An independent path instance can be created in two ways. The first is via the -:py:meth:`GraphicsContext.get_empty_path` method. The second method is to use -the :class:`CompiledPath` class imported from the backend being used. The -interface of a :class:`CompiledPath` instance is the same as the -:ref:`kiva_path_functions` (modulo :py:meth:`get_empty_path`). +:py:meth:`~.AbstractGraphicsContext.get_empty_path` method. The second method +is to use the :class:`CompiledPath` class imported from the backend being used. +The interface of a :class:`CompiledPath` instance is the same as the +:ref:`kiva_path_functions` (modulo +:py:meth:`~.AbstractGraphicsContext.get_empty_path`). Once you have a path object, it can be drawn by adding it to the graphics -context with the :py:meth:`GraphicsContext.add_path` method (which adds the path -to the current path) and then calling any of the :ref:`kiva_drawing_functions` -which operate on the current path. +context with the :py:meth:`~.AbstractGraphicsContext.add_path` method (which +adds the path to the current path) and then calling any of the +:ref:`kiva_drawing_functions` which operate on the current path. For certain backends which support it, the -:py:meth:`GraphicsContext.draw_path_at_points` method can be used to draw a -path object at many different positions with a single function call. +:py:meth:`~.EnhancedAbstractGraphicsContext.draw_path_at_points` method can be +used to draw a path object at many different positions with a single function +call. Example ------- @@ -160,9 +187,10 @@ Kiva Image Rendering ==================== Drawing images in kiva is accomplished via -:py:meth:`GraphicsContext.draw_image`. A unique feature of drawing images -(relative to path drawing) is that you can apply an arbitrary translation and -scaling to the image without involving the current transformation matrix. +:py:meth:`~.AbstractGraphicsContext.draw_image`. A unique feature of drawing +images (relative to path drawing) is that you can apply an arbitrary +translation and scaling to the image without involving the current +transformation matrix. The signature for :py:meth:`draw_image` is straightforward: @@ -191,7 +219,7 @@ the scaling is the ratio of the image's width and height to those of the rectangle. In every case, ``rect`` will be transformed by the current transformation matrix. -Special considerations +Special Considerations ---------------------- If you only want to draw a subset of an image, you should pass only that subset to :py:meth:`draw_image`. The Kiva API does not support defining a "source" @@ -208,17 +236,20 @@ with the :py:meth:`set_image_interpolation` method. Saving images ------------- + One can also save the contents of a graphics context to an image. This is done -via the :py:meth:`save` method: +via the :py:meth:`~.AbstractGraphicsContext.save` method. Different backends +support different output formats, and so in most cases you want to render to +the graphics context that best matches the output (eg. a raster format for +JPEG or PNG, or SVG, PDF and PS backends for the approriate vector formats). .. automethod:: kiva.abstract_graphics_context.AbstractGraphicsContext.save :noindex: - Kiva Text Rendering =================== -Drawing text in kiva is accomplished via a few methods on +Drawing text in kiva is accomplished via a few methods on :class:`GraphicsContext`. There are three basic topics: selecting a font, measuring the size of rendered text, and drawing the text. @@ -233,10 +264,7 @@ Simplest: ``select_font`` ~~~~~~~~~~~~~~~~~~~~~~~~~ The simplest form of font selection is the -:py:meth:`GraphicsContext.select_font` method. The tradeoff for this simplicity -is that you're at the mercy of the backend's font lookup. If your desired font -isn't available from the system you're using, it's not defined what you will end -up with. +:py:meth:`~.AbstractGraphicsContext.select_font` method. ``select_font(name, size=12)`` @@ -245,22 +273,28 @@ up with. ``size`` is the size in points. -**Supported backends**: cairo, celiagg, pdf, ps, qpainter, quartz, svg. +*Supported backends*: cairo, celiagg, pdf, ps, qpainter, quartz, svg. The ``KivaFont`` trait and ``set_font`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you're already doing your drawing within an application using traits, you can -use the :class:`kiva.trait_defs.api.KivaFont` trait. +use the :class:`~kiva.trait_defs.kiva_font_trait.KivaFont` trait. -``KivaFont`` traits are initialized with a string which describes the font: -"Times Italic 18", "Courier Bold 10", etc. The *value* of the trait is a -:class:`kiva.fonttools.font.Font` instance which can be passed to the -:py:meth:`GraphicsContext.set_font` method. +:class:`~kiva.trait_defs.kiva_font_trait.KivaFont` traits are initialized with +a string which describes the font: "Times Italic 18", "Courier Bold 10", etc. +The *value* of the trait is a :class:`~kiva.fonttools.font.Font` instance which +can be passed to the :py:meth:`~.AbstractGraphicsContext.set_font` method. -**Supported backends**: all backends +*Supported backends*: all backends +.. note:: + The :class:`~kiva.trait_defs.kiva_font_trait.KivaFont` parser is very + simplistic and special-cases some words. For example "roman" means a + generic serif-style font family, so for example a face name of "Times New + Roman" will not resolve as expected. In these cases, use a + :class:`~kiva.fonttools.font.Font` instance. ``Font`` objects ~~~~~~~~~~~~~~~~ @@ -268,7 +302,7 @@ use the :class:`kiva.trait_defs.api.KivaFont` trait. If you don't want to rely on the font description parsing in ``KivaFont``, you can also manually construct a :class:`kiva.fonttools.font.Font` instance. Once you have a ``Font`` instance, it can be passed to the -:py:meth:`GraphicsContext.set_font` method. +:py:meth:`~.AbstractGraphicsContext.set_font` method. ``Font(face_name="", size=12, family=SWISS, weight=NORMAL, style=NORMAL)`` @@ -287,6 +321,32 @@ desired font. ``style`` is a constant from :py:mod:`kiva.constants`. Pick from ``NORMAL`` or ``ITALIC``. +*Supported backends*: all backends + +Resolving Fonts +--------------- + +In all of the above cases, Kiva attempts to find a good concrete font that +matches the specification to do the drawing. However it is possible that the +desired font is not available on the system where the code is running. In that +case Kiva will fall back to a default font, and Kiva includes a basic font in +case all else fails. + +Different backends use different mechanisms for font resolution. For example +the "qpainter" backend uses Qt's font resolution system, while the SVG backend +translates the font to an SVG font description and leaves it up to the viewer +application to find an appropriate font. + +However a number of backends do not have any built-in font support, and so in +those cases Kiva's :mod:`kiva.fonttools` module is used to find system font +files which match the requirements. Application developers who wish to ship +particular fonts as resources with their application can add these to the +fonttools management system via the +:py:func:`~kiva.fonttools.app_font.add_application_fonts` function (see +:ref:`adding_custom_fonts` for more details). + +Further details are available in the +:ref:`kiva_font_management` section. Measuring Text -------------- diff --git a/docs/source/kiva/font_manager.rst b/docs/source/kiva/font_manager.rst index f73cd4589..c6ba7fb24 100644 --- a/docs/source/kiva/font_manager.rst +++ b/docs/source/kiva/font_manager.rst @@ -1,3 +1,5 @@ +.. _kiva_font_management: + Kiva Font Management ==================== @@ -8,6 +10,11 @@ refactored to emphasize that it's an internal detail which should be avoided by This document aims to describe the structure of the code circa version 5.1.0. +.. note:: + Some Kiva backends don't use the :class:`FontManager` to resolve fonts + but instead use internal methods. For example the QPainter backend uses + Qt's font management system to resolve font definitons. + Overview -------- The basic function of :class:`FontManager` is to provide a :py:meth:`findfont` @@ -17,14 +24,14 @@ is running. To accomplish this, it: -1. Scans the file system for files with known font file extensions (``.ttf``, - ``.ttc``, ``.otf``, ``.afm``) [:py:func:`scan_system_fonts`] -2. Examines all the font files identified in the first step and extracts their - metadata using `fonttools `_. - [:py:func:`create_font_database`] -3. The :class:`FontManager` is then ready to be used. - :class:`Font ` instances call :py:meth:`findfont` - on the global :class:`FontManager` singleton as needed. +1. Scans the file system for files with known font file extensions (``.ttf``, + ``.ttc``, ``.otf``, ``.afm``) [:py:func:`scan_system_fonts`] +2. Examines all the font files identified in the first step and extracts their + metadata using `fonttools `_. + [:py:func:`create_font_database`] +3. The :class:`FontManager` is then ready to be used. + :class:`Font ` instances call :py:meth:`findfont` + on the global :class:`FontManager` singleton as needed. Because scanning a system for available fonts is quite an expensive operation, :class:`FontManager` stores a @@ -40,18 +47,51 @@ Font Resolution The :py:meth:`findfont` method uses a somewhat complex process for finding the best match to a given font query. -1. If a ``directory`` keyword argument was passed, only fonts whose files are - children of ``directory`` will be checked. If none match, a default font is - returned. -2. If a ``directory`` is not specified, the search is first narrowed by the - font family (or families) designated by the query. This is possible because - the match scoring algorithm gives very bad scores to fonts whose family does - not match the query. -3. A score is computed for each font using the scoring functions - [:py:func:`score_family`, :py:func:`score_size`, :py:func:`score_stretch`, - :py:func:`score_style`, :py:func:`score_variant`, and - :py:func:`score_weight`] -4. If the score meets a certain threshold, the matching font is returned. - Otherwise a default is returned. -5. If the query was successful, it is added to a local query cache which will - avoid the scoring process if a matching query is later performed again. +1. If a ``directory`` keyword argument was passed, only fonts whose files are + children of ``directory`` will be checked. If none match, a default font is + returned. +2. If a ``directory`` is not specified, the search is first narrowed by the + font family (or families) designated by the query. This is possible because + the match scoring algorithm gives very bad scores to fonts whose family does + not match the query. +3. A score is computed for each font using the scoring functions + [:py:func:`score_family`, :py:func:`score_size`, :py:func:`score_stretch`, + :py:func:`score_style`, :py:func:`score_variant`, and + :py:func:`score_weight`] +4. If the score meets a certain threshold, the matching font is returned. + Otherwise a default is returned. +5. If the query was successful, it is added to a local query cache which will + avoid the scoring process if a matching query is later performed again. + +.. _adding_custom_fonts: + +Adding Custom Fonts +------------------- + +Because font resolution relies heavily on the system that the code is running +on, font matching may not always result in a good match for the desired font. +Kiva ships with a fallback font in case no matching font can be found (this +frequently happens on headless servers with no GUI libraries installed). +However developers may want to ensure that the fonts that they want are always +available by including the font files in the resources that are packaged with +their application. + +Kiva provides :py:func:`~kiva.fonttools.app_font.add_application_fonts` as a +mechanism to register additional fonts with the application font resolution +system. This function should be called early in the application start-up +process with a list of paths of font files to add to the system. These fonts +will also be added to the Qt and/or Wx font databases as well, as appropriate. + +Typical usage might look something like the following:: + + from importlib.resources import files + from kiva.fonttools.api import add_application_fonts + + font_file_1 = files(my_package.resources) / "my_font_1.ttf" + font_file_2 = files(my_package.resources) / "my_font_2.ttf" + + add_application_fonts([font_file_1, font_file_2]) + +.. note:: + The font files need to be actual files on the filesystem, they can't be + stored in zip files. diff --git a/docs/source/kiva/overview.rst b/docs/source/kiva/overview.rst index 19b935f38..f285a72a0 100644 --- a/docs/source/kiva/overview.rst +++ b/docs/source/kiva/overview.rst @@ -11,28 +11,31 @@ Kiva is a 2D vector drawing interface providing functionality similar to the 2D drawing routines of `OpenGL `_ , the HTML5 Canvas element and many other similar 2D vector drawing APIs. Rather than re-implementing everything, Kiva is a Python interface layer that sits on top -of many different back-ends which are in fact provided by some of these -libraries, depending on the platform, GUI toolkit, and capabilities of the -system. +of many different back-ends, some of which are in fact provided by some of +these libraries. Which back-ends are available depends on the platform, GUI +toolkit, and capabilities of the system. For example the Quartz backend is only +available on Mac OS systems, while the QPainter backend is available if PyQt +or PySide are installed in the current Python environment. This approach permits code to be written to the Kiva API, but produce output that could be rendered to a GUI window, an image file, a PDF file, or a number of other possible output formats without any (or at least minimal) changes to the image generation code. -Kiva is the base drawing layer of the Chaco plotting library, and is what is -responsible for actually drawing pixels on the screen. Developers interested -in writing code that renders new plots or other graphical features for Chaco -will need to be at least passingly familiar with the Kiva drawing API. +Kiva is the base drawing layer of the `Chaco `_ +plotting library, and is what is responsible for actually drawing pixels on the +screen. Developers interested in writing code that renders new plots or other +graphical features for Chaco will need to be at least passingly familiar with +the Kiva drawing API. The most important Kiva backend is the Agg or "Image" backend, which is a Python extension module which wraps the C++ -`Anti-grain geometry `_ drawing library into a -Python extension and exposes the Kiva API. The Agg renders the vector drawing -commands into a raster image which can then be saved as a standard image format -(such as PNG or JPEG) or copied into a GUI window. The Agg backend should be -available on any platform, and should work even if there is no GUI or windowing -system available. +`Anti-grain geometry `_ +drawing library into a Python extension and exposes the Kiva API. Agg renders +the vector drawing commands into a raster image which can then be saved as a +standard image format (such as PNG or JPEG) or copied into a GUI window. The +Agg backend should be available on any platform, and should work even if there +is no GUI or windowing system available. Kiva Concepts ============= @@ -192,31 +195,44 @@ Paths The basic drawing operations are performed by building a path out of primitive operations, and then performing stroking and/or filling operations with it. -The simplest path operations are ``move_to()`` and ``line_to()`` which -respectively move the current point in the path to the specified point, and -add a line to the path from the current point to the specified point. +The simplest path operations are :py:meth:`~.AbstractGraphicsContext.move_to` +and :py:meth:`~.AbstractGraphicsContext.line_to` which respectively move the +current point in the path to the specified point, and add a line to the path +from the current point to the specified point. In addition to the straight line commands, there are 4 arc commands for adding -curves to a path: ``curve_to()`` which draws a cubic bezier curve, -``quad_curve_to()`` which draws a quadratic bezier curve, ``arc()`` which -draws a circular arc based on a center and radius, and ``arc_to()`` which -draws a circular arc from one point to another. - -Finally, the ``rect()`` method adds a rectangle to the path. - -In addition there are convenience methods ``lines()``, ``rects()`` and -``line_set()`` which add multiple lines or rectangles to a path, reading from -appropriately shaped numpy arrays. +curves to a path: :py:meth:`~.AbstractGraphicsContext.curve_to` which draws a +cubic bezier curve, :py:meth:`~.AbstractGraphicsContext.quad_curve_to` which +draws a quadratic bezier curve, :py:meth:`~.AbstractGraphicsContext.arc` which +draws a circular arc based on a center and radius, and +:py:meth:`~.AbstractGraphicsContext.arc_to` which draws a circular arc from one +point to another. + +Finally, the :py:meth:`~.AbstractGraphicsContext.rect` method adds a rectangle +to the path. + +In addition there are convenience methods +:py:meth:`~.AbstractGraphicsContext.lines`, +:py:meth:`~.AbstractGraphicsContext.rects` and +:py:meth:`~.AbstractGraphicsContext.line_set` which add multiple lines or +rectangles to a path, reading from appropriately shaped NumPy arrays. None of these methods make any change to the visible image until the path is -either stroked with ``stroke_path()`` or filled with ``fill_path()``. The way -these actions are performed depends upon certain state of the graphics context. +drawn with :py:meth:`~.AbstractGraphicsContext.draw_path` or the convenience +methods :py:meth:`~.AbstractGraphicsContext.stroke_path`, +:py:meth:`~.AbstractGraphicsContext.fill_path`, or +:py:meth:`~.AbstractGraphicsContext.eof_fill_path`. The way +these actions are performed depends upon the state of the graphics context. For stroking, the graphics context keeps track of the color to use with -``set_stroke_color()``, the thickness of the line with ``set_line_width()``, -the way that lines are joined with ``set_line_join()`` and -``set_miter_limit()``, and the way that they are ended with ``set_line_cap()``. -Lines can also be dashed using the ``set_line_dash()`` method which takes a +:py:meth:`~.AbstractGraphicsContext.set_stroke_color`, the thickness of the +line with :py:meth:`~.AbstractGraphicsContext.set_line_width`, +the way that lines are joined with +:py:meth:`~.AbstractGraphicsContext.set_line_join` and +:py:meth:`~.AbstractGraphicsContext.set_miter_limit`, and the way that they are +ended with :py:meth:`~.AbstractGraphicsContext.set_line_cap`. +Lines can also be dashed using the +:py:meth:`~.AbstractGraphicsContext.set_line_dash` method which takes a pattern of numbers to use for lengths of on and off, and an optional ``phase`` for where to start in the pattern. @@ -300,10 +316,11 @@ Dashes:: .. image:: images/dashes.png -Before filling a path, the colour of the fill is via the ``set_fill_color()`` -method, and gradient fills can be done via the ``set_linear_gradient()`` and -``set_radial_gradient()`` methods. Finally, there are two different fill modes -available: +Before filling a path, the color of the fill is via the +:py:meth:`~.AbstractGraphicsContext.set_fill_color` method, and gradient fills +can be done via the :py:meth:`~.AbstractGraphicsContext.set_linear_gradient` +and :py:meth:`~.AbstractGraphicsContext.set_radial_gradient` methods. Finally, +there are two different fill modes available: `even-odd fill `_ and `non-zero winding fill `_ @@ -343,7 +360,8 @@ Text ---- Text can be rendered at a point by first setting the font to use, then setting -the text location using ``set_text_position()`` and then ``show_text()`` to +the text location using :py:meth:`~.AbstractGraphicsContextset_text_position` +and then :py:meth:`~.AbstractGraphicsContext.show_text` to render the text:: from kiva.api import Font @@ -361,54 +379,60 @@ render the text:: Text defaults to being rendered filled, but can be rendered with an outline. +Images +------ + +Raster images from NumPy arrays, Pillow ``Image`` objects, or some Kiva +graphics contexts can be rendered into a graphics context using the +:py:meth:`~.AbstractGraphicsContext.draw_image` method. + Kiva Backends ============= +The Kiva package comes with a number of backends included. + GUI-capable ----------- Each of these backends can be used to draw the contents of windows in a graphical user interface. -kiva.agg/image -~~~~~~~~~~~~~~ -This is a wrapper of the popular Anti-Grain Geometry C++ library. It is the -current default backend. +kiva.agg/image/oldagg + This is a wrapper of the popular Anti-Grain Geometry C++ library. It is the + current default backend. This backend will be replaced by the celiagg + backend in a future release. cairo -~~~~~ -A backend based on the `Cairo graphics library `_. + A backend based on the `Cairo graphics library `_. celiagg -~~~~~~~ -A newer wrapper of Anti-Grain Geometry which is maintained outside of -kiva/enable. + A newer wrapper of Anti-Grain Geometry which is maintained outside of + kiva/enable. It is planned that this will become the default image backend + in a future release. gl -~~ -OpenGL drawing. This backend is quite limited compared to others. + OpenGL drawing. This backend is quite limited compared to others. qpainter -~~~~~~~~ -Qt ``QPainter`` drawing. This is only availble with the Qt toolkit. + Qt ``QPainter`` drawing. This is only availble with the Qt toolkit. quartz -~~~~~~ -macOS Quartz graphics (ie `CGContext `_). -This is only available on macOS. + MacOS Quartz graphics (ie `CGContext `_). + This is only available on macOS. + +blend2d + An experimental backend using the `Blend2D `_ + `Python wrapper `_. File-only --------- Each of these backends can be used to create an output file. pdf -~~~ -A backend which writes PDF files. + A backend which writes PDF files. ps -~~ -A backend which writes PostScript files. + A backend which writes PostScript files. svg -~~~ -A backend which writes SVG files. + A backend which writes SVG files. diff --git a/docs/source/kiva/writing_a_backend.rst b/docs/source/kiva/writing_a_backend.rst new file mode 100644 index 000000000..85feac9fd --- /dev/null +++ b/docs/source/kiva/writing_a_backend.rst @@ -0,0 +1,40 @@ +.. _writing_a_backend: + +Writing A Kiva Backend +====================== + +To write a Kiva backend you need to implement all the abstract methods of the +:py:class:`.AbstractGraphicsContext`. Once this is done, instances of the new +backend should be able to be used in most places that one of the current +backends is. Most sophisticated 2D drawing libraries are very similar to +Kiva in terms of the facilities they offer, and so in many cases this is just +a matter of translating between the Kiva API and the underlying library. + +In some cases, however, the underlying drawing capabilities are more primitive. + +GraphicsContextBase +------------------- + +Kiva provides a core set of functionality in +:py:class:`~kiva.basecore2d.GraphicsContextBase` that makes it easier to +implement the full Kiva API on top of a more basic drawing API (for example +one which can't draw Bezier curves or arcs, or have a notion of graphics +state). To use this class, you need to subclass and implement a number of +``device_`` methods that actually perform the drawing operations. These +include: + +- ``device_update_fill_state`` +- ``device_update_line_state`` +- ``device_fill_points`` +- ``device_stroke_points`` +- ``device_draw_image`` +- ``device_show_text`` +- ``device_get_full_text_extent`` +- ``device_set_clipping_path`` +- ``device_destroy_clipping_path`` + +The :py:class:`~kiva.basecore2d.GraphicsContextBase` class tracks all the +required state internally, including saving and restoring state. However the +device code needs to be able to handle applying the current affine +transformations and styling when drawing polygons, text and images, either in +the implementation code or in the underlying graphics library. diff --git a/kiva/fonttools/font_manager.py b/kiva/fonttools/font_manager.py index b91518632..b24805bcf 100644 --- a/kiva/fonttools/font_manager.py +++ b/kiva/fonttools/font_manager.py @@ -8,14 +8,13 @@ # # Thanks for using Enthought open source! """ -####### NOTE ####### -This is based heavily on matplotlib's font_manager.py SVN rev 8713 -(git commit f8e4c6ce2408044bc89b78b3c72e54deb1999fb5), -but has been modified quite a bit in the decade since it was copied. -#################### - A module for finding, managing, and using fonts across platforms. +.. note:: + This is based heavily on matplotlib's font_manager.py SVN rev 8713 + (git commit f8e4c6ce2408044bc89b78b3c72e54deb1999fb5), + but has been modified quite a bit in the decade since it was copied. + The design is based on the `W3C Cascading Style Sheet, Level 1 (CSS1) font specification `_. Future versions may implement the Level 2 or 2.1 specifications.