aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/matplotlib/py3/src
diff options
context:
space:
mode:
authorshumkovnd <shumkovnd@yandex-team.com>2023-11-10 14:39:34 +0300
committershumkovnd <shumkovnd@yandex-team.com>2023-11-10 16:42:24 +0300
commit77eb2d3fdcec5c978c64e025ced2764c57c00285 (patch)
treec51edb0748ca8d4a08d7c7323312c27ba1a8b79a /contrib/python/matplotlib/py3/src
parentdd6d20cadb65582270ac23f4b3b14ae189704b9d (diff)
downloadydb-77eb2d3fdcec5c978c64e025ced2764c57c00285.tar.gz
KIKIMR-19287: add task_stats_drawing script
Diffstat (limited to 'contrib/python/matplotlib/py3/src')
-rw-r--r--contrib/python/matplotlib/py3/src/_backend_agg.cpp176
-rw-r--r--contrib/python/matplotlib/py3/src/_backend_agg.h1280
-rw-r--r--contrib/python/matplotlib/py3/src/_backend_agg_basic_types.h123
-rw-r--r--contrib/python/matplotlib/py3/src/_backend_agg_wrapper.cpp646
-rw-r--r--contrib/python/matplotlib/py3/src/_c_internal_utils.c211
-rw-r--r--contrib/python/matplotlib/py3/src/_image_resample.h832
-rw-r--r--contrib/python/matplotlib/py3/src/_image_wrapper.cpp297
-rw-r--r--contrib/python/matplotlib/py3/src/_path.h1251
-rw-r--r--contrib/python/matplotlib/py3/src/_path_wrapper.cpp772
-rw-r--r--contrib/python/matplotlib/py3/src/_qhull_wrapper.cpp340
-rw-r--r--contrib/python/matplotlib/py3/src/_tkagg.cpp370
-rw-r--r--contrib/python/matplotlib/py3/src/_tkmini.h110
-rw-r--r--contrib/python/matplotlib/py3/src/_ttconv.cpp96
-rw-r--r--contrib/python/matplotlib/py3/src/agg_workaround.h85
-rw-r--r--contrib/python/matplotlib/py3/src/array.h80
-rw-r--r--contrib/python/matplotlib/py3/src/checkdep_freetype2.c19
-rw-r--r--contrib/python/matplotlib/py3/src/ft2font.cpp840
-rw-r--r--contrib/python/matplotlib/py3/src/ft2font.h159
-rw-r--r--contrib/python/matplotlib/py3/src/ft2font_wrapper.cpp1578
-rw-r--r--contrib/python/matplotlib/py3/src/mplutils.h99
-rw-r--r--contrib/python/matplotlib/py3/src/numpy_cpp.h578
-rw-r--r--contrib/python/matplotlib/py3/src/path_converters.h1106
-rw-r--r--contrib/python/matplotlib/py3/src/py_adaptors.h248
-rw-r--r--contrib/python/matplotlib/py3/src/py_converters.cpp558
-rw-r--r--contrib/python/matplotlib/py3/src/py_converters.h48
-rw-r--r--contrib/python/matplotlib/py3/src/py_exceptions.h72
-rw-r--r--contrib/python/matplotlib/py3/src/tri/_tri.cpp2074
-rw-r--r--contrib/python/matplotlib/py3/src/tri/_tri.h799
-rw-r--r--contrib/python/matplotlib/py3/src/tri/_tri_wrapper.cpp58
29 files changed, 14905 insertions, 0 deletions
diff --git a/contrib/python/matplotlib/py3/src/_backend_agg.cpp b/contrib/python/matplotlib/py3/src/_backend_agg.cpp
new file mode 100644
index 0000000000..335e409719
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/_backend_agg.cpp
@@ -0,0 +1,176 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+#define NO_IMPORT_ARRAY
+
+#include "_backend_agg.h"
+#include "mplutils.h"
+
+void BufferRegion::to_string_argb(uint8_t *buf)
+{
+ unsigned char *pix;
+ unsigned char tmp;
+ size_t i, j;
+
+ memcpy(buf, data, (size_t) height * stride);
+
+ for (i = 0; i < (size_t)height; ++i) {
+ pix = buf + i * stride;
+ for (j = 0; j < (size_t)width; ++j) {
+ // Convert rgba to argb
+ tmp = pix[2];
+ pix[2] = pix[0];
+ pix[0] = tmp;
+ pix += 4;
+ }
+ }
+}
+
+RendererAgg::RendererAgg(unsigned int width, unsigned int height, double dpi)
+ : width(width),
+ height(height),
+ dpi(dpi),
+ NUMBYTES((size_t)width * (size_t)height * 4),
+ pixBuffer(NULL),
+ renderingBuffer(),
+ alphaBuffer(NULL),
+ alphaMaskRenderingBuffer(),
+ alphaMask(alphaMaskRenderingBuffer),
+ pixfmtAlphaMask(alphaMaskRenderingBuffer),
+ rendererBaseAlphaMask(),
+ rendererAlphaMask(),
+ scanlineAlphaMask(),
+ slineP8(),
+ slineBin(),
+ pixFmt(),
+ rendererBase(),
+ rendererAA(),
+ rendererBin(),
+ theRasterizer(32768),
+ lastclippath(NULL),
+ _fill_color(agg::rgba(1, 1, 1, 0))
+{
+ unsigned stride(width * 4);
+
+ pixBuffer = new agg::int8u[NUMBYTES];
+ renderingBuffer.attach(pixBuffer, width, height, stride);
+ pixFmt.attach(renderingBuffer);
+ rendererBase.attach(pixFmt);
+ rendererBase.clear(_fill_color);
+ rendererAA.attach(rendererBase);
+ rendererBin.attach(rendererBase);
+ hatch_size = int(dpi);
+ hatchBuffer = new agg::int8u[hatch_size * hatch_size * 4];
+ hatchRenderingBuffer.attach(hatchBuffer, hatch_size, hatch_size, hatch_size * 4);
+}
+
+RendererAgg::~RendererAgg()
+{
+ delete[] hatchBuffer;
+ delete[] alphaBuffer;
+ delete[] pixBuffer;
+}
+
+void RendererAgg::create_alpha_buffers()
+{
+ if (!alphaBuffer) {
+ alphaBuffer = new agg::int8u[width * height];
+ alphaMaskRenderingBuffer.attach(alphaBuffer, width, height, width);
+ rendererBaseAlphaMask.attach(pixfmtAlphaMask);
+ rendererAlphaMask.attach(rendererBaseAlphaMask);
+ }
+}
+
+BufferRegion *RendererAgg::copy_from_bbox(agg::rect_d in_rect)
+{
+ agg::rect_i rect(
+ (int)in_rect.x1, height - (int)in_rect.y2, (int)in_rect.x2, height - (int)in_rect.y1);
+
+ BufferRegion *reg = NULL;
+ reg = new BufferRegion(rect);
+
+ agg::rendering_buffer rbuf;
+ rbuf.attach(reg->get_data(), reg->get_width(), reg->get_height(), reg->get_stride());
+
+ pixfmt pf(rbuf);
+ renderer_base rb(pf);
+ rb.copy_from(renderingBuffer, &rect, -rect.x1, -rect.y1);
+
+ return reg;
+}
+
+void RendererAgg::restore_region(BufferRegion &region)
+{
+ if (region.get_data() == NULL) {
+ throw std::runtime_error("Cannot restore_region from NULL data");
+ }
+
+ agg::rendering_buffer rbuf;
+ rbuf.attach(region.get_data(), region.get_width(), region.get_height(), region.get_stride());
+
+ rendererBase.copy_from(rbuf, 0, region.get_rect().x1, region.get_rect().y1);
+}
+
+// Restore the part of the saved region with offsets
+void
+RendererAgg::restore_region(BufferRegion &region, int xx1, int yy1, int xx2, int yy2, int x, int y )
+{
+ if (region.get_data() == NULL) {
+ throw std::runtime_error("Cannot restore_region from NULL data");
+ }
+
+ agg::rect_i &rrect = region.get_rect();
+
+ agg::rect_i rect(xx1 - rrect.x1, (yy1 - rrect.y1), xx2 - rrect.x1, (yy2 - rrect.y1));
+
+ agg::rendering_buffer rbuf;
+ rbuf.attach(region.get_data(), region.get_width(), region.get_height(), region.get_stride());
+
+ rendererBase.copy_from(rbuf, &rect, x, y);
+}
+
+bool RendererAgg::render_clippath(py::PathIterator &clippath,
+ const agg::trans_affine &clippath_trans,
+ e_snap_mode snap_mode)
+{
+ typedef agg::conv_transform<py::PathIterator> transformed_path_t;
+ typedef PathNanRemover<transformed_path_t> nan_removed_t;
+ /* Unlike normal Paths, the clip path cannot be clipped to the Figure bbox,
+ * because it needs to remain a complete closed path, so there is no
+ * PathClipper<nan_removed_t> step. */
+ typedef PathSnapper<nan_removed_t> snapped_t;
+ typedef PathSimplifier<snapped_t> simplify_t;
+ typedef agg::conv_curve<simplify_t> curve_t;
+
+ bool has_clippath = (clippath.total_vertices() != 0);
+
+ if (has_clippath &&
+ (clippath.get_id() != lastclippath || clippath_trans != lastclippath_transform)) {
+ create_alpha_buffers();
+ agg::trans_affine trans(clippath_trans);
+ trans *= agg::trans_affine_scaling(1.0, -1.0);
+ trans *= agg::trans_affine_translation(0.0, (double)height);
+
+ rendererBaseAlphaMask.clear(agg::gray8(0, 0));
+ transformed_path_t transformed_clippath(clippath, trans);
+ nan_removed_t nan_removed_clippath(transformed_clippath, true, clippath.has_codes());
+ snapped_t snapped_clippath(nan_removed_clippath, snap_mode, clippath.total_vertices(), 0.0);
+ simplify_t simplified_clippath(snapped_clippath,
+ clippath.should_simplify() && !clippath.has_codes(),
+ clippath.simplify_threshold());
+ curve_t curved_clippath(simplified_clippath);
+ theRasterizer.add_path(curved_clippath);
+ rendererAlphaMask.color(agg::gray8(255, 255));
+ agg::render_scanlines(theRasterizer, scanlineAlphaMask, rendererAlphaMask);
+ lastclippath = clippath.get_id();
+ lastclippath_transform = clippath_trans;
+ }
+
+ return has_clippath;
+}
+
+void RendererAgg::clear()
+{
+ //"clear the rendered buffer";
+
+ rendererBase.clear(_fill_color);
+}
diff --git a/contrib/python/matplotlib/py3/src/_backend_agg.h b/contrib/python/matplotlib/py3/src/_backend_agg.h
new file mode 100644
index 0000000000..61c24232a8
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/_backend_agg.h
@@ -0,0 +1,1280 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+/* _backend_agg.h
+*/
+
+#ifndef MPL_BACKEND_AGG_H
+#define MPL_BACKEND_AGG_H
+
+#include <cmath>
+#include <vector>
+#include <algorithm>
+
+#include "agg_alpha_mask_u8.h"
+#include "agg_conv_curve.h"
+#include "agg_conv_dash.h"
+#include "agg_conv_stroke.h"
+#include "agg_image_accessors.h"
+#include "agg_pixfmt_amask_adaptor.h"
+#include "agg_pixfmt_gray.h"
+#include "agg_pixfmt_rgba.h"
+#include "agg_rasterizer_scanline_aa.h"
+#include "agg_renderer_base.h"
+#include "agg_renderer_scanline.h"
+#include "agg_rendering_buffer.h"
+#include "agg_scanline_bin.h"
+#include "agg_scanline_p.h"
+#include "agg_scanline_storage_aa.h"
+#include "agg_scanline_storage_bin.h"
+#include "agg_scanline_u.h"
+#include "agg_span_allocator.h"
+#include "agg_span_converter.h"
+#include "agg_span_gouraud_rgba.h"
+#include "agg_span_image_filter_gray.h"
+#include "agg_span_image_filter_rgba.h"
+#include "agg_span_interpolator_linear.h"
+#include "agg_span_pattern_rgba.h"
+#include "util/agg_color_conv_rgb8.h"
+
+#include "_backend_agg_basic_types.h"
+#include "path_converters.h"
+#include "array.h"
+#include "agg_workaround.h"
+
+/**********************************************************************/
+
+// a helper class to pass agg::buffer objects around.
+
+class BufferRegion
+{
+ public:
+ BufferRegion(const agg::rect_i &r) : rect(r)
+ {
+ width = r.x2 - r.x1;
+ height = r.y2 - r.y1;
+ stride = width * 4;
+ data = new agg::int8u[stride * height];
+ }
+
+ virtual ~BufferRegion()
+ {
+ delete[] data;
+ };
+
+ agg::int8u *get_data()
+ {
+ return data;
+ }
+
+ agg::rect_i &get_rect()
+ {
+ return rect;
+ }
+
+ int get_width()
+ {
+ return width;
+ }
+
+ int get_height()
+ {
+ return height;
+ }
+
+ int get_stride()
+ {
+ return stride;
+ }
+
+ void to_string_argb(uint8_t *buf);
+
+ private:
+ agg::int8u *data;
+ agg::rect_i rect;
+ int width;
+ int height;
+ int stride;
+
+ private:
+ // prevent copying
+ BufferRegion(const BufferRegion &);
+ BufferRegion &operator=(const BufferRegion &);
+};
+
+#define MARKER_CACHE_SIZE 512
+
+// the renderer
+class RendererAgg
+{
+ public:
+
+ typedef fixed_blender_rgba_plain<agg::rgba8, agg::order_rgba> fixed_blender_rgba32_plain;
+ typedef agg::pixfmt_alpha_blend_rgba<fixed_blender_rgba32_plain, agg::rendering_buffer> pixfmt;
+ typedef agg::renderer_base<pixfmt> renderer_base;
+ typedef agg::renderer_scanline_aa_solid<renderer_base> renderer_aa;
+ typedef agg::renderer_scanline_bin_solid<renderer_base> renderer_bin;
+ typedef agg::rasterizer_scanline_aa<agg::rasterizer_sl_clip_dbl> rasterizer;
+
+ typedef agg::scanline_p8 scanline_p8;
+ typedef agg::scanline_bin scanline_bin;
+ typedef agg::amask_no_clip_gray8 alpha_mask_type;
+ typedef agg::scanline_u8_am<alpha_mask_type> scanline_am;
+
+ typedef agg::renderer_base<agg::pixfmt_gray8> renderer_base_alpha_mask_type;
+ typedef agg::renderer_scanline_aa_solid<renderer_base_alpha_mask_type> renderer_alpha_mask_type;
+
+ /* TODO: Remove facepair_t */
+ typedef std::pair<bool, agg::rgba> facepair_t;
+
+ RendererAgg(unsigned int width, unsigned int height, double dpi);
+
+ virtual ~RendererAgg();
+
+ unsigned int get_width()
+ {
+ return width;
+ }
+
+ unsigned int get_height()
+ {
+ return height;
+ }
+
+ template <class PathIterator>
+ void draw_path(GCAgg &gc, PathIterator &path, agg::trans_affine &trans, agg::rgba &color);
+
+ template <class PathIterator>
+ void draw_markers(GCAgg &gc,
+ PathIterator &marker_path,
+ agg::trans_affine &marker_path_trans,
+ PathIterator &path,
+ agg::trans_affine &trans,
+ agg::rgba face);
+
+ template <class ImageArray>
+ void draw_text_image(GCAgg &gc, ImageArray &image, int x, int y, double angle);
+
+ template <class ImageArray>
+ void draw_image(GCAgg &gc,
+ double x,
+ double y,
+ ImageArray &image);
+
+ template <class PathGenerator,
+ class TransformArray,
+ class OffsetArray,
+ class ColorArray,
+ class LineWidthArray,
+ class AntialiasedArray>
+ void draw_path_collection(GCAgg &gc,
+ agg::trans_affine &master_transform,
+ PathGenerator &path,
+ TransformArray &transforms,
+ OffsetArray &offsets,
+ agg::trans_affine &offset_trans,
+ ColorArray &facecolors,
+ ColorArray &edgecolors,
+ LineWidthArray &linewidths,
+ DashesVector &linestyles,
+ AntialiasedArray &antialiaseds);
+
+ template <class CoordinateArray, class OffsetArray, class ColorArray>
+ void draw_quad_mesh(GCAgg &gc,
+ agg::trans_affine &master_transform,
+ unsigned int mesh_width,
+ unsigned int mesh_height,
+ CoordinateArray &coordinates,
+ OffsetArray &offsets,
+ agg::trans_affine &offset_trans,
+ ColorArray &facecolors,
+ bool antialiased,
+ ColorArray &edgecolors);
+
+ template <class PointArray, class ColorArray>
+ void draw_gouraud_triangle(GCAgg &gc,
+ PointArray &points,
+ ColorArray &colors,
+ agg::trans_affine &trans);
+
+ template <class PointArray, class ColorArray>
+ void draw_gouraud_triangles(GCAgg &gc,
+ PointArray &points,
+ ColorArray &colors,
+ agg::trans_affine &trans);
+
+ agg::rect_i get_content_extents();
+ void clear();
+
+ BufferRegion *copy_from_bbox(agg::rect_d in_rect);
+ void restore_region(BufferRegion &reg);
+ void restore_region(BufferRegion &region, int xx1, int yy1, int xx2, int yy2, int x, int y);
+
+ unsigned int width, height;
+ double dpi;
+ size_t NUMBYTES; // the number of bytes in buffer
+
+ agg::int8u *pixBuffer;
+ agg::rendering_buffer renderingBuffer;
+
+ agg::int8u *alphaBuffer;
+ agg::rendering_buffer alphaMaskRenderingBuffer;
+ alpha_mask_type alphaMask;
+ agg::pixfmt_gray8 pixfmtAlphaMask;
+ renderer_base_alpha_mask_type rendererBaseAlphaMask;
+ renderer_alpha_mask_type rendererAlphaMask;
+ scanline_am scanlineAlphaMask;
+
+ scanline_p8 slineP8;
+ scanline_bin slineBin;
+ pixfmt pixFmt;
+ renderer_base rendererBase;
+ renderer_aa rendererAA;
+ renderer_bin rendererBin;
+ rasterizer theRasterizer;
+
+ void *lastclippath;
+ agg::trans_affine lastclippath_transform;
+
+ size_t hatch_size;
+ agg::int8u *hatchBuffer;
+ agg::rendering_buffer hatchRenderingBuffer;
+
+ agg::rgba _fill_color;
+
+ protected:
+ inline double points_to_pixels(double points)
+ {
+ return points * dpi / 72.0;
+ }
+
+ template <class R>
+ void set_clipbox(const agg::rect_d &cliprect, R &rasterizer);
+
+ bool render_clippath(py::PathIterator &clippath, const agg::trans_affine &clippath_trans, e_snap_mode snap_mode);
+
+ template <class PathIteratorType>
+ void _draw_path(PathIteratorType &path, bool has_clippath, const facepair_t &face, GCAgg &gc);
+
+ template <class PathIterator,
+ class PathGenerator,
+ class TransformArray,
+ class OffsetArray,
+ class ColorArray,
+ class LineWidthArray,
+ class AntialiasedArray>
+ void _draw_path_collection_generic(GCAgg &gc,
+ agg::trans_affine master_transform,
+ const agg::rect_d &cliprect,
+ PathIterator &clippath,
+ const agg::trans_affine &clippath_trans,
+ PathGenerator &path_generator,
+ TransformArray &transforms,
+ OffsetArray &offsets,
+ const agg::trans_affine &offset_trans,
+ ColorArray &facecolors,
+ ColorArray &edgecolors,
+ LineWidthArray &linewidths,
+ DashesVector &linestyles,
+ AntialiasedArray &antialiaseds,
+ bool check_snap,
+ bool has_codes);
+
+ template <class PointArray, class ColorArray>
+ void _draw_gouraud_triangle(PointArray &points,
+ ColorArray &colors,
+ agg::trans_affine trans,
+ bool has_clippath);
+
+ private:
+ void create_alpha_buffers();
+
+ // prevent copying
+ RendererAgg(const RendererAgg &);
+ RendererAgg &operator=(const RendererAgg &);
+};
+
+/***************************************************************************
+ * Implementation
+ */
+
+template <class path_t>
+inline void
+RendererAgg::_draw_path(path_t &path, bool has_clippath, const facepair_t &face, GCAgg &gc)
+{
+ typedef agg::conv_stroke<path_t> stroke_t;
+ typedef agg::conv_dash<path_t> dash_t;
+ typedef agg::conv_stroke<dash_t> stroke_dash_t;
+ typedef agg::pixfmt_amask_adaptor<pixfmt, alpha_mask_type> pixfmt_amask_type;
+ typedef agg::renderer_base<pixfmt_amask_type> amask_ren_type;
+ typedef agg::renderer_scanline_aa_solid<amask_ren_type> amask_aa_renderer_type;
+ typedef agg::renderer_scanline_bin_solid<amask_ren_type> amask_bin_renderer_type;
+
+ // Render face
+ if (face.first) {
+ theRasterizer.add_path(path);
+
+ if (gc.isaa) {
+ if (has_clippath) {
+ pixfmt_amask_type pfa(pixFmt, alphaMask);
+ amask_ren_type r(pfa);
+ amask_aa_renderer_type ren(r);
+ ren.color(face.second);
+ agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren);
+ } else {
+ rendererAA.color(face.second);
+ agg::render_scanlines(theRasterizer, slineP8, rendererAA);
+ }
+ } else {
+ if (has_clippath) {
+ pixfmt_amask_type pfa(pixFmt, alphaMask);
+ amask_ren_type r(pfa);
+ amask_bin_renderer_type ren(r);
+ ren.color(face.second);
+ agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren);
+ } else {
+ rendererBin.color(face.second);
+ agg::render_scanlines(theRasterizer, slineP8, rendererBin);
+ }
+ }
+ }
+
+ // Render hatch
+ if (gc.has_hatchpath()) {
+ // Reset any clipping that may be in effect, since we'll be
+ // drawing the hatch in a scratch buffer at origin (0, 0)
+ theRasterizer.reset_clipping();
+ rendererBase.reset_clipping(true);
+
+ // Create and transform the path
+ typedef agg::conv_transform<py::PathIterator> hatch_path_trans_t;
+ typedef agg::conv_curve<hatch_path_trans_t> hatch_path_curve_t;
+ typedef agg::conv_stroke<hatch_path_curve_t> hatch_path_stroke_t;
+
+ py::PathIterator hatch_path(gc.hatchpath);
+ agg::trans_affine hatch_trans;
+ hatch_trans *= agg::trans_affine_scaling(1.0, -1.0);
+ hatch_trans *= agg::trans_affine_translation(0.0, 1.0);
+ hatch_trans *= agg::trans_affine_scaling(hatch_size, hatch_size);
+ hatch_path_trans_t hatch_path_trans(hatch_path, hatch_trans);
+ hatch_path_curve_t hatch_path_curve(hatch_path_trans);
+ hatch_path_stroke_t hatch_path_stroke(hatch_path_curve);
+ hatch_path_stroke.width(points_to_pixels(gc.hatch_linewidth));
+ hatch_path_stroke.line_cap(agg::square_cap);
+
+ // Render the path into the hatch buffer
+ pixfmt hatch_img_pixf(hatchRenderingBuffer);
+ renderer_base rb(hatch_img_pixf);
+ renderer_aa rs(rb);
+ rb.clear(_fill_color);
+ rs.color(gc.hatch_color);
+
+ theRasterizer.add_path(hatch_path_curve);
+ agg::render_scanlines(theRasterizer, slineP8, rs);
+ theRasterizer.add_path(hatch_path_stroke);
+ agg::render_scanlines(theRasterizer, slineP8, rs);
+
+ // Put clipping back on, if originally set on entry to this
+ // function
+ set_clipbox(gc.cliprect, theRasterizer);
+ if (has_clippath) {
+ render_clippath(gc.clippath.path, gc.clippath.trans, gc.snap_mode);
+ }
+
+ // Transfer the hatch to the main image buffer
+ typedef agg::image_accessor_wrap<pixfmt,
+ agg::wrap_mode_repeat_auto_pow2,
+ agg::wrap_mode_repeat_auto_pow2> img_source_type;
+ typedef agg::span_pattern_rgba<img_source_type> span_gen_type;
+ agg::span_allocator<agg::rgba8> sa;
+ img_source_type img_src(hatch_img_pixf);
+ span_gen_type sg(img_src, 0, 0);
+ theRasterizer.add_path(path);
+
+ if (has_clippath) {
+ pixfmt_amask_type pfa(pixFmt, alphaMask);
+ amask_ren_type ren(pfa);
+ agg::render_scanlines_aa(theRasterizer, slineP8, ren, sa, sg);
+ } else {
+ agg::render_scanlines_aa(theRasterizer, slineP8, rendererBase, sa, sg);
+ }
+ }
+
+ // Render stroke
+ if (gc.linewidth != 0.0) {
+ double linewidth = points_to_pixels(gc.linewidth);
+ if (!gc.isaa) {
+ linewidth = (linewidth < 0.5) ? 0.5 : mpl_round(linewidth);
+ }
+ if (gc.dashes.size() == 0) {
+ stroke_t stroke(path);
+ stroke.width(points_to_pixels(gc.linewidth));
+ stroke.line_cap(gc.cap);
+ stroke.line_join(gc.join);
+ stroke.miter_limit(points_to_pixels(gc.linewidth));
+ theRasterizer.add_path(stroke);
+ } else {
+ dash_t dash(path);
+ gc.dashes.dash_to_stroke(dash, dpi, gc.isaa);
+ stroke_dash_t stroke(dash);
+ stroke.line_cap(gc.cap);
+ stroke.line_join(gc.join);
+ stroke.width(linewidth);
+ stroke.miter_limit(points_to_pixels(gc.linewidth));
+ theRasterizer.add_path(stroke);
+ }
+
+ if (gc.isaa) {
+ if (has_clippath) {
+ pixfmt_amask_type pfa(pixFmt, alphaMask);
+ amask_ren_type r(pfa);
+ amask_aa_renderer_type ren(r);
+ ren.color(gc.color);
+ agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren);
+ } else {
+ rendererAA.color(gc.color);
+ agg::render_scanlines(theRasterizer, slineP8, rendererAA);
+ }
+ } else {
+ if (has_clippath) {
+ pixfmt_amask_type pfa(pixFmt, alphaMask);
+ amask_ren_type r(pfa);
+ amask_bin_renderer_type ren(r);
+ ren.color(gc.color);
+ agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren);
+ } else {
+ rendererBin.color(gc.color);
+ agg::render_scanlines(theRasterizer, slineBin, rendererBin);
+ }
+ }
+ }
+}
+
+template <class PathIterator>
+inline void
+RendererAgg::draw_path(GCAgg &gc, PathIterator &path, agg::trans_affine &trans, agg::rgba &color)
+{
+ typedef agg::conv_transform<py::PathIterator> transformed_path_t;
+ typedef PathNanRemover<transformed_path_t> nan_removed_t;
+ typedef PathClipper<nan_removed_t> clipped_t;
+ typedef PathSnapper<clipped_t> snapped_t;
+ typedef PathSimplifier<snapped_t> simplify_t;
+ typedef agg::conv_curve<simplify_t> curve_t;
+ typedef Sketch<curve_t> sketch_t;
+
+ facepair_t face(color.a != 0.0, color);
+
+ theRasterizer.reset_clipping();
+ rendererBase.reset_clipping(true);
+ set_clipbox(gc.cliprect, theRasterizer);
+ bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans, gc.snap_mode);
+
+ trans *= agg::trans_affine_scaling(1.0, -1.0);
+ trans *= agg::trans_affine_translation(0.0, (double)height);
+ bool clip = !face.first && !gc.has_hatchpath();
+ bool simplify = path.should_simplify() && clip;
+ double snapping_linewidth = points_to_pixels(gc.linewidth);
+ if (gc.color.a == 0.0) {
+ snapping_linewidth = 0.0;
+ }
+
+ transformed_path_t tpath(path, trans);
+ nan_removed_t nan_removed(tpath, true, path.has_codes());
+ clipped_t clipped(nan_removed, clip, width, height);
+ snapped_t snapped(clipped, gc.snap_mode, path.total_vertices(), snapping_linewidth);
+ simplify_t simplified(snapped, simplify, path.simplify_threshold());
+ curve_t curve(simplified);
+ sketch_t sketch(curve, gc.sketch.scale, gc.sketch.length, gc.sketch.randomness);
+
+ _draw_path(sketch, has_clippath, face, gc);
+}
+
+template <class PathIterator>
+inline void RendererAgg::draw_markers(GCAgg &gc,
+ PathIterator &marker_path,
+ agg::trans_affine &marker_trans,
+ PathIterator &path,
+ agg::trans_affine &trans,
+ agg::rgba color)
+{
+ typedef agg::conv_transform<py::PathIterator> transformed_path_t;
+ typedef PathNanRemover<transformed_path_t> nan_removed_t;
+ typedef PathSnapper<nan_removed_t> snap_t;
+ typedef agg::conv_curve<snap_t> curve_t;
+ typedef agg::conv_stroke<curve_t> stroke_t;
+ typedef agg::pixfmt_amask_adaptor<pixfmt, alpha_mask_type> pixfmt_amask_type;
+ typedef agg::renderer_base<pixfmt_amask_type> amask_ren_type;
+ typedef agg::renderer_scanline_aa_solid<amask_ren_type> amask_aa_renderer_type;
+
+ // Deal with the difference in y-axis direction
+ marker_trans *= agg::trans_affine_scaling(1.0, -1.0);
+
+ trans *= agg::trans_affine_scaling(1.0, -1.0);
+ trans *= agg::trans_affine_translation(0.5, (double)height + 0.5);
+
+ transformed_path_t marker_path_transformed(marker_path, marker_trans);
+ nan_removed_t marker_path_nan_removed(marker_path_transformed, true, marker_path.has_codes());
+ snap_t marker_path_snapped(marker_path_nan_removed,
+ gc.snap_mode,
+ marker_path.total_vertices(),
+ points_to_pixels(gc.linewidth));
+ curve_t marker_path_curve(marker_path_snapped);
+
+ if (!marker_path_snapped.is_snapping()) {
+ // If the path snapper isn't in effect, at least make sure the marker
+ // at (0, 0) is in the center of a pixel. This, importantly, makes
+ // the circle markers look centered around the point they refer to.
+ marker_trans *= agg::trans_affine_translation(0.5, 0.5);
+ }
+
+ transformed_path_t path_transformed(path, trans);
+ nan_removed_t path_nan_removed(path_transformed, false, false);
+ snap_t path_snapped(path_nan_removed, SNAP_FALSE, path.total_vertices(), 0.0);
+ curve_t path_curve(path_snapped);
+ path_curve.rewind(0);
+
+ facepair_t face(color.a != 0.0, color);
+
+ // maxim's suggestions for cached scanlines
+ agg::scanline_storage_aa8 scanlines;
+ theRasterizer.reset();
+ theRasterizer.reset_clipping();
+ rendererBase.reset_clipping(true);
+ agg::rect_i marker_size(0x7FFFFFFF, 0x7FFFFFFF, -0x7FFFFFFF, -0x7FFFFFFF);
+
+ agg::int8u staticFillCache[MARKER_CACHE_SIZE];
+ agg::int8u staticStrokeCache[MARKER_CACHE_SIZE];
+ agg::int8u *fillCache = staticFillCache;
+ agg::int8u *strokeCache = staticStrokeCache;
+
+ try
+ {
+ unsigned fillSize = 0;
+ if (face.first) {
+ theRasterizer.add_path(marker_path_curve);
+ agg::render_scanlines(theRasterizer, slineP8, scanlines);
+ fillSize = scanlines.byte_size();
+ if (fillSize >= MARKER_CACHE_SIZE) {
+ fillCache = new agg::int8u[fillSize];
+ }
+ scanlines.serialize(fillCache);
+ marker_size = agg::rect_i(scanlines.min_x(),
+ scanlines.min_y(),
+ scanlines.max_x(),
+ scanlines.max_y());
+ }
+
+ stroke_t stroke(marker_path_curve);
+ stroke.width(points_to_pixels(gc.linewidth));
+ stroke.line_cap(gc.cap);
+ stroke.line_join(gc.join);
+ stroke.miter_limit(points_to_pixels(gc.linewidth));
+ theRasterizer.reset();
+ theRasterizer.add_path(stroke);
+ agg::render_scanlines(theRasterizer, slineP8, scanlines);
+ unsigned strokeSize = scanlines.byte_size();
+ if (strokeSize >= MARKER_CACHE_SIZE) {
+ strokeCache = new agg::int8u[strokeSize];
+ }
+ scanlines.serialize(strokeCache);
+ marker_size = agg::rect_i(std::min(marker_size.x1, scanlines.min_x()),
+ std::min(marker_size.y1, scanlines.min_y()),
+ std::max(marker_size.x2, scanlines.max_x()),
+ std::max(marker_size.y2, scanlines.max_y()));
+
+ theRasterizer.reset_clipping();
+ rendererBase.reset_clipping(true);
+ set_clipbox(gc.cliprect, rendererBase);
+ bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans, gc.snap_mode);
+
+ double x, y;
+
+ agg::serialized_scanlines_adaptor_aa8 sa;
+ agg::serialized_scanlines_adaptor_aa8::embedded_scanline sl;
+
+ agg::rect_d clipping_rect(-1.0 - marker_size.x2,
+ -1.0 - marker_size.y2,
+ 1.0 + width - marker_size.x1,
+ 1.0 + height - marker_size.y1);
+
+ if (has_clippath) {
+ while (path_curve.vertex(&x, &y) != agg::path_cmd_stop) {
+ if (!(std::isfinite(x) && std::isfinite(y))) {
+ continue;
+ }
+
+ /* These values are correctly snapped above -- so we don't want
+ to round here, we really only want to truncate */
+ x = floor(x);
+ y = floor(y);
+
+ // Cull points outside the boundary of the image.
+ // Values that are too large may overflow and create
+ // segfaults.
+ // http://sourceforge.net/tracker/?func=detail&aid=2865490&group_id=80706&atid=560720
+ if (!clipping_rect.hit_test(x, y)) {
+ continue;
+ }
+
+ pixfmt_amask_type pfa(pixFmt, alphaMask);
+ amask_ren_type r(pfa);
+ amask_aa_renderer_type ren(r);
+
+ if (face.first) {
+ ren.color(face.second);
+ sa.init(fillCache, fillSize, x, y);
+ agg::render_scanlines(sa, sl, ren);
+ }
+ ren.color(gc.color);
+ sa.init(strokeCache, strokeSize, x, y);
+ agg::render_scanlines(sa, sl, ren);
+ }
+ } else {
+ while (path_curve.vertex(&x, &y) != agg::path_cmd_stop) {
+ if (!(std::isfinite(x) && std::isfinite(y))) {
+ continue;
+ }
+
+ /* These values are correctly snapped above -- so we don't want
+ to round here, we really only want to truncate */
+ x = floor(x);
+ y = floor(y);
+
+ // Cull points outside the boundary of the image.
+ // Values that are too large may overflow and create
+ // segfaults.
+ // http://sourceforge.net/tracker/?func=detail&aid=2865490&group_id=80706&atid=560720
+ if (!clipping_rect.hit_test(x, y)) {
+ continue;
+ }
+
+ if (face.first) {
+ rendererAA.color(face.second);
+ sa.init(fillCache, fillSize, x, y);
+ agg::render_scanlines(sa, sl, rendererAA);
+ }
+
+ rendererAA.color(gc.color);
+ sa.init(strokeCache, strokeSize, x, y);
+ agg::render_scanlines(sa, sl, rendererAA);
+ }
+ }
+ }
+ catch (...)
+ {
+ if (fillCache != staticFillCache)
+ delete[] fillCache;
+ if (strokeCache != staticStrokeCache)
+ delete[] strokeCache;
+ theRasterizer.reset_clipping();
+ rendererBase.reset_clipping(true);
+ throw;
+ }
+
+ if (fillCache != staticFillCache)
+ delete[] fillCache;
+ if (strokeCache != staticStrokeCache)
+ delete[] strokeCache;
+
+ theRasterizer.reset_clipping();
+ rendererBase.reset_clipping(true);
+}
+
+/**
+ * This is a custom span generator that converts spans in the
+ * 8-bit inverted greyscale font buffer to rgba that agg can use.
+ */
+template <class ChildGenerator>
+class font_to_rgba
+{
+ public:
+ typedef ChildGenerator child_type;
+ typedef agg::rgba8 color_type;
+ typedef typename child_type::color_type child_color_type;
+ typedef agg::span_allocator<child_color_type> span_alloc_type;
+
+ private:
+ child_type *_gen;
+ color_type _color;
+ span_alloc_type _allocator;
+
+ public:
+ font_to_rgba(child_type *gen, color_type color) : _gen(gen), _color(color)
+ {
+ }
+
+ inline void generate(color_type *output_span, int x, int y, unsigned len)
+ {
+ _allocator.allocate(len);
+ child_color_type *input_span = _allocator.span();
+ _gen->generate(input_span, x, y, len);
+
+ do {
+ *output_span = _color;
+ output_span->a = ((unsigned int)_color.a * (unsigned int)input_span->v) >> 8;
+ ++output_span;
+ ++input_span;
+ } while (--len);
+ }
+
+ void prepare()
+ {
+ _gen->prepare();
+ }
+};
+
+template <class ImageArray>
+inline void RendererAgg::draw_text_image(GCAgg &gc, ImageArray &image, int x, int y, double angle)
+{
+ typedef agg::span_allocator<agg::rgba8> color_span_alloc_type;
+ typedef agg::span_interpolator_linear<> interpolator_type;
+ typedef agg::image_accessor_clip<agg::pixfmt_gray8> image_accessor_type;
+ typedef agg::span_image_filter_gray<image_accessor_type, interpolator_type> image_span_gen_type;
+ typedef font_to_rgba<image_span_gen_type> span_gen_type;
+ typedef agg::renderer_scanline_aa<renderer_base, color_span_alloc_type, span_gen_type>
+ renderer_type;
+
+ theRasterizer.reset_clipping();
+ rendererBase.reset_clipping(true);
+ if (angle != 0.0) {
+ agg::rendering_buffer srcbuf(
+ image.data(), (unsigned)image.dim(1),
+ (unsigned)image.dim(0), (unsigned)image.dim(1));
+ agg::pixfmt_gray8 pixf_img(srcbuf);
+
+ set_clipbox(gc.cliprect, theRasterizer);
+
+ agg::trans_affine mtx;
+ mtx *= agg::trans_affine_translation(0, -image.dim(0));
+ mtx *= agg::trans_affine_rotation(-angle * (agg::pi / 180.0));
+ mtx *= agg::trans_affine_translation(x, y);
+
+ agg::path_storage rect;
+ rect.move_to(0, 0);
+ rect.line_to(image.dim(1), 0);
+ rect.line_to(image.dim(1), image.dim(0));
+ rect.line_to(0, image.dim(0));
+ rect.line_to(0, 0);
+ agg::conv_transform<agg::path_storage> rect2(rect, mtx);
+
+ agg::trans_affine inv_mtx(mtx);
+ inv_mtx.invert();
+
+ agg::image_filter_lut filter;
+ filter.calculate(agg::image_filter_spline36());
+ interpolator_type interpolator(inv_mtx);
+ color_span_alloc_type sa;
+ image_accessor_type ia(pixf_img, agg::gray8(0));
+ image_span_gen_type image_span_generator(ia, interpolator, filter);
+ span_gen_type output_span_generator(&image_span_generator, gc.color);
+ renderer_type ri(rendererBase, sa, output_span_generator);
+
+ theRasterizer.add_path(rect2);
+ agg::render_scanlines(theRasterizer, slineP8, ri);
+ } else {
+ agg::rect_i fig, text;
+
+ int deltay = y - image.dim(0);
+
+ fig.init(0, 0, width, height);
+ text.init(x, deltay, x + image.dim(1), y);
+ text.clip(fig);
+
+ if (gc.cliprect.x1 != 0.0 || gc.cliprect.y1 != 0.0 || gc.cliprect.x2 != 0.0 || gc.cliprect.y2 != 0.0) {
+ agg::rect_i clip;
+
+ clip.init(mpl_round_to_int(gc.cliprect.x1),
+ mpl_round_to_int(height - gc.cliprect.y2),
+ mpl_round_to_int(gc.cliprect.x2),
+ mpl_round_to_int(height - gc.cliprect.y1));
+ text.clip(clip);
+ }
+
+ if (text.x2 > text.x1) {
+ int deltax = text.x2 - text.x1;
+ int deltax2 = text.x1 - x;
+ for (int yi = text.y1; yi < text.y2; ++yi) {
+ pixFmt.blend_solid_hspan(text.x1, yi, deltax, gc.color,
+ &image(yi - deltay, deltax2));
+ }
+ }
+ }
+}
+
+class span_conv_alpha
+{
+ public:
+ typedef agg::rgba8 color_type;
+
+ double m_alpha;
+
+ span_conv_alpha(double alpha) : m_alpha(alpha)
+ {
+ }
+
+ void prepare()
+ {
+ }
+ void generate(color_type *span, int x, int y, unsigned len) const
+ {
+ do {
+ span->a = (agg::int8u)((double)span->a * m_alpha);
+ ++span;
+ } while (--len);
+ }
+};
+
+template <class ImageArray>
+inline void RendererAgg::draw_image(GCAgg &gc,
+ double x,
+ double y,
+ ImageArray &image)
+{
+ double alpha = gc.alpha;
+
+ theRasterizer.reset_clipping();
+ rendererBase.reset_clipping(true);
+ set_clipbox(gc.cliprect, theRasterizer);
+ bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans, gc.snap_mode);
+
+ agg::rendering_buffer buffer;
+ buffer.attach(
+ image.data(), (unsigned)image.dim(1), (unsigned)image.dim(0), -(int)image.dim(1) * 4);
+ pixfmt pixf(buffer);
+
+ if (has_clippath) {
+ agg::trans_affine mtx;
+ agg::path_storage rect;
+
+ mtx *= agg::trans_affine_translation((int)x, (int)(height - (y + image.dim(0))));
+
+ rect.move_to(0, 0);
+ rect.line_to(image.dim(1), 0);
+ rect.line_to(image.dim(1), image.dim(0));
+ rect.line_to(0, image.dim(0));
+ rect.line_to(0, 0);
+
+ agg::conv_transform<agg::path_storage> rect2(rect, mtx);
+
+ agg::trans_affine inv_mtx(mtx);
+ inv_mtx.invert();
+
+ typedef agg::span_allocator<agg::rgba8> color_span_alloc_type;
+ typedef agg::image_accessor_clip<pixfmt> image_accessor_type;
+ typedef agg::span_interpolator_linear<> interpolator_type;
+ typedef agg::span_image_filter_rgba_nn<image_accessor_type, interpolator_type>
+ image_span_gen_type;
+ typedef agg::span_converter<image_span_gen_type, span_conv_alpha> span_conv;
+
+ color_span_alloc_type sa;
+ image_accessor_type ia(pixf, agg::rgba8(0, 0, 0, 0));
+ interpolator_type interpolator(inv_mtx);
+ image_span_gen_type image_span_generator(ia, interpolator);
+ span_conv_alpha conv_alpha(alpha);
+ span_conv spans(image_span_generator, conv_alpha);
+
+ typedef agg::pixfmt_amask_adaptor<pixfmt, alpha_mask_type> pixfmt_amask_type;
+ typedef agg::renderer_base<pixfmt_amask_type> amask_ren_type;
+ typedef agg::renderer_scanline_aa<amask_ren_type, color_span_alloc_type, span_conv>
+ renderer_type_alpha;
+
+ pixfmt_amask_type pfa(pixFmt, alphaMask);
+ amask_ren_type r(pfa);
+ renderer_type_alpha ri(r, sa, spans);
+
+ theRasterizer.add_path(rect2);
+ agg::render_scanlines(theRasterizer, scanlineAlphaMask, ri);
+ } else {
+ set_clipbox(gc.cliprect, rendererBase);
+ rendererBase.blend_from(
+ pixf, 0, (int)x, (int)(height - (y + image.dim(0))), (agg::int8u)(alpha * 255));
+ }
+
+ rendererBase.reset_clipping(true);
+}
+
+template <class PathIterator,
+ class PathGenerator,
+ class TransformArray,
+ class OffsetArray,
+ class ColorArray,
+ class LineWidthArray,
+ class AntialiasedArray>
+inline void RendererAgg::_draw_path_collection_generic(GCAgg &gc,
+ agg::trans_affine master_transform,
+ const agg::rect_d &cliprect,
+ PathIterator &clippath,
+ const agg::trans_affine &clippath_trans,
+ PathGenerator &path_generator,
+ TransformArray &transforms,
+ OffsetArray &offsets,
+ const agg::trans_affine &offset_trans,
+ ColorArray &facecolors,
+ ColorArray &edgecolors,
+ LineWidthArray &linewidths,
+ DashesVector &linestyles,
+ AntialiasedArray &antialiaseds,
+ bool check_snap,
+ bool has_codes)
+{
+ typedef agg::conv_transform<typename PathGenerator::path_iterator> transformed_path_t;
+ typedef PathNanRemover<transformed_path_t> nan_removed_t;
+ typedef PathClipper<nan_removed_t> clipped_t;
+ typedef PathSnapper<clipped_t> snapped_t;
+ typedef agg::conv_curve<snapped_t> snapped_curve_t;
+ typedef agg::conv_curve<clipped_t> curve_t;
+
+ size_t Npaths = path_generator.num_paths();
+ size_t Noffsets = offsets.size();
+ size_t N = std::max(Npaths, Noffsets);
+
+ size_t Ntransforms = transforms.size();
+ size_t Nfacecolors = facecolors.size();
+ size_t Nedgecolors = edgecolors.size();
+ size_t Nlinewidths = linewidths.size();
+ size_t Nlinestyles = std::min(linestyles.size(), N);
+ size_t Naa = antialiaseds.size();
+
+ if ((Nfacecolors == 0 && Nedgecolors == 0) || Npaths == 0) {
+ return;
+ }
+
+ // Handle any clipping globally
+ theRasterizer.reset_clipping();
+ rendererBase.reset_clipping(true);
+ set_clipbox(cliprect, theRasterizer);
+ bool has_clippath = render_clippath(clippath, clippath_trans, gc.snap_mode);
+
+ // Set some defaults, assuming no face or edge
+ gc.linewidth = 0.0;
+ facepair_t face;
+ face.first = Nfacecolors != 0;
+ agg::trans_affine trans;
+ bool do_clip = !face.first && !gc.has_hatchpath();
+
+ for (int i = 0; i < (int)N; ++i) {
+ typename PathGenerator::path_iterator path = path_generator(i);
+
+ if (Ntransforms) {
+ int it = i % Ntransforms;
+ trans = agg::trans_affine(transforms(it, 0, 0),
+ transforms(it, 1, 0),
+ transforms(it, 0, 1),
+ transforms(it, 1, 1),
+ transforms(it, 0, 2),
+ transforms(it, 1, 2));
+ trans *= master_transform;
+ } else {
+ trans = master_transform;
+ }
+
+ if (Noffsets) {
+ double xo = offsets(i % Noffsets, 0);
+ double yo = offsets(i % Noffsets, 1);
+ offset_trans.transform(&xo, &yo);
+ trans *= agg::trans_affine_translation(xo, yo);
+ }
+
+ // These transformations must be done post-offsets
+ trans *= agg::trans_affine_scaling(1.0, -1.0);
+ trans *= agg::trans_affine_translation(0.0, (double)height);
+
+ if (Nfacecolors) {
+ int ic = i % Nfacecolors;
+ face.second = agg::rgba(facecolors(ic, 0), facecolors(ic, 1), facecolors(ic, 2), facecolors(ic, 3));
+ }
+
+ if (Nedgecolors) {
+ int ic = i % Nedgecolors;
+ gc.color = agg::rgba(edgecolors(ic, 0), edgecolors(ic, 1), edgecolors(ic, 2), edgecolors(ic, 3));
+
+ if (Nlinewidths) {
+ gc.linewidth = linewidths(i % Nlinewidths);
+ } else {
+ gc.linewidth = 1.0;
+ }
+ if (Nlinestyles) {
+ gc.dashes = linestyles[i % Nlinestyles];
+ }
+ }
+
+ if (check_snap) {
+ gc.isaa = antialiaseds(i % Naa);
+
+ transformed_path_t tpath(path, trans);
+ nan_removed_t nan_removed(tpath, true, has_codes);
+ clipped_t clipped(nan_removed, do_clip, width, height);
+ snapped_t snapped(
+ clipped, gc.snap_mode, path.total_vertices(), points_to_pixels(gc.linewidth));
+ if (has_codes) {
+ snapped_curve_t curve(snapped);
+ _draw_path(curve, has_clippath, face, gc);
+ } else {
+ _draw_path(snapped, has_clippath, face, gc);
+ }
+ } else {
+ gc.isaa = antialiaseds(i % Naa);
+
+ transformed_path_t tpath(path, trans);
+ nan_removed_t nan_removed(tpath, true, has_codes);
+ clipped_t clipped(nan_removed, do_clip, width, height);
+ if (has_codes) {
+ curve_t curve(clipped);
+ _draw_path(curve, has_clippath, face, gc);
+ } else {
+ _draw_path(clipped, has_clippath, face, gc);
+ }
+ }
+ }
+}
+
+template <class PathGenerator,
+ class TransformArray,
+ class OffsetArray,
+ class ColorArray,
+ class LineWidthArray,
+ class AntialiasedArray>
+inline void RendererAgg::draw_path_collection(GCAgg &gc,
+ agg::trans_affine &master_transform,
+ PathGenerator &path,
+ TransformArray &transforms,
+ OffsetArray &offsets,
+ agg::trans_affine &offset_trans,
+ ColorArray &facecolors,
+ ColorArray &edgecolors,
+ LineWidthArray &linewidths,
+ DashesVector &linestyles,
+ AntialiasedArray &antialiaseds)
+{
+ _draw_path_collection_generic(gc,
+ master_transform,
+ gc.cliprect,
+ gc.clippath.path,
+ gc.clippath.trans,
+ path,
+ transforms,
+ offsets,
+ offset_trans,
+ facecolors,
+ edgecolors,
+ linewidths,
+ linestyles,
+ antialiaseds,
+ true,
+ true);
+}
+
+template <class CoordinateArray>
+class QuadMeshGenerator
+{
+ unsigned m_meshWidth;
+ unsigned m_meshHeight;
+ CoordinateArray m_coordinates;
+
+ class QuadMeshPathIterator
+ {
+ unsigned m_iterator;
+ unsigned m_m, m_n;
+ const CoordinateArray *m_coordinates;
+
+ public:
+ QuadMeshPathIterator(unsigned m, unsigned n, const CoordinateArray *coordinates)
+ : m_iterator(0), m_m(m), m_n(n), m_coordinates(coordinates)
+ {
+ }
+
+ private:
+ inline unsigned vertex(unsigned idx, double *x, double *y)
+ {
+ size_t m = m_m + ((idx & 0x2) >> 1);
+ size_t n = m_n + (((idx + 1) & 0x2) >> 1);
+ *x = (*m_coordinates)(n, m, 0);
+ *y = (*m_coordinates)(n, m, 1);
+ return (idx) ? agg::path_cmd_line_to : agg::path_cmd_move_to;
+ }
+
+ public:
+ inline unsigned vertex(double *x, double *y)
+ {
+ if (m_iterator >= total_vertices()) {
+ return agg::path_cmd_stop;
+ }
+ return vertex(m_iterator++, x, y);
+ }
+
+ inline void rewind(unsigned path_id)
+ {
+ m_iterator = path_id;
+ }
+
+ inline unsigned total_vertices()
+ {
+ return 5;
+ }
+
+ inline bool should_simplify()
+ {
+ return false;
+ }
+ };
+
+ public:
+ typedef QuadMeshPathIterator path_iterator;
+
+ inline QuadMeshGenerator(unsigned meshWidth, unsigned meshHeight, CoordinateArray &coordinates)
+ : m_meshWidth(meshWidth), m_meshHeight(meshHeight), m_coordinates(coordinates)
+ {
+ }
+
+ inline size_t num_paths() const
+ {
+ return (size_t) m_meshWidth * m_meshHeight;
+ }
+
+ inline path_iterator operator()(size_t i) const
+ {
+ return QuadMeshPathIterator(i % m_meshWidth, i / m_meshWidth, &m_coordinates);
+ }
+};
+
+template <class CoordinateArray, class OffsetArray, class ColorArray>
+inline void RendererAgg::draw_quad_mesh(GCAgg &gc,
+ agg::trans_affine &master_transform,
+ unsigned int mesh_width,
+ unsigned int mesh_height,
+ CoordinateArray &coordinates,
+ OffsetArray &offsets,
+ agg::trans_affine &offset_trans,
+ ColorArray &facecolors,
+ bool antialiased,
+ ColorArray &edgecolors)
+{
+ QuadMeshGenerator<CoordinateArray> path_generator(mesh_width, mesh_height, coordinates);
+
+ array::empty<double> transforms;
+ array::scalar<double, 1> linewidths(gc.linewidth);
+ array::scalar<uint8_t, 1> antialiaseds(antialiased);
+ DashesVector linestyles;
+
+ _draw_path_collection_generic(gc,
+ master_transform,
+ gc.cliprect,
+ gc.clippath.path,
+ gc.clippath.trans,
+ path_generator,
+ transforms,
+ offsets,
+ offset_trans,
+ facecolors,
+ edgecolors,
+ linewidths,
+ linestyles,
+ antialiaseds,
+ true, // check_snap
+ false);
+}
+
+template <class PointArray, class ColorArray>
+inline void RendererAgg::_draw_gouraud_triangle(PointArray &points,
+ ColorArray &colors,
+ agg::trans_affine trans,
+ bool has_clippath)
+{
+ typedef agg::rgba8 color_t;
+ typedef agg::span_gouraud_rgba<color_t> span_gen_t;
+ typedef agg::span_allocator<color_t> span_alloc_t;
+
+ trans *= agg::trans_affine_scaling(1.0, -1.0);
+ trans *= agg::trans_affine_translation(0.0, (double)height);
+
+ double tpoints[3][2];
+
+ for (int i = 0; i < 3; ++i) {
+ for (int j = 0; j < 2; ++j) {
+ tpoints[i][j] = points(i, j);
+ }
+ trans.transform(&tpoints[i][0], &tpoints[i][1]);
+ if(std::isnan(tpoints[i][0]) || std::isnan(tpoints[i][1])) {
+ return;
+ }
+ }
+
+ span_alloc_t span_alloc;
+ span_gen_t span_gen;
+
+ span_gen.colors(agg::rgba(colors(0, 0), colors(0, 1), colors(0, 2), colors(0, 3)),
+ agg::rgba(colors(1, 0), colors(1, 1), colors(1, 2), colors(1, 3)),
+ agg::rgba(colors(2, 0), colors(2, 1), colors(2, 2), colors(2, 3)));
+ span_gen.triangle(tpoints[0][0],
+ tpoints[0][1],
+ tpoints[1][0],
+ tpoints[1][1],
+ tpoints[2][0],
+ tpoints[2][1],
+ 0.5);
+
+ theRasterizer.add_path(span_gen);
+
+ if (has_clippath) {
+ typedef agg::pixfmt_amask_adaptor<pixfmt, alpha_mask_type> pixfmt_amask_type;
+ typedef agg::renderer_base<pixfmt_amask_type> amask_ren_type;
+ typedef agg::renderer_scanline_aa<amask_ren_type, span_alloc_t, span_gen_t>
+ amask_aa_renderer_type;
+
+ pixfmt_amask_type pfa(pixFmt, alphaMask);
+ amask_ren_type r(pfa);
+ amask_aa_renderer_type ren(r, span_alloc, span_gen);
+ agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren);
+ } else {
+ agg::render_scanlines_aa(theRasterizer, slineP8, rendererBase, span_alloc, span_gen);
+ }
+}
+
+template <class PointArray, class ColorArray>
+inline void RendererAgg::draw_gouraud_triangle(GCAgg &gc,
+ PointArray &points,
+ ColorArray &colors,
+ agg::trans_affine &trans)
+{
+ theRasterizer.reset_clipping();
+ rendererBase.reset_clipping(true);
+ set_clipbox(gc.cliprect, theRasterizer);
+ bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans, gc.snap_mode);
+
+ _draw_gouraud_triangle(points, colors, trans, has_clippath);
+}
+
+template <class PointArray, class ColorArray>
+inline void RendererAgg::draw_gouraud_triangles(GCAgg &gc,
+ PointArray &points,
+ ColorArray &colors,
+ agg::trans_affine &trans)
+{
+ theRasterizer.reset_clipping();
+ rendererBase.reset_clipping(true);
+ set_clipbox(gc.cliprect, theRasterizer);
+ bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans, gc.snap_mode);
+
+ for (int i = 0; i < points.dim(0); ++i) {
+ typename PointArray::sub_t point = points.subarray(i);
+ typename ColorArray::sub_t color = colors.subarray(i);
+
+ _draw_gouraud_triangle(point, color, trans, has_clippath);
+ }
+}
+
+template <class R>
+void RendererAgg::set_clipbox(const agg::rect_d &cliprect, R &rasterizer)
+{
+ // set the clip rectangle from the gc
+
+ if (cliprect.x1 != 0.0 || cliprect.y1 != 0.0 || cliprect.x2 != 0.0 || cliprect.y2 != 0.0) {
+ rasterizer.clip_box(std::max(int(floor(cliprect.x1 + 0.5)), 0),
+ std::max(int(floor(height - cliprect.y1 + 0.5)), 0),
+ std::min(int(floor(cliprect.x2 + 0.5)), int(width)),
+ std::min(int(floor(height - cliprect.y2 + 0.5)), int(height)));
+ } else {
+ rasterizer.clip_box(0, 0, width, height);
+ }
+}
+
+#endif
diff --git a/contrib/python/matplotlib/py3/src/_backend_agg_basic_types.h b/contrib/python/matplotlib/py3/src/_backend_agg_basic_types.h
new file mode 100644
index 0000000000..21a84bb6a5
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/_backend_agg_basic_types.h
@@ -0,0 +1,123 @@
+#ifndef MPL_BACKEND_AGG_BASIC_TYPES_H
+#define MPL_BACKEND_AGG_BASIC_TYPES_H
+
+/* Contains some simple types from the Agg backend that are also used
+ by other modules */
+
+#include <vector>
+
+#include "agg_color_rgba.h"
+#include "agg_math_stroke.h"
+#include "path_converters.h"
+
+#include "py_adaptors.h"
+
+struct ClipPath
+{
+ py::PathIterator path;
+ agg::trans_affine trans;
+};
+
+struct SketchParams
+{
+ double scale;
+ double length;
+ double randomness;
+};
+
+class Dashes
+{
+ typedef std::vector<std::pair<double, double> > dash_t;
+ double dash_offset;
+ dash_t dashes;
+
+ public:
+ double get_dash_offset() const
+ {
+ return dash_offset;
+ }
+ void set_dash_offset(double x)
+ {
+ dash_offset = x;
+ }
+ void add_dash_pair(double length, double skip)
+ {
+ dashes.push_back(std::make_pair(length, skip));
+ }
+ size_t size() const
+ {
+ return dashes.size();
+ }
+
+ template <class T>
+ void dash_to_stroke(T &stroke, double dpi, bool isaa)
+ {
+ double scaleddpi = dpi / 72.0;
+ for (dash_t::const_iterator i = dashes.begin(); i != dashes.end(); ++i) {
+ double val0 = i->first;
+ double val1 = i->second;
+ val0 = val0 * scaleddpi;
+ val1 = val1 * scaleddpi;
+ if (!isaa) {
+ val0 = (int)val0 + 0.5;
+ val1 = (int)val1 + 0.5;
+ }
+ stroke.add_dash(val0, val1);
+ }
+ stroke.dash_start(get_dash_offset() * scaleddpi);
+ }
+};
+
+typedef std::vector<Dashes> DashesVector;
+
+class GCAgg
+{
+ public:
+ GCAgg()
+ : linewidth(1.0),
+ alpha(1.0),
+ cap(agg::butt_cap),
+ join(agg::round_join),
+ snap_mode(SNAP_FALSE)
+ {
+ }
+
+ ~GCAgg()
+ {
+ }
+
+ double linewidth;
+ double alpha;
+ bool forced_alpha;
+ agg::rgba color;
+ bool isaa;
+
+ agg::line_cap_e cap;
+ agg::line_join_e join;
+
+ agg::rect_d cliprect;
+
+ ClipPath clippath;
+
+ Dashes dashes;
+
+ e_snap_mode snap_mode;
+
+ py::PathIterator hatchpath;
+ agg::rgba hatch_color;
+ double hatch_linewidth;
+
+ SketchParams sketch;
+
+ bool has_hatchpath()
+ {
+ return hatchpath.total_vertices() != 0;
+ }
+
+ private:
+ // prevent copying
+ GCAgg(const GCAgg &);
+ GCAgg &operator=(const GCAgg &);
+};
+
+#endif
diff --git a/contrib/python/matplotlib/py3/src/_backend_agg_wrapper.cpp b/contrib/python/matplotlib/py3/src/_backend_agg_wrapper.cpp
new file mode 100644
index 0000000000..ee69729be7
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/_backend_agg_wrapper.cpp
@@ -0,0 +1,646 @@
+#include "mplutils.h"
+#include "numpy_cpp.h"
+#include "py_converters.h"
+#include "_backend_agg.h"
+
+typedef struct
+{
+ PyObject_HEAD
+ RendererAgg *x;
+ Py_ssize_t shape[3];
+ Py_ssize_t strides[3];
+ Py_ssize_t suboffsets[3];
+} PyRendererAgg;
+
+static PyTypeObject PyRendererAggType;
+
+typedef struct
+{
+ PyObject_HEAD
+ BufferRegion *x;
+ Py_ssize_t shape[3];
+ Py_ssize_t strides[3];
+ Py_ssize_t suboffsets[3];
+} PyBufferRegion;
+
+static PyTypeObject PyBufferRegionType;
+
+
+/**********************************************************************
+ * BufferRegion
+ * */
+
+static PyObject *PyBufferRegion_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ PyBufferRegion *self;
+ self = (PyBufferRegion *)type->tp_alloc(type, 0);
+ self->x = NULL;
+ return (PyObject *)self;
+}
+
+static void PyBufferRegion_dealloc(PyBufferRegion *self)
+{
+ delete self->x;
+ Py_TYPE(self)->tp_free((PyObject *)self);
+}
+
+static PyObject *PyBufferRegion_to_string(PyBufferRegion *self, PyObject *args)
+{
+ char const* msg =
+ "BufferRegion.to_string is deprecated since Matplotlib 3.7 and will "
+ "be removed two minor releases later; use np.asarray(region) instead.";
+ if (PyErr_WarnEx(PyExc_DeprecationWarning, msg, 1)) {
+ return NULL;
+ }
+ return PyBytes_FromStringAndSize((const char *)self->x->get_data(),
+ (Py_ssize_t) self->x->get_height() * self->x->get_stride());
+}
+
+/* TODO: This doesn't seem to be used internally. Remove? */
+
+static PyObject *PyBufferRegion_set_x(PyBufferRegion *self, PyObject *args)
+{
+ int x;
+ if (!PyArg_ParseTuple(args, "i:set_x", &x)) {
+ return NULL;
+ }
+ self->x->get_rect().x1 = x;
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *PyBufferRegion_set_y(PyBufferRegion *self, PyObject *args)
+{
+ int y;
+ if (!PyArg_ParseTuple(args, "i:set_y", &y)) {
+ return NULL;
+ }
+ self->x->get_rect().y1 = y;
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *PyBufferRegion_get_extents(PyBufferRegion *self, PyObject *args)
+{
+ agg::rect_i rect = self->x->get_rect();
+
+ return Py_BuildValue("IIII", rect.x1, rect.y1, rect.x2, rect.y2);
+}
+
+static PyObject *PyBufferRegion_to_string_argb(PyBufferRegion *self, PyObject *args)
+{
+ char const* msg =
+ "BufferRegion.to_string_argb is deprecated since Matplotlib 3.7 and "
+ "will be removed two minor releases later; use "
+ "np.take(region, [2, 1, 0, 3], axis=2) instead.";
+ if (PyErr_WarnEx(PyExc_DeprecationWarning, msg, 1)) {
+ return NULL;
+ }
+ PyObject *bufobj;
+ uint8_t *buf;
+ Py_ssize_t height, stride;
+ height = self->x->get_height();
+ stride = self->x->get_stride();
+ bufobj = PyBytes_FromStringAndSize(NULL, height * stride);
+ buf = (uint8_t *)PyBytes_AS_STRING(bufobj);
+
+ CALL_CPP_CLEANUP("to_string_argb", (self->x->to_string_argb(buf)), Py_DECREF(bufobj));
+
+ return bufobj;
+}
+
+int PyBufferRegion_get_buffer(PyBufferRegion *self, Py_buffer *buf, int flags)
+{
+ Py_INCREF(self);
+ buf->obj = (PyObject *)self;
+ buf->buf = self->x->get_data();
+ buf->len = (Py_ssize_t)self->x->get_width() * (Py_ssize_t)self->x->get_height() * 4;
+ buf->readonly = 0;
+ buf->format = (char *)"B";
+ buf->ndim = 3;
+ self->shape[0] = self->x->get_height();
+ self->shape[1] = self->x->get_width();
+ self->shape[2] = 4;
+ buf->shape = self->shape;
+ self->strides[0] = self->x->get_width() * 4;
+ self->strides[1] = 4;
+ self->strides[2] = 1;
+ buf->strides = self->strides;
+ buf->suboffsets = NULL;
+ buf->itemsize = 1;
+ buf->internal = NULL;
+
+ return 1;
+}
+
+static PyTypeObject *PyBufferRegion_init_type()
+{
+ static PyMethodDef methods[] = {
+ { "to_string", (PyCFunction)PyBufferRegion_to_string, METH_NOARGS, NULL },
+ { "to_string_argb", (PyCFunction)PyBufferRegion_to_string_argb, METH_NOARGS, NULL },
+ { "set_x", (PyCFunction)PyBufferRegion_set_x, METH_VARARGS, NULL },
+ { "set_y", (PyCFunction)PyBufferRegion_set_y, METH_VARARGS, NULL },
+ { "get_extents", (PyCFunction)PyBufferRegion_get_extents, METH_NOARGS, NULL },
+ { NULL }
+ };
+
+ static PyBufferProcs buffer_procs;
+ buffer_procs.bf_getbuffer = (getbufferproc)PyBufferRegion_get_buffer;
+
+ PyBufferRegionType.tp_name = "matplotlib.backends._backend_agg.BufferRegion";
+ PyBufferRegionType.tp_basicsize = sizeof(PyBufferRegion);
+ PyBufferRegionType.tp_dealloc = (destructor)PyBufferRegion_dealloc;
+ PyBufferRegionType.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
+ PyBufferRegionType.tp_methods = methods;
+ PyBufferRegionType.tp_new = PyBufferRegion_new;
+ PyBufferRegionType.tp_as_buffer = &buffer_procs;
+
+ return &PyBufferRegionType;
+}
+
+/**********************************************************************
+ * RendererAgg
+ * */
+
+static PyObject *PyRendererAgg_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ PyRendererAgg *self;
+ self = (PyRendererAgg *)type->tp_alloc(type, 0);
+ self->x = NULL;
+ return (PyObject *)self;
+}
+
+static int PyRendererAgg_init(PyRendererAgg *self, PyObject *args, PyObject *kwds)
+{
+ unsigned int width;
+ unsigned int height;
+ double dpi;
+ int debug = 0;
+
+ if (!PyArg_ParseTuple(args, "IId|i:RendererAgg", &width, &height, &dpi, &debug)) {
+ return -1;
+ }
+
+ if (dpi <= 0.0) {
+ PyErr_SetString(PyExc_ValueError, "dpi must be positive");
+ return -1;
+ }
+
+ if (width >= 1 << 16 || height >= 1 << 16) {
+ PyErr_Format(
+ PyExc_ValueError,
+ "Image size of %dx%d pixels is too large. "
+ "It must be less than 2^16 in each direction.",
+ width, height);
+ return -1;
+ }
+
+ CALL_CPP_INIT("RendererAgg", self->x = new RendererAgg(width, height, dpi))
+
+ return 0;
+}
+
+static void PyRendererAgg_dealloc(PyRendererAgg *self)
+{
+ delete self->x;
+ Py_TYPE(self)->tp_free((PyObject *)self);
+}
+
+static PyObject *PyRendererAgg_draw_path(PyRendererAgg *self, PyObject *args)
+{
+ GCAgg gc;
+ py::PathIterator path;
+ agg::trans_affine trans;
+ PyObject *faceobj = NULL;
+ agg::rgba face;
+
+ if (!PyArg_ParseTuple(args,
+ "O&O&O&|O:draw_path",
+ &convert_gcagg,
+ &gc,
+ &convert_path,
+ &path,
+ &convert_trans_affine,
+ &trans,
+ &faceobj)) {
+ return NULL;
+ }
+
+ if (!convert_face(faceobj, gc, &face)) {
+ return NULL;
+ }
+
+ CALL_CPP("draw_path", (self->x->draw_path(gc, path, trans, face)));
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *PyRendererAgg_draw_text_image(PyRendererAgg *self, PyObject *args)
+{
+ numpy::array_view<agg::int8u, 2> image;
+ double x;
+ double y;
+ double angle;
+ GCAgg gc;
+
+ if (!PyArg_ParseTuple(args,
+ "O&dddO&:draw_text_image",
+ &image.converter_contiguous,
+ &image,
+ &x,
+ &y,
+ &angle,
+ &convert_gcagg,
+ &gc)) {
+ return NULL;
+ }
+
+ CALL_CPP("draw_text_image", (self->x->draw_text_image(gc, image, x, y, angle)));
+
+ Py_RETURN_NONE;
+}
+
+PyObject *PyRendererAgg_draw_markers(PyRendererAgg *self, PyObject *args)
+{
+ GCAgg gc;
+ py::PathIterator marker_path;
+ agg::trans_affine marker_path_trans;
+ py::PathIterator path;
+ agg::trans_affine trans;
+ PyObject *faceobj = NULL;
+ agg::rgba face;
+
+ if (!PyArg_ParseTuple(args,
+ "O&O&O&O&O&|O:draw_markers",
+ &convert_gcagg,
+ &gc,
+ &convert_path,
+ &marker_path,
+ &convert_trans_affine,
+ &marker_path_trans,
+ &convert_path,
+ &path,
+ &convert_trans_affine,
+ &trans,
+ &faceobj)) {
+ return NULL;
+ }
+
+ if (!convert_face(faceobj, gc, &face)) {
+ return NULL;
+ }
+
+ CALL_CPP("draw_markers",
+ (self->x->draw_markers(gc, marker_path, marker_path_trans, path, trans, face)));
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *PyRendererAgg_draw_image(PyRendererAgg *self, PyObject *args)
+{
+ GCAgg gc;
+ double x;
+ double y;
+ numpy::array_view<agg::int8u, 3> image;
+
+ if (!PyArg_ParseTuple(args,
+ "O&ddO&:draw_image",
+ &convert_gcagg,
+ &gc,
+ &x,
+ &y,
+ &image.converter_contiguous,
+ &image)) {
+ return NULL;
+ }
+
+ x = mpl_round(x);
+ y = mpl_round(y);
+
+ gc.alpha = 1.0;
+ CALL_CPP("draw_image", (self->x->draw_image(gc, x, y, image)));
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+PyRendererAgg_draw_path_collection(PyRendererAgg *self, PyObject *args)
+{
+ GCAgg gc;
+ agg::trans_affine master_transform;
+ py::PathGenerator paths;
+ numpy::array_view<const double, 3> transforms;
+ numpy::array_view<const double, 2> offsets;
+ agg::trans_affine offset_trans;
+ numpy::array_view<const double, 2> facecolors;
+ numpy::array_view<const double, 2> edgecolors;
+ numpy::array_view<const double, 1> linewidths;
+ DashesVector dashes;
+ numpy::array_view<const uint8_t, 1> antialiaseds;
+ PyObject *ignored;
+ PyObject *offset_position; // offset position is no longer used
+
+ if (!PyArg_ParseTuple(args,
+ "O&O&O&O&O&O&O&O&O&O&O&OO:draw_path_collection",
+ &convert_gcagg,
+ &gc,
+ &convert_trans_affine,
+ &master_transform,
+ &convert_pathgen,
+ &paths,
+ &convert_transforms,
+ &transforms,
+ &convert_points,
+ &offsets,
+ &convert_trans_affine,
+ &offset_trans,
+ &convert_colors,
+ &facecolors,
+ &convert_colors,
+ &edgecolors,
+ &linewidths.converter,
+ &linewidths,
+ &convert_dashes_vector,
+ &dashes,
+ &antialiaseds.converter,
+ &antialiaseds,
+ &ignored,
+ &offset_position)) {
+ return NULL;
+ }
+
+ CALL_CPP("draw_path_collection",
+ (self->x->draw_path_collection(gc,
+ master_transform,
+ paths,
+ transforms,
+ offsets,
+ offset_trans,
+ facecolors,
+ edgecolors,
+ linewidths,
+ dashes,
+ antialiaseds)));
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *PyRendererAgg_draw_quad_mesh(PyRendererAgg *self, PyObject *args)
+{
+ GCAgg gc;
+ agg::trans_affine master_transform;
+ unsigned int mesh_width;
+ unsigned int mesh_height;
+ numpy::array_view<const double, 3> coordinates;
+ numpy::array_view<const double, 2> offsets;
+ agg::trans_affine offset_trans;
+ numpy::array_view<const double, 2> facecolors;
+ bool antialiased;
+ numpy::array_view<const double, 2> edgecolors;
+
+ if (!PyArg_ParseTuple(args,
+ "O&O&IIO&O&O&O&O&O&:draw_quad_mesh",
+ &convert_gcagg,
+ &gc,
+ &convert_trans_affine,
+ &master_transform,
+ &mesh_width,
+ &mesh_height,
+ &coordinates.converter,
+ &coordinates,
+ &convert_points,
+ &offsets,
+ &convert_trans_affine,
+ &offset_trans,
+ &convert_colors,
+ &facecolors,
+ &convert_bool,
+ &antialiased,
+ &convert_colors,
+ &edgecolors)) {
+ return NULL;
+ }
+
+ CALL_CPP("draw_quad_mesh",
+ (self->x->draw_quad_mesh(gc,
+ master_transform,
+ mesh_width,
+ mesh_height,
+ coordinates,
+ offsets,
+ offset_trans,
+ facecolors,
+ antialiased,
+ edgecolors)));
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+PyRendererAgg_draw_gouraud_triangle(PyRendererAgg *self, PyObject *args)
+{
+ GCAgg gc;
+ numpy::array_view<const double, 2> points;
+ numpy::array_view<const double, 2> colors;
+ agg::trans_affine trans;
+
+ if (!PyArg_ParseTuple(args,
+ "O&O&O&O&|O:draw_gouraud_triangle",
+ &convert_gcagg,
+ &gc,
+ &points.converter,
+ &points,
+ &colors.converter,
+ &colors,
+ &convert_trans_affine,
+ &trans)) {
+ return NULL;
+ }
+
+ if (points.dim(0) != 3 || points.dim(1) != 2) {
+ PyErr_Format(PyExc_ValueError,
+ "points must have shape (3, 2), "
+ "got (%" NPY_INTP_FMT ", %" NPY_INTP_FMT ")",
+ points.dim(0), points.dim(1));
+ return NULL;
+ }
+
+ if (colors.dim(0) != 3 || colors.dim(1) != 4) {
+ PyErr_Format(PyExc_ValueError,
+ "colors must have shape (3, 4), "
+ "got (%" NPY_INTP_FMT ", %" NPY_INTP_FMT ")",
+ colors.dim(0), colors.dim(1));
+ return NULL;
+ }
+
+
+ CALL_CPP("draw_gouraud_triangle", (self->x->draw_gouraud_triangle(gc, points, colors, trans)));
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+PyRendererAgg_draw_gouraud_triangles(PyRendererAgg *self, PyObject *args)
+{
+ GCAgg gc;
+ numpy::array_view<const double, 3> points;
+ numpy::array_view<const double, 3> colors;
+ agg::trans_affine trans;
+
+ if (!PyArg_ParseTuple(args,
+ "O&O&O&O&|O:draw_gouraud_triangles",
+ &convert_gcagg,
+ &gc,
+ &points.converter,
+ &points,
+ &colors.converter,
+ &colors,
+ &convert_trans_affine,
+ &trans)) {
+ return NULL;
+ }
+ if (points.size() && !check_trailing_shape(points, "points", 3, 2)) {
+ return NULL;
+ }
+ if (colors.size() && !check_trailing_shape(colors, "colors", 3, 4)) {
+ return NULL;
+ }
+ if (points.size() != colors.size()) {
+ PyErr_Format(PyExc_ValueError,
+ "points and colors arrays must be the same length, got "
+ "%" NPY_INTP_FMT " points and %" NPY_INTP_FMT "colors",
+ points.dim(0), colors.dim(0));
+ return NULL;
+ }
+
+ CALL_CPP("draw_gouraud_triangles", self->x->draw_gouraud_triangles(gc, points, colors, trans));
+
+ Py_RETURN_NONE;
+}
+
+int PyRendererAgg_get_buffer(PyRendererAgg *self, Py_buffer *buf, int flags)
+{
+ Py_INCREF(self);
+ buf->obj = (PyObject *)self;
+ buf->buf = self->x->pixBuffer;
+ buf->len = (Py_ssize_t)self->x->get_width() * (Py_ssize_t)self->x->get_height() * 4;
+ buf->readonly = 0;
+ buf->format = (char *)"B";
+ buf->ndim = 3;
+ self->shape[0] = self->x->get_height();
+ self->shape[1] = self->x->get_width();
+ self->shape[2] = 4;
+ buf->shape = self->shape;
+ self->strides[0] = self->x->get_width() * 4;
+ self->strides[1] = 4;
+ self->strides[2] = 1;
+ buf->strides = self->strides;
+ buf->suboffsets = NULL;
+ buf->itemsize = 1;
+ buf->internal = NULL;
+
+ return 1;
+}
+
+static PyObject *PyRendererAgg_clear(PyRendererAgg *self, PyObject *args)
+{
+ CALL_CPP("clear", self->x->clear());
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *PyRendererAgg_copy_from_bbox(PyRendererAgg *self, PyObject *args)
+{
+ agg::rect_d bbox;
+ BufferRegion *reg;
+ PyObject *regobj;
+
+ if (!PyArg_ParseTuple(args, "O&:copy_from_bbox", &convert_rect, &bbox)) {
+ return 0;
+ }
+
+ CALL_CPP("copy_from_bbox", (reg = self->x->copy_from_bbox(bbox)));
+
+ regobj = PyBufferRegion_new(&PyBufferRegionType, NULL, NULL);
+ ((PyBufferRegion *)regobj)->x = reg;
+
+ return regobj;
+}
+
+static PyObject *PyRendererAgg_restore_region(PyRendererAgg *self, PyObject *args)
+{
+ PyBufferRegion *regobj;
+ int xx1 = 0, yy1 = 0, xx2 = 0, yy2 = 0, x = 0, y = 0;
+
+ if (!PyArg_ParseTuple(args,
+ "O!|iiiiii:restore_region",
+ &PyBufferRegionType,
+ &regobj,
+ &xx1,
+ &yy1,
+ &xx2,
+ &yy2,
+ &x,
+ &y)) {
+ return 0;
+ }
+
+ if (PySequence_Size(args) == 1) {
+ CALL_CPP("restore_region", self->x->restore_region(*(regobj->x)));
+ } else {
+ CALL_CPP("restore_region", self->x->restore_region(*(regobj->x), xx1, yy1, xx2, yy2, x, y));
+ }
+
+ Py_RETURN_NONE;
+}
+
+static PyTypeObject *PyRendererAgg_init_type()
+{
+ static PyMethodDef methods[] = {
+ {"draw_path", (PyCFunction)PyRendererAgg_draw_path, METH_VARARGS, NULL},
+ {"draw_markers", (PyCFunction)PyRendererAgg_draw_markers, METH_VARARGS, NULL},
+ {"draw_text_image", (PyCFunction)PyRendererAgg_draw_text_image, METH_VARARGS, NULL},
+ {"draw_image", (PyCFunction)PyRendererAgg_draw_image, METH_VARARGS, NULL},
+ {"draw_path_collection", (PyCFunction)PyRendererAgg_draw_path_collection, METH_VARARGS, NULL},
+ {"draw_quad_mesh", (PyCFunction)PyRendererAgg_draw_quad_mesh, METH_VARARGS, NULL},
+ {"draw_gouraud_triangle", (PyCFunction)PyRendererAgg_draw_gouraud_triangle, METH_VARARGS, NULL},
+ {"draw_gouraud_triangles", (PyCFunction)PyRendererAgg_draw_gouraud_triangles, METH_VARARGS, NULL},
+
+ {"clear", (PyCFunction)PyRendererAgg_clear, METH_NOARGS, NULL},
+
+ {"copy_from_bbox", (PyCFunction)PyRendererAgg_copy_from_bbox, METH_VARARGS, NULL},
+ {"restore_region", (PyCFunction)PyRendererAgg_restore_region, METH_VARARGS, NULL},
+ {NULL}
+ };
+
+ static PyBufferProcs buffer_procs;
+ buffer_procs.bf_getbuffer = (getbufferproc)PyRendererAgg_get_buffer;
+
+ PyRendererAggType.tp_name = "matplotlib.backends._backend_agg.RendererAgg";
+ PyRendererAggType.tp_basicsize = sizeof(PyRendererAgg);
+ PyRendererAggType.tp_dealloc = (destructor)PyRendererAgg_dealloc;
+ PyRendererAggType.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
+ PyRendererAggType.tp_methods = methods;
+ PyRendererAggType.tp_init = (initproc)PyRendererAgg_init;
+ PyRendererAggType.tp_new = PyRendererAgg_new;
+ PyRendererAggType.tp_as_buffer = &buffer_procs;
+
+ return &PyRendererAggType;
+}
+
+static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "_backend_agg" };
+
+PyMODINIT_FUNC PyInit__backend_agg(void)
+{
+ import_array();
+ PyObject *m;
+ if (!(m = PyModule_Create(&moduledef))
+ || prepare_and_add_type(PyRendererAgg_init_type(), m)
+ // BufferRegion is not constructible from Python, thus not added to the module.
+ || PyType_Ready(PyBufferRegion_init_type())
+ ) {
+ Py_XDECREF(m);
+ return NULL;
+ }
+ return m;
+}
diff --git a/contrib/python/matplotlib/py3/src/_c_internal_utils.c b/contrib/python/matplotlib/py3/src/_c_internal_utils.c
new file mode 100644
index 0000000000..f1bd22a42c
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/_c_internal_utils.c
@@ -0,0 +1,211 @@
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+#ifdef __linux__
+#include <dlfcn.h>
+#endif
+#ifdef _WIN32
+#include <Objbase.h>
+#include <Shobjidl.h>
+#include <Windows.h>
+#endif
+
+static PyObject*
+mpl_display_is_valid(PyObject* module)
+{
+#ifdef __linux__
+ void* libX11;
+ // The getenv check is redundant but helps performance as it is much faster
+ // than dlopen().
+ if (getenv("DISPLAY")
+ && (libX11 = dlopen("libX11.so.6", RTLD_LAZY))) {
+ struct Display* display = NULL;
+ struct Display* (* XOpenDisplay)(char const*) =
+ dlsym(libX11, "XOpenDisplay");
+ int (* XCloseDisplay)(struct Display*) =
+ dlsym(libX11, "XCloseDisplay");
+ if (XOpenDisplay && XCloseDisplay
+ && (display = XOpenDisplay(NULL))) {
+ XCloseDisplay(display);
+ }
+ if (dlclose(libX11)) {
+ PyErr_SetString(PyExc_RuntimeError, dlerror());
+ return NULL;
+ }
+ if (display) {
+ Py_RETURN_TRUE;
+ }
+ }
+ void* libwayland_client;
+ if (getenv("WAYLAND_DISPLAY")
+ && (libwayland_client = dlopen("libwayland-client.so.0", RTLD_LAZY))) {
+ struct wl_display* display = NULL;
+ struct wl_display* (* wl_display_connect)(char const*) =
+ dlsym(libwayland_client, "wl_display_connect");
+ void (* wl_display_disconnect)(struct wl_display*) =
+ dlsym(libwayland_client, "wl_display_disconnect");
+ if (wl_display_connect && wl_display_disconnect
+ && (display = wl_display_connect(NULL))) {
+ wl_display_disconnect(display);
+ }
+ if (dlclose(libwayland_client)) {
+ PyErr_SetString(PyExc_RuntimeError, dlerror());
+ return NULL;
+ }
+ if (display) {
+ Py_RETURN_TRUE;
+ }
+ }
+ Py_RETURN_FALSE;
+#else
+ Py_RETURN_TRUE;
+#endif
+}
+
+static PyObject*
+mpl_GetCurrentProcessExplicitAppUserModelID(PyObject* module)
+{
+#ifdef _WIN32
+ wchar_t* appid = NULL;
+ HRESULT hr = GetCurrentProcessExplicitAppUserModelID(&appid);
+ if (FAILED(hr)) {
+ return PyErr_SetFromWindowsErr(hr);
+ }
+ PyObject* py_appid = PyUnicode_FromWideChar(appid, -1);
+ CoTaskMemFree(appid);
+ return py_appid;
+#else
+ Py_RETURN_NONE;
+#endif
+}
+
+static PyObject*
+mpl_SetCurrentProcessExplicitAppUserModelID(PyObject* module, PyObject* arg)
+{
+#ifdef _WIN32
+ wchar_t* appid = PyUnicode_AsWideCharString(arg, NULL);
+ if (!appid) {
+ return NULL;
+ }
+ HRESULT hr = SetCurrentProcessExplicitAppUserModelID(appid);
+ PyMem_Free(appid);
+ if (FAILED(hr)) {
+ return PyErr_SetFromWindowsErr(hr);
+ }
+ Py_RETURN_NONE;
+#else
+ Py_RETURN_NONE;
+#endif
+}
+
+static PyObject*
+mpl_GetForegroundWindow(PyObject* module)
+{
+#ifdef _WIN32
+ return PyLong_FromVoidPtr(GetForegroundWindow());
+#else
+ Py_RETURN_NONE;
+#endif
+}
+
+static PyObject*
+mpl_SetForegroundWindow(PyObject* module, PyObject *arg)
+{
+#ifdef _WIN32
+ HWND handle = PyLong_AsVoidPtr(arg);
+ if (PyErr_Occurred()) {
+ return NULL;
+ }
+ if (!SetForegroundWindow(handle)) {
+ return PyErr_Format(PyExc_RuntimeError, "Error setting window");
+ }
+ Py_RETURN_NONE;
+#else
+ Py_RETURN_NONE;
+#endif
+}
+
+static PyObject*
+mpl_SetProcessDpiAwareness_max(PyObject* module)
+{
+#ifdef _WIN32
+#ifdef _DPI_AWARENESS_CONTEXTS_
+ // These functions and options were added in later Windows 10 updates, so
+ // must be loaded dynamically.
+ typedef BOOL (WINAPI *IsValidDpiAwarenessContext_t)(DPI_AWARENESS_CONTEXT);
+ typedef BOOL (WINAPI *SetProcessDpiAwarenessContext_t)(DPI_AWARENESS_CONTEXT);
+
+ HMODULE user32 = LoadLibrary("user32.dll");
+ IsValidDpiAwarenessContext_t IsValidDpiAwarenessContextPtr =
+ (IsValidDpiAwarenessContext_t)GetProcAddress(
+ user32, "IsValidDpiAwarenessContext");
+ SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContextPtr =
+ (SetProcessDpiAwarenessContext_t)GetProcAddress(
+ user32, "SetProcessDpiAwarenessContext");
+ DPI_AWARENESS_CONTEXT ctxs[3] = {
+ DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2, // Win10 Creators Update
+ DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, // Win10
+ DPI_AWARENESS_CONTEXT_SYSTEM_AWARE}; // Win10
+ if (IsValidDpiAwarenessContextPtr != NULL
+ && SetProcessDpiAwarenessContextPtr != NULL) {
+ for (int i = 0; i < sizeof(ctxs) / sizeof(DPI_AWARENESS_CONTEXT); ++i) {
+ if (IsValidDpiAwarenessContextPtr(ctxs[i])) {
+ SetProcessDpiAwarenessContextPtr(ctxs[i]);
+ break;
+ }
+ }
+ } else {
+ // Added in Windows Vista.
+ SetProcessDPIAware();
+ }
+ FreeLibrary(user32);
+#else
+ // Added in Windows Vista.
+ SetProcessDPIAware();
+#endif
+#endif
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef functions[] = {
+ {"display_is_valid", (PyCFunction)mpl_display_is_valid, METH_NOARGS,
+ "display_is_valid()\n--\n\n"
+ "Check whether the current X11 or Wayland display is valid.\n\n"
+ "On Linux, returns True if either $DISPLAY is set and XOpenDisplay(NULL)\n"
+ "succeeds, or $WAYLAND_DISPLAY is set and wl_display_connect(NULL)\n"
+ "succeeds.\n\n"
+ "On other platforms, always returns True."},
+ {"Win32_GetCurrentProcessExplicitAppUserModelID",
+ (PyCFunction)mpl_GetCurrentProcessExplicitAppUserModelID, METH_NOARGS,
+ "Win32_GetCurrentProcessExplicitAppUserModelID()\n--\n\n"
+ "Wrapper for Windows's GetCurrentProcessExplicitAppUserModelID.\n\n"
+ "On non-Windows platforms, always returns None."},
+ {"Win32_SetCurrentProcessExplicitAppUserModelID",
+ (PyCFunction)mpl_SetCurrentProcessExplicitAppUserModelID, METH_O,
+ "Win32_SetCurrentProcessExplicitAppUserModelID(appid, /)\n--\n\n"
+ "Wrapper for Windows's SetCurrentProcessExplicitAppUserModelID.\n\n"
+ "On non-Windows platforms, does nothing."},
+ {"Win32_GetForegroundWindow",
+ (PyCFunction)mpl_GetForegroundWindow, METH_NOARGS,
+ "Win32_GetForegroundWindow()\n--\n\n"
+ "Wrapper for Windows' GetForegroundWindow.\n\n"
+ "On non-Windows platforms, always returns None."},
+ {"Win32_SetForegroundWindow",
+ (PyCFunction)mpl_SetForegroundWindow, METH_O,
+ "Win32_SetForegroundWindow(hwnd, /)\n--\n\n"
+ "Wrapper for Windows' SetForegroundWindow.\n\n"
+ "On non-Windows platforms, does nothing."},
+ {"Win32_SetProcessDpiAwareness_max",
+ (PyCFunction)mpl_SetProcessDpiAwareness_max, METH_NOARGS,
+ "Win32_SetProcessDpiAwareness_max()\n--\n\n"
+ "Set Windows' process DPI awareness to best option available.\n\n"
+ "On non-Windows platforms, does nothing."},
+ {NULL, NULL}}; // sentinel.
+static PyModuleDef util_module = {
+ PyModuleDef_HEAD_INIT, "_c_internal_utils", NULL, 0, functions
+};
+
+#pragma GCC visibility push(default)
+PyMODINIT_FUNC PyInit__c_internal_utils(void)
+{
+ return PyModule_Create(&util_module);
+}
diff --git a/contrib/python/matplotlib/py3/src/_image_resample.h b/contrib/python/matplotlib/py3/src/_image_resample.h
new file mode 100644
index 0000000000..10763fb01d
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/_image_resample.h
@@ -0,0 +1,832 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+#ifndef MPL_RESAMPLE_H
+#define MPL_RESAMPLE_H
+
+#include "agg_image_accessors.h"
+#include "agg_path_storage.h"
+#include "agg_pixfmt_gray.h"
+#include "agg_pixfmt_rgb.h"
+#include "agg_pixfmt_rgba.h"
+#include "agg_renderer_base.h"
+#include "agg_renderer_scanline.h"
+#include "agg_rasterizer_scanline_aa.h"
+#include "agg_scanline_u.h"
+#include "agg_span_allocator.h"
+#include "agg_span_converter.h"
+#include "agg_span_image_filter_gray.h"
+#include "agg_span_image_filter_rgba.h"
+#include "agg_span_interpolator_adaptor.h"
+#include "agg_span_interpolator_linear.h"
+
+#include "agg_workaround.h"
+
+// Based on:
+
+//----------------------------------------------------------------------------
+// Anti-Grain Geometry - Version 2.4
+// Copyright (C) 2002-2005 Maxim Shemanarev (http://antigrain.com/)
+//
+// Permission to copy, use, modify, sell and distribute this software
+// is granted provided this copyright notice appears in all copies.
+// This software is provided "as is" without express or implied
+// warranty, and with no claim as to its suitability for any purpose.
+//
+//----------------------------------------------------------------------------
+// Contact: mcseem@antigrain.com
+// mcseemagg@yahoo.com
+// http://antigrain.com/
+//----------------------------------------------------------------------------
+//
+// Adaptation for high precision colors has been sponsored by
+// Liberty Technology Systems, Inc., visit http://lib-sys.com
+//
+// Liberty Technology Systems, Inc. is the provider of
+// PostScript and PDF technology for software developers.
+//
+
+//===================================================================gray64
+namespace agg
+{
+ struct gray64
+ {
+ typedef double value_type;
+ typedef double calc_type;
+ typedef double long_type;
+ typedef gray64 self_type;
+
+ value_type v;
+ value_type a;
+
+ //--------------------------------------------------------------------
+ gray64() {}
+
+ //--------------------------------------------------------------------
+ explicit gray64(value_type v_, value_type a_ = 1) :
+ v(v_), a(a_) {}
+
+ //--------------------------------------------------------------------
+ gray64(const self_type& c, value_type a_) :
+ v(c.v), a(a_) {}
+
+ //--------------------------------------------------------------------
+ gray64(const gray64& c) :
+ v(c.v),
+ a(c.a) {}
+
+ //--------------------------------------------------------------------
+ static AGG_INLINE double to_double(value_type a)
+ {
+ return a;
+ }
+
+ //--------------------------------------------------------------------
+ static AGG_INLINE value_type from_double(double a)
+ {
+ return value_type(a);
+ }
+
+ //--------------------------------------------------------------------
+ static AGG_INLINE value_type empty_value()
+ {
+ return 0;
+ }
+
+ //--------------------------------------------------------------------
+ static AGG_INLINE value_type full_value()
+ {
+ return 1;
+ }
+
+ //--------------------------------------------------------------------
+ AGG_INLINE bool is_transparent() const
+ {
+ return a <= 0;
+ }
+
+ //--------------------------------------------------------------------
+ AGG_INLINE bool is_opaque() const
+ {
+ return a >= 1;
+ }
+
+ //--------------------------------------------------------------------
+ static AGG_INLINE value_type invert(value_type x)
+ {
+ return 1 - x;
+ }
+
+ //--------------------------------------------------------------------
+ static AGG_INLINE value_type multiply(value_type a, value_type b)
+ {
+ return value_type(a * b);
+ }
+
+ //--------------------------------------------------------------------
+ static AGG_INLINE value_type demultiply(value_type a, value_type b)
+ {
+ return (b == 0) ? 0 : value_type(a / b);
+ }
+
+ //--------------------------------------------------------------------
+ template<typename T>
+ static AGG_INLINE T downscale(T a)
+ {
+ return a;
+ }
+
+ //--------------------------------------------------------------------
+ template<typename T>
+ static AGG_INLINE T downshift(T a, unsigned n)
+ {
+ return n > 0 ? a / (1 << n) : a;
+ }
+
+ //--------------------------------------------------------------------
+ static AGG_INLINE value_type mult_cover(value_type a, cover_type b)
+ {
+ return value_type(a * b / cover_mask);
+ }
+
+ //--------------------------------------------------------------------
+ static AGG_INLINE cover_type scale_cover(cover_type a, value_type b)
+ {
+ return cover_type(uround(a * b));
+ }
+
+ //--------------------------------------------------------------------
+ // Interpolate p to q by a, assuming q is premultiplied by a.
+ static AGG_INLINE value_type prelerp(value_type p, value_type q, value_type a)
+ {
+ return (1 - a) * p + q; // more accurate than "p + q - p * a"
+ }
+
+ //--------------------------------------------------------------------
+ // Interpolate p to q by a.
+ static AGG_INLINE value_type lerp(value_type p, value_type q, value_type a)
+ {
+ // The form "p + a * (q - p)" avoids a multiplication, but may produce an
+ // inaccurate result. For example, "p + (q - p)" may not be exactly equal
+ // to q. Therefore, stick to the basic expression, which at least produces
+ // the correct result at either extreme.
+ return (1 - a) * p + a * q;
+ }
+
+ //--------------------------------------------------------------------
+ self_type& clear()
+ {
+ v = a = 0;
+ return *this;
+ }
+
+ //--------------------------------------------------------------------
+ self_type& transparent()
+ {
+ a = 0;
+ return *this;
+ }
+
+ //--------------------------------------------------------------------
+ self_type& opacity(double a_)
+ {
+ if (a_ < 0) a = 0;
+ else if (a_ > 1) a = 1;
+ else a = value_type(a_);
+ return *this;
+ }
+
+ //--------------------------------------------------------------------
+ double opacity() const
+ {
+ return a;
+ }
+
+
+ //--------------------------------------------------------------------
+ self_type& premultiply()
+ {
+ if (a < 0) v = 0;
+ else if(a < 1) v *= a;
+ return *this;
+ }
+
+ //--------------------------------------------------------------------
+ self_type& demultiply()
+ {
+ if (a < 0) v = 0;
+ else if (a < 1) v /= a;
+ return *this;
+ }
+
+ //--------------------------------------------------------------------
+ self_type gradient(self_type c, double k) const
+ {
+ return self_type(
+ value_type(v + (c.v - v) * k),
+ value_type(a + (c.a - a) * k));
+ }
+
+ //--------------------------------------------------------------------
+ static self_type no_color() { return self_type(0,0); }
+ };
+
+
+ //====================================================================rgba32
+ struct rgba64
+ {
+ typedef double value_type;
+ typedef double calc_type;
+ typedef double long_type;
+ typedef rgba64 self_type;
+
+ value_type r;
+ value_type g;
+ value_type b;
+ value_type a;
+
+ //--------------------------------------------------------------------
+ rgba64() {}
+
+ //--------------------------------------------------------------------
+ rgba64(value_type r_, value_type g_, value_type b_, value_type a_= 1) :
+ r(r_), g(g_), b(b_), a(a_) {}
+
+ //--------------------------------------------------------------------
+ rgba64(const self_type& c, float a_) :
+ r(c.r), g(c.g), b(c.b), a(a_) {}
+
+ //--------------------------------------------------------------------
+ rgba64(const rgba& c) :
+ r(value_type(c.r)), g(value_type(c.g)), b(value_type(c.b)), a(value_type(c.a)) {}
+
+ //--------------------------------------------------------------------
+ operator rgba() const
+ {
+ return rgba(r, g, b, a);
+ }
+
+ //--------------------------------------------------------------------
+ static AGG_INLINE double to_double(value_type a)
+ {
+ return a;
+ }
+
+ //--------------------------------------------------------------------
+ static AGG_INLINE value_type from_double(double a)
+ {
+ return value_type(a);
+ }
+
+ //--------------------------------------------------------------------
+ static AGG_INLINE value_type empty_value()
+ {
+ return 0;
+ }
+
+ //--------------------------------------------------------------------
+ static AGG_INLINE value_type full_value()
+ {
+ return 1;
+ }
+
+ //--------------------------------------------------------------------
+ AGG_INLINE bool is_transparent() const
+ {
+ return a <= 0;
+ }
+
+ //--------------------------------------------------------------------
+ AGG_INLINE bool is_opaque() const
+ {
+ return a >= 1;
+ }
+
+ //--------------------------------------------------------------------
+ static AGG_INLINE value_type invert(value_type x)
+ {
+ return 1 - x;
+ }
+
+ //--------------------------------------------------------------------
+ static AGG_INLINE value_type multiply(value_type a, value_type b)
+ {
+ return value_type(a * b);
+ }
+
+ //--------------------------------------------------------------------
+ static AGG_INLINE value_type demultiply(value_type a, value_type b)
+ {
+ return (b == 0) ? 0 : value_type(a / b);
+ }
+
+ //--------------------------------------------------------------------
+ template<typename T>
+ static AGG_INLINE T downscale(T a)
+ {
+ return a;
+ }
+
+ //--------------------------------------------------------------------
+ template<typename T>
+ static AGG_INLINE T downshift(T a, unsigned n)
+ {
+ return n > 0 ? a / (1 << n) : a;
+ }
+
+ //--------------------------------------------------------------------
+ static AGG_INLINE value_type mult_cover(value_type a, cover_type b)
+ {
+ return value_type(a * b / cover_mask);
+ }
+
+ //--------------------------------------------------------------------
+ static AGG_INLINE cover_type scale_cover(cover_type a, value_type b)
+ {
+ return cover_type(uround(a * b));
+ }
+
+ //--------------------------------------------------------------------
+ // Interpolate p to q by a, assuming q is premultiplied by a.
+ static AGG_INLINE value_type prelerp(value_type p, value_type q, value_type a)
+ {
+ return (1 - a) * p + q; // more accurate than "p + q - p * a"
+ }
+
+ //--------------------------------------------------------------------
+ // Interpolate p to q by a.
+ static AGG_INLINE value_type lerp(value_type p, value_type q, value_type a)
+ {
+ // The form "p + a * (q - p)" avoids a multiplication, but may produce an
+ // inaccurate result. For example, "p + (q - p)" may not be exactly equal
+ // to q. Therefore, stick to the basic expression, which at least produces
+ // the correct result at either extreme.
+ return (1 - a) * p + a * q;
+ }
+
+ //--------------------------------------------------------------------
+ self_type& clear()
+ {
+ r = g = b = a = 0;
+ return *this;
+ }
+
+ //--------------------------------------------------------------------
+ self_type& transparent()
+ {
+ a = 0;
+ return *this;
+ }
+
+ //--------------------------------------------------------------------
+ AGG_INLINE self_type& opacity(double a_)
+ {
+ if (a_ < 0) a = 0;
+ else if (a_ > 1) a = 1;
+ else a = value_type(a_);
+ return *this;
+ }
+
+ //--------------------------------------------------------------------
+ double opacity() const
+ {
+ return a;
+ }
+
+ //--------------------------------------------------------------------
+ AGG_INLINE self_type& premultiply()
+ {
+ if (a < 1)
+ {
+ if (a <= 0)
+ {
+ r = g = b = 0;
+ }
+ else
+ {
+ r *= a;
+ g *= a;
+ b *= a;
+ }
+ }
+ return *this;
+ }
+
+ //--------------------------------------------------------------------
+ AGG_INLINE self_type& demultiply()
+ {
+ if (a < 1)
+ {
+ if (a <= 0)
+ {
+ r = g = b = 0;
+ }
+ else
+ {
+ r /= a;
+ g /= a;
+ b /= a;
+ }
+ }
+ return *this;
+ }
+
+ //--------------------------------------------------------------------
+ AGG_INLINE self_type gradient(const self_type& c, double k) const
+ {
+ self_type ret;
+ ret.r = value_type(r + (c.r - r) * k);
+ ret.g = value_type(g + (c.g - g) * k);
+ ret.b = value_type(b + (c.b - b) * k);
+ ret.a = value_type(a + (c.a - a) * k);
+ return ret;
+ }
+
+ //--------------------------------------------------------------------
+ AGG_INLINE void add(const self_type& c, unsigned cover)
+ {
+ if (cover == cover_mask)
+ {
+ if (c.is_opaque())
+ {
+ *this = c;
+ return;
+ }
+ else
+ {
+ r += c.r;
+ g += c.g;
+ b += c.b;
+ a += c.a;
+ }
+ }
+ else
+ {
+ r += mult_cover(c.r, cover);
+ g += mult_cover(c.g, cover);
+ b += mult_cover(c.b, cover);
+ a += mult_cover(c.a, cover);
+ }
+ if (a > 1) a = 1;
+ if (r > a) r = a;
+ if (g > a) g = a;
+ if (b > a) b = a;
+ }
+
+ //--------------------------------------------------------------------
+ static self_type no_color() { return self_type(0,0,0,0); }
+ };
+}
+
+
+typedef enum {
+ NEAREST,
+ BILINEAR,
+ BICUBIC,
+ SPLINE16,
+ SPLINE36,
+ HANNING,
+ HAMMING,
+ HERMITE,
+ KAISER,
+ QUADRIC,
+ CATROM,
+ GAUSSIAN,
+ BESSEL,
+ MITCHELL,
+ SINC,
+ LANCZOS,
+ BLACKMAN,
+ _n_interpolation
+} interpolation_e;
+
+
+// T is rgba if and only if it has an T::r field.
+template<typename T, typename = void> struct is_grayscale : std::true_type {};
+template<typename T> struct is_grayscale<T, decltype(T::r, void())> : std::false_type {};
+
+
+template<typename color_type>
+struct type_mapping
+{
+ using blender_type = typename std::conditional<
+ is_grayscale<color_type>::value,
+ agg::blender_gray<color_type>,
+ typename std::conditional<
+ std::is_same<color_type, agg::rgba8>::value,
+ fixed_blender_rgba_plain<color_type, agg::order_rgba>,
+ agg::blender_rgba_plain<color_type, agg::order_rgba>
+ >::type
+ >::type;
+ using pixfmt_type = typename std::conditional<
+ is_grayscale<color_type>::value,
+ agg::pixfmt_alpha_blend_gray<blender_type, agg::rendering_buffer>,
+ agg::pixfmt_alpha_blend_rgba<blender_type, agg::rendering_buffer>
+ >::type;
+ using pixfmt_pre_type = typename std::conditional<
+ is_grayscale<color_type>::value,
+ pixfmt_type,
+ agg::pixfmt_alpha_blend_rgba<
+ typename std::conditional<
+ std::is_same<color_type, agg::rgba8>::value,
+ fixed_blender_rgba_pre<color_type, agg::order_rgba>,
+ agg::blender_rgba_pre<color_type, agg::order_rgba>
+ >::type,
+ agg::rendering_buffer>
+ >::type;
+ template<typename A> using span_gen_affine_type = typename std::conditional<
+ is_grayscale<color_type>::value,
+ agg::span_image_resample_gray_affine<A>,
+ agg::span_image_resample_rgba_affine<A>
+ >::type;
+ template<typename A, typename B> using span_gen_filter_type = typename std::conditional<
+ is_grayscale<color_type>::value,
+ agg::span_image_filter_gray<A, B>,
+ agg::span_image_filter_rgba<A, B>
+ >::type;
+ template<typename A, typename B> using span_gen_nn_type = typename std::conditional<
+ is_grayscale<color_type>::value,
+ agg::span_image_filter_gray_nn<A, B>,
+ agg::span_image_filter_rgba_nn<A, B>
+ >::type;
+};
+
+
+template<typename color_type>
+class span_conv_alpha
+{
+public:
+ span_conv_alpha(const double alpha) :
+ m_alpha(alpha)
+ {
+ }
+
+ void prepare() {}
+
+ void generate(color_type* span, int x, int y, unsigned len) const
+ {
+ if (m_alpha != 1.0) {
+ do {
+ span->a *= m_alpha;
+ ++span;
+ } while (--len);
+ }
+ }
+private:
+
+ const double m_alpha;
+};
+
+
+/* A class to use a lookup table for a transformation */
+class lookup_distortion
+{
+public:
+ lookup_distortion(const double *mesh, int in_width, int in_height,
+ int out_width, int out_height) :
+ m_mesh(mesh),
+ m_in_width(in_width),
+ m_in_height(in_height),
+ m_out_width(out_width),
+ m_out_height(out_height)
+ {}
+
+ void calculate(int* x, int* y) {
+ if (m_mesh) {
+ double dx = double(*x) / agg::image_subpixel_scale;
+ double dy = double(*y) / agg::image_subpixel_scale;
+ if (dx >= 0 && dx < m_out_width &&
+ dy >= 0 && dy < m_out_height) {
+ const double *coord = m_mesh + (int(dy) * m_out_width + int(dx)) * 2;
+ *x = int(coord[0] * agg::image_subpixel_scale);
+ *y = int(coord[1] * agg::image_subpixel_scale);
+ }
+ }
+ }
+
+protected:
+ const double *m_mesh;
+ int m_in_width;
+ int m_in_height;
+ int m_out_width;
+ int m_out_height;
+};
+
+
+struct resample_params_t {
+ interpolation_e interpolation;
+ bool is_affine;
+ agg::trans_affine affine;
+ const double *transform_mesh;
+ bool resample;
+ bool norm;
+ double radius;
+ double alpha;
+};
+
+
+static void get_filter(const resample_params_t &params,
+ agg::image_filter_lut &filter)
+{
+ switch (params.interpolation) {
+ case NEAREST:
+ case _n_interpolation:
+ // Never should get here. Here to silence compiler warnings.
+ break;
+
+ case HANNING:
+ filter.calculate(agg::image_filter_hanning(), params.norm);
+ break;
+
+ case HAMMING:
+ filter.calculate(agg::image_filter_hamming(), params.norm);
+ break;
+
+ case HERMITE:
+ filter.calculate(agg::image_filter_hermite(), params.norm);
+ break;
+
+ case BILINEAR:
+ filter.calculate(agg::image_filter_bilinear(), params.norm);
+ break;
+
+ case BICUBIC:
+ filter.calculate(agg::image_filter_bicubic(), params.norm);
+ break;
+
+ case SPLINE16:
+ filter.calculate(agg::image_filter_spline16(), params.norm);
+ break;
+
+ case SPLINE36:
+ filter.calculate(agg::image_filter_spline36(), params.norm);
+ break;
+
+ case KAISER:
+ filter.calculate(agg::image_filter_kaiser(), params.norm);
+ break;
+
+ case QUADRIC:
+ filter.calculate(agg::image_filter_quadric(), params.norm);
+ break;
+
+ case CATROM:
+ filter.calculate(agg::image_filter_catrom(), params.norm);
+ break;
+
+ case GAUSSIAN:
+ filter.calculate(agg::image_filter_gaussian(), params.norm);
+ break;
+
+ case BESSEL:
+ filter.calculate(agg::image_filter_bessel(), params.norm);
+ break;
+
+ case MITCHELL:
+ filter.calculate(agg::image_filter_mitchell(), params.norm);
+ break;
+
+ case SINC:
+ filter.calculate(agg::image_filter_sinc(params.radius), params.norm);
+ break;
+
+ case LANCZOS:
+ filter.calculate(agg::image_filter_lanczos(params.radius), params.norm);
+ break;
+
+ case BLACKMAN:
+ filter.calculate(agg::image_filter_blackman(params.radius), params.norm);
+ break;
+ }
+}
+
+
+template<typename color_type>
+void resample(
+ const void *input, int in_width, int in_height,
+ void *output, int out_width, int out_height,
+ resample_params_t &params)
+{
+ using type_mapping_t = type_mapping<color_type>;
+
+ using input_pixfmt_t = typename type_mapping_t::pixfmt_type;
+ using output_pixfmt_t = typename type_mapping_t::pixfmt_type;
+
+ using renderer_t = agg::renderer_base<output_pixfmt_t>;
+ using rasterizer_t = agg::rasterizer_scanline_aa<agg::rasterizer_sl_clip_dbl>;
+
+ using reflect_t = agg::wrap_mode_reflect;
+ using image_accessor_t = agg::image_accessor_wrap<input_pixfmt_t, reflect_t, reflect_t>;
+
+ using span_alloc_t = agg::span_allocator<color_type>;
+ using span_conv_alpha_t = span_conv_alpha<color_type>;
+
+ using affine_interpolator_t = agg::span_interpolator_linear<>;
+ using arbitrary_interpolator_t =
+ agg::span_interpolator_adaptor<agg::span_interpolator_linear<>, lookup_distortion>;
+
+ size_t itemsize = sizeof(color_type);
+ if (is_grayscale<color_type>::value) {
+ itemsize /= 2; // agg::grayXX includes an alpha channel which we don't have.
+ }
+
+ if (params.interpolation != NEAREST &&
+ params.is_affine &&
+ fabs(params.affine.sx) == 1.0 &&
+ fabs(params.affine.sy) == 1.0 &&
+ params.affine.shx == 0.0 &&
+ params.affine.shy == 0.0) {
+ params.interpolation = NEAREST;
+ }
+
+ span_alloc_t span_alloc;
+ rasterizer_t rasterizer;
+ agg::scanline_u8 scanline;
+
+ span_conv_alpha_t conv_alpha(params.alpha);
+
+ agg::rendering_buffer input_buffer;
+ input_buffer.attach(
+ (unsigned char *)input, in_width, in_height, in_width * itemsize);
+ input_pixfmt_t input_pixfmt(input_buffer);
+ image_accessor_t input_accessor(input_pixfmt);
+
+ agg::rendering_buffer output_buffer;
+ output_buffer.attach(
+ (unsigned char *)output, out_width, out_height, out_width * itemsize);
+ output_pixfmt_t output_pixfmt(output_buffer);
+ renderer_t renderer(output_pixfmt);
+
+ agg::trans_affine inverted = params.affine;
+ inverted.invert();
+
+ rasterizer.clip_box(0, 0, out_width, out_height);
+
+ agg::path_storage path;
+ if (params.is_affine) {
+ path.move_to(0, 0);
+ path.line_to(in_width, 0);
+ path.line_to(in_width, in_height);
+ path.line_to(0, in_height);
+ path.close_polygon();
+ agg::conv_transform<agg::path_storage> rectangle(path, params.affine);
+ rasterizer.add_path(rectangle);
+ } else {
+ path.move_to(0, 0);
+ path.line_to(out_width, 0);
+ path.line_to(out_width, out_height);
+ path.line_to(0, out_height);
+ path.close_polygon();
+ rasterizer.add_path(path);
+ }
+
+ if (params.interpolation == NEAREST) {
+ if (params.is_affine) {
+ using span_gen_t = typename type_mapping_t::template span_gen_nn_type<image_accessor_t, affine_interpolator_t>;
+ using span_conv_t = agg::span_converter<span_gen_t, span_conv_alpha_t>;
+ using nn_renderer_t = agg::renderer_scanline_aa<renderer_t, span_alloc_t, span_conv_t>;
+ affine_interpolator_t interpolator(inverted);
+ span_gen_t span_gen(input_accessor, interpolator);
+ span_conv_t span_conv(span_gen, conv_alpha);
+ nn_renderer_t nn_renderer(renderer, span_alloc, span_conv);
+ agg::render_scanlines(rasterizer, scanline, nn_renderer);
+ } else {
+ using span_gen_t = typename type_mapping_t::template span_gen_nn_type<image_accessor_t, arbitrary_interpolator_t>;
+ using span_conv_t = agg::span_converter<span_gen_t, span_conv_alpha_t>;
+ using nn_renderer_t = agg::renderer_scanline_aa<renderer_t, span_alloc_t, span_conv_t>;
+ lookup_distortion dist(
+ params.transform_mesh, in_width, in_height, out_width, out_height);
+ arbitrary_interpolator_t interpolator(inverted, dist);
+ span_gen_t span_gen(input_accessor, interpolator);
+ span_conv_t span_conv(span_gen, conv_alpha);
+ nn_renderer_t nn_renderer(renderer, span_alloc, span_conv);
+ agg::render_scanlines(rasterizer, scanline, nn_renderer);
+ }
+ } else {
+ agg::image_filter_lut filter;
+ get_filter(params, filter);
+
+ if (params.is_affine && params.resample) {
+ using span_gen_t = typename type_mapping_t::template span_gen_affine_type<image_accessor_t>;
+ using span_conv_t = agg::span_converter<span_gen_t, span_conv_alpha_t>;
+ using int_renderer_t = agg::renderer_scanline_aa<renderer_t, span_alloc_t, span_conv_t>;
+ affine_interpolator_t interpolator(inverted);
+ span_gen_t span_gen(input_accessor, interpolator, filter);
+ span_conv_t span_conv(span_gen, conv_alpha);
+ int_renderer_t int_renderer(renderer, span_alloc, span_conv);
+ agg::render_scanlines(rasterizer, scanline, int_renderer);
+ } else {
+ using span_gen_t = typename type_mapping_t::template span_gen_filter_type<image_accessor_t, arbitrary_interpolator_t>;
+ using span_conv_t = agg::span_converter<span_gen_t, span_conv_alpha_t>;
+ using int_renderer_t = agg::renderer_scanline_aa<renderer_t, span_alloc_t, span_conv_t>;
+ lookup_distortion dist(
+ params.transform_mesh, in_width, in_height, out_width, out_height);
+ arbitrary_interpolator_t interpolator(inverted, dist);
+ span_gen_t span_gen(input_accessor, interpolator, filter);
+ span_conv_t span_conv(span_gen, conv_alpha);
+ int_renderer_t int_renderer(renderer, span_alloc, span_conv);
+ agg::render_scanlines(rasterizer, scanline, int_renderer);
+ }
+ }
+}
+
+#endif /* MPL_RESAMPLE_H */
diff --git a/contrib/python/matplotlib/py3/src/_image_wrapper.cpp b/contrib/python/matplotlib/py3/src/_image_wrapper.cpp
new file mode 100644
index 0000000000..ca6ae8b222
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/_image_wrapper.cpp
@@ -0,0 +1,297 @@
+#include "mplutils.h"
+#include "_image_resample.h"
+#include "numpy_cpp.h"
+#include "py_converters.h"
+
+
+/**********************************************************************
+ * Free functions
+ * */
+
+const char* image_resample__doc__ =
+"resample(input_array, output_array, transform, interpolation=NEAREST, resample=False, alpha=1.0, norm=False, radius=1.0)\n"
+"--\n\n"
+
+"Resample input_array, blending it in-place into output_array, using an\n"
+"affine transformation.\n\n"
+
+"Parameters\n"
+"----------\n"
+"input_array : 2-d or 3-d NumPy array of float, double or `numpy.uint8`\n"
+" If 2-d, the image is grayscale. If 3-d, the image must be of size\n"
+" 4 in the last dimension and represents RGBA data.\n\n"
+
+"output_array : 2-d or 3-d NumPy array of float, double or `numpy.uint8`\n"
+" The dtype and number of dimensions must match `input_array`.\n\n"
+
+"transform : matplotlib.transforms.Transform instance\n"
+" The transformation from the input array to the output array.\n\n"
+
+"interpolation : int, default: NEAREST\n"
+" The interpolation method. Must be one of the following constants\n"
+" defined in this module:\n\n"
+
+" NEAREST, BILINEAR, BICUBIC, SPLINE16, SPLINE36,\n"
+" HANNING, HAMMING, HERMITE, KAISER, QUADRIC, CATROM, GAUSSIAN,\n"
+" BESSEL, MITCHELL, SINC, LANCZOS, BLACKMAN\n\n"
+
+"resample : bool, optional\n"
+" When `True`, use a full resampling method. When `False`, only\n"
+" resample when the output image is larger than the input image.\n\n"
+
+"alpha : float, default: 1\n"
+" The transparency level, from 0 (transparent) to 1 (opaque).\n\n"
+
+"norm : bool, default: False\n"
+" Whether to norm the interpolation function.\n\n"
+
+"radius: float, default: 1\n"
+" The radius of the kernel, if method is SINC, LANCZOS or BLACKMAN.\n";
+
+
+static PyArrayObject *
+_get_transform_mesh(PyObject *py_affine, npy_intp *dims)
+{
+ /* TODO: Could we get away with float, rather than double, arrays here? */
+
+ /* Given a non-affine transform object, create a mesh that maps
+ every pixel in the output image to the input image. This is used
+ as a lookup table during the actual resampling. */
+
+ PyObject *py_inverse = NULL;
+ npy_intp out_dims[3];
+
+ out_dims[0] = dims[0] * dims[1];
+ out_dims[1] = 2;
+
+ py_inverse = PyObject_CallMethod(py_affine, "inverted", NULL);
+ if (py_inverse == NULL) {
+ return NULL;
+ }
+
+ numpy::array_view<double, 2> input_mesh(out_dims);
+ double *p = (double *)input_mesh.data();
+
+ for (npy_intp y = 0; y < dims[0]; ++y) {
+ for (npy_intp x = 0; x < dims[1]; ++x) {
+ *p++ = (double)x;
+ *p++ = (double)y;
+ }
+ }
+
+ PyObject *output_mesh = PyObject_CallMethod(
+ py_inverse, "transform", "O", input_mesh.pyobj_steal());
+
+ Py_DECREF(py_inverse);
+
+ if (output_mesh == NULL) {
+ return NULL;
+ }
+
+ PyArrayObject *output_mesh_array =
+ (PyArrayObject *)PyArray_ContiguousFromAny(
+ output_mesh, NPY_DOUBLE, 2, 2);
+
+ Py_DECREF(output_mesh);
+
+ if (output_mesh_array == NULL) {
+ return NULL;
+ }
+
+ return output_mesh_array;
+}
+
+
+static PyObject *
+image_resample(PyObject *self, PyObject* args, PyObject *kwargs)
+{
+ PyObject *py_input = NULL;
+ PyObject *py_output = NULL;
+ PyObject *py_transform = NULL;
+ resample_params_t params;
+
+ PyArrayObject *input = NULL;
+ PyArrayObject *output = NULL;
+ PyArrayObject *transform_mesh = NULL;
+ int ndim;
+ int type;
+
+ params.interpolation = NEAREST;
+ params.transform_mesh = NULL;
+ params.resample = false;
+ params.norm = false;
+ params.radius = 1.0;
+ params.alpha = 1.0;
+
+ const char *kwlist[] = {
+ "input_array", "output_array", "transform", "interpolation",
+ "resample", "alpha", "norm", "radius", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(
+ args, kwargs, "OOO|iO&dO&d:resample", (char **)kwlist,
+ &py_input, &py_output, &py_transform,
+ &params.interpolation, &convert_bool, &params.resample,
+ &params.alpha, &convert_bool, &params.norm, &params.radius)) {
+ return NULL;
+ }
+
+ if (params.interpolation < 0 || params.interpolation >= _n_interpolation) {
+ PyErr_Format(PyExc_ValueError, "Invalid interpolation value %d",
+ params.interpolation);
+ goto error;
+ }
+
+ input = (PyArrayObject *)PyArray_FromAny(
+ py_input, NULL, 2, 3, NPY_ARRAY_C_CONTIGUOUS, NULL);
+ if (!input) {
+ goto error;
+ }
+ ndim = PyArray_NDIM(input);
+ type = PyArray_TYPE(input);
+
+ if (!PyArray_Check(py_output)) {
+ PyErr_SetString(PyExc_ValueError, "Output array must be a NumPy array");
+ goto error;
+ }
+ output = (PyArrayObject *)py_output;
+ if (PyArray_NDIM(output) != ndim) {
+ PyErr_Format(
+ PyExc_ValueError,
+ "Input (%dD) and output (%dD) have different dimensionalities.",
+ ndim, PyArray_NDIM(output));
+ goto error;
+ }
+ // PyArray_FromAny above checks that input is 2D or 3D.
+ if (ndim == 3 && (PyArray_DIM(input, 2) != 4 || PyArray_DIM(output, 2) != 4)) {
+ PyErr_Format(
+ PyExc_ValueError,
+ "If 3D, input and output arrays must be RGBA with shape (M, N, 4); "
+ "got trailing dimensions of %" NPY_INTP_FMT " and %" NPY_INTP_FMT
+ " respectively", PyArray_DIM(input, 2), PyArray_DIM(output, 2));
+ goto error;
+ }
+ if (PyArray_TYPE(output) != type) {
+ PyErr_SetString(PyExc_ValueError, "Mismatched types");
+ goto error;
+ }
+ if (!PyArray_IS_C_CONTIGUOUS(output)) {
+ PyErr_SetString(PyExc_ValueError, "Output array must be C-contiguous");
+ goto error;
+ }
+
+ if (py_transform == NULL || py_transform == Py_None) {
+ params.is_affine = true;
+ } else {
+ PyObject *py_is_affine;
+ int py_is_affine2;
+ py_is_affine = PyObject_GetAttrString(py_transform, "is_affine");
+ if (!py_is_affine) {
+ goto error;
+ }
+
+ py_is_affine2 = PyObject_IsTrue(py_is_affine);
+ Py_DECREF(py_is_affine);
+
+ if (py_is_affine2 == -1) {
+ goto error;
+ } else if (py_is_affine2) {
+ if (!convert_trans_affine(py_transform, &params.affine)) {
+ goto error;
+ }
+ params.is_affine = true;
+ } else {
+ transform_mesh = _get_transform_mesh(
+ py_transform, PyArray_DIMS(output));
+ if (!transform_mesh) {
+ goto error;
+ }
+ params.transform_mesh = (double *)PyArray_DATA(transform_mesh);
+ params.is_affine = false;
+ }
+ }
+
+ if (auto resampler =
+ (ndim == 2) ? (
+ (type == NPY_UINT8) ? resample<agg::gray8> :
+ (type == NPY_INT8) ? resample<agg::gray8> :
+ (type == NPY_UINT16) ? resample<agg::gray16> :
+ (type == NPY_INT16) ? resample<agg::gray16> :
+ (type == NPY_FLOAT32) ? resample<agg::gray32> :
+ (type == NPY_FLOAT64) ? resample<agg::gray64> :
+ nullptr) : (
+ // ndim == 3
+ (type == NPY_UINT8) ? resample<agg::rgba8> :
+ (type == NPY_INT8) ? resample<agg::rgba8> :
+ (type == NPY_UINT16) ? resample<agg::rgba16> :
+ (type == NPY_INT16) ? resample<agg::rgba16> :
+ (type == NPY_FLOAT32) ? resample<agg::rgba32> :
+ (type == NPY_FLOAT64) ? resample<agg::rgba64> :
+ nullptr)) {
+ Py_BEGIN_ALLOW_THREADS
+ resampler(
+ PyArray_DATA(input), PyArray_DIM(input, 1), PyArray_DIM(input, 0),
+ PyArray_DATA(output), PyArray_DIM(output, 1), PyArray_DIM(output, 0),
+ params);
+ Py_END_ALLOW_THREADS
+ } else {
+ PyErr_SetString(
+ PyExc_ValueError,
+ "arrays must be of dtype byte, short, float32 or float64");
+ goto error;
+ }
+
+ Py_DECREF(input);
+ Py_XDECREF(transform_mesh);
+ Py_RETURN_NONE;
+
+ error:
+ Py_XDECREF(input);
+ Py_XDECREF(transform_mesh);
+ return NULL;
+}
+
+static PyMethodDef module_functions[] = {
+ {"resample", (PyCFunction)image_resample, METH_VARARGS|METH_KEYWORDS, image_resample__doc__},
+ {NULL}
+};
+
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT, "_image", NULL, 0, module_functions,
+};
+
+PyMODINIT_FUNC PyInit__image(void)
+{
+ PyObject *m;
+
+ import_array();
+
+ m = PyModule_Create(&moduledef);
+
+ if (m == NULL) {
+ return NULL;
+ }
+
+ if (PyModule_AddIntConstant(m, "NEAREST", NEAREST) ||
+ PyModule_AddIntConstant(m, "BILINEAR", BILINEAR) ||
+ PyModule_AddIntConstant(m, "BICUBIC", BICUBIC) ||
+ PyModule_AddIntConstant(m, "SPLINE16", SPLINE16) ||
+ PyModule_AddIntConstant(m, "SPLINE36", SPLINE36) ||
+ PyModule_AddIntConstant(m, "HANNING", HANNING) ||
+ PyModule_AddIntConstant(m, "HAMMING", HAMMING) ||
+ PyModule_AddIntConstant(m, "HERMITE", HERMITE) ||
+ PyModule_AddIntConstant(m, "KAISER", KAISER) ||
+ PyModule_AddIntConstant(m, "QUADRIC", QUADRIC) ||
+ PyModule_AddIntConstant(m, "CATROM", CATROM) ||
+ PyModule_AddIntConstant(m, "GAUSSIAN", GAUSSIAN) ||
+ PyModule_AddIntConstant(m, "BESSEL", BESSEL) ||
+ PyModule_AddIntConstant(m, "MITCHELL", MITCHELL) ||
+ PyModule_AddIntConstant(m, "SINC", SINC) ||
+ PyModule_AddIntConstant(m, "LANCZOS", LANCZOS) ||
+ PyModule_AddIntConstant(m, "BLACKMAN", BLACKMAN) ||
+ PyModule_AddIntConstant(m, "_n_interpolation", _n_interpolation)) {
+ Py_DECREF(m);
+ return NULL;
+ }
+
+ return m;
+}
diff --git a/contrib/python/matplotlib/py3/src/_path.h b/contrib/python/matplotlib/py3/src/_path.h
new file mode 100644
index 0000000000..61c4ed07d0
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/_path.h
@@ -0,0 +1,1251 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+#ifndef MPL_PATH_H
+#define MPL_PATH_H
+
+#include <limits>
+#include <math.h>
+#include <vector>
+#include <cmath>
+#include <algorithm>
+#include <string>
+
+#include "agg_conv_contour.h"
+#include "agg_conv_curve.h"
+#include "agg_conv_stroke.h"
+#include "agg_conv_transform.h"
+#include "agg_path_storage.h"
+#include "agg_trans_affine.h"
+
+#include "path_converters.h"
+#include "_backend_agg_basic_types.h"
+#include "numpy_cpp.h"
+
+struct XY
+{
+ double x;
+ double y;
+
+ XY(double x_, double y_) : x(x_), y(y_)
+ {
+ }
+
+ bool operator==(const XY& o)
+ {
+ return (x == o.x && y == o.y);
+ }
+
+ bool operator!=(const XY& o)
+ {
+ return (x != o.x || y != o.y);
+ }
+};
+
+typedef std::vector<XY> Polygon;
+
+void _finalize_polygon(std::vector<Polygon> &result, int closed_only)
+{
+ if (result.size() == 0) {
+ return;
+ }
+
+ Polygon &polygon = result.back();
+
+ /* Clean up the last polygon in the result. */
+ if (polygon.size() == 0) {
+ result.pop_back();
+ } else if (closed_only) {
+ if (polygon.size() < 3) {
+ result.pop_back();
+ } else if (polygon.front() != polygon.back()) {
+ polygon.push_back(polygon.front());
+ }
+ }
+}
+
+//
+// The following function was found in the Agg 2.3 examples (interactive_polygon.cpp).
+// It has been generalized to work on (possibly curved) polylines, rather than
+// just polygons. The original comments have been kept intact.
+// -- Michael Droettboom 2007-10-02
+//
+//======= Crossings Multiply algorithm of InsideTest ========================
+//
+// By Eric Haines, 3D/Eye Inc, erich@eye.com
+//
+// This version is usually somewhat faster than the original published in
+// Graphics Gems IV; by turning the division for testing the X axis crossing
+// into a tricky multiplication test this part of the test became faster,
+// which had the additional effect of making the test for "both to left or
+// both to right" a bit slower for triangles than simply computing the
+// intersection each time. The main increase is in triangle testing speed,
+// which was about 15% faster; all other polygon complexities were pretty much
+// the same as before. On machines where division is very expensive (not the
+// case on the HP 9000 series on which I tested) this test should be much
+// faster overall than the old code. Your mileage may (in fact, will) vary,
+// depending on the machine and the test data, but in general I believe this
+// code is both shorter and faster. This test was inspired by unpublished
+// Graphics Gems submitted by Joseph Samosky and Mark Haigh-Hutchinson.
+// Related work by Samosky is in:
+//
+// Samosky, Joseph, "SectionView: A system for interactively specifying and
+// visualizing sections through three-dimensional medical image data",
+// M.S. Thesis, Department of Electrical Engineering and Computer Science,
+// Massachusetts Institute of Technology, 1993.
+//
+// Shoot a test ray along +X axis. The strategy is to compare vertex Y values
+// to the testing point's Y and quickly discard edges which are entirely to one
+// side of the test ray. Note that CONVEX and WINDING code can be added as
+// for the CrossingsTest() code; it is left out here for clarity.
+//
+// Input 2D polygon _pgon_ with _numverts_ number of vertices and test point
+// _point_, returns 1 if inside, 0 if outside.
+template <class PathIterator, class PointArray, class ResultArray>
+void point_in_path_impl(PointArray &points, PathIterator &path, ResultArray &inside_flag)
+{
+ uint8_t yflag1;
+ double vtx0, vty0, vtx1, vty1;
+ double tx, ty;
+ double sx, sy;
+ double x, y;
+ size_t i;
+ bool all_done;
+
+ size_t n = points.size();
+
+ std::vector<uint8_t> yflag0(n);
+ std::vector<uint8_t> subpath_flag(n);
+
+ path.rewind(0);
+
+ for (i = 0; i < n; ++i) {
+ inside_flag[i] = 0;
+ }
+
+ unsigned code = 0;
+ do {
+ if (code != agg::path_cmd_move_to) {
+ code = path.vertex(&x, &y);
+ if (code == agg::path_cmd_stop ||
+ (code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) {
+ continue;
+ }
+ }
+
+ sx = vtx0 = vtx1 = x;
+ sy = vty0 = vty1 = y;
+
+ for (i = 0; i < n; ++i) {
+ ty = points(i, 1);
+
+ if (std::isfinite(ty)) {
+ // get test bit for above/below X axis
+ yflag0[i] = (vty0 >= ty);
+
+ subpath_flag[i] = 0;
+ }
+ }
+
+ do {
+ code = path.vertex(&x, &y);
+
+ // The following cases denote the beginning on a new subpath
+ if (code == agg::path_cmd_stop ||
+ (code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) {
+ x = sx;
+ y = sy;
+ } else if (code == agg::path_cmd_move_to) {
+ break;
+ }
+
+ for (i = 0; i < n; ++i) {
+ tx = points(i, 0);
+ ty = points(i, 1);
+
+ if (!(std::isfinite(tx) && std::isfinite(ty))) {
+ continue;
+ }
+
+ yflag1 = (vty1 >= ty);
+ // Check if endpoints straddle (are on opposite sides) of
+ // X axis (i.e. the Y's differ); if so, +X ray could
+ // intersect this edge. The old test also checked whether
+ // the endpoints are both to the right or to the left of
+ // the test point. However, given the faster intersection
+ // point computation used below, this test was found to be
+ // a break-even proposition for most polygons and a loser
+ // for triangles (where 50% or more of the edges which
+ // survive this test will cross quadrants and so have to
+ // have the X intersection computed anyway). I credit
+ // Joseph Samosky with inspiring me to try dropping the
+ // "both left or both right" part of my code.
+ if (yflag0[i] != yflag1) {
+ // Check intersection of pgon segment with +X ray.
+ // Note if >= point's X; if so, the ray hits it. The
+ // division operation is avoided for the ">=" test by
+ // checking the sign of the first vertex wrto the test
+ // point; idea inspired by Joseph Samosky's and Mark
+ // Haigh-Hutchinson's different polygon inclusion
+ // tests.
+ if (((vty1 - ty) * (vtx0 - vtx1) >= (vtx1 - tx) * (vty0 - vty1)) == yflag1) {
+ subpath_flag[i] ^= 1;
+ }
+ }
+
+ // Move to the next pair of vertices, retaining info as
+ // possible.
+ yflag0[i] = yflag1;
+ }
+
+ vtx0 = vtx1;
+ vty0 = vty1;
+
+ vtx1 = x;
+ vty1 = y;
+ } while (code != agg::path_cmd_stop &&
+ (code & agg::path_cmd_end_poly) != agg::path_cmd_end_poly);
+
+ all_done = true;
+ for (i = 0; i < n; ++i) {
+ tx = points(i, 0);
+ ty = points(i, 1);
+
+ if (!(std::isfinite(tx) && std::isfinite(ty))) {
+ continue;
+ }
+
+ yflag1 = (vty1 >= ty);
+ if (yflag0[i] != yflag1) {
+ if (((vty1 - ty) * (vtx0 - vtx1) >= (vtx1 - tx) * (vty0 - vty1)) == yflag1) {
+ subpath_flag[i] = subpath_flag[i] ^ true;
+ }
+ }
+ inside_flag[i] |= subpath_flag[i];
+ if (inside_flag[i] == 0) {
+ all_done = false;
+ }
+ }
+
+ if (all_done) {
+ break;
+ }
+ } while (code != agg::path_cmd_stop);
+}
+
+template <class PathIterator, class PointArray, class ResultArray>
+inline void points_in_path(PointArray &points,
+ const double r,
+ PathIterator &path,
+ agg::trans_affine &trans,
+ ResultArray &result)
+{
+ typedef agg::conv_transform<PathIterator> transformed_path_t;
+ typedef PathNanRemover<transformed_path_t> no_nans_t;
+ typedef agg::conv_curve<no_nans_t> curve_t;
+ typedef agg::conv_contour<curve_t> contour_t;
+
+ size_t i;
+ for (i = 0; i < points.size(); ++i) {
+ result[i] = false;
+ }
+
+ if (path.total_vertices() < 3) {
+ return;
+ }
+
+ transformed_path_t trans_path(path, trans);
+ no_nans_t no_nans_path(trans_path, true, path.has_codes());
+ curve_t curved_path(no_nans_path);
+ if (r != 0.0) {
+ contour_t contoured_path(curved_path);
+ contoured_path.width(r);
+ point_in_path_impl(points, contoured_path, result);
+ } else {
+ point_in_path_impl(points, curved_path, result);
+ }
+}
+
+template <class PathIterator>
+inline bool point_in_path(
+ double x, double y, const double r, PathIterator &path, agg::trans_affine &trans)
+{
+ npy_intp shape[] = {1, 2};
+ numpy::array_view<double, 2> points(shape);
+ points(0, 0) = x;
+ points(0, 1) = y;
+
+ int result[1];
+ result[0] = 0;
+
+ points_in_path(points, r, path, trans, result);
+
+ return result[0] != 0;
+}
+
+template <class PathIterator>
+inline bool point_on_path(
+ double x, double y, const double r, PathIterator &path, agg::trans_affine &trans)
+{
+ typedef agg::conv_transform<PathIterator> transformed_path_t;
+ typedef PathNanRemover<transformed_path_t> no_nans_t;
+ typedef agg::conv_curve<no_nans_t> curve_t;
+ typedef agg::conv_stroke<curve_t> stroke_t;
+
+ npy_intp shape[] = {1, 2};
+ numpy::array_view<double, 2> points(shape);
+ points(0, 0) = x;
+ points(0, 1) = y;
+
+ int result[1];
+ result[0] = 0;
+
+ transformed_path_t trans_path(path, trans);
+ no_nans_t nan_removed_path(trans_path, true, path.has_codes());
+ curve_t curved_path(nan_removed_path);
+ stroke_t stroked_path(curved_path);
+ stroked_path.width(r * 2.0);
+ point_in_path_impl(points, stroked_path, result);
+ return result[0] != 0;
+}
+
+struct extent_limits
+{
+ double x0;
+ double y0;
+ double x1;
+ double y1;
+ double xm;
+ double ym;
+};
+
+void reset_limits(extent_limits &e)
+{
+ e.x0 = std::numeric_limits<double>::infinity();
+ e.y0 = std::numeric_limits<double>::infinity();
+ e.x1 = -std::numeric_limits<double>::infinity();
+ e.y1 = -std::numeric_limits<double>::infinity();
+ /* xm and ym are the minimum positive values in the data, used
+ by log scaling */
+ e.xm = std::numeric_limits<double>::infinity();
+ e.ym = std::numeric_limits<double>::infinity();
+}
+
+inline void update_limits(double x, double y, extent_limits &e)
+{
+ if (x < e.x0)
+ e.x0 = x;
+ if (y < e.y0)
+ e.y0 = y;
+ if (x > e.x1)
+ e.x1 = x;
+ if (y > e.y1)
+ e.y1 = y;
+ /* xm and ym are the minimum positive values in the data, used
+ by log scaling */
+ if (x > 0.0 && x < e.xm)
+ e.xm = x;
+ if (y > 0.0 && y < e.ym)
+ e.ym = y;
+}
+
+template <class PathIterator>
+void update_path_extents(PathIterator &path, agg::trans_affine &trans, extent_limits &extents)
+{
+ typedef agg::conv_transform<PathIterator> transformed_path_t;
+ typedef PathNanRemover<transformed_path_t> nan_removed_t;
+ double x, y;
+ unsigned code;
+
+ transformed_path_t tpath(path, trans);
+ nan_removed_t nan_removed(tpath, true, path.has_codes());
+
+ nan_removed.rewind(0);
+
+ while ((code = nan_removed.vertex(&x, &y)) != agg::path_cmd_stop) {
+ if ((code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) {
+ continue;
+ }
+ update_limits(x, y, extents);
+ }
+}
+
+template <class PathGenerator, class TransformArray, class OffsetArray>
+void get_path_collection_extents(agg::trans_affine &master_transform,
+ PathGenerator &paths,
+ TransformArray &transforms,
+ OffsetArray &offsets,
+ agg::trans_affine &offset_trans,
+ extent_limits &extent)
+{
+ if (offsets.size() != 0 && offsets.dim(1) != 2) {
+ throw std::runtime_error("Offsets array must have shape (N, 2)");
+ }
+
+ size_t Npaths = paths.size();
+ size_t Noffsets = offsets.size();
+ size_t N = std::max(Npaths, Noffsets);
+ size_t Ntransforms = std::min(transforms.size(), N);
+ size_t i;
+
+ agg::trans_affine trans;
+
+ reset_limits(extent);
+
+ for (i = 0; i < N; ++i) {
+ typename PathGenerator::path_iterator path(paths(i % Npaths));
+ if (Ntransforms) {
+ size_t ti = i % Ntransforms;
+ trans = agg::trans_affine(transforms(ti, 0, 0),
+ transforms(ti, 1, 0),
+ transforms(ti, 0, 1),
+ transforms(ti, 1, 1),
+ transforms(ti, 0, 2),
+ transforms(ti, 1, 2));
+ } else {
+ trans = master_transform;
+ }
+
+ if (Noffsets) {
+ double xo = offsets(i % Noffsets, 0);
+ double yo = offsets(i % Noffsets, 1);
+ offset_trans.transform(&xo, &yo);
+ trans *= agg::trans_affine_translation(xo, yo);
+ }
+
+ update_path_extents(path, trans, extent);
+ }
+}
+
+template <class PathGenerator, class TransformArray, class OffsetArray>
+void point_in_path_collection(double x,
+ double y,
+ double radius,
+ agg::trans_affine &master_transform,
+ PathGenerator &paths,
+ TransformArray &transforms,
+ OffsetArray &offsets,
+ agg::trans_affine &offset_trans,
+ bool filled,
+ std::vector<int> &result)
+{
+ size_t Npaths = paths.size();
+
+ if (Npaths == 0) {
+ return;
+ }
+
+ size_t Noffsets = offsets.size();
+ size_t N = std::max(Npaths, Noffsets);
+ size_t Ntransforms = std::min(transforms.size(), N);
+ size_t i;
+
+ agg::trans_affine trans;
+
+ for (i = 0; i < N; ++i) {
+ typename PathGenerator::path_iterator path = paths(i % Npaths);
+
+ if (Ntransforms) {
+ size_t ti = i % Ntransforms;
+ trans = agg::trans_affine(transforms(ti, 0, 0),
+ transforms(ti, 1, 0),
+ transforms(ti, 0, 1),
+ transforms(ti, 1, 1),
+ transforms(ti, 0, 2),
+ transforms(ti, 1, 2));
+ trans *= master_transform;
+ } else {
+ trans = master_transform;
+ }
+
+ if (Noffsets) {
+ double xo = offsets(i % Noffsets, 0);
+ double yo = offsets(i % Noffsets, 1);
+ offset_trans.transform(&xo, &yo);
+ trans *= agg::trans_affine_translation(xo, yo);
+ }
+
+ if (filled) {
+ if (point_in_path(x, y, radius, path, trans)) {
+ result.push_back(i);
+ }
+ } else {
+ if (point_on_path(x, y, radius, path, trans)) {
+ result.push_back(i);
+ }
+ }
+ }
+}
+
+template <class PathIterator1, class PathIterator2>
+bool path_in_path(PathIterator1 &a,
+ agg::trans_affine &atrans,
+ PathIterator2 &b,
+ agg::trans_affine &btrans)
+{
+ typedef agg::conv_transform<PathIterator2> transformed_path_t;
+ typedef PathNanRemover<transformed_path_t> no_nans_t;
+ typedef agg::conv_curve<no_nans_t> curve_t;
+
+ if (a.total_vertices() < 3) {
+ return false;
+ }
+
+ transformed_path_t b_path_trans(b, btrans);
+ no_nans_t b_no_nans(b_path_trans, true, b.has_codes());
+ curve_t b_curved(b_no_nans);
+
+ double x, y;
+ b_curved.rewind(0);
+ while (b_curved.vertex(&x, &y) != agg::path_cmd_stop) {
+ if (!point_in_path(x, y, 0.0, a, atrans)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/** The clip_path_to_rect code here is a clean-room implementation of
+ the Sutherland-Hodgman clipping algorithm described here:
+
+ https://en.wikipedia.org/wiki/Sutherland-Hodgman_clipping_algorithm
+*/
+
+namespace clip_to_rect_filters
+{
+/* There are four different passes needed to create/remove
+ vertices (one for each side of the rectangle). The differences
+ between those passes are encapsulated in these functor classes.
+*/
+struct bisectx
+{
+ double m_x;
+
+ bisectx(double x) : m_x(x)
+ {
+ }
+
+ inline void bisect(double sx, double sy, double px, double py, double *bx, double *by) const
+ {
+ *bx = m_x;
+ double dx = px - sx;
+ double dy = py - sy;
+ *by = sy + dy * ((m_x - sx) / dx);
+ }
+};
+
+struct xlt : public bisectx
+{
+ xlt(double x) : bisectx(x)
+ {
+ }
+
+ inline bool is_inside(double x, double y) const
+ {
+ return x <= m_x;
+ }
+};
+
+struct xgt : public bisectx
+{
+ xgt(double x) : bisectx(x)
+ {
+ }
+
+ inline bool is_inside(double x, double y) const
+ {
+ return x >= m_x;
+ }
+};
+
+struct bisecty
+{
+ double m_y;
+
+ bisecty(double y) : m_y(y)
+ {
+ }
+
+ inline void bisect(double sx, double sy, double px, double py, double *bx, double *by) const
+ {
+ *by = m_y;
+ double dx = px - sx;
+ double dy = py - sy;
+ *bx = sx + dx * ((m_y - sy) / dy);
+ }
+};
+
+struct ylt : public bisecty
+{
+ ylt(double y) : bisecty(y)
+ {
+ }
+
+ inline bool is_inside(double x, double y) const
+ {
+ return y <= m_y;
+ }
+};
+
+struct ygt : public bisecty
+{
+ ygt(double y) : bisecty(y)
+ {
+ }
+
+ inline bool is_inside(double x, double y) const
+ {
+ return y >= m_y;
+ }
+};
+}
+
+template <class Filter>
+inline void clip_to_rect_one_step(const Polygon &polygon, Polygon &result, const Filter &filter)
+{
+ double sx, sy, px, py, bx, by;
+ bool sinside, pinside;
+ result.clear();
+
+ if (polygon.size() == 0) {
+ return;
+ }
+
+ sx = polygon.back().x;
+ sy = polygon.back().y;
+ for (Polygon::const_iterator i = polygon.begin(); i != polygon.end(); ++i) {
+ px = i->x;
+ py = i->y;
+
+ sinside = filter.is_inside(sx, sy);
+ pinside = filter.is_inside(px, py);
+
+ if (sinside ^ pinside) {
+ filter.bisect(sx, sy, px, py, &bx, &by);
+ result.push_back(XY(bx, by));
+ }
+
+ if (pinside) {
+ result.push_back(XY(px, py));
+ }
+
+ sx = px;
+ sy = py;
+ }
+}
+
+template <class PathIterator>
+void
+clip_path_to_rect(PathIterator &path, agg::rect_d &rect, bool inside, std::vector<Polygon> &results)
+{
+ double xmin, ymin, xmax, ymax;
+ if (rect.x1 < rect.x2) {
+ xmin = rect.x1;
+ xmax = rect.x2;
+ } else {
+ xmin = rect.x2;
+ xmax = rect.x1;
+ }
+
+ if (rect.y1 < rect.y2) {
+ ymin = rect.y1;
+ ymax = rect.y2;
+ } else {
+ ymin = rect.y2;
+ ymax = rect.y1;
+ }
+
+ if (!inside) {
+ std::swap(xmin, xmax);
+ std::swap(ymin, ymax);
+ }
+
+ typedef agg::conv_curve<PathIterator> curve_t;
+ curve_t curve(path);
+
+ Polygon polygon1, polygon2;
+ double x = 0, y = 0;
+ unsigned code = 0;
+ curve.rewind(0);
+
+ do {
+ // Grab the next subpath and store it in polygon1
+ polygon1.clear();
+ do {
+ if (code == agg::path_cmd_move_to) {
+ polygon1.push_back(XY(x, y));
+ }
+
+ code = curve.vertex(&x, &y);
+
+ if (code == agg::path_cmd_stop) {
+ break;
+ }
+
+ if (code != agg::path_cmd_move_to) {
+ polygon1.push_back(XY(x, y));
+ }
+ } while ((code & agg::path_cmd_end_poly) != agg::path_cmd_end_poly);
+
+ // The result of each step is fed into the next (note the
+ // swapping of polygon1 and polygon2 at each step).
+ clip_to_rect_one_step(polygon1, polygon2, clip_to_rect_filters::xlt(xmax));
+ clip_to_rect_one_step(polygon2, polygon1, clip_to_rect_filters::xgt(xmin));
+ clip_to_rect_one_step(polygon1, polygon2, clip_to_rect_filters::ylt(ymax));
+ clip_to_rect_one_step(polygon2, polygon1, clip_to_rect_filters::ygt(ymin));
+
+ // Empty polygons aren't very useful, so skip them
+ if (polygon1.size()) {
+ _finalize_polygon(results, 1);
+ results.push_back(polygon1);
+ }
+ } while (code != agg::path_cmd_stop);
+
+ _finalize_polygon(results, 1);
+}
+
+template <class VerticesArray, class ResultArray>
+void affine_transform_2d(VerticesArray &vertices, agg::trans_affine &trans, ResultArray &result)
+{
+ if (vertices.size() != 0 && vertices.dim(1) != 2) {
+ throw std::runtime_error("Invalid vertices array.");
+ }
+
+ size_t n = vertices.size();
+ double x;
+ double y;
+ double t0;
+ double t1;
+ double t;
+
+ for (size_t i = 0; i < n; ++i) {
+ x = vertices(i, 0);
+ y = vertices(i, 1);
+
+ t0 = trans.sx * x;
+ t1 = trans.shx * y;
+ t = t0 + t1 + trans.tx;
+ result(i, 0) = t;
+
+ t0 = trans.shy * x;
+ t1 = trans.sy * y;
+ t = t0 + t1 + trans.ty;
+ result(i, 1) = t;
+ }
+}
+
+template <class VerticesArray, class ResultArray>
+void affine_transform_1d(VerticesArray &vertices, agg::trans_affine &trans, ResultArray &result)
+{
+ if (vertices.dim(0) != 2) {
+ throw std::runtime_error("Invalid vertices array.");
+ }
+
+ double x;
+ double y;
+ double t0;
+ double t1;
+ double t;
+
+ x = vertices(0);
+ y = vertices(1);
+
+ t0 = trans.sx * x;
+ t1 = trans.shx * y;
+ t = t0 + t1 + trans.tx;
+ result(0) = t;
+
+ t0 = trans.shy * x;
+ t1 = trans.sy * y;
+ t = t0 + t1 + trans.ty;
+ result(1) = t;
+}
+
+template <class BBoxArray>
+int count_bboxes_overlapping_bbox(agg::rect_d &a, BBoxArray &bboxes)
+{
+ agg::rect_d b;
+ int count = 0;
+
+ if (a.x2 < a.x1) {
+ std::swap(a.x1, a.x2);
+ }
+ if (a.y2 < a.y1) {
+ std::swap(a.y1, a.y2);
+ }
+
+ size_t num_bboxes = bboxes.size();
+ for (size_t i = 0; i < num_bboxes; ++i) {
+ b = agg::rect_d(bboxes(i, 0, 0), bboxes(i, 0, 1), bboxes(i, 1, 0), bboxes(i, 1, 1));
+
+ if (b.x2 < b.x1) {
+ std::swap(b.x1, b.x2);
+ }
+ if (b.y2 < b.y1) {
+ std::swap(b.y1, b.y2);
+ }
+ if (!((b.x2 <= a.x1) || (b.y2 <= a.y1) || (b.x1 >= a.x2) || (b.y1 >= a.y2))) {
+ ++count;
+ }
+ }
+
+ return count;
+}
+
+
+inline bool isclose(double a, double b)
+{
+ // relative and absolute tolerance values are chosen empirically
+ // it looks the atol value matters here because of round-off errors
+ const double rtol = 1e-10;
+ const double atol = 1e-13;
+
+ // as per python's math.isclose
+ return fabs(a-b) <= fmax(rtol * fmax(fabs(a), fabs(b)), atol);
+}
+
+
+inline bool segments_intersect(const double &x1,
+ const double &y1,
+ const double &x2,
+ const double &y2,
+ const double &x3,
+ const double &y3,
+ const double &x4,
+ const double &y4)
+{
+ // determinant
+ double den = ((y4 - y3) * (x2 - x1)) - ((x4 - x3) * (y2 - y1));
+
+ // If den == 0 we have two possibilities:
+ if (isclose(den, 0.0)) {
+ double t_area = (x2*y3 - x3*y2) - x1*(y3 - y2) + y1*(x3 - x2);
+ // 1 - If the area of the triangle made by the 3 first points (2 from the first segment
+ // plus one from the second) is zero, they are collinear
+ if (isclose(t_area, 0.0)) {
+ if (x1 == x2 && x2 == x3) { // segments have infinite slope (vertical lines)
+ // and lie on the same line
+ return (fmin(y1, y2) <= fmin(y3, y4) && fmin(y3, y4) <= fmax(y1, y2)) ||
+ (fmin(y3, y4) <= fmin(y1, y2) && fmin(y1, y2) <= fmax(y3, y4));
+ }
+ else {
+ return (fmin(x1, x2) <= fmin(x3, x4) && fmin(x3, x4) <= fmax(x1, x2)) ||
+ (fmin(x3, x4) <= fmin(x1, x2) && fmin(x1, x2) <= fmax(x3, x4));
+ }
+ }
+ // 2 - If t_area is not zero, the segments are parallel, but not collinear
+ else {
+ return false;
+ }
+ }
+
+ const double n1 = ((x4 - x3) * (y1 - y3)) - ((y4 - y3) * (x1 - x3));
+ const double n2 = ((x2 - x1) * (y1 - y3)) - ((y2 - y1) * (x1 - x3));
+
+ const double u1 = n1 / den;
+ const double u2 = n2 / den;
+
+ return ((u1 > 0.0 || isclose(u1, 0.0)) &&
+ (u1 < 1.0 || isclose(u1, 1.0)) &&
+ (u2 > 0.0 || isclose(u2, 0.0)) &&
+ (u2 < 1.0 || isclose(u2, 1.0)));
+}
+
+template <class PathIterator1, class PathIterator2>
+bool path_intersects_path(PathIterator1 &p1, PathIterator2 &p2)
+{
+ typedef PathNanRemover<py::PathIterator> no_nans_t;
+ typedef agg::conv_curve<no_nans_t> curve_t;
+
+ if (p1.total_vertices() < 2 || p2.total_vertices() < 2) {
+ return false;
+ }
+
+ no_nans_t n1(p1, true, p1.has_codes());
+ no_nans_t n2(p2, true, p2.has_codes());
+
+ curve_t c1(n1);
+ curve_t c2(n2);
+
+ double x11, y11, x12, y12;
+ double x21, y21, x22, y22;
+
+ c1.vertex(&x11, &y11);
+ while (c1.vertex(&x12, &y12) != agg::path_cmd_stop) {
+ // if the segment in path 1 is (almost) 0 length, skip to next vertex
+ if ((isclose((x11 - x12) * (x11 - x12) + (y11 - y12) * (y11 - y12), 0))){
+ continue;
+ }
+ c2.rewind(0);
+ c2.vertex(&x21, &y21);
+
+ while (c2.vertex(&x22, &y22) != agg::path_cmd_stop) {
+ // if the segment in path 2 is (almost) 0 length, skip to next vertex
+ if ((isclose((x21 - x22) * (x21 - x22) + (y21 - y22) * (y21 - y22), 0))){
+ continue;
+ }
+
+ if (segments_intersect(x11, y11, x12, y12, x21, y21, x22, y22)) {
+ return true;
+ }
+ x21 = x22;
+ y21 = y22;
+ }
+ x11 = x12;
+ y11 = y12;
+ }
+
+ return false;
+}
+
+// returns whether the segment from (x1,y1) to (x2,y2)
+// intersects the rectangle centered at (cx,cy) with size (w,h)
+// see doc/segment_intersects_rectangle.svg for a more detailed explanation
+inline bool segment_intersects_rectangle(double x1, double y1,
+ double x2, double y2,
+ double cx, double cy,
+ double w, double h)
+{
+ return fabs(x1 + x2 - 2.0 * cx) < fabs(x1 - x2) + w &&
+ fabs(y1 + y2 - 2.0 * cy) < fabs(y1 - y2) + h &&
+ 2.0 * fabs((x1 - cx) * (y1 - y2) - (y1 - cy) * (x1 - x2)) <
+ w * fabs(y1 - y2) + h * fabs(x1 - x2);
+}
+
+template <class PathIterator>
+bool path_intersects_rectangle(PathIterator &path,
+ double rect_x1, double rect_y1,
+ double rect_x2, double rect_y2,
+ bool filled)
+{
+ typedef PathNanRemover<py::PathIterator> no_nans_t;
+ typedef agg::conv_curve<no_nans_t> curve_t;
+
+ if (path.total_vertices() == 0) {
+ return false;
+ }
+
+ no_nans_t no_nans(path, true, path.has_codes());
+ curve_t curve(no_nans);
+
+ double cx = (rect_x1 + rect_x2) * 0.5, cy = (rect_y1 + rect_y2) * 0.5;
+ double w = fabs(rect_x1 - rect_x2), h = fabs(rect_y1 - rect_y2);
+
+ double x1, y1, x2, y2;
+
+ curve.vertex(&x1, &y1);
+ if (2.0 * fabs(x1 - cx) <= w && 2.0 * fabs(y1 - cy) <= h) {
+ return true;
+ }
+
+ while (curve.vertex(&x2, &y2) != agg::path_cmd_stop) {
+ if (segment_intersects_rectangle(x1, y1, x2, y2, cx, cy, w, h)) {
+ return true;
+ }
+ x1 = x2;
+ y1 = y2;
+ }
+
+ if (filled) {
+ agg::trans_affine trans;
+ if (point_in_path(cx, cy, 0.0, path, trans)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+template <class PathIterator>
+void convert_path_to_polygons(PathIterator &path,
+ agg::trans_affine &trans,
+ double width,
+ double height,
+ int closed_only,
+ std::vector<Polygon> &result)
+{
+ typedef agg::conv_transform<py::PathIterator> transformed_path_t;
+ typedef PathNanRemover<transformed_path_t> nan_removal_t;
+ typedef PathClipper<nan_removal_t> clipped_t;
+ typedef PathSimplifier<clipped_t> simplify_t;
+ typedef agg::conv_curve<simplify_t> curve_t;
+
+ bool do_clip = width != 0.0 && height != 0.0;
+ bool simplify = path.should_simplify();
+
+ transformed_path_t tpath(path, trans);
+ nan_removal_t nan_removed(tpath, true, path.has_codes());
+ clipped_t clipped(nan_removed, do_clip, width, height);
+ simplify_t simplified(clipped, simplify, path.simplify_threshold());
+ curve_t curve(simplified);
+
+ result.push_back(Polygon());
+ Polygon *polygon = &result.back();
+ double x, y;
+ unsigned code;
+
+ while ((code = curve.vertex(&x, &y)) != agg::path_cmd_stop) {
+ if ((code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) {
+ _finalize_polygon(result, 1);
+ result.push_back(Polygon());
+ polygon = &result.back();
+ } else {
+ if (code == agg::path_cmd_move_to) {
+ _finalize_polygon(result, closed_only);
+ result.push_back(Polygon());
+ polygon = &result.back();
+ }
+ polygon->push_back(XY(x, y));
+ }
+ }
+
+ _finalize_polygon(result, closed_only);
+}
+
+template <class VertexSource>
+void
+__cleanup_path(VertexSource &source, std::vector<double> &vertices, std::vector<npy_uint8> &codes)
+{
+ unsigned code;
+ double x, y;
+ do {
+ code = source.vertex(&x, &y);
+ vertices.push_back(x);
+ vertices.push_back(y);
+ codes.push_back((npy_uint8)code);
+ } while (code != agg::path_cmd_stop);
+}
+
+template <class PathIterator>
+void cleanup_path(PathIterator &path,
+ agg::trans_affine &trans,
+ bool remove_nans,
+ bool do_clip,
+ const agg::rect_base<double> &rect,
+ e_snap_mode snap_mode,
+ double stroke_width,
+ bool do_simplify,
+ bool return_curves,
+ SketchParams sketch_params,
+ std::vector<double> &vertices,
+ std::vector<unsigned char> &codes)
+{
+ typedef agg::conv_transform<py::PathIterator> transformed_path_t;
+ typedef PathNanRemover<transformed_path_t> nan_removal_t;
+ typedef PathClipper<nan_removal_t> clipped_t;
+ typedef PathSnapper<clipped_t> snapped_t;
+ typedef PathSimplifier<snapped_t> simplify_t;
+ typedef agg::conv_curve<simplify_t> curve_t;
+ typedef Sketch<curve_t> sketch_t;
+
+ transformed_path_t tpath(path, trans);
+ nan_removal_t nan_removed(tpath, remove_nans, path.has_codes());
+ clipped_t clipped(nan_removed, do_clip, rect);
+ snapped_t snapped(clipped, snap_mode, path.total_vertices(), stroke_width);
+ simplify_t simplified(snapped, do_simplify, path.simplify_threshold());
+
+ vertices.reserve(path.total_vertices() * 2);
+ codes.reserve(path.total_vertices());
+
+ if (return_curves && sketch_params.scale == 0.0) {
+ __cleanup_path(simplified, vertices, codes);
+ } else {
+ curve_t curve(simplified);
+ sketch_t sketch(curve, sketch_params.scale, sketch_params.length, sketch_params.randomness);
+ __cleanup_path(sketch, vertices, codes);
+ }
+}
+
+void quad2cubic(double x0, double y0,
+ double x1, double y1,
+ double x2, double y2,
+ double *outx, double *outy)
+{
+
+ outx[0] = x0 + 2./3. * (x1 - x0);
+ outy[0] = y0 + 2./3. * (y1 - y0);
+ outx[1] = outx[0] + 1./3. * (x2 - x0);
+ outy[1] = outy[0] + 1./3. * (y2 - y0);
+ outx[2] = x2;
+ outy[2] = y2;
+}
+
+
+void __add_number(double val, char format_code, int precision,
+ std::string& buffer)
+{
+ if (precision == -1) {
+ // Special-case for compat with old ttconv code, which *truncated*
+ // values with a cast to int instead of rounding them as printf
+ // would do. The only point where non-integer values arise is from
+ // quad2cubic conversion (as we already perform a first truncation
+ // on Python's side), which can introduce additional floating point
+ // error (by adding 2/3 delta-x and then 1/3 delta-x), so compensate by
+ // first rounding to the closest 1/3 and then truncating.
+ char str[255];
+ PyOS_snprintf(str, 255, "%d", (int)(round(val * 3)) / 3);
+ buffer += str;
+ } else {
+ char *str = PyOS_double_to_string(
+ val, format_code, precision, Py_DTSF_ADD_DOT_0, NULL);
+ // Delete trailing zeros and decimal point
+ char *c = str + strlen(str) - 1; // Start at last character.
+ // Rewind through all the zeros and, if present, the trailing decimal
+ // point. Py_DTSF_ADD_DOT_0 ensures we won't go past the start of str.
+ while (*c == '0') {
+ --c;
+ }
+ if (*c == '.') {
+ --c;
+ }
+ try {
+ buffer.append(str, c + 1);
+ } catch (std::bad_alloc& e) {
+ PyMem_Free(str);
+ throw e;
+ }
+ PyMem_Free(str);
+ }
+}
+
+
+template <class PathIterator>
+bool __convert_to_string(PathIterator &path,
+ int precision,
+ char **codes,
+ bool postfix,
+ std::string& buffer)
+{
+ const char format_code = 'f';
+
+ double x[3];
+ double y[3];
+ double last_x = 0.0;
+ double last_y = 0.0;
+
+ unsigned code;
+
+ while ((code = path.vertex(&x[0], &y[0])) != agg::path_cmd_stop) {
+ if (code == CLOSEPOLY) {
+ buffer += codes[4];
+ } else if (code < 5) {
+ size_t size = NUM_VERTICES[code];
+
+ for (size_t i = 1; i < size; ++i) {
+ unsigned subcode = path.vertex(&x[i], &y[i]);
+ if (subcode != code) {
+ return false;
+ }
+ }
+
+ /* For formats that don't support quad curves, convert to
+ cubic curves */
+ if (code == CURVE3 && codes[code - 1][0] == '\0') {
+ quad2cubic(last_x, last_y, x[0], y[0], x[1], y[1], x, y);
+ code++;
+ size = 3;
+ }
+
+ if (!postfix) {
+ buffer += codes[code - 1];
+ buffer += ' ';
+ }
+
+ for (size_t i = 0; i < size; ++i) {
+ __add_number(x[i], format_code, precision, buffer);
+ buffer += ' ';
+ __add_number(y[i], format_code, precision, buffer);
+ buffer += ' ';
+ }
+
+ if (postfix) {
+ buffer += codes[code - 1];
+ }
+
+ last_x = x[size - 1];
+ last_y = y[size - 1];
+ } else {
+ // Unknown code value
+ return false;
+ }
+
+ buffer += '\n';
+ }
+
+ return true;
+}
+
+template <class PathIterator>
+bool convert_to_string(PathIterator &path,
+ agg::trans_affine &trans,
+ agg::rect_d &clip_rect,
+ bool simplify,
+ SketchParams sketch_params,
+ int precision,
+ char **codes,
+ bool postfix,
+ std::string& buffer)
+{
+ size_t buffersize;
+ typedef agg::conv_transform<py::PathIterator> transformed_path_t;
+ typedef PathNanRemover<transformed_path_t> nan_removal_t;
+ typedef PathClipper<nan_removal_t> clipped_t;
+ typedef PathSimplifier<clipped_t> simplify_t;
+ typedef agg::conv_curve<simplify_t> curve_t;
+ typedef Sketch<curve_t> sketch_t;
+
+ bool do_clip = (clip_rect.x1 < clip_rect.x2 && clip_rect.y1 < clip_rect.y2);
+
+ transformed_path_t tpath(path, trans);
+ nan_removal_t nan_removed(tpath, true, path.has_codes());
+ clipped_t clipped(nan_removed, do_clip, clip_rect);
+ simplify_t simplified(clipped, simplify, path.simplify_threshold());
+
+ buffersize = (size_t) path.total_vertices() * (precision + 5) * 4;
+ if (buffersize == 0) {
+ return true;
+ }
+
+ if (sketch_params.scale != 0.0) {
+ buffersize *= 10;
+ }
+
+ buffer.reserve(buffersize);
+
+ if (sketch_params.scale == 0.0) {
+ return __convert_to_string(simplified, precision, codes, postfix, buffer);
+ } else {
+ curve_t curve(simplified);
+ sketch_t sketch(curve, sketch_params.scale, sketch_params.length, sketch_params.randomness);
+ return __convert_to_string(sketch, precision, codes, postfix, buffer);
+ }
+
+}
+
+template<class T>
+bool is_sorted_and_has_non_nan(PyArrayObject *array)
+{
+ char* ptr = PyArray_BYTES(array);
+ npy_intp size = PyArray_DIM(array, 0),
+ stride = PyArray_STRIDE(array, 0);
+ using limits = std::numeric_limits<T>;
+ T last = limits::has_infinity ? -limits::infinity() : limits::min();
+ bool found_non_nan = false;
+
+ for (npy_intp i = 0; i < size; ++i, ptr += stride) {
+ T current = *(T*)ptr;
+ // The following tests !isnan(current), but also works for integral
+ // types. (The isnan(IntegralType) overload is absent on MSVC.)
+ if (current == current) {
+ found_non_nan = true;
+ if (current < last) {
+ return false;
+ }
+ last = current;
+ }
+ }
+ return found_non_nan;
+};
+
+
+#endif
diff --git a/contrib/python/matplotlib/py3/src/_path_wrapper.cpp b/contrib/python/matplotlib/py3/src/_path_wrapper.cpp
new file mode 100644
index 0000000000..369d9e0308
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/_path_wrapper.cpp
@@ -0,0 +1,772 @@
+#include "numpy_cpp.h"
+
+#include "_path.h"
+
+#include "py_converters.h"
+#include "py_adaptors.h"
+
+PyObject *convert_polygon_vector(std::vector<Polygon> &polygons)
+{
+ PyObject *pyresult = PyList_New(polygons.size());
+
+ for (size_t i = 0; i < polygons.size(); ++i) {
+ Polygon poly = polygons[i];
+ npy_intp dims[2];
+ dims[1] = 2;
+
+ dims[0] = (npy_intp)poly.size();
+
+ numpy::array_view<double, 2> subresult(dims);
+ memcpy(subresult.data(), &poly[0], sizeof(double) * poly.size() * 2);
+
+ if (PyList_SetItem(pyresult, i, subresult.pyobj())) {
+ Py_DECREF(pyresult);
+ return NULL;
+ }
+ }
+
+ return pyresult;
+}
+
+const char *Py_point_in_path__doc__ =
+ "point_in_path(x, y, radius, path, trans)\n"
+ "--\n\n";
+
+static PyObject *Py_point_in_path(PyObject *self, PyObject *args)
+{
+ double x, y, r;
+ py::PathIterator path;
+ agg::trans_affine trans;
+ bool result;
+
+ if (!PyArg_ParseTuple(args,
+ "dddO&O&:point_in_path",
+ &x,
+ &y,
+ &r,
+ &convert_path,
+ &path,
+ &convert_trans_affine,
+ &trans)) {
+ return NULL;
+ }
+
+ CALL_CPP("point_in_path", (result = point_in_path(x, y, r, path, trans)));
+
+ if (result) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+}
+
+const char *Py_points_in_path__doc__ =
+ "points_in_path(points, radius, path, trans)\n"
+ "--\n\n";
+
+static PyObject *Py_points_in_path(PyObject *self, PyObject *args)
+{
+ numpy::array_view<const double, 2> points;
+ double r;
+ py::PathIterator path;
+ agg::trans_affine trans;
+
+ if (!PyArg_ParseTuple(args,
+ "O&dO&O&:points_in_path",
+ &convert_points,
+ &points,
+ &r,
+ &convert_path,
+ &path,
+ &convert_trans_affine,
+ &trans)) {
+ return NULL;
+ }
+
+ npy_intp dims[] = { (npy_intp)points.size() };
+ numpy::array_view<uint8_t, 1> results(dims);
+
+ CALL_CPP("points_in_path", (points_in_path(points, r, path, trans, results)));
+
+ return results.pyobj();
+}
+
+const char *Py_update_path_extents__doc__ =
+ "update_path_extents(path, trans, rect, minpos, ignore)\n"
+ "--\n\n";
+
+static PyObject *Py_update_path_extents(PyObject *self, PyObject *args)
+{
+ py::PathIterator path;
+ agg::trans_affine trans;
+ agg::rect_d rect;
+ numpy::array_view<double, 1> minpos;
+ int ignore;
+ int changed;
+
+ if (!PyArg_ParseTuple(args,
+ "O&O&O&O&i:update_path_extents",
+ &convert_path,
+ &path,
+ &convert_trans_affine,
+ &trans,
+ &convert_rect,
+ &rect,
+ &minpos.converter,
+ &minpos,
+ &ignore)) {
+ return NULL;
+ }
+
+ if (minpos.dim(0) != 2) {
+ PyErr_Format(PyExc_ValueError,
+ "minpos must be of length 2, got %" NPY_INTP_FMT,
+ minpos.dim(0));
+ return NULL;
+ }
+
+ extent_limits e;
+
+ if (ignore) {
+ CALL_CPP("update_path_extents", reset_limits(e));
+ } else {
+ if (rect.x1 > rect.x2) {
+ e.x0 = std::numeric_limits<double>::infinity();
+ e.x1 = -std::numeric_limits<double>::infinity();
+ } else {
+ e.x0 = rect.x1;
+ e.x1 = rect.x2;
+ }
+ if (rect.y1 > rect.y2) {
+ e.y0 = std::numeric_limits<double>::infinity();
+ e.y1 = -std::numeric_limits<double>::infinity();
+ } else {
+ e.y0 = rect.y1;
+ e.y1 = rect.y2;
+ }
+ e.xm = minpos(0);
+ e.ym = minpos(1);
+ }
+
+ CALL_CPP("update_path_extents", (update_path_extents(path, trans, e)));
+
+ changed = (e.x0 != rect.x1 || e.y0 != rect.y1 || e.x1 != rect.x2 || e.y1 != rect.y2 ||
+ e.xm != minpos(0) || e.ym != minpos(1));
+
+ npy_intp extentsdims[] = { 2, 2 };
+ numpy::array_view<double, 2> outextents(extentsdims);
+ outextents(0, 0) = e.x0;
+ outextents(0, 1) = e.y0;
+ outextents(1, 0) = e.x1;
+ outextents(1, 1) = e.y1;
+
+ npy_intp minposdims[] = { 2 };
+ numpy::array_view<double, 1> outminpos(minposdims);
+ outminpos(0) = e.xm;
+ outminpos(1) = e.ym;
+
+ return Py_BuildValue(
+ "NNi", outextents.pyobj(), outminpos.pyobj(), changed);
+}
+
+const char *Py_get_path_collection_extents__doc__ =
+ "get_path_collection_extents("
+ "master_transform, paths, transforms, offsets, offset_transform)\n"
+ "--\n\n";
+
+static PyObject *Py_get_path_collection_extents(PyObject *self, PyObject *args)
+{
+ agg::trans_affine master_transform;
+ py::PathGenerator paths;
+ numpy::array_view<const double, 3> transforms;
+ numpy::array_view<const double, 2> offsets;
+ agg::trans_affine offset_trans;
+ extent_limits e;
+
+ if (!PyArg_ParseTuple(args,
+ "O&O&O&O&O&:get_path_collection_extents",
+ &convert_trans_affine,
+ &master_transform,
+ &convert_pathgen,
+ &paths,
+ &convert_transforms,
+ &transforms,
+ &convert_points,
+ &offsets,
+ &convert_trans_affine,
+ &offset_trans)) {
+ return NULL;
+ }
+
+ CALL_CPP("get_path_collection_extents",
+ (get_path_collection_extents(
+ master_transform, paths, transforms, offsets, offset_trans, e)));
+
+ npy_intp dims[] = { 2, 2 };
+ numpy::array_view<double, 2> extents(dims);
+ extents(0, 0) = e.x0;
+ extents(0, 1) = e.y0;
+ extents(1, 0) = e.x1;
+ extents(1, 1) = e.y1;
+
+ npy_intp minposdims[] = { 2 };
+ numpy::array_view<double, 1> minpos(minposdims);
+ minpos(0) = e.xm;
+ minpos(1) = e.ym;
+
+ return Py_BuildValue("NN", extents.pyobj(), minpos.pyobj());
+}
+
+const char *Py_point_in_path_collection__doc__ =
+ "point_in_path_collection("
+ "x, y, radius, master_transform, paths, transforms, offsets, "
+ "offset_trans, filled)\n"
+ "--\n\n";
+
+static PyObject *Py_point_in_path_collection(PyObject *self, PyObject *args)
+{
+ double x, y, radius;
+ agg::trans_affine master_transform;
+ py::PathGenerator paths;
+ numpy::array_view<const double, 3> transforms;
+ numpy::array_view<const double, 2> offsets;
+ agg::trans_affine offset_trans;
+ bool filled;
+ std::vector<int> result;
+
+ if (!PyArg_ParseTuple(args,
+ "dddO&O&O&O&O&O&:point_in_path_collection",
+ &x,
+ &y,
+ &radius,
+ &convert_trans_affine,
+ &master_transform,
+ &convert_pathgen,
+ &paths,
+ &convert_transforms,
+ &transforms,
+ &convert_points,
+ &offsets,
+ &convert_trans_affine,
+ &offset_trans,
+ &convert_bool,
+ &filled)) {
+ return NULL;
+ }
+
+ CALL_CPP("point_in_path_collection",
+ (point_in_path_collection(x,
+ y,
+ radius,
+ master_transform,
+ paths,
+ transforms,
+ offsets,
+ offset_trans,
+ filled,
+ result)));
+
+ npy_intp dims[] = {(npy_intp)result.size() };
+ numpy::array_view<int, 1> pyresult(dims);
+ if (result.size() > 0) {
+ memcpy(pyresult.data(), &result[0], result.size() * sizeof(int));
+ }
+ return pyresult.pyobj();
+}
+
+const char *Py_path_in_path__doc__ =
+ "path_in_path(path_a, trans_a, path_b, trans_b)\n"
+ "--\n\n";
+
+static PyObject *Py_path_in_path(PyObject *self, PyObject *args)
+{
+ py::PathIterator a;
+ agg::trans_affine atrans;
+ py::PathIterator b;
+ agg::trans_affine btrans;
+ bool result;
+
+ if (!PyArg_ParseTuple(args,
+ "O&O&O&O&:path_in_path",
+ &convert_path,
+ &a,
+ &convert_trans_affine,
+ &atrans,
+ &convert_path,
+ &b,
+ &convert_trans_affine,
+ &btrans)) {
+ return NULL;
+ }
+
+ CALL_CPP("path_in_path", (result = path_in_path(a, atrans, b, btrans)));
+
+ if (result) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+}
+
+const char *Py_clip_path_to_rect__doc__ =
+ "clip_path_to_rect(path, rect, inside)\n"
+ "--\n\n";
+
+static PyObject *Py_clip_path_to_rect(PyObject *self, PyObject *args)
+{
+ py::PathIterator path;
+ agg::rect_d rect;
+ bool inside;
+ std::vector<Polygon> result;
+
+ if (!PyArg_ParseTuple(args,
+ "O&O&O&:clip_path_to_rect",
+ &convert_path,
+ &path,
+ &convert_rect,
+ &rect,
+ &convert_bool,
+ &inside)) {
+ return NULL;
+ }
+
+ CALL_CPP("clip_path_to_rect", (clip_path_to_rect(path, rect, inside, result)));
+
+ return convert_polygon_vector(result);
+}
+
+const char *Py_affine_transform__doc__ =
+ "affine_transform(points, trans)\n"
+ "--\n\n";
+
+static PyObject *Py_affine_transform(PyObject *self, PyObject *args)
+{
+ PyObject *vertices_obj;
+ agg::trans_affine trans;
+
+ if (!PyArg_ParseTuple(args,
+ "OO&:affine_transform",
+ &vertices_obj,
+ &convert_trans_affine,
+ &trans)) {
+ return NULL;
+ }
+
+ PyArrayObject* vertices_arr = (PyArrayObject *)PyArray_ContiguousFromAny(vertices_obj, NPY_DOUBLE, 1, 2);
+ if (vertices_arr == NULL) {
+ return NULL;
+ }
+
+ if (PyArray_NDIM(vertices_arr) == 2) {
+ numpy::array_view<double, 2> vertices(vertices_arr);
+ Py_DECREF(vertices_arr);
+
+ npy_intp dims[] = { (npy_intp)vertices.size(), 2 };
+ numpy::array_view<double, 2> result(dims);
+ CALL_CPP("affine_transform", (affine_transform_2d(vertices, trans, result)));
+ return result.pyobj();
+ } else { // PyArray_NDIM(vertices_arr) == 1
+ numpy::array_view<double, 1> vertices(vertices_arr);
+ Py_DECREF(vertices_arr);
+
+ npy_intp dims[] = { (npy_intp)vertices.size() };
+ numpy::array_view<double, 1> result(dims);
+ CALL_CPP("affine_transform", (affine_transform_1d(vertices, trans, result)));
+ return result.pyobj();
+ }
+}
+
+const char *Py_count_bboxes_overlapping_bbox__doc__ =
+ "count_bboxes_overlapping_bbox(bbox, bboxes)\n"
+ "--\n\n";
+
+static PyObject *Py_count_bboxes_overlapping_bbox(PyObject *self, PyObject *args)
+{
+ agg::rect_d bbox;
+ numpy::array_view<const double, 3> bboxes;
+ int result;
+
+ if (!PyArg_ParseTuple(args,
+ "O&O&:count_bboxes_overlapping_bbox",
+ &convert_rect,
+ &bbox,
+ &convert_bboxes,
+ &bboxes)) {
+ return NULL;
+ }
+
+ CALL_CPP("count_bboxes_overlapping_bbox",
+ (result = count_bboxes_overlapping_bbox(bbox, bboxes)));
+
+ return PyLong_FromLong(result);
+}
+
+const char *Py_path_intersects_path__doc__ =
+ "path_intersects_path(path1, path2, filled=False)\n"
+ "--\n\n";
+
+static PyObject *Py_path_intersects_path(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ py::PathIterator p1;
+ py::PathIterator p2;
+ agg::trans_affine t1;
+ agg::trans_affine t2;
+ int filled = 0;
+ const char *names[] = { "p1", "p2", "filled", NULL };
+ bool result;
+
+ if (!PyArg_ParseTupleAndKeywords(args,
+ kwds,
+ "O&O&i:path_intersects_path",
+ (char **)names,
+ &convert_path,
+ &p1,
+ &convert_path,
+ &p2,
+ &filled)) {
+ return NULL;
+ }
+
+ CALL_CPP("path_intersects_path", (result = path_intersects_path(p1, p2)));
+ if (filled) {
+ if (!result) {
+ CALL_CPP("path_intersects_path",
+ (result = path_in_path(p1, t1, p2, t2)));
+ }
+ if (!result) {
+ CALL_CPP("path_intersects_path",
+ (result = path_in_path(p2, t1, p1, t2)));
+ }
+ }
+
+ if (result) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+}
+
+const char *Py_path_intersects_rectangle__doc__ =
+ "path_intersects_rectangle("
+ "path, rect_x1, rect_y1, rect_x2, rect_y2, filled=False)\n"
+ "--\n\n";
+
+static PyObject *Py_path_intersects_rectangle(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ py::PathIterator path;
+ double rect_x1, rect_y1, rect_x2, rect_y2;
+ bool filled = false;
+ const char *names[] = { "path", "rect_x1", "rect_y1", "rect_x2", "rect_y2", "filled", NULL };
+ bool result;
+
+ if (!PyArg_ParseTupleAndKeywords(args,
+ kwds,
+ "O&dddd|O&:path_intersects_rectangle",
+ (char **)names,
+ &convert_path,
+ &path,
+ &rect_x1,
+ &rect_y1,
+ &rect_x2,
+ &rect_y2,
+ &convert_bool,
+ &filled)) {
+ return NULL;
+ }
+
+ CALL_CPP("path_intersects_rectangle", (result = path_intersects_rectangle(path, rect_x1, rect_y1, rect_x2, rect_y2, filled)));
+
+ if (result) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+}
+
+const char *Py_convert_path_to_polygons__doc__ =
+ "convert_path_to_polygons(path, trans, width=0, height=0)\n"
+ "--\n\n";
+
+static PyObject *Py_convert_path_to_polygons(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ py::PathIterator path;
+ agg::trans_affine trans;
+ double width = 0.0, height = 0.0;
+ int closed_only = 1;
+ std::vector<Polygon> result;
+ const char *names[] = { "path", "transform", "width", "height", "closed_only", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args,
+ kwds,
+ "O&O&|ddi:convert_path_to_polygons",
+ (char **)names,
+ &convert_path,
+ &path,
+ &convert_trans_affine,
+ &trans,
+ &width,
+ &height,
+ &closed_only)) {
+ return NULL;
+ }
+
+ CALL_CPP("convert_path_to_polygons",
+ (convert_path_to_polygons(path, trans, width, height, closed_only, result)));
+
+ return convert_polygon_vector(result);
+}
+
+const char *Py_cleanup_path__doc__ =
+ "cleanup_path("
+ "path, trans, remove_nans, clip_rect, snap_mode, stroke_width, simplify, "
+ "return_curves, sketch)\n"
+ "--\n\n";
+
+static PyObject *Py_cleanup_path(PyObject *self, PyObject *args)
+{
+ py::PathIterator path;
+ agg::trans_affine trans;
+ bool remove_nans;
+ agg::rect_d clip_rect;
+ e_snap_mode snap_mode;
+ double stroke_width;
+ PyObject *simplifyobj;
+ bool simplify = false;
+ bool return_curves;
+ SketchParams sketch;
+
+ if (!PyArg_ParseTuple(args,
+ "O&O&O&O&O&dOO&O&:cleanup_path",
+ &convert_path,
+ &path,
+ &convert_trans_affine,
+ &trans,
+ &convert_bool,
+ &remove_nans,
+ &convert_rect,
+ &clip_rect,
+ &convert_snap,
+ &snap_mode,
+ &stroke_width,
+ &simplifyobj,
+ &convert_bool,
+ &return_curves,
+ &convert_sketch_params,
+ &sketch)) {
+ return NULL;
+ }
+
+ if (simplifyobj == Py_None) {
+ simplify = path.should_simplify();
+ } else {
+ switch (PyObject_IsTrue(simplifyobj)) {
+ case 0: simplify = false; break;
+ case 1: simplify = true; break;
+ default: return NULL; // errored.
+ }
+ }
+
+ bool do_clip = (clip_rect.x1 < clip_rect.x2 && clip_rect.y1 < clip_rect.y2);
+
+ std::vector<double> vertices;
+ std::vector<npy_uint8> codes;
+
+ CALL_CPP("cleanup_path",
+ (cleanup_path(path,
+ trans,
+ remove_nans,
+ do_clip,
+ clip_rect,
+ snap_mode,
+ stroke_width,
+ simplify,
+ return_curves,
+ sketch,
+ vertices,
+ codes)));
+
+ size_t length = codes.size();
+
+ npy_intp vertices_dims[] = {(npy_intp)length, 2 };
+ numpy::array_view<double, 2> pyvertices(vertices_dims);
+
+ npy_intp codes_dims[] = {(npy_intp)length };
+ numpy::array_view<unsigned char, 1> pycodes(codes_dims);
+
+ memcpy(pyvertices.data(), &vertices[0], sizeof(double) * 2 * length);
+ memcpy(pycodes.data(), &codes[0], sizeof(unsigned char) * length);
+
+ return Py_BuildValue("NN", pyvertices.pyobj(), pycodes.pyobj());
+}
+
+const char *Py_convert_to_string__doc__ =
+ "convert_to_string("
+ "path, trans, clip_rect, simplify, sketch, precision, codes, postfix)\n"
+ "--\n\n"
+ "Convert *path* to a bytestring.\n"
+ "\n"
+ "The first five parameters (up to *sketch*) are interpreted as in\n"
+ "`.cleanup_path`. The following ones are detailed below.\n"
+ "\n"
+ "Parameters\n"
+ "----------\n"
+ "path : Path\n"
+ "trans : Transform or None\n"
+ "clip_rect : sequence of 4 floats, or None\n"
+ "simplify : bool\n"
+ "sketch : tuple of 3 floats, or None\n"
+ "precision : int\n"
+ " The precision used to \"%.*f\"-format the values. Trailing zeros\n"
+ " and decimal points are always removed. (precision=-1 is a special\n"
+ " case used to implement ttconv-back-compatible conversion.)\n"
+ "codes : sequence of 5 bytestrings\n"
+ " The bytes representation of each opcode (MOVETO, LINETO, CURVE3,\n"
+ " CURVE4, CLOSEPOLY), in that order. If the bytes for CURVE3 is\n"
+ " empty, quad segments are automatically converted to cubic ones\n"
+ " (this is used by backends such as pdf and ps, which do not support\n"
+ " quads).\n"
+ "postfix : bool\n"
+ " Whether the opcode comes after the values (True) or before (False).\n"
+ ;
+
+static PyObject *Py_convert_to_string(PyObject *self, PyObject *args)
+{
+ py::PathIterator path;
+ agg::trans_affine trans;
+ agg::rect_d cliprect;
+ PyObject *simplifyobj;
+ bool simplify = false;
+ SketchParams sketch;
+ int precision;
+ char *codes[5];
+ bool postfix;
+ std::string buffer;
+ bool status;
+
+ if (!PyArg_ParseTuple(args,
+ "O&O&O&OO&i(yyyyy)O&:convert_to_string",
+ &convert_path,
+ &path,
+ &convert_trans_affine,
+ &trans,
+ &convert_rect,
+ &cliprect,
+ &simplifyobj,
+ &convert_sketch_params,
+ &sketch,
+ &precision,
+ &codes[0],
+ &codes[1],
+ &codes[2],
+ &codes[3],
+ &codes[4],
+ &convert_bool,
+ &postfix)) {
+ return NULL;
+ }
+
+ if (simplifyobj == Py_None) {
+ simplify = path.should_simplify();
+ } else {
+ switch (PyObject_IsTrue(simplifyobj)) {
+ case 0: simplify = false; break;
+ case 1: simplify = true; break;
+ default: return NULL; // errored.
+ }
+ }
+
+ CALL_CPP("convert_to_string",
+ (status = convert_to_string(
+ path, trans, cliprect, simplify, sketch,
+ precision, codes, postfix, buffer)));
+
+ if (!status) {
+ PyErr_SetString(PyExc_ValueError, "Malformed path codes");
+ return NULL;
+ }
+
+ return PyBytes_FromStringAndSize(buffer.c_str(), buffer.size());
+}
+
+
+const char *Py_is_sorted_and_has_non_nan__doc__ =
+ "is_sorted_and_has_non_nan(array, /)\n"
+ "--\n\n"
+ "Return whether the 1D *array* is monotonically increasing, ignoring NaNs,\n"
+ "and has at least one non-nan value.";
+
+static PyObject *Py_is_sorted_and_has_non_nan(PyObject *self, PyObject *obj)
+{
+ bool result;
+
+ PyArrayObject *array = (PyArrayObject *)PyArray_FromAny(
+ obj, NULL, 1, 1, 0, NULL);
+
+ if (array == NULL) {
+ return NULL;
+ }
+
+ /* Handle just the most common types here, otherwise coerce to double */
+ switch (PyArray_TYPE(array)) {
+ case NPY_INT:
+ result = is_sorted_and_has_non_nan<npy_int>(array);
+ break;
+ case NPY_LONG:
+ result = is_sorted_and_has_non_nan<npy_long>(array);
+ break;
+ case NPY_LONGLONG:
+ result = is_sorted_and_has_non_nan<npy_longlong>(array);
+ break;
+ case NPY_FLOAT:
+ result = is_sorted_and_has_non_nan<npy_float>(array);
+ break;
+ case NPY_DOUBLE:
+ result = is_sorted_and_has_non_nan<npy_double>(array);
+ break;
+ default:
+ Py_DECREF(array);
+ array = (PyArrayObject *)PyArray_FromObject(obj, NPY_DOUBLE, 1, 1);
+ if (array == NULL) {
+ return NULL;
+ }
+ result = is_sorted_and_has_non_nan<npy_double>(array);
+ }
+
+ Py_DECREF(array);
+
+ if (result) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+}
+
+
+static PyMethodDef module_functions[] = {
+ {"point_in_path", (PyCFunction)Py_point_in_path, METH_VARARGS, Py_point_in_path__doc__},
+ {"points_in_path", (PyCFunction)Py_points_in_path, METH_VARARGS, Py_points_in_path__doc__},
+ {"update_path_extents", (PyCFunction)Py_update_path_extents, METH_VARARGS, Py_update_path_extents__doc__},
+ {"get_path_collection_extents", (PyCFunction)Py_get_path_collection_extents, METH_VARARGS, Py_get_path_collection_extents__doc__},
+ {"point_in_path_collection", (PyCFunction)Py_point_in_path_collection, METH_VARARGS, Py_point_in_path_collection__doc__},
+ {"path_in_path", (PyCFunction)Py_path_in_path, METH_VARARGS, Py_path_in_path__doc__},
+ {"clip_path_to_rect", (PyCFunction)Py_clip_path_to_rect, METH_VARARGS, Py_clip_path_to_rect__doc__},
+ {"affine_transform", (PyCFunction)Py_affine_transform, METH_VARARGS, Py_affine_transform__doc__},
+ {"count_bboxes_overlapping_bbox", (PyCFunction)Py_count_bboxes_overlapping_bbox, METH_VARARGS, Py_count_bboxes_overlapping_bbox__doc__},
+ {"path_intersects_path", (PyCFunction)Py_path_intersects_path, METH_VARARGS|METH_KEYWORDS, Py_path_intersects_path__doc__},
+ {"path_intersects_rectangle", (PyCFunction)Py_path_intersects_rectangle, METH_VARARGS|METH_KEYWORDS, Py_path_intersects_rectangle__doc__},
+ {"convert_path_to_polygons", (PyCFunction)Py_convert_path_to_polygons, METH_VARARGS|METH_KEYWORDS, Py_convert_path_to_polygons__doc__},
+ {"cleanup_path", (PyCFunction)Py_cleanup_path, METH_VARARGS, Py_cleanup_path__doc__},
+ {"convert_to_string", (PyCFunction)Py_convert_to_string, METH_VARARGS, Py_convert_to_string__doc__},
+ {"is_sorted_and_has_non_nan", (PyCFunction)Py_is_sorted_and_has_non_nan, METH_O, Py_is_sorted_and_has_non_nan__doc__},
+ {NULL}
+};
+
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT, "_path", NULL, 0, module_functions
+};
+
+PyMODINIT_FUNC PyInit__path(void)
+{
+ import_array();
+ return PyModule_Create(&moduledef);
+}
diff --git a/contrib/python/matplotlib/py3/src/_qhull_wrapper.cpp b/contrib/python/matplotlib/py3/src/_qhull_wrapper.cpp
new file mode 100644
index 0000000000..7e4f306305
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/_qhull_wrapper.cpp
@@ -0,0 +1,340 @@
+/*
+ * Wrapper module for libqhull, providing Delaunay triangulation.
+ *
+ * This module's methods should not be accessed directly. To obtain a Delaunay
+ * triangulation, construct an instance of the matplotlib.tri.Triangulation
+ * class without specifying a triangles array.
+ */
+#define PY_SSIZE_T_CLEAN
+#include "Python.h"
+#include "numpy_cpp.h"
+#ifdef _MSC_VER
+/* The Qhull header does not declare this as extern "C", but only MSVC seems to
+ * do name mangling on global variables. We thus need to declare this before
+ * the header so that it treats it correctly, and doesn't mangle the name. */
+extern "C" {
+extern const char qh_version[];
+}
+#endif
+#include "libqhull_r/qhull_ra.h"
+#include <cstdio>
+#include <vector>
+
+
+#ifndef MPL_DEVNULL
+#error "MPL_DEVNULL must be defined as the OS-equivalent of /dev/null"
+#endif
+
+#define STRINGIFY(x) STR(x)
+#define STR(x) #x
+
+
+static const char* qhull_error_msg[6] = {
+ "", /* 0 = qh_ERRnone */
+ "input inconsistency", /* 1 = qh_ERRinput */
+ "singular input data", /* 2 = qh_ERRsingular */
+ "precision error", /* 3 = qh_ERRprec */
+ "insufficient memory", /* 4 = qh_ERRmem */
+ "internal error"}; /* 5 = qh_ERRqhull */
+
+
+/* Return the indices of the 3 vertices that comprise the specified facet (i.e.
+ * triangle). */
+static void
+get_facet_vertices(qhT* qh, const facetT* facet, int indices[3])
+{
+ vertexT *vertex, **vertexp;
+ FOREACHvertex_(facet->vertices) {
+ *indices++ = qh_pointid(qh, vertex->point);
+ }
+}
+
+/* Return the indices of the 3 triangles that are neighbors of the specified
+ * facet (triangle). */
+static void
+get_facet_neighbours(const facetT* facet, std::vector<int>& tri_indices,
+ int indices[3])
+{
+ facetT *neighbor, **neighborp;
+ FOREACHneighbor_(facet) {
+ *indices++ = (neighbor->upperdelaunay ? -1 : tri_indices[neighbor->id]);
+ }
+}
+
+/* Return true if the specified points arrays contain at least 3 unique points,
+ * or false otherwise. */
+static bool
+at_least_3_unique_points(npy_intp npoints, const double* x, const double* y)
+{
+ int i;
+ const int unique1 = 0; /* First unique point has index 0. */
+ int unique2 = 0; /* Second unique point index is 0 until set. */
+
+ if (npoints < 3) {
+ return false;
+ }
+
+ for (i = 1; i < npoints; ++i) {
+ if (unique2 == 0) {
+ /* Looking for second unique point. */
+ if (x[i] != x[unique1] || y[i] != y[unique1]) {
+ unique2 = i;
+ }
+ }
+ else {
+ /* Looking for third unique point. */
+ if ( (x[i] != x[unique1] || y[i] != y[unique1]) &&
+ (x[i] != x[unique2] || y[i] != y[unique2]) ) {
+ /* 3 unique points found, with indices 0, unique2 and i. */
+ return true;
+ }
+ }
+ }
+
+ /* Run out of points before 3 unique points found. */
+ return false;
+}
+
+/* Holds on to info from Qhull so that it can be destructed automatically. */
+class QhullInfo {
+public:
+ QhullInfo(FILE *error_file, qhT* qh) {
+ this->error_file = error_file;
+ this->qh = qh;
+ }
+
+ ~QhullInfo() {
+ qh_freeqhull(this->qh, !qh_ALL);
+ int curlong, totlong; /* Memory remaining. */
+ qh_memfreeshort(this->qh, &curlong, &totlong);
+ if (curlong || totlong) {
+ PyErr_WarnEx(PyExc_RuntimeWarning,
+ "Qhull could not free all allocated memory", 1);
+ }
+
+ if (this->error_file != stderr) {
+ fclose(error_file);
+ }
+ }
+
+private:
+ FILE* error_file;
+ qhT* qh;
+};
+
+/* Delaunay implementation method.
+ * If hide_qhull_errors is true then qhull error messages are discarded;
+ * if it is false then they are written to stderr. */
+static PyObject*
+delaunay_impl(npy_intp npoints, const double* x, const double* y,
+ bool hide_qhull_errors)
+{
+ qhT qh_qh; /* qh variable type and name must be like */
+ qhT* qh = &qh_qh; /* this for Qhull macros to work correctly. */
+ facetT* facet;
+ int i, ntri, max_facet_id;
+ int exitcode; /* Value returned from qh_new_qhull(). */
+ const int ndim = 2;
+ double x_mean = 0.0;
+ double y_mean = 0.0;
+
+ QHULL_LIB_CHECK
+
+ /* Allocate points. */
+ std::vector<coordT> points(npoints * ndim);
+
+ /* Determine mean x, y coordinates. */
+ for (i = 0; i < npoints; ++i) {
+ x_mean += x[i];
+ y_mean += y[i];
+ }
+ x_mean /= npoints;
+ y_mean /= npoints;
+
+ /* Prepare points array to pass to qhull. */
+ for (i = 0; i < npoints; ++i) {
+ points[2*i ] = x[i] - x_mean;
+ points[2*i+1] = y[i] - y_mean;
+ }
+
+ /* qhull expects a FILE* to write errors to. */
+ FILE* error_file = NULL;
+ if (hide_qhull_errors) {
+ /* qhull errors are ignored by writing to OS-equivalent of /dev/null.
+ * Rather than have OS-specific code here, instead it is determined by
+ * setupext.py and passed in via the macro MPL_DEVNULL. */
+ error_file = fopen(STRINGIFY(MPL_DEVNULL), "w");
+ if (error_file == NULL) {
+ throw std::runtime_error("Could not open devnull");
+ }
+ }
+ else {
+ /* qhull errors written to stderr. */
+ error_file = stderr;
+ }
+
+ /* Perform Delaunay triangulation. */
+ QhullInfo info(error_file, qh);
+ qh_zero(qh, error_file);
+ exitcode = qh_new_qhull(qh, ndim, (int)npoints, points.data(), False,
+ (char*)"qhull d Qt Qbb Qc Qz", NULL, error_file);
+ if (exitcode != qh_ERRnone) {
+ PyErr_Format(PyExc_RuntimeError,
+ "Error in qhull Delaunay triangulation calculation: %s (exitcode=%d)%s",
+ qhull_error_msg[exitcode], exitcode,
+ hide_qhull_errors ? "; use python verbose option (-v) to see original qhull error." : "");
+ return NULL;
+ }
+
+ /* Split facets so that they only have 3 points each. */
+ qh_triangulate(qh);
+
+ /* Determine ntri and max_facet_id.
+ Note that libqhull uses macros to iterate through collections. */
+ ntri = 0;
+ FORALLfacets {
+ if (!facet->upperdelaunay) {
+ ++ntri;
+ }
+ }
+
+ max_facet_id = qh->facet_id - 1;
+
+ /* Create array to map facet id to triangle index. */
+ std::vector<int> tri_indices(max_facet_id+1);
+
+ /* Allocate Python arrays to return. */
+ npy_intp dims[2] = {ntri, 3};
+ numpy::array_view<int, ndim> triangles(dims);
+ int* triangles_ptr = triangles.data();
+
+ numpy::array_view<int, ndim> neighbors(dims);
+ int* neighbors_ptr = neighbors.data();
+
+ /* Determine triangles array and set tri_indices array. */
+ i = 0;
+ FORALLfacets {
+ if (!facet->upperdelaunay) {
+ int indices[3];
+ tri_indices[facet->id] = i++;
+ get_facet_vertices(qh, facet, indices);
+ *triangles_ptr++ = (facet->toporient ? indices[0] : indices[2]);
+ *triangles_ptr++ = indices[1];
+ *triangles_ptr++ = (facet->toporient ? indices[2] : indices[0]);
+ }
+ else {
+ tri_indices[facet->id] = -1;
+ }
+ }
+
+ /* Determine neighbors array. */
+ FORALLfacets {
+ if (!facet->upperdelaunay) {
+ int indices[3];
+ get_facet_neighbours(facet, tri_indices, indices);
+ *neighbors_ptr++ = (facet->toporient ? indices[2] : indices[0]);
+ *neighbors_ptr++ = (facet->toporient ? indices[0] : indices[2]);
+ *neighbors_ptr++ = indices[1];
+ }
+ }
+
+ PyObject* tuple = PyTuple_New(2);
+ if (tuple == 0) {
+ throw std::runtime_error("Failed to create Python tuple");
+ }
+
+ PyTuple_SET_ITEM(tuple, 0, triangles.pyobj());
+ PyTuple_SET_ITEM(tuple, 1, neighbors.pyobj());
+ return tuple;
+}
+
+/* Process Python arguments and call Delaunay implementation method. */
+static PyObject*
+delaunay(PyObject *self, PyObject *args)
+{
+ numpy::array_view<double, 1> xarray;
+ numpy::array_view<double, 1> yarray;
+ PyObject* ret;
+ npy_intp npoints;
+ const double* x;
+ const double* y;
+ int verbose = 0;
+
+ if (!PyArg_ParseTuple(args, "O&O&i:delaunay",
+ &xarray.converter_contiguous, &xarray,
+ &yarray.converter_contiguous, &yarray,
+ &verbose)) {
+ return NULL;
+ }
+
+ npoints = xarray.dim(0);
+ if (npoints != yarray.dim(0)) {
+ PyErr_SetString(PyExc_ValueError,
+ "x and y must be 1D arrays of the same length");
+ return NULL;
+ }
+
+ if (npoints < 3) {
+ PyErr_SetString(PyExc_ValueError,
+ "x and y arrays must have a length of at least 3");
+ return NULL;
+ }
+
+ x = xarray.data();
+ y = yarray.data();
+
+ if (!at_least_3_unique_points(npoints, x, y)) {
+ PyErr_SetString(PyExc_ValueError,
+ "x and y arrays must consist of at least 3 unique points");
+ return NULL;
+ }
+
+ CALL_CPP("qhull.delaunay",
+ (ret = delaunay_impl(npoints, x, y, verbose == 0)));
+
+ return ret;
+}
+
+/* Return qhull version string for assistance in debugging. */
+static PyObject*
+version(PyObject *self, PyObject *arg)
+{
+ return PyBytes_FromString(qh_version);
+}
+
+static PyMethodDef qhull_methods[] = {
+ {"delaunay", delaunay, METH_VARARGS,
+ "delaunay(x, y, verbose, /)\n"
+ "--\n\n"
+ "Compute a Delaunay triangulation.\n"
+ "\n"
+ "Parameters\n"
+ "----------\n"
+ "x, y : 1d arrays\n"
+ " The coordinates of the point set, which must consist of at least\n"
+ " three unique points.\n"
+ "verbose : int\n"
+ " Python's verbosity level.\n"
+ "\n"
+ "Returns\n"
+ "-------\n"
+ "triangles, neighbors : int arrays, shape (ntri, 3)\n"
+ " Indices of triangle vertices and indices of triangle neighbors.\n"
+ },
+ {"version", version, METH_NOARGS,
+ "version()\n--\n\n"
+ "Return the qhull version string."},
+ {NULL, NULL, 0, NULL}
+};
+
+static struct PyModuleDef qhull_module = {
+ PyModuleDef_HEAD_INIT,
+ "qhull", "Computing Delaunay triangulations.\n", -1, qhull_methods
+};
+
+PyMODINIT_FUNC
+PyInit__qhull(void)
+{
+ import_array();
+ return PyModule_Create(&qhull_module);
+}
diff --git a/contrib/python/matplotlib/py3/src/_tkagg.cpp b/contrib/python/matplotlib/py3/src/_tkagg.cpp
new file mode 100644
index 0000000000..5c36b3f07f
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/_tkagg.cpp
@@ -0,0 +1,370 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+// Where is PIL?
+//
+// Many years ago, Matplotlib used to include code from PIL (the Python Imaging
+// Library). Since then, the code has changed a lot - the organizing principle
+// and methods of operation are now quite different. Because our review of
+// the codebase showed that all the code that came from PIL was removed or
+// rewritten, we have removed the PIL licensing information. If you want PIL,
+// you can get it at https://python-pillow.org/
+
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+
+#ifdef _WIN32
+#define WIN32_DLL
+#endif
+#ifdef __CYGWIN__
+/*
+ * Unfortunately cygwin's libdl inherits restrictions from the underlying
+ * Windows OS, at least currently. Therefore, a symbol may be loaded from a
+ * module by dlsym() only if it is really located in the given module,
+ * dependencies are not included. So we have to use native WinAPI on Cygwin
+ * also.
+ */
+#define WIN32_DLL
+static inline PyObject *PyErr_SetFromWindowsErr(int ierr) {
+ PyErr_SetString(PyExc_OSError, "Call to EnumProcessModules failed");
+ return NULL;
+}
+#endif
+
+#ifdef WIN32_DLL
+#include <string>
+#include <windows.h>
+#include <commctrl.h>
+#define PSAPI_VERSION 1
+#include <psapi.h> // Must be linked with 'psapi' library
+#define dlsym GetProcAddress
+#else
+#include <dlfcn.h>
+#endif
+
+// Include our own excerpts from the Tcl / Tk headers
+#include "_tkmini.h"
+
+static int convert_voidptr(PyObject *obj, void *p)
+{
+ void **val = (void **)p;
+ *val = PyLong_AsVoidPtr(obj);
+ return *val != NULL ? 1 : !PyErr_Occurred();
+}
+
+// Global vars for Tk functions. We load these symbols from the tkinter
+// extension module or loaded Tk libraries at run-time.
+static Tk_FindPhoto_t TK_FIND_PHOTO;
+static Tk_PhotoPutBlock_t TK_PHOTO_PUT_BLOCK;
+// Global vars for Tcl functions. We load these symbols from the tkinter
+// extension module or loaded Tcl libraries at run-time.
+static Tcl_SetVar_t TCL_SETVAR;
+
+static PyObject *mpl_tk_blit(PyObject *self, PyObject *args)
+{
+ Tcl_Interp *interp;
+ char const *photo_name;
+ int height, width;
+ unsigned char *data_ptr;
+ int comp_rule;
+ int put_retval;
+ int o0, o1, o2, o3;
+ int x1, x2, y1, y2;
+ Tk_PhotoHandle photo;
+ Tk_PhotoImageBlock block;
+ if (!PyArg_ParseTuple(args, "O&s(iiO&)i(iiii)(iiii):blit",
+ convert_voidptr, &interp, &photo_name,
+ &height, &width, convert_voidptr, &data_ptr,
+ &comp_rule,
+ &o0, &o1, &o2, &o3,
+ &x1, &x2, &y1, &y2)) {
+ goto exit;
+ }
+ if (!(photo = TK_FIND_PHOTO(interp, photo_name))) {
+ PyErr_SetString(PyExc_ValueError, "Failed to extract Tk_PhotoHandle");
+ goto exit;
+ }
+ if (0 > y1 || y1 > y2 || y2 > height || 0 > x1 || x1 > x2 || x2 > width) {
+ PyErr_SetString(PyExc_ValueError, "Attempting to draw out of bounds");
+ goto exit;
+ }
+ if (comp_rule != TK_PHOTO_COMPOSITE_OVERLAY && comp_rule != TK_PHOTO_COMPOSITE_SET) {
+ PyErr_SetString(PyExc_ValueError, "Invalid comp_rule argument");
+ goto exit;
+ }
+
+ Py_BEGIN_ALLOW_THREADS
+ block.pixelPtr = data_ptr + 4 * ((height - y2) * width + x1);
+ block.width = x2 - x1;
+ block.height = y2 - y1;
+ block.pitch = 4 * width;
+ block.pixelSize = 4;
+ block.offset[0] = o0;
+ block.offset[1] = o1;
+ block.offset[2] = o2;
+ block.offset[3] = o3;
+ put_retval = TK_PHOTO_PUT_BLOCK(
+ interp, photo, &block, x1, height - y2, x2 - x1, y2 - y1, comp_rule);
+ Py_END_ALLOW_THREADS
+ if (put_retval == TCL_ERROR) {
+ return PyErr_NoMemory();
+ }
+
+exit:
+ if (PyErr_Occurred()) {
+ return NULL;
+ } else {
+ Py_RETURN_NONE;
+ }
+}
+
+#ifdef WIN32_DLL
+LRESULT CALLBACK
+DpiSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
+ UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
+{
+ switch (uMsg) {
+ case WM_DPICHANGED:
+ // This function is a subclassed window procedure, and so is run during
+ // the Tcl/Tk event loop. Unfortunately, Tkinter has a *second* lock on
+ // Tcl threading that is not exposed publicly, but is currently taken
+ // while we're in the window procedure. So while we can take the GIL to
+ // call Python code, we must not also call *any* Tk code from Python.
+ // So stay with Tcl calls in C only.
+ {
+ // This variable naming must match the name used in
+ // lib/matplotlib/backends/_backend_tk.py:FigureManagerTk.
+ std::string var_name("window_dpi");
+ var_name += std::to_string((unsigned long long)hwnd);
+
+ // X is high word, Y is low word, but they are always equal.
+ std::string dpi = std::to_string(LOWORD(wParam));
+
+ Tcl_Interp* interp = (Tcl_Interp*)dwRefData;
+ TCL_SETVAR(interp, var_name.c_str(), dpi.c_str(), 0);
+ }
+ return 0;
+ case WM_NCDESTROY:
+ RemoveWindowSubclass(hwnd, DpiSubclassProc, uIdSubclass);
+ break;
+ }
+
+ return DefSubclassProc(hwnd, uMsg, wParam, lParam);
+}
+#endif
+
+static PyObject*
+mpl_tk_enable_dpi_awareness(PyObject* self, PyObject*const* args,
+ Py_ssize_t nargs)
+{
+ if (nargs != 2) {
+ return PyErr_Format(PyExc_TypeError,
+ "enable_dpi_awareness() takes 2 positional "
+ "arguments but %zd were given",
+ nargs);
+ }
+
+#ifdef WIN32_DLL
+ HWND frame_handle = NULL;
+ Tcl_Interp *interp = NULL;
+
+ if (!convert_voidptr(args[0], &frame_handle)) {
+ return NULL;
+ }
+ if (!convert_voidptr(args[1], &interp)) {
+ return NULL;
+ }
+
+#ifdef _DPI_AWARENESS_CONTEXTS_
+ HMODULE user32 = LoadLibrary("user32.dll");
+
+ typedef DPI_AWARENESS_CONTEXT (WINAPI *GetWindowDpiAwarenessContext_t)(HWND);
+ GetWindowDpiAwarenessContext_t GetWindowDpiAwarenessContextPtr =
+ (GetWindowDpiAwarenessContext_t)GetProcAddress(
+ user32, "GetWindowDpiAwarenessContext");
+ if (GetWindowDpiAwarenessContextPtr == NULL) {
+ FreeLibrary(user32);
+ Py_RETURN_FALSE;
+ }
+
+ typedef BOOL (WINAPI *AreDpiAwarenessContextsEqual_t)(DPI_AWARENESS_CONTEXT,
+ DPI_AWARENESS_CONTEXT);
+ AreDpiAwarenessContextsEqual_t AreDpiAwarenessContextsEqualPtr =
+ (AreDpiAwarenessContextsEqual_t)GetProcAddress(
+ user32, "AreDpiAwarenessContextsEqual");
+ if (AreDpiAwarenessContextsEqualPtr == NULL) {
+ FreeLibrary(user32);
+ Py_RETURN_FALSE;
+ }
+
+ DPI_AWARENESS_CONTEXT ctx = GetWindowDpiAwarenessContextPtr(frame_handle);
+ bool per_monitor = (
+ AreDpiAwarenessContextsEqualPtr(
+ ctx, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) ||
+ AreDpiAwarenessContextsEqualPtr(
+ ctx, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE));
+
+ if (per_monitor) {
+ // Per monitor aware means we need to handle WM_DPICHANGED by wrapping
+ // the Window Procedure, and the Python side needs to trace the Tk
+ // window_dpi variable stored on interp.
+ SetWindowSubclass(frame_handle, DpiSubclassProc, 0, (DWORD_PTR)interp);
+ }
+ FreeLibrary(user32);
+ return PyBool_FromLong(per_monitor);
+#endif
+#endif
+
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef functions[] = {
+ { "blit", (PyCFunction)mpl_tk_blit, METH_VARARGS },
+ { "enable_dpi_awareness", (PyCFunction)mpl_tk_enable_dpi_awareness,
+ METH_FASTCALL },
+ { NULL, NULL } /* sentinel */
+};
+
+// Functions to fill global Tcl/Tk function pointers by dynamic loading.
+
+template <class T>
+bool load_tcl_tk(T lib)
+{
+ // Try to fill Tcl/Tk global vars with function pointers. Return whether
+ // all of them have been filled.
+ if (auto ptr = dlsym(lib, "Tcl_SetVar")) {
+ TCL_SETVAR = (Tcl_SetVar_t)ptr;
+ }
+ if (auto ptr = dlsym(lib, "Tk_FindPhoto")) {
+ TK_FIND_PHOTO = (Tk_FindPhoto_t)ptr;
+ }
+ if (auto ptr = dlsym(lib, "Tk_PhotoPutBlock")) {
+ TK_PHOTO_PUT_BLOCK = (Tk_PhotoPutBlock_t)ptr;
+ }
+ return TCL_SETVAR && TK_FIND_PHOTO && TK_PHOTO_PUT_BLOCK;
+}
+
+#ifdef WIN32_DLL
+
+/* On Windows, we can't load the tkinter module to get the Tcl/Tk symbols,
+ * because Windows does not load symbols into the library name-space of
+ * importing modules. So, knowing that tkinter has already been imported by
+ * Python, we scan all modules in the running process for the Tcl/Tk function
+ * names.
+ */
+
+void load_tkinter_funcs(void)
+{
+ HANDLE process = GetCurrentProcess(); // Pseudo-handle, doesn't need closing.
+ HMODULE* modules = NULL;
+ DWORD size;
+ if (!EnumProcessModules(process, NULL, 0, &size)) {
+ PyErr_SetFromWindowsErr(0);
+ goto exit;
+ }
+ if (!(modules = static_cast<HMODULE*>(malloc(size)))) {
+ PyErr_NoMemory();
+ goto exit;
+ }
+ if (!EnumProcessModules(process, modules, size, &size)) {
+ PyErr_SetFromWindowsErr(0);
+ goto exit;
+ }
+ for (unsigned i = 0; i < size / sizeof(HMODULE); ++i) {
+ if (load_tcl_tk(modules[i])) {
+ return;
+ }
+ }
+exit:
+ free(modules);
+}
+
+#else // not Windows
+
+/*
+ * On Unix, we can get the Tk symbols from the tkinter module, because tkinter
+ * uses these symbols, and the symbols are therefore visible in the tkinter
+ * dynamic library (module).
+ */
+
+void load_tkinter_funcs(void)
+{
+ // Load tkinter global funcs from tkinter compiled module.
+ void *main_program = NULL, *tkinter_lib = NULL;
+ PyObject *module = NULL, *py_path = NULL, *py_path_b = NULL;
+ char *path;
+
+ // Try loading from the main program namespace first.
+ main_program = dlopen(NULL, RTLD_LAZY);
+ if (load_tcl_tk(main_program)) {
+ goto exit;
+ }
+ // Clear exception triggered when we didn't find symbols above.
+ PyErr_Clear();
+
+ // Handle PyPy first, as that import will correctly fail on CPython.
+ module = PyImport_ImportModule("_tkinter.tklib_cffi"); // PyPy
+ if (!module) {
+ PyErr_Clear();
+ module = PyImport_ImportModule("_tkinter"); // CPython
+ }
+ if (!(module &&
+ (py_path = PyObject_GetAttrString(module, "__file__")) &&
+ (py_path_b = PyUnicode_EncodeFSDefault(py_path)) &&
+ (path = PyBytes_AsString(py_path_b)))) {
+ goto exit;
+ }
+ tkinter_lib = dlopen(path, RTLD_LAZY);
+ if (!tkinter_lib) {
+ PyErr_SetString(PyExc_RuntimeError, dlerror());
+ goto exit;
+ }
+ if (load_tcl_tk(tkinter_lib)) {
+ goto exit;
+ }
+
+exit:
+ // We don't need to keep a reference open as the main program & tkinter
+ // have been imported. Try to close each library separately (otherwise the
+ // second dlclose could clear a dlerror from the first dlclose).
+ bool raised_dlerror = false;
+ if (main_program && dlclose(main_program) && !raised_dlerror) {
+ PyErr_SetString(PyExc_RuntimeError, dlerror());
+ raised_dlerror = true;
+ }
+ if (tkinter_lib && dlclose(tkinter_lib) && !raised_dlerror) {
+ PyErr_SetString(PyExc_RuntimeError, dlerror());
+ raised_dlerror = true;
+ }
+ Py_XDECREF(module);
+ Py_XDECREF(py_path);
+ Py_XDECREF(py_path_b);
+}
+#endif // end not Windows
+
+static PyModuleDef _tkagg_module = {
+ PyModuleDef_HEAD_INIT, "_tkagg", NULL, -1, functions
+};
+
+PyMODINIT_FUNC PyInit__tkagg(void)
+{
+ load_tkinter_funcs();
+ PyObject *type, *value, *traceback;
+ PyErr_Fetch(&type, &value, &traceback);
+ // Always raise ImportError (normalizing a previously set exception if
+ // needed) to interact properly with backend auto-fallback.
+ if (value) {
+ PyErr_NormalizeException(&type, &value, &traceback);
+ PyErr_SetObject(PyExc_ImportError, value);
+ return NULL;
+ } else if (!TCL_SETVAR) {
+ PyErr_SetString(PyExc_ImportError, "Failed to load Tcl_SetVar");
+ return NULL;
+ } else if (!TK_FIND_PHOTO) {
+ PyErr_SetString(PyExc_ImportError, "Failed to load Tk_FindPhoto");
+ return NULL;
+ } else if (!TK_PHOTO_PUT_BLOCK) {
+ PyErr_SetString(PyExc_ImportError, "Failed to load Tk_PhotoPutBlock");
+ return NULL;
+ }
+ return PyModule_Create(&_tkagg_module);
+}
diff --git a/contrib/python/matplotlib/py3/src/_tkmini.h b/contrib/python/matplotlib/py3/src/_tkmini.h
new file mode 100644
index 0000000000..85f245815e
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/_tkmini.h
@@ -0,0 +1,110 @@
+/* Small excerpts from the Tcl / Tk 8.6 headers
+ *
+ * License terms copied from:
+ * http://www.tcl.tk/software/tcltk/license.html
+ * as of 20 May 2016.
+ *
+ * Copyright (c) 1987-1994 The Regents of the University of California.
+ * Copyright (c) 1993-1996 Lucent Technologies.
+ * Copyright (c) 1994-1998 Sun Microsystems, Inc.
+ * Copyright (c) 1998-2000 by Scriptics Corporation.
+ * Copyright (c) 2002 by Kevin B. Kenny. All rights reserved.
+ *
+ * This software is copyrighted by the Regents of the University
+ * of California, Sun Microsystems, Inc., Scriptics Corporation,
+ * and other parties. The following terms apply to all files
+ * associated with the software unless explicitly disclaimed in
+ * individual files.
+ *
+ * The authors hereby grant permission to use, copy, modify,
+ * distribute, and license this software and its documentation
+ * for any purpose, provided that existing copyright notices are
+ * retained in all copies and that this notice is included
+ * verbatim in any distributions. No written agreement, license,
+ * or royalty fee is required for any of the authorized uses.
+ * Modifications to this software may be copyrighted by their
+ * authors and need not follow the licensing terms described
+ * here, provided that the new terms are clearly indicated on
+ * the first page of each file where they apply.
+ *
+ * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO
+ * ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS
+ * SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN
+ * IF THE AUTHORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON
+ * AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE NO
+ * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
+ * ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ * GOVERNMENT USE: If you are acquiring this software on behalf
+ * of the U.S. government, the Government shall have only
+ * "Restricted Rights" in the software and related documentation
+ * as defined in the Federal Acquisition Regulations (FARs) in
+ * Clause 52.227.19 (c) (2). If you are acquiring the software
+ * on behalf of the Department of Defense, the software shall be
+ * classified as "Commercial Computer Software" and the
+ * Government shall have only "Restricted Rights" as defined in
+ * Clause 252.227-7013 (c) (1) of DFARs. Notwithstanding the
+ * foregoing, the authors grant the U.S. Government and others
+ * acting in its behalf permission to use and distribute the
+ * software in accordance with the terms specified in this
+ * license
+ */
+
+/*
+ * Unless otherwise noted, these definitions are stable from Tcl / Tk 8.5
+ * through Tck / Tk master as of 21 May 2016
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Users of versions of Tcl >= 8.6 encouraged to treat Tcl_Interp as an opaque
+ * pointer. The following definition results when TCL_NO_DEPRECATED defined.
+ */
+typedef struct Tcl_Interp Tcl_Interp;
+
+/* Tk header excerpts */
+
+typedef void *Tk_PhotoHandle;
+
+typedef struct Tk_PhotoImageBlock
+{
+ unsigned char *pixelPtr;
+ int width;
+ int height;
+ int pitch;
+ int pixelSize;
+ int offset[4];
+} Tk_PhotoImageBlock;
+
+#define TK_PHOTO_COMPOSITE_OVERLAY 0 // apply transparency rules pixel-wise
+#define TK_PHOTO_COMPOSITE_SET 1 // set image buffer directly
+#define TCL_OK 0
+#define TCL_ERROR 1
+
+/* Typedefs derived from function signatures in Tk header */
+/* Tk_FindPhoto typedef */
+typedef Tk_PhotoHandle (*Tk_FindPhoto_t) (Tcl_Interp *interp, const char
+ *imageName);
+/* Tk_PhotoPutBLock typedef */
+typedef int (*Tk_PhotoPutBlock_t) (Tcl_Interp *interp, Tk_PhotoHandle handle,
+ Tk_PhotoImageBlock *blockPtr, int x, int y,
+ int width, int height, int compRule);
+
+/* Typedefs derived from function signatures in Tcl header */
+/* Tcl_SetVar typedef */
+typedef const char *(*Tcl_SetVar_t)(Tcl_Interp *interp, const char *varName,
+ const char *newValue, int flags);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/contrib/python/matplotlib/py3/src/_ttconv.cpp b/contrib/python/matplotlib/py3/src/_ttconv.cpp
new file mode 100644
index 0000000000..72fdfba696
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/_ttconv.cpp
@@ -0,0 +1,96 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+/*
+ _ttconv.c
+
+ Python wrapper for TrueType conversion library in ../ttconv.
+ */
+#include "mplutils.h"
+
+#include <pybind11/pybind11.h>
+#include "ttconv/pprdrv.h"
+#include <vector>
+
+namespace py = pybind11;
+
+/**
+ * An implementation of TTStreamWriter that writes to a Python
+ * file-like object.
+ */
+class PythonFileWriter : public TTStreamWriter
+{
+ py::function _write_method;
+
+ public:
+ PythonFileWriter(py::object& file_object)
+ : _write_method(file_object.attr("write")) {}
+
+ virtual void write(const char *a)
+ {
+ PyObject* decoded = PyUnicode_DecodeLatin1(a, strlen(a), "");
+ if (decoded == NULL) {
+ throw py::error_already_set();
+ }
+ _write_method(py::handle(decoded));
+ Py_DECREF(decoded);
+ }
+};
+
+static void convert_ttf_to_ps(
+ const char *filename,
+ py::object &output,
+ int fonttype,
+ py::iterable* glyph_ids)
+{
+ PythonFileWriter output_(output);
+
+ std::vector<int> glyph_ids_;
+ if (glyph_ids) {
+ for (py::handle glyph_id: *glyph_ids) {
+ glyph_ids_.push_back(glyph_id.cast<int>());
+ }
+ }
+
+ if (fonttype != 3 && fonttype != 42) {
+ throw py::value_error(
+ "fonttype must be either 3 (raw Postscript) or 42 (embedded Truetype)");
+ }
+
+ try
+ {
+ insert_ttfont(filename, output_, static_cast<font_type_enum>(fonttype), glyph_ids_);
+ }
+ catch (TTException &e)
+ {
+ throw std::runtime_error(e.getMessage());
+ }
+ catch (...)
+ {
+ throw std::runtime_error("Unknown C++ exception");
+ }
+}
+
+PYBIND11_MODULE(_ttconv, m) {
+ m.doc() = "Module to handle converting and subsetting TrueType "
+ "fonts to Postscript Type 3, Postscript Type 42 and "
+ "Pdf Type 3 fonts.";
+ m.def("convert_ttf_to_ps", &convert_ttf_to_ps,
+ py::arg("filename"),
+ py::arg("output"),
+ py::arg("fonttype"),
+ py::arg("glyph_ids") = py::none(),
+ "Converts the Truetype font into a Type 3 or Type 42 Postscript font, "
+ "optionally subsetting the font to only the desired set of characters.\n"
+ "\n"
+ "filename is the path to a TTF font file.\n"
+ "output is a Python file-like object with a write method that the Postscript "
+ "font data will be written to.\n"
+ "fonttype may be either 3 or 42. Type 3 is a \"raw Postscript\" font. "
+ "Type 42 is an embedded Truetype font. Glyph subsetting is not supported "
+ "for Type 42 fonts within this module (needs to be done externally).\n"
+ "glyph_ids (optional) is a list of glyph ids (integers) to keep when "
+ "subsetting to a Type 3 font. If glyph_ids is not provided or is None, "
+ "then all glyphs will be included. If any of the glyphs specified are "
+ "composite glyphs, then the component glyphs will also be included."
+ );
+}
diff --git a/contrib/python/matplotlib/py3/src/agg_workaround.h b/contrib/python/matplotlib/py3/src/agg_workaround.h
new file mode 100644
index 0000000000..4762195192
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/agg_workaround.h
@@ -0,0 +1,85 @@
+#ifndef MPL_AGG_WORKAROUND_H
+#define MPL_AGG_WORKAROUND_H
+
+#include "agg_pixfmt_rgba.h"
+
+/**********************************************************************
+ WORKAROUND: This class is to workaround a bug in Agg SVN where the
+ blending of RGBA32 pixels does not preserve enough precision
+*/
+
+template<class ColorT, class Order>
+struct fixed_blender_rgba_pre : agg::conv_rgba_pre<ColorT, Order>
+{
+ typedef ColorT color_type;
+ typedef Order order_type;
+ typedef typename color_type::value_type value_type;
+ typedef typename color_type::calc_type calc_type;
+ typedef typename color_type::long_type long_type;
+ enum base_scale_e
+ {
+ base_shift = color_type::base_shift,
+ base_mask = color_type::base_mask
+ };
+
+ //--------------------------------------------------------------------
+ static AGG_INLINE void blend_pix(value_type* p,
+ value_type cr, value_type cg, value_type cb,
+ value_type alpha, agg::cover_type cover)
+ {
+ blend_pix(p,
+ color_type::mult_cover(cr, cover),
+ color_type::mult_cover(cg, cover),
+ color_type::mult_cover(cb, cover),
+ color_type::mult_cover(alpha, cover));
+ }
+
+ //--------------------------------------------------------------------
+ static AGG_INLINE void blend_pix(value_type* p,
+ value_type cr, value_type cg, value_type cb,
+ value_type alpha)
+ {
+ alpha = base_mask - alpha;
+ p[Order::R] = (value_type)(((p[Order::R] * alpha) >> base_shift) + cr);
+ p[Order::G] = (value_type)(((p[Order::G] * alpha) >> base_shift) + cg);
+ p[Order::B] = (value_type)(((p[Order::B] * alpha) >> base_shift) + cb);
+ p[Order::A] = (value_type)(base_mask - ((alpha * (base_mask - p[Order::A])) >> base_shift));
+ }
+};
+
+
+template<class ColorT, class Order>
+struct fixed_blender_rgba_plain : agg::conv_rgba_plain<ColorT, Order>
+{
+ typedef ColorT color_type;
+ typedef Order order_type;
+ typedef typename color_type::value_type value_type;
+ typedef typename color_type::calc_type calc_type;
+ typedef typename color_type::long_type long_type;
+ enum base_scale_e { base_shift = color_type::base_shift };
+
+ //--------------------------------------------------------------------
+ static AGG_INLINE void blend_pix(value_type* p,
+ value_type cr, value_type cg, value_type cb, value_type alpha, agg::cover_type cover)
+ {
+ blend_pix(p, cr, cg, cb, color_type::mult_cover(alpha, cover));
+ }
+
+ //--------------------------------------------------------------------
+ static AGG_INLINE void blend_pix(value_type* p,
+ value_type cr, value_type cg, value_type cb, value_type alpha)
+ {
+ if(alpha == 0) return;
+ calc_type a = p[Order::A];
+ calc_type r = p[Order::R] * a;
+ calc_type g = p[Order::G] * a;
+ calc_type b = p[Order::B] * a;
+ a = ((alpha + a) << base_shift) - alpha * a;
+ p[Order::A] = (value_type)(a >> base_shift);
+ p[Order::R] = (value_type)((((cr << base_shift) - r) * alpha + (r << base_shift)) / a);
+ p[Order::G] = (value_type)((((cg << base_shift) - g) * alpha + (g << base_shift)) / a);
+ p[Order::B] = (value_type)((((cb << base_shift) - b) * alpha + (b << base_shift)) / a);
+ }
+};
+
+#endif
diff --git a/contrib/python/matplotlib/py3/src/array.h b/contrib/python/matplotlib/py3/src/array.h
new file mode 100644
index 0000000000..47d8299554
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/array.h
@@ -0,0 +1,80 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+/* Utilities to create scalars and empty arrays that behave like the
+ Numpy array wrappers in numpy_cpp.h */
+
+#ifndef MPL_SCALAR_H
+#define MPL_SCALAR_H
+
+namespace array
+{
+
+template <typename T, int ND>
+class scalar
+{
+ public:
+ T m_value;
+
+ scalar(const T value) : m_value(value)
+ {
+ }
+
+ T &operator()(int i, int j = 0, int k = 0)
+ {
+ return m_value;
+ }
+
+ const T &operator()(int i, int j = 0, int k = 0) const
+ {
+ return m_value;
+ }
+
+ int dim(size_t i)
+ {
+ return 1;
+ }
+
+ size_t size()
+ {
+ return 1;
+ }
+};
+
+template <typename T>
+class empty
+{
+ public:
+ typedef empty<T> sub_t;
+
+ empty()
+ {
+ }
+
+ T &operator()(int i, int j = 0, int k = 0)
+ {
+ throw std::runtime_error("Accessed empty array");
+ }
+
+ const T &operator()(int i, int j = 0, int k = 0) const
+ {
+ throw std::runtime_error("Accessed empty array");
+ }
+
+ sub_t operator[](int i) const
+ {
+ return empty<T>();
+ }
+
+ int dim(size_t i) const
+ {
+ return 0;
+ }
+
+ size_t size() const
+ {
+ return 0;
+ }
+};
+}
+
+#endif
diff --git a/contrib/python/matplotlib/py3/src/checkdep_freetype2.c b/contrib/python/matplotlib/py3/src/checkdep_freetype2.c
new file mode 100644
index 0000000000..8d9d8ca24a
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/checkdep_freetype2.c
@@ -0,0 +1,19 @@
+#ifdef __has_include
+ #if !__has_include(<ft2build.h>)
+ #error "FreeType version 2.3 or higher is required. \
+You may unset the system_freetype entry in mplsetup.cfg to let Matplotlib download it."
+ #endif
+#endif
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+#define XSTR(x) STR(x)
+#define STR(x) #x
+
+#pragma message("Compiling with FreeType version " \
+ XSTR(FREETYPE_MAJOR) "." XSTR(FREETYPE_MINOR) "." XSTR(FREETYPE_PATCH) ".")
+#if FREETYPE_MAJOR << 16 + FREETYPE_MINOR << 8 + FREETYPE_PATCH < 0x020300
+ #error "FreeType version 2.3 or higher is required. \
+You may unset the system_freetype entry in mplsetup.cfg to let Matplotlib download it."
+#endif
diff --git a/contrib/python/matplotlib/py3/src/ft2font.cpp b/contrib/python/matplotlib/py3/src/ft2font.cpp
new file mode 100644
index 0000000000..9750413741
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/ft2font.cpp
@@ -0,0 +1,840 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+#define NO_IMPORT_ARRAY
+
+#include <algorithm>
+#include <iterator>
+#include <sstream>
+#include <stdexcept>
+#include <string>
+
+#include "ft2font.h"
+#include "mplutils.h"
+#include "numpy_cpp.h"
+#include "py_exceptions.h"
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846264338328
+#endif
+
+/**
+ To improve the hinting of the fonts, this code uses a hack
+ presented here:
+
+ http://agg.sourceforge.net/antigrain.com/research/font_rasterization/index.html
+
+ The idea is to limit the effect of hinting in the x-direction, while
+ preserving hinting in the y-direction. Since freetype does not
+ support this directly, the dpi in the x-direction is set higher than
+ in the y-direction, which affects the hinting grid. Then, a global
+ transform is placed on the font to shrink it back to the desired
+ size. While it is a bit surprising that the dpi setting affects
+ hinting, whereas the global transform does not, this is documented
+ behavior of FreeType, and therefore hopefully unlikely to change.
+ The FreeType 2 tutorial says:
+
+ NOTE: The transformation is applied to every glyph that is
+ loaded through FT_Load_Glyph and is completely independent of
+ any hinting process. This means that you won't get the same
+ results if you load a glyph at the size of 24 pixels, or a glyph
+ at the size at 12 pixels scaled by 2 through a transform,
+ because the hints will have been computed differently (except
+ you have disabled hints).
+ */
+
+FT_Library _ft2Library;
+
+// FreeType error codes; loaded as per fterror.h.
+static char const* ft_error_string(FT_Error error) {
+#undef __FTERRORS_H__
+#define FT_ERROR_START_LIST switch (error) {
+#define FT_ERRORDEF( e, v, s ) case v: return s;
+#define FT_ERROR_END_LIST default: return NULL; }
+#include FT_ERRORS_H
+}
+
+void throw_ft_error(std::string message, FT_Error error) {
+ char const* s = ft_error_string(error);
+ std::ostringstream os("");
+ if (s) {
+ os << message << " (" << s << "; error code 0x" << std::hex << error << ")";
+ } else { // Should not occur, but don't add another error from failed lookup.
+ os << message << " (error code 0x" << std::hex << error << ")";
+ }
+ throw std::runtime_error(os.str());
+}
+
+FT2Image::FT2Image() : m_dirty(true), m_buffer(NULL), m_width(0), m_height(0)
+{
+}
+
+FT2Image::FT2Image(unsigned long width, unsigned long height)
+ : m_dirty(true), m_buffer(NULL), m_width(0), m_height(0)
+{
+ resize(width, height);
+}
+
+FT2Image::~FT2Image()
+{
+ delete[] m_buffer;
+}
+
+void FT2Image::resize(long width, long height)
+{
+ if (width <= 0) {
+ width = 1;
+ }
+ if (height <= 0) {
+ height = 1;
+ }
+ size_t numBytes = width * height;
+
+ if ((unsigned long)width != m_width || (unsigned long)height != m_height) {
+ if (numBytes > m_width * m_height) {
+ delete[] m_buffer;
+ m_buffer = NULL;
+ m_buffer = new unsigned char[numBytes];
+ }
+
+ m_width = (unsigned long)width;
+ m_height = (unsigned long)height;
+ }
+
+ if (numBytes && m_buffer) {
+ memset(m_buffer, 0, numBytes);
+ }
+
+ m_dirty = true;
+}
+
+void FT2Image::draw_bitmap(FT_Bitmap *bitmap, FT_Int x, FT_Int y)
+{
+ FT_Int image_width = (FT_Int)m_width;
+ FT_Int image_height = (FT_Int)m_height;
+ FT_Int char_width = bitmap->width;
+ FT_Int char_height = bitmap->rows;
+
+ FT_Int x1 = std::min(std::max(x, 0), image_width);
+ FT_Int y1 = std::min(std::max(y, 0), image_height);
+ FT_Int x2 = std::min(std::max(x + char_width, 0), image_width);
+ FT_Int y2 = std::min(std::max(y + char_height, 0), image_height);
+
+ FT_Int x_start = std::max(0, -x);
+ FT_Int y_offset = y1 - std::max(0, -y);
+
+ if (bitmap->pixel_mode == FT_PIXEL_MODE_GRAY) {
+ for (FT_Int i = y1; i < y2; ++i) {
+ unsigned char *dst = m_buffer + (i * image_width + x1);
+ unsigned char *src = bitmap->buffer + (((i - y_offset) * bitmap->pitch) + x_start);
+ for (FT_Int j = x1; j < x2; ++j, ++dst, ++src)
+ *dst |= *src;
+ }
+ } else if (bitmap->pixel_mode == FT_PIXEL_MODE_MONO) {
+ for (FT_Int i = y1; i < y2; ++i) {
+ unsigned char *dst = m_buffer + (i * image_width + x1);
+ unsigned char *src = bitmap->buffer + ((i - y_offset) * bitmap->pitch);
+ for (FT_Int j = x1; j < x2; ++j, ++dst) {
+ int x = (j - x1 + x_start);
+ int val = *(src + (x >> 3)) & (1 << (7 - (x & 0x7)));
+ *dst = val ? 255 : *dst;
+ }
+ }
+ } else {
+ throw std::runtime_error("Unknown pixel mode");
+ }
+
+ m_dirty = true;
+}
+
+void FT2Image::draw_rect(unsigned long x0, unsigned long y0, unsigned long x1, unsigned long y1)
+{
+ if (x0 > m_width || x1 > m_width || y0 > m_height || y1 > m_height) {
+ throw std::runtime_error("Rect coords outside image bounds");
+ }
+
+ size_t top = y0 * m_width;
+ size_t bottom = y1 * m_width;
+ for (size_t i = x0; i < x1 + 1; ++i) {
+ m_buffer[i + top] = 255;
+ m_buffer[i + bottom] = 255;
+ }
+
+ for (size_t j = y0 + 1; j < y1; ++j) {
+ m_buffer[x0 + j * m_width] = 255;
+ m_buffer[x1 + j * m_width] = 255;
+ }
+
+ m_dirty = true;
+}
+
+void
+FT2Image::draw_rect_filled(unsigned long x0, unsigned long y0, unsigned long x1, unsigned long y1)
+{
+ x0 = std::min(x0, m_width);
+ y0 = std::min(y0, m_height);
+ x1 = std::min(x1 + 1, m_width);
+ y1 = std::min(y1 + 1, m_height);
+
+ for (size_t j = y0; j < y1; j++) {
+ for (size_t i = x0; i < x1; i++) {
+ m_buffer[i + j * m_width] = 255;
+ }
+ }
+
+ m_dirty = true;
+}
+
+static void ft_glyph_warn(FT_ULong charcode)
+{
+ PyObject *text_helpers = NULL, *tmp = NULL;
+ if (!(text_helpers = PyImport_ImportModule("matplotlib._text_helpers")) ||
+ !(tmp = PyObject_CallMethod(text_helpers, "warn_on_missing_glyph", "k", charcode))) {
+ goto exit;
+ }
+exit:
+ Py_XDECREF(text_helpers);
+ Py_XDECREF(tmp);
+ if (PyErr_Occurred()) {
+ throw py::exception();
+ }
+}
+
+static FT_UInt
+ft_get_char_index_or_warn(FT_Face face, FT_ULong charcode, bool warn = true)
+{
+ FT_UInt glyph_index = FT_Get_Char_Index(face, charcode);
+ if (glyph_index) {
+ return glyph_index;
+ }
+ if (warn) {
+ ft_glyph_warn(charcode);
+ }
+ return 0;
+}
+
+// ft_outline_decomposer should be passed to FT_Outline_Decompose. On the
+// first pass, vertices and codes are set to NULL, and index is simply
+// incremented for each vertex that should be inserted, so that it is set, at
+// the end, to the total number of vertices. On a second pass, vertices and
+// codes should point to correctly sized arrays, and index set again to zero,
+// to get fill vertices and codes with the outline decomposition.
+struct ft_outline_decomposer
+{
+ int index;
+ double* vertices;
+ unsigned char* codes;
+};
+
+static int
+ft_outline_move_to(FT_Vector const* to, void* user)
+{
+ ft_outline_decomposer* d = reinterpret_cast<ft_outline_decomposer*>(user);
+ if (d->codes) {
+ if (d->index) {
+ // Appending CLOSEPOLY is important to make patheffects work.
+ *(d->vertices++) = 0;
+ *(d->vertices++) = 0;
+ *(d->codes++) = CLOSEPOLY;
+ }
+ *(d->vertices++) = to->x * (1. / 64.);
+ *(d->vertices++) = to->y * (1. / 64.);
+ *(d->codes++) = MOVETO;
+ }
+ d->index += d->index ? 2 : 1;
+ return 0;
+}
+
+static int
+ft_outline_line_to(FT_Vector const* to, void* user)
+{
+ ft_outline_decomposer* d = reinterpret_cast<ft_outline_decomposer*>(user);
+ if (d->codes) {
+ *(d->vertices++) = to->x * (1. / 64.);
+ *(d->vertices++) = to->y * (1. / 64.);
+ *(d->codes++) = LINETO;
+ }
+ d->index++;
+ return 0;
+}
+
+static int
+ft_outline_conic_to(FT_Vector const* control, FT_Vector const* to, void* user)
+{
+ ft_outline_decomposer* d = reinterpret_cast<ft_outline_decomposer*>(user);
+ if (d->codes) {
+ *(d->vertices++) = control->x * (1. / 64.);
+ *(d->vertices++) = control->y * (1. / 64.);
+ *(d->vertices++) = to->x * (1. / 64.);
+ *(d->vertices++) = to->y * (1. / 64.);
+ *(d->codes++) = CURVE3;
+ *(d->codes++) = CURVE3;
+ }
+ d->index += 2;
+ return 0;
+}
+
+static int
+ft_outline_cubic_to(
+ FT_Vector const* c1, FT_Vector const* c2, FT_Vector const* to, void* user)
+{
+ ft_outline_decomposer* d = reinterpret_cast<ft_outline_decomposer*>(user);
+ if (d->codes) {
+ *(d->vertices++) = c1->x * (1. / 64.);
+ *(d->vertices++) = c1->y * (1. / 64.);
+ *(d->vertices++) = c2->x * (1. / 64.);
+ *(d->vertices++) = c2->y * (1. / 64.);
+ *(d->vertices++) = to->x * (1. / 64.);
+ *(d->vertices++) = to->y * (1. / 64.);
+ *(d->codes++) = CURVE4;
+ *(d->codes++) = CURVE4;
+ *(d->codes++) = CURVE4;
+ }
+ d->index += 3;
+ return 0;
+}
+
+static FT_Outline_Funcs ft_outline_funcs = {
+ ft_outline_move_to,
+ ft_outline_line_to,
+ ft_outline_conic_to,
+ ft_outline_cubic_to};
+
+PyObject*
+FT2Font::get_path()
+{
+ if (!face->glyph) {
+ PyErr_SetString(PyExc_RuntimeError, "No glyph loaded");
+ return NULL;
+ }
+ ft_outline_decomposer decomposer = {};
+ if (FT_Error error =
+ FT_Outline_Decompose(
+ &face->glyph->outline, &ft_outline_funcs, &decomposer)) {
+ PyErr_Format(PyExc_RuntimeError,
+ "FT_Outline_Decompose failed with error 0x%x", error);
+ return NULL;
+ }
+ if (!decomposer.index) { // Don't append CLOSEPOLY to null glyphs.
+ npy_intp vertices_dims[2] = { 0, 2 };
+ numpy::array_view<double, 2> vertices(vertices_dims);
+ npy_intp codes_dims[1] = { 0 };
+ numpy::array_view<unsigned char, 1> codes(codes_dims);
+ return Py_BuildValue("NN", vertices.pyobj(), codes.pyobj());
+ }
+ npy_intp vertices_dims[2] = { decomposer.index + 1, 2 };
+ numpy::array_view<double, 2> vertices(vertices_dims);
+ npy_intp codes_dims[1] = { decomposer.index + 1 };
+ numpy::array_view<unsigned char, 1> codes(codes_dims);
+ decomposer.index = 0;
+ decomposer.vertices = vertices.data();
+ decomposer.codes = codes.data();
+ if (FT_Error error =
+ FT_Outline_Decompose(
+ &face->glyph->outline, &ft_outline_funcs, &decomposer)) {
+ PyErr_Format(PyExc_RuntimeError,
+ "FT_Outline_Decompose failed with error 0x%x", error);
+ return NULL;
+ }
+ *(decomposer.vertices++) = 0;
+ *(decomposer.vertices++) = 0;
+ *(decomposer.codes++) = CLOSEPOLY;
+ return Py_BuildValue("NN", vertices.pyobj(), codes.pyobj());
+}
+
+FT2Font::FT2Font(FT_Open_Args &open_args,
+ long hinting_factor_,
+ std::vector<FT2Font *> &fallback_list)
+ : image(), face(NULL)
+{
+ clear();
+
+ FT_Error error = FT_Open_Face(_ft2Library, &open_args, 0, &face);
+ if (error) {
+ throw_ft_error("Can not load face", error);
+ }
+
+ // set default kerning factor to 0, i.e., no kerning manipulation
+ kerning_factor = 0;
+
+ // set a default fontsize 12 pt at 72dpi
+ hinting_factor = hinting_factor_;
+
+ error = FT_Set_Char_Size(face, 12 * 64, 0, 72 * (unsigned int)hinting_factor, 72);
+ if (error) {
+ FT_Done_Face(face);
+ throw_ft_error("Could not set the fontsize", error);
+ }
+
+ if (open_args.stream != NULL) {
+ face->face_flags |= FT_FACE_FLAG_EXTERNAL_STREAM;
+ }
+
+ FT_Matrix transform = { 65536 / hinting_factor, 0, 0, 65536 };
+ FT_Set_Transform(face, &transform, 0);
+
+ // Set fallbacks
+ std::copy(fallback_list.begin(), fallback_list.end(), std::back_inserter(fallbacks));
+}
+
+FT2Font::~FT2Font()
+{
+ for (size_t i = 0; i < glyphs.size(); i++) {
+ FT_Done_Glyph(glyphs[i]);
+ }
+
+ if (face) {
+ FT_Done_Face(face);
+ }
+}
+
+void FT2Font::clear()
+{
+ pen.x = 0;
+ pen.y = 0;
+
+ for (size_t i = 0; i < glyphs.size(); i++) {
+ FT_Done_Glyph(glyphs[i]);
+ }
+
+ glyphs.clear();
+ glyph_to_font.clear();
+ char_to_font.clear();
+
+ for (size_t i = 0; i < fallbacks.size(); i++) {
+ fallbacks[i]->clear();
+ }
+}
+
+void FT2Font::set_size(double ptsize, double dpi)
+{
+ FT_Error error = FT_Set_Char_Size(
+ face, (FT_F26Dot6)(ptsize * 64), 0, (FT_UInt)(dpi * hinting_factor), (FT_UInt)dpi);
+ if (error) {
+ throw_ft_error("Could not set the fontsize", error);
+ }
+ FT_Matrix transform = { 65536 / hinting_factor, 0, 0, 65536 };
+ FT_Set_Transform(face, &transform, 0);
+
+ for (size_t i = 0; i < fallbacks.size(); i++) {
+ fallbacks[i]->set_size(ptsize, dpi);
+ }
+}
+
+void FT2Font::set_charmap(int i)
+{
+ if (i >= face->num_charmaps) {
+ throw std::runtime_error("i exceeds the available number of char maps");
+ }
+ FT_CharMap charmap = face->charmaps[i];
+ if (FT_Error error = FT_Set_Charmap(face, charmap)) {
+ throw_ft_error("Could not set the charmap", error);
+ }
+}
+
+void FT2Font::select_charmap(unsigned long i)
+{
+ if (FT_Error error = FT_Select_Charmap(face, (FT_Encoding)i)) {
+ throw_ft_error("Could not set the charmap", error);
+ }
+}
+
+int FT2Font::get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, bool fallback = false)
+{
+ if (fallback && glyph_to_font.find(left) != glyph_to_font.end() &&
+ glyph_to_font.find(right) != glyph_to_font.end()) {
+ FT2Font *left_ft_object = glyph_to_font[left];
+ FT2Font *right_ft_object = glyph_to_font[right];
+ if (left_ft_object != right_ft_object) {
+ // we do not know how to do kerning between different fonts
+ return 0;
+ }
+ // if left_ft_object is the same as right_ft_object,
+ // do the exact same thing which set_text does.
+ return right_ft_object->get_kerning(left, right, mode, false);
+ }
+ else
+ {
+ FT_Vector delta;
+ return get_kerning(left, right, mode, delta);
+ }
+}
+
+int FT2Font::get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, FT_Vector &delta)
+{
+ if (!FT_HAS_KERNING(face)) {
+ return 0;
+ }
+
+ if (!FT_Get_Kerning(face, left, right, mode, &delta)) {
+ return (int)(delta.x) / (hinting_factor << kerning_factor);
+ } else {
+ return 0;
+ }
+}
+
+void FT2Font::set_kerning_factor(int factor)
+{
+ kerning_factor = factor;
+ for (size_t i = 0; i < fallbacks.size(); i++) {
+ fallbacks[i]->set_kerning_factor(factor);
+ }
+}
+
+void FT2Font::set_text(
+ size_t N, uint32_t *codepoints, double angle, FT_Int32 flags, std::vector<double> &xys)
+{
+ FT_Matrix matrix; /* transformation matrix */
+
+ angle = angle * (2 * M_PI / 360.0);
+
+ // this computes width and height in subpixels so we have to multiply by 64
+ double cosangle = cos(angle) * 0x10000L;
+ double sinangle = sin(angle) * 0x10000L;
+
+ matrix.xx = (FT_Fixed)cosangle;
+ matrix.xy = (FT_Fixed)-sinangle;
+ matrix.yx = (FT_Fixed)sinangle;
+ matrix.yy = (FT_Fixed)cosangle;
+
+ clear();
+
+ bbox.xMin = bbox.yMin = 32000;
+ bbox.xMax = bbox.yMax = -32000;
+
+ FT_UInt previous = 0;
+ FT2Font *previous_ft_object = NULL;
+
+ for (size_t n = 0; n < N; n++) {
+ FT_UInt glyph_index = 0;
+ FT_BBox glyph_bbox;
+ FT_Pos last_advance;
+
+ FT_Error charcode_error, glyph_error;
+ FT2Font *ft_object_with_glyph = this;
+ bool was_found = load_char_with_fallback(ft_object_with_glyph, glyph_index, glyphs,
+ char_to_font, glyph_to_font, codepoints[n], flags,
+ charcode_error, glyph_error, false);
+ if (!was_found) {
+ ft_glyph_warn((FT_ULong)codepoints[n]);
+
+ // render missing glyph tofu
+ // come back to top-most font
+ ft_object_with_glyph = this;
+ char_to_font[codepoints[n]] = ft_object_with_glyph;
+ glyph_to_font[glyph_index] = ft_object_with_glyph;
+ ft_object_with_glyph->load_glyph(glyph_index, flags, ft_object_with_glyph, false);
+ }
+
+ // retrieve kerning distance and move pen position
+ if ((ft_object_with_glyph == previous_ft_object) && // if both fonts are the same
+ ft_object_with_glyph->has_kerning() && // if the font knows how to kern
+ previous && glyph_index // and we really have 2 glyphs
+ ) {
+ FT_Vector delta;
+ pen.x += ft_object_with_glyph->get_kerning(previous, glyph_index, FT_KERNING_DEFAULT, delta);
+ }
+
+ // extract glyph image and store it in our table
+ FT_Glyph &thisGlyph = glyphs[glyphs.size() - 1];
+
+ last_advance = ft_object_with_glyph->get_face()->glyph->advance.x;
+ FT_Glyph_Transform(thisGlyph, 0, &pen);
+ FT_Glyph_Transform(thisGlyph, &matrix, 0);
+ xys.push_back(pen.x);
+ xys.push_back(pen.y);
+
+ FT_Glyph_Get_CBox(thisGlyph, FT_GLYPH_BBOX_SUBPIXELS, &glyph_bbox);
+
+ bbox.xMin = std::min(bbox.xMin, glyph_bbox.xMin);
+ bbox.xMax = std::max(bbox.xMax, glyph_bbox.xMax);
+ bbox.yMin = std::min(bbox.yMin, glyph_bbox.yMin);
+ bbox.yMax = std::max(bbox.yMax, glyph_bbox.yMax);
+
+ pen.x += last_advance;
+
+ previous = glyph_index;
+ previous_ft_object = ft_object_with_glyph;
+
+ }
+
+ FT_Vector_Transform(&pen, &matrix);
+ advance = pen.x;
+
+ if (bbox.xMin > bbox.xMax) {
+ bbox.xMin = bbox.yMin = bbox.xMax = bbox.yMax = 0;
+ }
+}
+
+void FT2Font::load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool fallback = false)
+{
+ // if this is parent FT2Font, cache will be filled in 2 ways:
+ // 1. set_text was previously called
+ // 2. set_text was not called and fallback was enabled
+ if (fallback && char_to_font.find(charcode) != char_to_font.end()) {
+ ft_object = char_to_font[charcode];
+ // since it will be assigned to ft_object anyway
+ FT2Font *throwaway = NULL;
+ ft_object->load_char(charcode, flags, throwaway, false);
+ } else if (fallback) {
+ FT_UInt final_glyph_index;
+ FT_Error charcode_error, glyph_error;
+ FT2Font *ft_object_with_glyph = this;
+ bool was_found = load_char_with_fallback(ft_object_with_glyph, final_glyph_index, glyphs, char_to_font,
+ glyph_to_font, charcode, flags, charcode_error, glyph_error, true);
+ if (!was_found) {
+ ft_glyph_warn(charcode);
+ if (charcode_error) {
+ throw_ft_error("Could not load charcode", charcode_error);
+ }
+ else if (glyph_error) {
+ throw_ft_error("Could not load charcode", glyph_error);
+ }
+ }
+ ft_object = ft_object_with_glyph;
+ } else {
+ ft_object = this;
+ FT_UInt glyph_index = ft_get_char_index_or_warn(face, (FT_ULong)charcode);
+
+ if (FT_Error error = FT_Load_Glyph(face, glyph_index, flags)) {
+ throw_ft_error("Could not load charcode", error);
+ }
+ FT_Glyph thisGlyph;
+ if (FT_Error error = FT_Get_Glyph(face->glyph, &thisGlyph)) {
+ throw_ft_error("Could not get glyph", error);
+ }
+ glyphs.push_back(thisGlyph);
+ }
+}
+
+
+bool FT2Font::get_char_fallback_index(FT_ULong charcode, int& index) const
+{
+ FT_UInt glyph_index = FT_Get_Char_Index(face, charcode);
+ if (glyph_index) {
+ // -1 means the host has the char and we do not need to fallback
+ index = -1;
+ return true;
+ } else {
+ int inner_index = 0;
+ bool was_found;
+
+ for (size_t i = 0; i < fallbacks.size(); ++i) {
+ // TODO handle recursion somehow!
+ was_found = fallbacks[i]->get_char_fallback_index(charcode, inner_index);
+ if (was_found) {
+ index = i;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+
+bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph,
+ FT_UInt &final_glyph_index,
+ std::vector<FT_Glyph> &parent_glyphs,
+ std::unordered_map<long, FT2Font *> &parent_char_to_font,
+ std::unordered_map<FT_UInt, FT2Font *> &parent_glyph_to_font,
+ long charcode,
+ FT_Int32 flags,
+ FT_Error &charcode_error,
+ FT_Error &glyph_error,
+ bool override = false)
+{
+ FT_UInt glyph_index = FT_Get_Char_Index(face, charcode);
+
+ if (glyph_index || override) {
+ charcode_error = FT_Load_Glyph(face, glyph_index, flags);
+ if (charcode_error) {
+ return false;
+ }
+
+ FT_Glyph thisGlyph;
+ glyph_error = FT_Get_Glyph(face->glyph, &thisGlyph);
+ if (glyph_error) {
+ return false;
+ }
+
+ final_glyph_index = glyph_index;
+
+ // cache the result for future
+ // need to store this for anytime a character is loaded from a parent
+ // FT2Font object or to generate a mapping of individual characters to fonts
+ ft_object_with_glyph = this;
+ parent_glyph_to_font[final_glyph_index] = this;
+ parent_char_to_font[charcode] = this;
+ parent_glyphs.push_back(thisGlyph);
+ return true;
+ }
+
+ else {
+ for (size_t i = 0; i < fallbacks.size(); ++i) {
+ bool was_found = fallbacks[i]->load_char_with_fallback(
+ ft_object_with_glyph, final_glyph_index, parent_glyphs, parent_char_to_font,
+ parent_glyph_to_font, charcode, flags, charcode_error, glyph_error, override);
+ if (was_found) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
+
+void FT2Font::load_glyph(FT_UInt glyph_index,
+ FT_Int32 flags,
+ FT2Font *&ft_object,
+ bool fallback = false)
+{
+ // cache is only for parent FT2Font
+ if (fallback && glyph_to_font.find(glyph_index) != glyph_to_font.end()) {
+ ft_object = glyph_to_font[glyph_index];
+ } else {
+ ft_object = this;
+ }
+
+ ft_object->load_glyph(glyph_index, flags);
+}
+
+void FT2Font::load_glyph(FT_UInt glyph_index, FT_Int32 flags)
+{
+ if (FT_Error error = FT_Load_Glyph(face, glyph_index, flags)) {
+ throw_ft_error("Could not load glyph", error);
+ }
+ FT_Glyph thisGlyph;
+ if (FT_Error error = FT_Get_Glyph(face->glyph, &thisGlyph)) {
+ throw_ft_error("Could not get glyph", error);
+ }
+ glyphs.push_back(thisGlyph);
+}
+
+FT_UInt FT2Font::get_char_index(FT_ULong charcode, bool fallback = false)
+{
+ FT2Font *ft_object = NULL;
+ if (fallback && char_to_font.find(charcode) != char_to_font.end()) {
+ // fallback denotes whether we want to search fallback list.
+ // should call set_text/load_char_with_fallback to parent FT2Font before
+ // wanting to use fallback list here. (since that populates the cache)
+ ft_object = char_to_font[charcode];
+ } else {
+ // set as self
+ ft_object = this;
+ }
+
+ // historically, get_char_index never raises a warning
+ return ft_get_char_index_or_warn(ft_object->get_face(), charcode, false);
+}
+
+void FT2Font::get_width_height(long *width, long *height)
+{
+ *width = advance;
+ *height = bbox.yMax - bbox.yMin;
+}
+
+long FT2Font::get_descent()
+{
+ return -bbox.yMin;
+}
+
+void FT2Font::get_bitmap_offset(long *x, long *y)
+{
+ *x = bbox.xMin;
+ *y = 0;
+}
+
+void FT2Font::draw_glyphs_to_bitmap(bool antialiased)
+{
+ long width = (bbox.xMax - bbox.xMin) / 64 + 2;
+ long height = (bbox.yMax - bbox.yMin) / 64 + 2;
+
+ image.resize(width, height);
+
+ for (size_t n = 0; n < glyphs.size(); n++) {
+ FT_Error error = FT_Glyph_To_Bitmap(
+ &glyphs[n], antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, 0, 1);
+ if (error) {
+ throw_ft_error("Could not convert glyph to bitmap", error);
+ }
+
+ FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyphs[n];
+ // now, draw to our target surface (convert position)
+
+ // bitmap left and top in pixel, string bbox in subpixel
+ FT_Int x = (FT_Int)(bitmap->left - (bbox.xMin * (1. / 64.)));
+ FT_Int y = (FT_Int)((bbox.yMax * (1. / 64.)) - bitmap->top + 1);
+
+ image.draw_bitmap(&bitmap->bitmap, x, y);
+ }
+}
+
+void FT2Font::get_xys(bool antialiased, std::vector<double> &xys)
+{
+ for (size_t n = 0; n < glyphs.size(); n++) {
+
+ FT_Error error = FT_Glyph_To_Bitmap(
+ &glyphs[n], antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, 0, 1);
+ if (error) {
+ throw_ft_error("Could not convert glyph to bitmap", error);
+ }
+
+ FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyphs[n];
+
+ // bitmap left and top in pixel, string bbox in subpixel
+ FT_Int x = (FT_Int)(bitmap->left - bbox.xMin * (1. / 64.));
+ FT_Int y = (FT_Int)(bbox.yMax * (1. / 64.) - bitmap->top + 1);
+ // make sure the index is non-neg
+ x = x < 0 ? 0 : x;
+ y = y < 0 ? 0 : y;
+ xys.push_back(x);
+ xys.push_back(y);
+ }
+}
+
+void FT2Font::draw_glyph_to_bitmap(FT2Image &im, int x, int y, size_t glyphInd, bool antialiased)
+{
+ FT_Vector sub_offset;
+ sub_offset.x = 0; // int((xd - (double)x) * 64.0);
+ sub_offset.y = 0; // int((yd - (double)y) * 64.0);
+
+ if (glyphInd >= glyphs.size()) {
+ throw std::runtime_error("glyph num is out of range");
+ }
+
+ FT_Error error = FT_Glyph_To_Bitmap(
+ &glyphs[glyphInd],
+ antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO,
+ &sub_offset, // additional translation
+ 1 // destroy image
+ );
+ if (error) {
+ throw_ft_error("Could not convert glyph to bitmap", error);
+ }
+
+ FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyphs[glyphInd];
+
+ im.draw_bitmap(&bitmap->bitmap, x + bitmap->left, y);
+}
+
+void FT2Font::get_glyph_name(unsigned int glyph_number, char *buffer, bool fallback = false)
+{
+ if (fallback && glyph_to_font.find(glyph_number) != glyph_to_font.end()) {
+ // cache is only for parent FT2Font
+ FT2Font *ft_object = glyph_to_font[glyph_number];
+ ft_object->get_glyph_name(glyph_number, buffer, false);
+ return;
+ }
+ if (!FT_HAS_GLYPH_NAMES(face)) {
+ /* Note that this generated name must match the name that
+ is generated by ttconv in ttfont_CharStrings_getname. */
+ PyOS_snprintf(buffer, 128, "uni%08x", glyph_number);
+ } else {
+ if (FT_Error error = FT_Get_Glyph_Name(face, glyph_number, buffer, 128)) {
+ throw_ft_error("Could not get glyph names", error);
+ }
+ }
+}
+
+long FT2Font::get_name_index(char *name)
+{
+ return FT_Get_Name_Index(face, (FT_String *)name);
+}
diff --git a/contrib/python/matplotlib/py3/src/ft2font.h b/contrib/python/matplotlib/py3/src/ft2font.h
new file mode 100644
index 0000000000..d566c3f9bd
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/ft2font.h
@@ -0,0 +1,159 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+/* A python interface to FreeType */
+#pragma once
+#ifndef MPL_FT2FONT_H
+#define MPL_FT2FONT_H
+#include <vector>
+#include <stdint.h>
+#include <unordered_map>
+
+extern "C" {
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_GLYPH_H
+#include FT_OUTLINE_H
+#include FT_SFNT_NAMES_H
+#include FT_TYPE1_TABLES_H
+#include FT_TRUETYPE_TABLES_H
+}
+
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+
+/*
+ By definition, FT_FIXED as 2 16bit values stored in a single long.
+ */
+#define FIXED_MAJOR(val) (signed short)((val & 0xffff0000) >> 16)
+#define FIXED_MINOR(val) (unsigned short)(val & 0xffff)
+
+// the FreeType string rendered into a width, height buffer
+class FT2Image
+{
+ public:
+ FT2Image();
+ FT2Image(unsigned long width, unsigned long height);
+ virtual ~FT2Image();
+
+ void resize(long width, long height);
+ void draw_bitmap(FT_Bitmap *bitmap, FT_Int x, FT_Int y);
+ void draw_rect(unsigned long x0, unsigned long y0, unsigned long x1, unsigned long y1);
+ void draw_rect_filled(unsigned long x0, unsigned long y0, unsigned long x1, unsigned long y1);
+
+ unsigned char *get_buffer()
+ {
+ return m_buffer;
+ }
+ unsigned long get_width()
+ {
+ return m_width;
+ }
+ unsigned long get_height()
+ {
+ return m_height;
+ }
+
+ private:
+ bool m_dirty;
+ unsigned char *m_buffer;
+ unsigned long m_width;
+ unsigned long m_height;
+
+ // prevent copying
+ FT2Image(const FT2Image &);
+ FT2Image &operator=(const FT2Image &);
+};
+
+extern FT_Library _ft2Library;
+
+class FT2Font
+{
+
+ public:
+ FT2Font(FT_Open_Args &open_args, long hinting_factor, std::vector<FT2Font *> &fallback_list);
+ virtual ~FT2Font();
+ void clear();
+ void set_size(double ptsize, double dpi);
+ void set_charmap(int i);
+ void select_charmap(unsigned long i);
+ void set_text(
+ size_t N, uint32_t *codepoints, double angle, FT_Int32 flags, std::vector<double> &xys);
+ int get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, bool fallback);
+ int get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, FT_Vector &delta);
+ void set_kerning_factor(int factor);
+ void load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool fallback);
+ bool load_char_with_fallback(FT2Font *&ft_object_with_glyph,
+ FT_UInt &final_glyph_index,
+ std::vector<FT_Glyph> &parent_glyphs,
+ std::unordered_map<long, FT2Font *> &parent_char_to_font,
+ std::unordered_map<FT_UInt, FT2Font *> &parent_glyph_to_font,
+ long charcode,
+ FT_Int32 flags,
+ FT_Error &charcode_error,
+ FT_Error &glyph_error,
+ bool override);
+ void load_glyph(FT_UInt glyph_index, FT_Int32 flags, FT2Font *&ft_object, bool fallback);
+ void load_glyph(FT_UInt glyph_index, FT_Int32 flags);
+ void get_width_height(long *width, long *height);
+ void get_bitmap_offset(long *x, long *y);
+ long get_descent();
+ // TODO: Since we know the size of the array upfront, we probably don't
+ // need to dynamically allocate like this
+ void get_xys(bool antialiased, std::vector<double> &xys);
+ void draw_glyphs_to_bitmap(bool antialiased);
+ void draw_glyph_to_bitmap(FT2Image &im, int x, int y, size_t glyphInd, bool antialiased);
+ void get_glyph_name(unsigned int glyph_number, char *buffer, bool fallback);
+ long get_name_index(char *name);
+ FT_UInt get_char_index(FT_ULong charcode, bool fallback);
+ PyObject* get_path();
+ bool get_char_fallback_index(FT_ULong charcode, int& index) const;
+
+ FT_Face const &get_face() const
+ {
+ return face;
+ }
+
+ FT2Image &get_image()
+ {
+ return image;
+ }
+ FT_Glyph const &get_last_glyph() const
+ {
+ return glyphs.back();
+ }
+ size_t get_last_glyph_index() const
+ {
+ return glyphs.size() - 1;
+ }
+ size_t get_num_glyphs() const
+ {
+ return glyphs.size();
+ }
+ long get_hinting_factor() const
+ {
+ return hinting_factor;
+ }
+ FT_Bool has_kerning() const
+ {
+ return FT_HAS_KERNING(face);
+ }
+
+ private:
+ FT2Image image;
+ FT_Face face;
+ FT_Vector pen; /* untransformed origin */
+ std::vector<FT_Glyph> glyphs;
+ std::vector<FT2Font *> fallbacks;
+ std::unordered_map<FT_UInt, FT2Font *> glyph_to_font;
+ std::unordered_map<long, FT2Font *> char_to_font;
+ FT_BBox bbox;
+ FT_Pos advance;
+ long hinting_factor;
+ int kerning_factor;
+
+ // prevent copying
+ FT2Font(const FT2Font &);
+ FT2Font &operator=(const FT2Font &);
+};
+
+#endif
diff --git a/contrib/python/matplotlib/py3/src/ft2font_wrapper.cpp b/contrib/python/matplotlib/py3/src/ft2font_wrapper.cpp
new file mode 100644
index 0000000000..7888a9c212
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/ft2font_wrapper.cpp
@@ -0,0 +1,1578 @@
+#include "mplutils.h"
+#include "ft2font.h"
+#include "py_converters.h"
+#include "py_exceptions.h"
+#include "numpy_cpp.h"
+
+// From Python
+#include <structmember.h>
+
+#include <set>
+#include <algorithm>
+
+#define STRINGIFY(s) XSTRINGIFY(s)
+#define XSTRINGIFY(s) #s
+
+static PyObject *convert_xys_to_array(std::vector<double> &xys)
+{
+ npy_intp dims[] = {(npy_intp)xys.size() / 2, 2 };
+ if (dims[0] > 0) {
+ return PyArray_SimpleNewFromData(2, dims, NPY_DOUBLE, &xys[0]);
+ } else {
+ return PyArray_SimpleNew(2, dims, NPY_DOUBLE);
+ }
+}
+
+/**********************************************************************
+ * FT2Image
+ * */
+
+typedef struct
+{
+ PyObject_HEAD
+ FT2Image *x;
+ Py_ssize_t shape[2];
+ Py_ssize_t strides[2];
+ Py_ssize_t suboffsets[2];
+} PyFT2Image;
+
+static PyTypeObject PyFT2ImageType;
+
+static PyObject *PyFT2Image_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ PyFT2Image *self;
+ self = (PyFT2Image *)type->tp_alloc(type, 0);
+ self->x = NULL;
+ return (PyObject *)self;
+}
+
+static int PyFT2Image_init(PyFT2Image *self, PyObject *args, PyObject *kwds)
+{
+ double width;
+ double height;
+
+ if (!PyArg_ParseTuple(args, "dd:FT2Image", &width, &height)) {
+ return -1;
+ }
+
+ CALL_CPP_INIT("FT2Image", (self->x = new FT2Image(width, height)));
+
+ return 0;
+}
+
+static void PyFT2Image_dealloc(PyFT2Image *self)
+{
+ delete self->x;
+ Py_TYPE(self)->tp_free((PyObject *)self);
+}
+
+const char *PyFT2Image_draw_rect__doc__ =
+ "draw_rect(self, x0, y0, x1, y1)\n"
+ "--\n\n"
+ "Draw an empty rectangle to the image.\n"
+ "\n"
+ ".. deprecated:: 3.8\n";
+;
+
+static PyObject *PyFT2Image_draw_rect(PyFT2Image *self, PyObject *args)
+{
+ char const* msg =
+ "FT2Image.draw_rect is deprecated since Matplotlib 3.8 and will be removed "
+ "two minor releases later as it is not used in the library. If you rely on "
+ "it, please let us know.";
+ if (PyErr_WarnEx(PyExc_DeprecationWarning, msg, 1)) {
+ return NULL;
+ }
+
+ double x0, y0, x1, y1;
+
+ if (!PyArg_ParseTuple(args, "dddd:draw_rect", &x0, &y0, &x1, &y1)) {
+ return NULL;
+ }
+
+ CALL_CPP("draw_rect", (self->x->draw_rect(x0, y0, x1, y1)));
+
+ Py_RETURN_NONE;
+}
+
+const char *PyFT2Image_draw_rect_filled__doc__ =
+ "draw_rect_filled(self, x0, y0, x1, y1)\n"
+ "--\n\n"
+ "Draw a filled rectangle to the image.\n";
+
+static PyObject *PyFT2Image_draw_rect_filled(PyFT2Image *self, PyObject *args)
+{
+ double x0, y0, x1, y1;
+
+ if (!PyArg_ParseTuple(args, "dddd:draw_rect_filled", &x0, &y0, &x1, &y1)) {
+ return NULL;
+ }
+
+ CALL_CPP("draw_rect_filled", (self->x->draw_rect_filled(x0, y0, x1, y1)));
+
+ Py_RETURN_NONE;
+}
+
+static int PyFT2Image_get_buffer(PyFT2Image *self, Py_buffer *buf, int flags)
+{
+ FT2Image *im = self->x;
+
+ Py_INCREF(self);
+ buf->obj = (PyObject *)self;
+ buf->buf = im->get_buffer();
+ buf->len = im->get_width() * im->get_height();
+ buf->readonly = 0;
+ buf->format = (char *)"B";
+ buf->ndim = 2;
+ self->shape[0] = im->get_height();
+ self->shape[1] = im->get_width();
+ buf->shape = self->shape;
+ self->strides[0] = im->get_width();
+ self->strides[1] = 1;
+ buf->strides = self->strides;
+ buf->suboffsets = NULL;
+ buf->itemsize = 1;
+ buf->internal = NULL;
+
+ return 1;
+}
+
+static PyTypeObject* PyFT2Image_init_type()
+{
+ static PyMethodDef methods[] = {
+ {"draw_rect", (PyCFunction)PyFT2Image_draw_rect, METH_VARARGS, PyFT2Image_draw_rect__doc__},
+ {"draw_rect_filled", (PyCFunction)PyFT2Image_draw_rect_filled, METH_VARARGS, PyFT2Image_draw_rect_filled__doc__},
+ {NULL}
+ };
+
+ static PyBufferProcs buffer_procs;
+ buffer_procs.bf_getbuffer = (getbufferproc)PyFT2Image_get_buffer;
+
+ PyFT2ImageType.tp_name = "matplotlib.ft2font.FT2Image";
+ PyFT2ImageType.tp_basicsize = sizeof(PyFT2Image);
+ PyFT2ImageType.tp_dealloc = (destructor)PyFT2Image_dealloc;
+ PyFT2ImageType.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
+ PyFT2ImageType.tp_methods = methods;
+ PyFT2ImageType.tp_new = PyFT2Image_new;
+ PyFT2ImageType.tp_init = (initproc)PyFT2Image_init;
+ PyFT2ImageType.tp_as_buffer = &buffer_procs;
+
+ return &PyFT2ImageType;
+}
+
+/**********************************************************************
+ * Glyph
+ * */
+
+typedef struct
+{
+ PyObject_HEAD
+ size_t glyphInd;
+ long width;
+ long height;
+ long horiBearingX;
+ long horiBearingY;
+ long horiAdvance;
+ long linearHoriAdvance;
+ long vertBearingX;
+ long vertBearingY;
+ long vertAdvance;
+ FT_BBox bbox;
+} PyGlyph;
+
+static PyTypeObject PyGlyphType;
+
+static PyObject *PyGlyph_from_FT2Font(const FT2Font *font)
+{
+ const FT_Face &face = font->get_face();
+ const long hinting_factor = font->get_hinting_factor();
+ const FT_Glyph &glyph = font->get_last_glyph();
+
+ PyGlyph *self;
+ self = (PyGlyph *)PyGlyphType.tp_alloc(&PyGlyphType, 0);
+
+ self->glyphInd = font->get_last_glyph_index();
+ FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_subpixels, &self->bbox);
+
+ self->width = face->glyph->metrics.width / hinting_factor;
+ self->height = face->glyph->metrics.height;
+ self->horiBearingX = face->glyph->metrics.horiBearingX / hinting_factor;
+ self->horiBearingY = face->glyph->metrics.horiBearingY;
+ self->horiAdvance = face->glyph->metrics.horiAdvance;
+ self->linearHoriAdvance = face->glyph->linearHoriAdvance / hinting_factor;
+ self->vertBearingX = face->glyph->metrics.vertBearingX;
+ self->vertBearingY = face->glyph->metrics.vertBearingY;
+ self->vertAdvance = face->glyph->metrics.vertAdvance;
+
+ return (PyObject *)self;
+}
+
+static void PyGlyph_dealloc(PyGlyph *self)
+{
+ Py_TYPE(self)->tp_free((PyObject *)self);
+}
+
+static PyObject *PyGlyph_get_bbox(PyGlyph *self, void *closure)
+{
+ return Py_BuildValue(
+ "llll", self->bbox.xMin, self->bbox.yMin, self->bbox.xMax, self->bbox.yMax);
+}
+
+static PyTypeObject *PyGlyph_init_type()
+{
+ static PyMemberDef members[] = {
+ {(char *)"width", T_LONG, offsetof(PyGlyph, width), READONLY, (char *)""},
+ {(char *)"height", T_LONG, offsetof(PyGlyph, height), READONLY, (char *)""},
+ {(char *)"horiBearingX", T_LONG, offsetof(PyGlyph, horiBearingX), READONLY, (char *)""},
+ {(char *)"horiBearingY", T_LONG, offsetof(PyGlyph, horiBearingY), READONLY, (char *)""},
+ {(char *)"horiAdvance", T_LONG, offsetof(PyGlyph, horiAdvance), READONLY, (char *)""},
+ {(char *)"linearHoriAdvance", T_LONG, offsetof(PyGlyph, linearHoriAdvance), READONLY, (char *)""},
+ {(char *)"vertBearingX", T_LONG, offsetof(PyGlyph, vertBearingX), READONLY, (char *)""},
+ {(char *)"vertBearingY", T_LONG, offsetof(PyGlyph, vertBearingY), READONLY, (char *)""},
+ {(char *)"vertAdvance", T_LONG, offsetof(PyGlyph, vertAdvance), READONLY, (char *)""},
+ {NULL}
+ };
+
+ static PyGetSetDef getset[] = {
+ {(char *)"bbox", (getter)PyGlyph_get_bbox, NULL, NULL, NULL},
+ {NULL}
+ };
+
+ PyGlyphType.tp_name = "matplotlib.ft2font.Glyph";
+ PyGlyphType.tp_basicsize = sizeof(PyGlyph);
+ PyGlyphType.tp_dealloc = (destructor)PyGlyph_dealloc;
+ PyGlyphType.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
+ PyGlyphType.tp_members = members;
+ PyGlyphType.tp_getset = getset;
+
+ return &PyGlyphType;
+}
+
+/**********************************************************************
+ * FT2Font
+ * */
+
+struct PyFT2Font
+{
+ PyObject_HEAD
+ FT2Font *x;
+ PyObject *py_file;
+ FT_StreamRec stream;
+ Py_ssize_t shape[2];
+ Py_ssize_t strides[2];
+ Py_ssize_t suboffsets[2];
+ std::vector<PyObject *> fallbacks;
+};
+
+static PyTypeObject PyFT2FontType;
+
+static unsigned long read_from_file_callback(FT_Stream stream,
+ unsigned long offset,
+ unsigned char *buffer,
+ unsigned long count)
+{
+ PyObject *py_file = ((PyFT2Font *)stream->descriptor.pointer)->py_file;
+ PyObject *seek_result = NULL, *read_result = NULL;
+ Py_ssize_t n_read = 0;
+ if (!(seek_result = PyObject_CallMethod(py_file, "seek", "k", offset))
+ || !(read_result = PyObject_CallMethod(py_file, "read", "k", count))) {
+ goto exit;
+ }
+ char *tmpbuf;
+ if (PyBytes_AsStringAndSize(read_result, &tmpbuf, &n_read) == -1) {
+ goto exit;
+ }
+ memcpy(buffer, tmpbuf, n_read);
+exit:
+ Py_XDECREF(seek_result);
+ Py_XDECREF(read_result);
+ if (PyErr_Occurred()) {
+ PyErr_WriteUnraisable(py_file);
+ if (!count) {
+ return 1; // Non-zero signals error, when count == 0.
+ }
+ }
+ return (unsigned long)n_read;
+}
+
+static void close_file_callback(FT_Stream stream)
+{
+ PyObject *type, *value, *traceback;
+ PyErr_Fetch(&type, &value, &traceback);
+ PyFT2Font *self = (PyFT2Font *)stream->descriptor.pointer;
+ PyObject *close_result = NULL;
+ if (!(close_result = PyObject_CallMethod(self->py_file, "close", ""))) {
+ goto exit;
+ }
+exit:
+ Py_XDECREF(close_result);
+ Py_CLEAR(self->py_file);
+ if (PyErr_Occurred()) {
+ PyErr_WriteUnraisable((PyObject*)self);
+ }
+ PyErr_Restore(type, value, traceback);
+}
+
+static PyObject *PyFT2Font_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ PyFT2Font *self;
+ self = (PyFT2Font *)type->tp_alloc(type, 0);
+ self->x = NULL;
+ self->py_file = NULL;
+ memset(&self->stream, 0, sizeof(FT_StreamRec));
+ return (PyObject *)self;
+}
+
+const char *PyFT2Font_init__doc__ =
+ "FT2Font(filename, hinting_factor=8, *, _fallback_list=None, _kerning_factor=0)\n"
+ "--\n\n"
+ "Create a new FT2Font object.\n"
+ "\n"
+ "Parameters\n"
+ "----------\n"
+ "filename : str or file-like\n"
+ " The source of the font data in a format (ttf or ttc) that FreeType can read\n"
+ "\n"
+ "hinting_factor : int, optional\n"
+ " Must be positive. Used to scale the hinting in the x-direction\n"
+ "_fallback_list : list of FT2Font, optional\n"
+ " A list of FT2Font objects used to find missing glyphs.\n"
+ "\n"
+ " .. warning::\n"
+ " This API is both private and provisional: do not use it directly\n"
+ "\n"
+ "_kerning_factor : int, optional\n"
+ " Used to adjust the degree of kerning.\n"
+ "\n"
+ " .. warning::\n"
+ " This API is private: do not use it directly\n"
+ "\n"
+ "Attributes\n"
+ "----------\n"
+ "num_faces : int\n"
+ " Number of faces in file.\n"
+ "face_flags, style_flags : int\n"
+ " Face and style flags; see the ft2font constants.\n"
+ "num_glyphs : int\n"
+ " Number of glyphs in the face.\n"
+ "family_name, style_name : str\n"
+ " Face family and style name.\n"
+ "num_fixed_sizes : int\n"
+ " Number of bitmap in the face.\n"
+ "scalable : bool\n"
+ " Whether face is scalable; attributes after this one are only\n"
+ " defined for scalable faces.\n"
+ "bbox : tuple[int, int, int, int]\n"
+ " Face global bounding box (xmin, ymin, xmax, ymax).\n"
+ "units_per_EM : int\n"
+ " Number of font units covered by the EM.\n"
+ "ascender, descender : int\n"
+ " Ascender and descender in 26.6 units.\n"
+ "height : int\n"
+ " Height in 26.6 units; used to compute a default line spacing\n"
+ " (baseline-to-baseline distance).\n"
+ "max_advance_width, max_advance_height : int\n"
+ " Maximum horizontal and vertical cursor advance for all glyphs.\n"
+ "underline_position, underline_thickness : int\n"
+ " Vertical position and thickness of the underline bar.\n"
+ "postscript_name : str\n"
+ " PostScript name of the font.\n";
+
+static int PyFT2Font_init(PyFT2Font *self, PyObject *args, PyObject *kwds)
+{
+ PyObject *filename = NULL, *open = NULL, *data = NULL, *fallback_list = NULL;
+ FT_Open_Args open_args;
+ long hinting_factor = 8;
+ int kerning_factor = 0;
+ const char *names[] = {
+ "filename", "hinting_factor", "_fallback_list", "_kerning_factor", NULL
+ };
+ std::vector<FT2Font *> fallback_fonts;
+ if (!PyArg_ParseTupleAndKeywords(
+ args, kwds, "O|l$Oi:FT2Font", (char **)names, &filename,
+ &hinting_factor, &fallback_list, &kerning_factor)) {
+ return -1;
+ }
+ if (hinting_factor <= 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "hinting_factor must be greater than 0");
+ goto exit;
+ }
+
+ self->stream.base = NULL;
+ self->stream.size = 0x7fffffff; // Unknown size.
+ self->stream.pos = 0;
+ self->stream.descriptor.pointer = self;
+ self->stream.read = &read_from_file_callback;
+ memset((void *)&open_args, 0, sizeof(FT_Open_Args));
+ open_args.flags = FT_OPEN_STREAM;
+ open_args.stream = &self->stream;
+
+ if (fallback_list) {
+ if (!PyList_Check(fallback_list)) {
+ PyErr_SetString(PyExc_TypeError, "Fallback list must be a list");
+ goto exit;
+ }
+ Py_ssize_t size = PyList_Size(fallback_list);
+
+ // go through fallbacks once to make sure the types are right
+ for (Py_ssize_t i = 0; i < size; ++i) {
+ // this returns a borrowed reference
+ PyObject* item = PyList_GetItem(fallback_list, i);
+ if (!PyObject_IsInstance(item, PyObject_Type(reinterpret_cast<PyObject *>(self)))) {
+ PyErr_SetString(PyExc_TypeError, "Fallback fonts must be FT2Font objects.");
+ goto exit;
+ }
+ }
+ // go through a second time to add them to our lists
+ for (Py_ssize_t i = 0; i < size; ++i) {
+ // this returns a borrowed reference
+ PyObject* item = PyList_GetItem(fallback_list, i);
+ // Increase the ref count, we will undo this in dealloc this makes
+ // sure things do not get gc'd under us!
+ Py_INCREF(item);
+ self->fallbacks.push_back(item);
+ // Also (locally) cache the underlying FT2Font objects. As long as
+ // the Python objects are kept alive, these pointer are good.
+ FT2Font *fback = reinterpret_cast<PyFT2Font *>(item)->x;
+ fallback_fonts.push_back(fback);
+ }
+ }
+
+ if (PyBytes_Check(filename) || PyUnicode_Check(filename)) {
+ if (!(open = PyDict_GetItemString(PyEval_GetBuiltins(), "open")) // Borrowed reference.
+ || !(self->py_file = PyObject_CallFunction(open, "Os", filename, "rb"))) {
+ goto exit;
+ }
+ self->stream.close = &close_file_callback;
+ } else if (!PyObject_HasAttrString(filename, "read")
+ || !(data = PyObject_CallMethod(filename, "read", "i", 0))
+ || !PyBytes_Check(data)) {
+ PyErr_SetString(PyExc_TypeError,
+ "First argument must be a path to a font file or a binary-mode file object");
+ Py_CLEAR(data);
+ goto exit;
+ } else {
+ self->py_file = filename;
+ self->stream.close = NULL;
+ Py_INCREF(filename);
+ }
+ Py_CLEAR(data);
+
+ CALL_CPP_FULL(
+ "FT2Font", (self->x = new FT2Font(open_args, hinting_factor, fallback_fonts)),
+ Py_CLEAR(self->py_file), -1);
+
+ CALL_CPP_INIT("FT2Font->set_kerning_factor", (self->x->set_kerning_factor(kerning_factor)));
+
+exit:
+ return PyErr_Occurred() ? -1 : 0;
+}
+
+static void PyFT2Font_dealloc(PyFT2Font *self)
+{
+ delete self->x;
+ for (size_t i = 0; i < self->fallbacks.size(); i++) {
+ Py_DECREF(self->fallbacks[i]);
+ }
+
+ Py_XDECREF(self->py_file);
+ Py_TYPE(self)->tp_free((PyObject *)self);
+}
+
+const char *PyFT2Font_clear__doc__ =
+ "clear(self)\n"
+ "--\n\n"
+ "Clear all the glyphs, reset for a new call to `.set_text`.\n";
+
+static PyObject *PyFT2Font_clear(PyFT2Font *self, PyObject *args)
+{
+ CALL_CPP("clear", (self->x->clear()));
+
+ Py_RETURN_NONE;
+}
+
+const char *PyFT2Font_set_size__doc__ =
+ "set_size(self, ptsize, dpi)\n"
+ "--\n\n"
+ "Set the point size and dpi of the text.\n";
+
+static PyObject *PyFT2Font_set_size(PyFT2Font *self, PyObject *args)
+{
+ double ptsize;
+ double dpi;
+
+ if (!PyArg_ParseTuple(args, "dd:set_size", &ptsize, &dpi)) {
+ return NULL;
+ }
+
+ CALL_CPP("set_size", (self->x->set_size(ptsize, dpi)));
+
+ Py_RETURN_NONE;
+}
+
+const char *PyFT2Font_set_charmap__doc__ =
+ "set_charmap(self, i)\n"
+ "--\n\n"
+ "Make the i-th charmap current.\n";
+
+static PyObject *PyFT2Font_set_charmap(PyFT2Font *self, PyObject *args)
+{
+ int i;
+
+ if (!PyArg_ParseTuple(args, "i:set_charmap", &i)) {
+ return NULL;
+ }
+
+ CALL_CPP("set_charmap", (self->x->set_charmap(i)));
+
+ Py_RETURN_NONE;
+}
+
+const char *PyFT2Font_select_charmap__doc__ =
+ "select_charmap(self, i)\n"
+ "--\n\n"
+ "Select a charmap by its FT_Encoding number.\n";
+
+static PyObject *PyFT2Font_select_charmap(PyFT2Font *self, PyObject *args)
+{
+ unsigned long i;
+
+ if (!PyArg_ParseTuple(args, "k:select_charmap", &i)) {
+ return NULL;
+ }
+
+ CALL_CPP("select_charmap", self->x->select_charmap(i));
+
+ Py_RETURN_NONE;
+}
+
+const char *PyFT2Font_get_kerning__doc__ =
+ "get_kerning(self, left, right, mode)\n"
+ "--\n\n"
+ "Get the kerning between *left* and *right* glyph indices.\n"
+ "*mode* is a kerning mode constant:\n\n"
+ "- KERNING_DEFAULT - Return scaled and grid-fitted kerning distances\n"
+ "- KERNING_UNFITTED - Return scaled but un-grid-fitted kerning distances\n"
+ "- KERNING_UNSCALED - Return the kerning vector in original font units\n";
+
+static PyObject *PyFT2Font_get_kerning(PyFT2Font *self, PyObject *args)
+{
+ FT_UInt left, right, mode;
+ int result;
+ int fallback = 1;
+
+ if (!PyArg_ParseTuple(args, "III:get_kerning", &left, &right, &mode)) {
+ return NULL;
+ }
+
+ CALL_CPP("get_kerning", (result = self->x->get_kerning(left, right, mode, (bool)fallback)));
+
+ return PyLong_FromLong(result);
+}
+
+const char *PyFT2Font_get_fontmap__doc__ =
+ "_get_fontmap(self, string)\n"
+ "--\n\n"
+ "Get a mapping between characters and the font that includes them.\n"
+ "A dictionary mapping unicode characters to PyFT2Font objects.";
+static PyObject *PyFT2Font_get_fontmap(PyFT2Font *self, PyObject *args, PyObject *kwds)
+{
+ PyObject *textobj;
+ const char *names[] = { "string", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(
+ args, kwds, "O:_get_fontmap", (char **)names, &textobj)) {
+ return NULL;
+ }
+
+ std::set<FT_ULong> codepoints;
+ size_t size;
+
+ if (PyUnicode_Check(textobj)) {
+ size = PyUnicode_GET_LENGTH(textobj);
+ for (size_t i = 0; i < size; ++i) {
+ codepoints.insert(PyUnicode_ReadChar(textobj, i));
+ }
+ } else {
+ PyErr_SetString(PyExc_TypeError, "string must be str");
+ return NULL;
+ }
+ PyObject *char_to_font;
+ if (!(char_to_font = PyDict_New())) {
+ return NULL;
+ }
+ for (auto it = codepoints.begin(); it != codepoints.end(); ++it) {
+ auto x = *it;
+ PyObject* target_font;
+ int index;
+ if (self->x->get_char_fallback_index(x, index)) {
+ if (index >= 0) {
+ target_font = self->fallbacks[index];
+ } else {
+ target_font = (PyObject *)self;
+ }
+ } else {
+ // TODO Handle recursion!
+ target_font = (PyObject *)self;
+ }
+
+ PyObject *key = NULL;
+ bool error = (!(key = PyUnicode_FromFormat("%c", x))
+ || (PyDict_SetItem(char_to_font, key, target_font) == -1));
+ Py_XDECREF(key);
+ if (error) {
+ Py_DECREF(char_to_font);
+ PyErr_SetString(PyExc_ValueError, "Something went very wrong");
+ return NULL;
+ }
+ }
+ return char_to_font;
+}
+
+
+const char *PyFT2Font_set_text__doc__ =
+ "set_text(self, string, angle=0.0, flags=32)\n"
+ "--\n\n"
+ "Set the text *string* and *angle*.\n"
+ "*flags* can be a bitwise-or of the LOAD_XXX constants;\n"
+ "the default value is LOAD_FORCE_AUTOHINT.\n"
+ "You must call this before `.draw_glyphs_to_bitmap`.\n"
+ "A sequence of x,y positions is returned.\n";
+
+static PyObject *PyFT2Font_set_text(PyFT2Font *self, PyObject *args, PyObject *kwds)
+{
+ PyObject *textobj;
+ double angle = 0.0;
+ FT_Int32 flags = FT_LOAD_FORCE_AUTOHINT;
+ std::vector<double> xys;
+ const char *names[] = { "string", "angle", "flags", NULL };
+
+ /* This makes a technically incorrect assumption that FT_Int32 is
+ int. In theory it can also be long, if the size of int is less
+ than 32 bits. This is very unlikely on modern platforms. */
+ if (!PyArg_ParseTupleAndKeywords(
+ args, kwds, "O|di:set_text", (char **)names, &textobj, &angle, &flags)) {
+ return NULL;
+ }
+
+ std::vector<uint32_t> codepoints;
+ size_t size;
+
+ if (PyUnicode_Check(textobj)) {
+ size = PyUnicode_GET_LENGTH(textobj);
+ codepoints.resize(size);
+ for (size_t i = 0; i < size; ++i) {
+ codepoints[i] = PyUnicode_ReadChar(textobj, i);
+ }
+ } else {
+ PyErr_SetString(PyExc_TypeError, "set_text requires str-input.");
+ return NULL;
+ }
+
+ uint32_t* codepoints_array = NULL;
+ if (size > 0) {
+ codepoints_array = &codepoints[0];
+ }
+ CALL_CPP("set_text", self->x->set_text(size, codepoints_array, angle, flags, xys));
+
+ return convert_xys_to_array(xys);
+}
+
+const char *PyFT2Font_get_num_glyphs__doc__ =
+ "get_num_glyphs(self)\n"
+ "--\n\n"
+ "Return the number of loaded glyphs.\n";
+
+static PyObject *PyFT2Font_get_num_glyphs(PyFT2Font *self, PyObject *args)
+{
+ return PyLong_FromSize_t(self->x->get_num_glyphs());
+}
+
+const char *PyFT2Font_load_char__doc__ =
+ "load_char(self, charcode, flags=32)\n"
+ "--\n\n"
+ "Load character with *charcode* in current fontfile and set glyph.\n"
+ "*flags* can be a bitwise-or of the LOAD_XXX constants;\n"
+ "the default value is LOAD_FORCE_AUTOHINT.\n"
+ "Return value is a Glyph object, with attributes\n\n"
+ "- width: glyph width\n"
+ "- height: glyph height\n"
+ "- bbox: the glyph bbox (xmin, ymin, xmax, ymax)\n"
+ "- horiBearingX: left side bearing in horizontal layouts\n"
+ "- horiBearingY: top side bearing in horizontal layouts\n"
+ "- horiAdvance: advance width for horizontal layout\n"
+ "- vertBearingX: left side bearing in vertical layouts\n"
+ "- vertBearingY: top side bearing in vertical layouts\n"
+ "- vertAdvance: advance height for vertical layout\n";
+
+static PyObject *PyFT2Font_load_char(PyFT2Font *self, PyObject *args, PyObject *kwds)
+{
+ long charcode;
+ int fallback = 1;
+ FT_Int32 flags = FT_LOAD_FORCE_AUTOHINT;
+ const char *names[] = { "charcode", "flags", NULL };
+
+ /* This makes a technically incorrect assumption that FT_Int32 is
+ int. In theory it can also be long, if the size of int is less
+ than 32 bits. This is very unlikely on modern platforms. */
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "l|i:load_char", (char **)names, &charcode,
+ &flags)) {
+ return NULL;
+ }
+
+ FT2Font *ft_object = NULL;
+ CALL_CPP("load_char", (self->x->load_char(charcode, flags, ft_object, (bool)fallback)));
+
+ return PyGlyph_from_FT2Font(ft_object);
+}
+
+const char *PyFT2Font_load_glyph__doc__ =
+ "load_glyph(self, glyphindex, flags=32)\n"
+ "--\n\n"
+ "Load character with *glyphindex* in current fontfile and set glyph.\n"
+ "*flags* can be a bitwise-or of the LOAD_XXX constants;\n"
+ "the default value is LOAD_FORCE_AUTOHINT.\n"
+ "Return value is a Glyph object, with attributes\n\n"
+ "- width: glyph width\n"
+ "- height: glyph height\n"
+ "- bbox: the glyph bbox (xmin, ymin, xmax, ymax)\n"
+ "- horiBearingX: left side bearing in horizontal layouts\n"
+ "- horiBearingY: top side bearing in horizontal layouts\n"
+ "- horiAdvance: advance width for horizontal layout\n"
+ "- vertBearingX: left side bearing in vertical layouts\n"
+ "- vertBearingY: top side bearing in vertical layouts\n"
+ "- vertAdvance: advance height for vertical layout\n";
+
+static PyObject *PyFT2Font_load_glyph(PyFT2Font *self, PyObject *args, PyObject *kwds)
+{
+ FT_UInt glyph_index;
+ FT_Int32 flags = FT_LOAD_FORCE_AUTOHINT;
+ int fallback = 1;
+ const char *names[] = { "glyph_index", "flags", NULL };
+
+ /* This makes a technically incorrect assumption that FT_Int32 is
+ int. In theory it can also be long, if the size of int is less
+ than 32 bits. This is very unlikely on modern platforms. */
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "I|i:load_glyph", (char **)names, &glyph_index,
+ &flags)) {
+ return NULL;
+ }
+
+ FT2Font *ft_object = NULL;
+ CALL_CPP("load_glyph", (self->x->load_glyph(glyph_index, flags, ft_object, (bool)fallback)));
+
+ return PyGlyph_from_FT2Font(ft_object);
+}
+
+const char *PyFT2Font_get_width_height__doc__ =
+ "get_width_height(self)\n"
+ "--\n\n"
+ "Get the width and height in 26.6 subpixels of the current string set by `.set_text`.\n"
+ "The rotation of the string is accounted for. To get width and height\n"
+ "in pixels, divide these values by 64.\n";
+
+static PyObject *PyFT2Font_get_width_height(PyFT2Font *self, PyObject *args)
+{
+ long width, height;
+
+ CALL_CPP("get_width_height", (self->x->get_width_height(&width, &height)));
+
+ return Py_BuildValue("ll", width, height);
+}
+
+const char *PyFT2Font_get_bitmap_offset__doc__ =
+ "get_bitmap_offset(self)\n"
+ "--\n\n"
+ "Get the (x, y) offset in 26.6 subpixels for the bitmap if ink hangs left or below (0, 0).\n"
+ "Since Matplotlib only supports left-to-right text, y is always 0.\n";
+
+static PyObject *PyFT2Font_get_bitmap_offset(PyFT2Font *self, PyObject *args)
+{
+ long x, y;
+
+ CALL_CPP("get_bitmap_offset", (self->x->get_bitmap_offset(&x, &y)));
+
+ return Py_BuildValue("ll", x, y);
+}
+
+const char *PyFT2Font_get_descent__doc__ =
+ "get_descent(self)\n"
+ "--\n\n"
+ "Get the descent in 26.6 subpixels of the current string set by `.set_text`.\n"
+ "The rotation of the string is accounted for. To get the descent\n"
+ "in pixels, divide this value by 64.\n";
+
+static PyObject *PyFT2Font_get_descent(PyFT2Font *self, PyObject *args)
+{
+ long descent;
+
+ CALL_CPP("get_descent", (descent = self->x->get_descent()));
+
+ return PyLong_FromLong(descent);
+}
+
+const char *PyFT2Font_draw_glyphs_to_bitmap__doc__ =
+ "draw_glyphs_to_bitmap(self, antialiased=True)\n"
+ "--\n\n"
+ "Draw the glyphs that were loaded by `.set_text` to the bitmap.\n"
+ "The bitmap size will be automatically set to include the glyphs.\n";
+
+static PyObject *PyFT2Font_draw_glyphs_to_bitmap(PyFT2Font *self, PyObject *args, PyObject *kwds)
+{
+ bool antialiased = true;
+ const char *names[] = { "antialiased", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O&:draw_glyphs_to_bitmap",
+ (char **)names, &convert_bool, &antialiased)) {
+ return NULL;
+ }
+
+ CALL_CPP("draw_glyphs_to_bitmap", (self->x->draw_glyphs_to_bitmap(antialiased)));
+
+ Py_RETURN_NONE;
+}
+
+const char *PyFT2Font_get_xys__doc__ =
+ "get_xys(self, antialiased=True)\n"
+ "--\n\n"
+ "Get the xy locations of the current glyphs.\n"
+ "\n"
+ ".. deprecated:: 3.8\n";
+
+static PyObject *PyFT2Font_get_xys(PyFT2Font *self, PyObject *args, PyObject *kwds)
+{
+ char const* msg =
+ "FT2Font.get_xys is deprecated since Matplotlib 3.8 and will be removed two "
+ "minor releases later as it is not used in the library. If you rely on it, "
+ "please let us know.";
+ if (PyErr_WarnEx(PyExc_DeprecationWarning, msg, 1)) {
+ return NULL;
+ }
+
+ bool antialiased = true;
+ std::vector<double> xys;
+ const char *names[] = { "antialiased", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O&:get_xys",
+ (char **)names, &convert_bool, &antialiased)) {
+ return NULL;
+ }
+
+ CALL_CPP("get_xys", (self->x->get_xys(antialiased, xys)));
+
+ return convert_xys_to_array(xys);
+}
+
+const char *PyFT2Font_draw_glyph_to_bitmap__doc__ =
+ "draw_glyph_to_bitmap(self, image, x, y, glyph, antialiased=True)\n"
+ "--\n\n"
+ "Draw a single glyph to the bitmap at pixel locations x, y\n"
+ "Note it is your responsibility to set up the bitmap manually\n"
+ "with ``set_bitmap_size(w, h)`` before this call is made.\n"
+ "\n"
+ "If you want automatic layout, use `.set_text` in combinations with\n"
+ "`.draw_glyphs_to_bitmap`. This function is instead intended for people\n"
+ "who want to render individual glyphs (e.g., returned by `.load_char`)\n"
+ "at precise locations.\n";
+
+static PyObject *PyFT2Font_draw_glyph_to_bitmap(PyFT2Font *self, PyObject *args, PyObject *kwds)
+{
+ PyFT2Image *image;
+ double xd, yd;
+ PyGlyph *glyph;
+ bool antialiased = true;
+ const char *names[] = { "image", "x", "y", "glyph", "antialiased", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args,
+ kwds,
+ "O!ddO!|O&:draw_glyph_to_bitmap",
+ (char **)names,
+ &PyFT2ImageType,
+ &image,
+ &xd,
+ &yd,
+ &PyGlyphType,
+ &glyph,
+ &convert_bool,
+ &antialiased)) {
+ return NULL;
+ }
+
+ CALL_CPP("draw_glyph_to_bitmap",
+ self->x->draw_glyph_to_bitmap(*(image->x), xd, yd, glyph->glyphInd, antialiased));
+
+ Py_RETURN_NONE;
+}
+
+const char *PyFT2Font_get_glyph_name__doc__ =
+ "get_glyph_name(self, index)\n"
+ "--\n\n"
+ "Retrieve the ASCII name of a given glyph *index* in a face.\n"
+ "\n"
+ "Due to Matplotlib's internal design, for fonts that do not contain glyph\n"
+ "names (per FT_FACE_FLAG_GLYPH_NAMES), this returns a made-up name which\n"
+ "does *not* roundtrip through `.get_name_index`.\n";
+
+static PyObject *PyFT2Font_get_glyph_name(PyFT2Font *self, PyObject *args)
+{
+ unsigned int glyph_number;
+ char buffer[128];
+ int fallback = 1;
+
+ if (!PyArg_ParseTuple(args, "I:get_glyph_name", &glyph_number)) {
+ return NULL;
+ }
+ CALL_CPP("get_glyph_name", (self->x->get_glyph_name(glyph_number, buffer, (bool)fallback)));
+ return PyUnicode_FromString(buffer);
+}
+
+const char *PyFT2Font_get_charmap__doc__ =
+ "get_charmap(self)\n"
+ "--\n\n"
+ "Return a dict that maps the character codes of the selected charmap\n"
+ "(Unicode by default) to their corresponding glyph indices.\n";
+
+static PyObject *PyFT2Font_get_charmap(PyFT2Font *self, PyObject *args)
+{
+ PyObject *charmap;
+ if (!(charmap = PyDict_New())) {
+ return NULL;
+ }
+ FT_UInt index;
+ FT_ULong code = FT_Get_First_Char(self->x->get_face(), &index);
+ while (index != 0) {
+ PyObject *key = NULL, *val = NULL;
+ bool error = (!(key = PyLong_FromLong(code))
+ || !(val = PyLong_FromLong(index))
+ || (PyDict_SetItem(charmap, key, val) == -1));
+ Py_XDECREF(key);
+ Py_XDECREF(val);
+ if (error) {
+ Py_DECREF(charmap);
+ return NULL;
+ }
+ code = FT_Get_Next_Char(self->x->get_face(), code, &index);
+ }
+ return charmap;
+}
+
+
+const char *PyFT2Font_get_char_index__doc__ =
+ "get_char_index(self, codepoint)\n"
+ "--\n\n"
+ "Return the glyph index corresponding to a character *codepoint*.\n";
+
+static PyObject *PyFT2Font_get_char_index(PyFT2Font *self, PyObject *args)
+{
+ FT_UInt index;
+ FT_ULong ccode;
+ int fallback = 1;
+
+ if (!PyArg_ParseTuple(args, "k:get_char_index", &ccode)) {
+ return NULL;
+ }
+
+ CALL_CPP("get_char_index", index = self->x->get_char_index(ccode, (bool)fallback));
+
+ return PyLong_FromLong(index);
+}
+
+
+const char *PyFT2Font_get_sfnt__doc__ =
+ "get_sfnt(self)\n"
+ "--\n\n"
+ "Load the entire SFNT names table, as a dict whose keys are\n"
+ "(platform-ID, ISO-encoding-scheme, language-code, and description)\n"
+ "tuples.\n";
+
+static PyObject *PyFT2Font_get_sfnt(PyFT2Font *self, PyObject *args)
+{
+ PyObject *names;
+
+ if (!(self->x->get_face()->face_flags & FT_FACE_FLAG_SFNT)) {
+ PyErr_SetString(PyExc_ValueError, "No SFNT name table");
+ return NULL;
+ }
+
+ size_t count = FT_Get_Sfnt_Name_Count(self->x->get_face());
+
+ names = PyDict_New();
+ if (names == NULL) {
+ return NULL;
+ }
+
+ for (FT_UInt j = 0; j < count; ++j) {
+ FT_SfntName sfnt;
+ FT_Error error = FT_Get_Sfnt_Name(self->x->get_face(), j, &sfnt);
+
+ if (error) {
+ Py_DECREF(names);
+ PyErr_SetString(PyExc_ValueError, "Could not get SFNT name");
+ return NULL;
+ }
+
+ PyObject *key = Py_BuildValue(
+ "HHHH", sfnt.platform_id, sfnt.encoding_id, sfnt.language_id, sfnt.name_id);
+ if (key == NULL) {
+ Py_DECREF(names);
+ return NULL;
+ }
+
+ PyObject *val = PyBytes_FromStringAndSize((const char *)sfnt.string, sfnt.string_len);
+ if (val == NULL) {
+ Py_DECREF(key);
+ Py_DECREF(names);
+ return NULL;
+ }
+
+ if (PyDict_SetItem(names, key, val)) {
+ Py_DECREF(key);
+ Py_DECREF(val);
+ Py_DECREF(names);
+ return NULL;
+ }
+
+ Py_DECREF(key);
+ Py_DECREF(val);
+ }
+
+ return names;
+}
+
+const char *PyFT2Font_get_name_index__doc__ =
+ "get_name_index(self, name)\n"
+ "--\n\n"
+ "Return the glyph index of a given glyph *name*.\n"
+ "The glyph index 0 means 'undefined character code'.\n";
+
+static PyObject *PyFT2Font_get_name_index(PyFT2Font *self, PyObject *args)
+{
+ char *glyphname;
+ long name_index;
+ if (!PyArg_ParseTuple(args, "s:get_name_index", &glyphname)) {
+ return NULL;
+ }
+ CALL_CPP("get_name_index", name_index = self->x->get_name_index(glyphname));
+ return PyLong_FromLong(name_index);
+}
+
+const char *PyFT2Font_get_ps_font_info__doc__ =
+ "get_ps_font_info(self)\n"
+ "--\n\n"
+ "Return the information in the PS Font Info structure.\n";
+
+static PyObject *PyFT2Font_get_ps_font_info(PyFT2Font *self, PyObject *args)
+{
+ PS_FontInfoRec fontinfo;
+
+ FT_Error error = FT_Get_PS_Font_Info(self->x->get_face(), &fontinfo);
+ if (error) {
+ PyErr_SetString(PyExc_ValueError, "Could not get PS font info");
+ return NULL;
+ }
+
+ return Py_BuildValue("ssssslbhH",
+ fontinfo.version ? fontinfo.version : "",
+ fontinfo.notice ? fontinfo.notice : "",
+ fontinfo.full_name ? fontinfo.full_name : "",
+ fontinfo.family_name ? fontinfo.family_name : "",
+ fontinfo.weight ? fontinfo.weight : "",
+ fontinfo.italic_angle,
+ fontinfo.is_fixed_pitch,
+ fontinfo.underline_position,
+ fontinfo.underline_thickness);
+}
+
+const char *PyFT2Font_get_sfnt_table__doc__ =
+ "get_sfnt_table(self, name)\n"
+ "--\n\n"
+ "Return one of the following SFNT tables: head, maxp, OS/2, hhea, "
+ "vhea, post, or pclt.\n";
+
+static PyObject *PyFT2Font_get_sfnt_table(PyFT2Font *self, PyObject *args)
+{
+ char *tagname;
+ if (!PyArg_ParseTuple(args, "s:get_sfnt_table", &tagname)) {
+ return NULL;
+ }
+
+ int tag;
+ const char *tags[] = { "head", "maxp", "OS/2", "hhea", "vhea", "post", "pclt", NULL };
+
+ for (tag = 0; tags[tag] != NULL; tag++) {
+ if (strncmp(tagname, tags[tag], 5) == 0) {
+ break;
+ }
+ }
+
+ void *table = FT_Get_Sfnt_Table(self->x->get_face(), (FT_Sfnt_Tag)tag);
+ if (!table) {
+ Py_RETURN_NONE;
+ }
+
+ switch (tag) {
+ case 0: {
+ char head_dict[] =
+ "{s:(h,H), s:(h,H), s:l, s:l, s:H, s:H,"
+ "s:(l,l), s:(l,l), s:h, s:h, s:h, s:h, s:H, s:H, s:h, s:h, s:h}";
+ TT_Header *t = (TT_Header *)table;
+ return Py_BuildValue(head_dict,
+ "version", FIXED_MAJOR(t->Table_Version), FIXED_MINOR(t->Table_Version),
+ "fontRevision", FIXED_MAJOR(t->Font_Revision), FIXED_MINOR(t->Font_Revision),
+ "checkSumAdjustment", t->CheckSum_Adjust,
+ "magicNumber", t->Magic_Number,
+ "flags", t->Flags,
+ "unitsPerEm", t->Units_Per_EM,
+ "created", t->Created[0], t->Created[1],
+ "modified", t->Modified[0], t->Modified[1],
+ "xMin", t->xMin,
+ "yMin", t->yMin,
+ "xMax", t->xMax,
+ "yMax", t->yMax,
+ "macStyle", t->Mac_Style,
+ "lowestRecPPEM", t->Lowest_Rec_PPEM,
+ "fontDirectionHint", t->Font_Direction,
+ "indexToLocFormat", t->Index_To_Loc_Format,
+ "glyphDataFormat", t->Glyph_Data_Format);
+ }
+ case 1: {
+ char maxp_dict[] =
+ "{s:(h,H), s:H, s:H, s:H, s:H, s:H, s:H,"
+ "s:H, s:H, s:H, s:H, s:H, s:H, s:H, s:H}";
+ TT_MaxProfile *t = (TT_MaxProfile *)table;
+ return Py_BuildValue(maxp_dict,
+ "version", FIXED_MAJOR(t->version), FIXED_MINOR(t->version),
+ "numGlyphs", t->numGlyphs,
+ "maxPoints", t->maxPoints,
+ "maxContours", t->maxContours,
+ "maxComponentPoints", t->maxCompositePoints,
+ "maxComponentContours", t->maxCompositeContours,
+ "maxZones", t->maxZones,
+ "maxTwilightPoints", t->maxTwilightPoints,
+ "maxStorage", t->maxStorage,
+ "maxFunctionDefs", t->maxFunctionDefs,
+ "maxInstructionDefs", t->maxInstructionDefs,
+ "maxStackElements", t->maxStackElements,
+ "maxSizeOfInstructions", t->maxSizeOfInstructions,
+ "maxComponentElements", t->maxComponentElements,
+ "maxComponentDepth", t->maxComponentDepth);
+ }
+ case 2: {
+ char os_2_dict[] =
+ "{s:H, s:h, s:H, s:H, s:H, s:h, s:h, s:h,"
+ "s:h, s:h, s:h, s:h, s:h, s:h, s:h, s:h, s:y#, s:(kkkk),"
+ "s:y#, s:H, s:H, s:H}";
+ TT_OS2 *t = (TT_OS2 *)table;
+ return Py_BuildValue(os_2_dict,
+ "version", t->version,
+ "xAvgCharWidth", t->xAvgCharWidth,
+ "usWeightClass", t->usWeightClass,
+ "usWidthClass", t->usWidthClass,
+ "fsType", t->fsType,
+ "ySubscriptXSize", t->ySubscriptXSize,
+ "ySubscriptYSize", t->ySubscriptYSize,
+ "ySubscriptXOffset", t->ySubscriptXOffset,
+ "ySubscriptYOffset", t->ySubscriptYOffset,
+ "ySuperscriptXSize", t->ySuperscriptXSize,
+ "ySuperscriptYSize", t->ySuperscriptYSize,
+ "ySuperscriptXOffset", t->ySuperscriptXOffset,
+ "ySuperscriptYOffset", t->ySuperscriptYOffset,
+ "yStrikeoutSize", t->yStrikeoutSize,
+ "yStrikeoutPosition", t->yStrikeoutPosition,
+ "sFamilyClass", t->sFamilyClass,
+ "panose", t->panose, Py_ssize_t(10),
+ "ulCharRange", t->ulUnicodeRange1, t->ulUnicodeRange2, t->ulUnicodeRange3, t->ulUnicodeRange4,
+ "achVendID", t->achVendID, Py_ssize_t(4),
+ "fsSelection", t->fsSelection,
+ "fsFirstCharIndex", t->usFirstCharIndex,
+ "fsLastCharIndex", t->usLastCharIndex);
+ }
+ case 3: {
+ char hhea_dict[] =
+ "{s:(h,H), s:h, s:h, s:h, s:H, s:h, s:h, s:h,"
+ "s:h, s:h, s:h, s:h, s:H}";
+ TT_HoriHeader *t = (TT_HoriHeader *)table;
+ return Py_BuildValue(hhea_dict,
+ "version", FIXED_MAJOR(t->Version), FIXED_MINOR(t->Version),
+ "ascent", t->Ascender,
+ "descent", t->Descender,
+ "lineGap", t->Line_Gap,
+ "advanceWidthMax", t->advance_Width_Max,
+ "minLeftBearing", t->min_Left_Side_Bearing,
+ "minRightBearing", t->min_Right_Side_Bearing,
+ "xMaxExtent", t->xMax_Extent,
+ "caretSlopeRise", t->caret_Slope_Rise,
+ "caretSlopeRun", t->caret_Slope_Run,
+ "caretOffset", t->caret_Offset,
+ "metricDataFormat", t->metric_Data_Format,
+ "numOfLongHorMetrics", t->number_Of_HMetrics);
+ }
+ case 4: {
+ char vhea_dict[] =
+ "{s:(h,H), s:h, s:h, s:h, s:H, s:h, s:h, s:h,"
+ "s:h, s:h, s:h, s:h, s:H}";
+ TT_VertHeader *t = (TT_VertHeader *)table;
+ return Py_BuildValue(vhea_dict,
+ "version", FIXED_MAJOR(t->Version), FIXED_MINOR(t->Version),
+ "vertTypoAscender", t->Ascender,
+ "vertTypoDescender", t->Descender,
+ "vertTypoLineGap", t->Line_Gap,
+ "advanceHeightMax", t->advance_Height_Max,
+ "minTopSideBearing", t->min_Top_Side_Bearing,
+ "minBottomSizeBearing", t->min_Bottom_Side_Bearing,
+ "yMaxExtent", t->yMax_Extent,
+ "caretSlopeRise", t->caret_Slope_Rise,
+ "caretSlopeRun", t->caret_Slope_Run,
+ "caretOffset", t->caret_Offset,
+ "metricDataFormat", t->metric_Data_Format,
+ "numOfLongVerMetrics", t->number_Of_VMetrics);
+ }
+ case 5: {
+ char post_dict[] = "{s:(h,H), s:(h,H), s:h, s:h, s:k, s:k, s:k, s:k, s:k}";
+ TT_Postscript *t = (TT_Postscript *)table;
+ return Py_BuildValue(post_dict,
+ "format", FIXED_MAJOR(t->FormatType), FIXED_MINOR(t->FormatType),
+ "italicAngle", FIXED_MAJOR(t->italicAngle), FIXED_MINOR(t->italicAngle),
+ "underlinePosition", t->underlinePosition,
+ "underlineThickness", t->underlineThickness,
+ "isFixedPitch", t->isFixedPitch,
+ "minMemType42", t->minMemType42,
+ "maxMemType42", t->maxMemType42,
+ "minMemType1", t->minMemType1,
+ "maxMemType1", t->maxMemType1);
+ }
+ case 6: {
+ char pclt_dict[] =
+ "{s:(h,H), s:k, s:H, s:H, s:H, s:H, s:H, s:H, s:y#, s:y#, s:b, "
+ "s:b, s:b}";
+ TT_PCLT *t = (TT_PCLT *)table;
+ return Py_BuildValue(pclt_dict,
+ "version", FIXED_MAJOR(t->Version), FIXED_MINOR(t->Version),
+ "fontNumber", t->FontNumber,
+ "pitch", t->Pitch,
+ "xHeight", t->xHeight,
+ "style", t->Style,
+ "typeFamily", t->TypeFamily,
+ "capHeight", t->CapHeight,
+ "symbolSet", t->SymbolSet,
+ "typeFace", t->TypeFace, Py_ssize_t(16),
+ "characterComplement", t->CharacterComplement, Py_ssize_t(8),
+ "strokeWeight", t->StrokeWeight,
+ "widthType", t->WidthType,
+ "serifStyle", t->SerifStyle);
+ }
+ default:
+ Py_RETURN_NONE;
+ }
+}
+
+const char *PyFT2Font_get_path__doc__ =
+ "get_path(self)\n"
+ "--\n\n"
+ "Get the path data from the currently loaded glyph as a tuple of vertices, "
+ "codes.\n";
+
+static PyObject *PyFT2Font_get_path(PyFT2Font *self, PyObject *args)
+{
+ CALL_CPP("get_path", return self->x->get_path());
+}
+
+const char *PyFT2Font_get_image__doc__ =
+ "get_image(self)\n"
+ "--\n\n"
+ "Return the underlying image buffer for this font object.\n";
+
+static PyObject *PyFT2Font_get_image(PyFT2Font *self, PyObject *args)
+{
+ FT2Image &im = self->x->get_image();
+ npy_intp dims[] = {(npy_intp)im.get_height(), (npy_intp)im.get_width() };
+ return PyArray_SimpleNewFromData(2, dims, NPY_UBYTE, im.get_buffer());
+}
+
+static PyObject *PyFT2Font_postscript_name(PyFT2Font *self, void *closure)
+{
+ const char *ps_name = FT_Get_Postscript_Name(self->x->get_face());
+ if (ps_name == NULL) {
+ ps_name = "UNAVAILABLE";
+ }
+
+ return PyUnicode_FromString(ps_name);
+}
+
+static PyObject *PyFT2Font_num_faces(PyFT2Font *self, void *closure)
+{
+ return PyLong_FromLong(self->x->get_face()->num_faces);
+}
+
+static PyObject *PyFT2Font_family_name(PyFT2Font *self, void *closure)
+{
+ const char *name = self->x->get_face()->family_name;
+ if (name == NULL) {
+ name = "UNAVAILABLE";
+ }
+ return PyUnicode_FromString(name);
+}
+
+static PyObject *PyFT2Font_style_name(PyFT2Font *self, void *closure)
+{
+ const char *name = self->x->get_face()->style_name;
+ if (name == NULL) {
+ name = "UNAVAILABLE";
+ }
+ return PyUnicode_FromString(name);
+}
+
+static PyObject *PyFT2Font_face_flags(PyFT2Font *self, void *closure)
+{
+ return PyLong_FromLong(self->x->get_face()->face_flags);
+}
+
+static PyObject *PyFT2Font_style_flags(PyFT2Font *self, void *closure)
+{
+ return PyLong_FromLong(self->x->get_face()->style_flags);
+}
+
+static PyObject *PyFT2Font_num_glyphs(PyFT2Font *self, void *closure)
+{
+ return PyLong_FromLong(self->x->get_face()->num_glyphs);
+}
+
+static PyObject *PyFT2Font_num_fixed_sizes(PyFT2Font *self, void *closure)
+{
+ return PyLong_FromLong(self->x->get_face()->num_fixed_sizes);
+}
+
+static PyObject *PyFT2Font_num_charmaps(PyFT2Font *self, void *closure)
+{
+ return PyLong_FromLong(self->x->get_face()->num_charmaps);
+}
+
+static PyObject *PyFT2Font_scalable(PyFT2Font *self, void *closure)
+{
+ if (FT_IS_SCALABLE(self->x->get_face())) {
+ Py_RETURN_TRUE;
+ }
+ Py_RETURN_FALSE;
+}
+
+static PyObject *PyFT2Font_units_per_EM(PyFT2Font *self, void *closure)
+{
+ return PyLong_FromLong(self->x->get_face()->units_per_EM);
+}
+
+static PyObject *PyFT2Font_get_bbox(PyFT2Font *self, void *closure)
+{
+ FT_BBox *bbox = &(self->x->get_face()->bbox);
+
+ return Py_BuildValue("llll",
+ bbox->xMin, bbox->yMin, bbox->xMax, bbox->yMax);
+}
+
+static PyObject *PyFT2Font_ascender(PyFT2Font *self, void *closure)
+{
+ return PyLong_FromLong(self->x->get_face()->ascender);
+}
+
+static PyObject *PyFT2Font_descender(PyFT2Font *self, void *closure)
+{
+ return PyLong_FromLong(self->x->get_face()->descender);
+}
+
+static PyObject *PyFT2Font_height(PyFT2Font *self, void *closure)
+{
+ return PyLong_FromLong(self->x->get_face()->height);
+}
+
+static PyObject *PyFT2Font_max_advance_width(PyFT2Font *self, void *closure)
+{
+ return PyLong_FromLong(self->x->get_face()->max_advance_width);
+}
+
+static PyObject *PyFT2Font_max_advance_height(PyFT2Font *self, void *closure)
+{
+ return PyLong_FromLong(self->x->get_face()->max_advance_height);
+}
+
+static PyObject *PyFT2Font_underline_position(PyFT2Font *self, void *closure)
+{
+ return PyLong_FromLong(self->x->get_face()->underline_position);
+}
+
+static PyObject *PyFT2Font_underline_thickness(PyFT2Font *self, void *closure)
+{
+ return PyLong_FromLong(self->x->get_face()->underline_thickness);
+}
+
+static PyObject *PyFT2Font_fname(PyFT2Font *self, void *closure)
+{
+ if (self->stream.close) { // Called passed a filename to the constructor.
+ return PyObject_GetAttrString(self->py_file, "name");
+ } else {
+ Py_INCREF(self->py_file);
+ return self->py_file;
+ }
+}
+
+static int PyFT2Font_get_buffer(PyFT2Font *self, Py_buffer *buf, int flags)
+{
+ FT2Image &im = self->x->get_image();
+
+ Py_INCREF(self);
+ buf->obj = (PyObject *)self;
+ buf->buf = im.get_buffer();
+ buf->len = im.get_width() * im.get_height();
+ buf->readonly = 0;
+ buf->format = (char *)"B";
+ buf->ndim = 2;
+ self->shape[0] = im.get_height();
+ self->shape[1] = im.get_width();
+ buf->shape = self->shape;
+ self->strides[0] = im.get_width();
+ self->strides[1] = 1;
+ buf->strides = self->strides;
+ buf->suboffsets = NULL;
+ buf->itemsize = 1;
+ buf->internal = NULL;
+
+ return 1;
+}
+
+static PyTypeObject *PyFT2Font_init_type()
+{
+ static PyGetSetDef getset[] = {
+ {(char *)"postscript_name", (getter)PyFT2Font_postscript_name, NULL, NULL, NULL},
+ {(char *)"num_faces", (getter)PyFT2Font_num_faces, NULL, NULL, NULL},
+ {(char *)"family_name", (getter)PyFT2Font_family_name, NULL, NULL, NULL},
+ {(char *)"style_name", (getter)PyFT2Font_style_name, NULL, NULL, NULL},
+ {(char *)"face_flags", (getter)PyFT2Font_face_flags, NULL, NULL, NULL},
+ {(char *)"style_flags", (getter)PyFT2Font_style_flags, NULL, NULL, NULL},
+ {(char *)"num_glyphs", (getter)PyFT2Font_num_glyphs, NULL, NULL, NULL},
+ {(char *)"num_fixed_sizes", (getter)PyFT2Font_num_fixed_sizes, NULL, NULL, NULL},
+ {(char *)"num_charmaps", (getter)PyFT2Font_num_charmaps, NULL, NULL, NULL},
+ {(char *)"scalable", (getter)PyFT2Font_scalable, NULL, NULL, NULL},
+ {(char *)"units_per_EM", (getter)PyFT2Font_units_per_EM, NULL, NULL, NULL},
+ {(char *)"bbox", (getter)PyFT2Font_get_bbox, NULL, NULL, NULL},
+ {(char *)"ascender", (getter)PyFT2Font_ascender, NULL, NULL, NULL},
+ {(char *)"descender", (getter)PyFT2Font_descender, NULL, NULL, NULL},
+ {(char *)"height", (getter)PyFT2Font_height, NULL, NULL, NULL},
+ {(char *)"max_advance_width", (getter)PyFT2Font_max_advance_width, NULL, NULL, NULL},
+ {(char *)"max_advance_height", (getter)PyFT2Font_max_advance_height, NULL, NULL, NULL},
+ {(char *)"underline_position", (getter)PyFT2Font_underline_position, NULL, NULL, NULL},
+ {(char *)"underline_thickness", (getter)PyFT2Font_underline_thickness, NULL, NULL, NULL},
+ {(char *)"fname", (getter)PyFT2Font_fname, NULL, NULL, NULL},
+ {NULL}
+ };
+
+ static PyMethodDef methods[] = {
+ {"clear", (PyCFunction)PyFT2Font_clear, METH_NOARGS, PyFT2Font_clear__doc__},
+ {"set_size", (PyCFunction)PyFT2Font_set_size, METH_VARARGS, PyFT2Font_set_size__doc__},
+ {"set_charmap", (PyCFunction)PyFT2Font_set_charmap, METH_VARARGS, PyFT2Font_set_charmap__doc__},
+ {"select_charmap", (PyCFunction)PyFT2Font_select_charmap, METH_VARARGS, PyFT2Font_select_charmap__doc__},
+ {"get_kerning", (PyCFunction)PyFT2Font_get_kerning, METH_VARARGS, PyFT2Font_get_kerning__doc__},
+ {"set_text", (PyCFunction)PyFT2Font_set_text, METH_VARARGS|METH_KEYWORDS, PyFT2Font_set_text__doc__},
+ {"_get_fontmap", (PyCFunction)PyFT2Font_get_fontmap, METH_VARARGS|METH_KEYWORDS, PyFT2Font_get_fontmap__doc__},
+ {"get_num_glyphs", (PyCFunction)PyFT2Font_get_num_glyphs, METH_NOARGS, PyFT2Font_get_num_glyphs__doc__},
+ {"load_char", (PyCFunction)PyFT2Font_load_char, METH_VARARGS|METH_KEYWORDS, PyFT2Font_load_char__doc__},
+ {"load_glyph", (PyCFunction)PyFT2Font_load_glyph, METH_VARARGS|METH_KEYWORDS, PyFT2Font_load_glyph__doc__},
+ {"get_width_height", (PyCFunction)PyFT2Font_get_width_height, METH_NOARGS, PyFT2Font_get_width_height__doc__},
+ {"get_bitmap_offset", (PyCFunction)PyFT2Font_get_bitmap_offset, METH_NOARGS, PyFT2Font_get_bitmap_offset__doc__},
+ {"get_descent", (PyCFunction)PyFT2Font_get_descent, METH_NOARGS, PyFT2Font_get_descent__doc__},
+ {"draw_glyphs_to_bitmap", (PyCFunction)PyFT2Font_draw_glyphs_to_bitmap, METH_VARARGS|METH_KEYWORDS, PyFT2Font_draw_glyphs_to_bitmap__doc__},
+ {"get_xys", (PyCFunction)PyFT2Font_get_xys, METH_VARARGS|METH_KEYWORDS, PyFT2Font_get_xys__doc__},
+ {"draw_glyph_to_bitmap", (PyCFunction)PyFT2Font_draw_glyph_to_bitmap, METH_VARARGS|METH_KEYWORDS, PyFT2Font_draw_glyph_to_bitmap__doc__},
+ {"get_glyph_name", (PyCFunction)PyFT2Font_get_glyph_name, METH_VARARGS, PyFT2Font_get_glyph_name__doc__},
+ {"get_charmap", (PyCFunction)PyFT2Font_get_charmap, METH_NOARGS, PyFT2Font_get_charmap__doc__},
+ {"get_char_index", (PyCFunction)PyFT2Font_get_char_index, METH_VARARGS, PyFT2Font_get_char_index__doc__},
+ {"get_sfnt", (PyCFunction)PyFT2Font_get_sfnt, METH_NOARGS, PyFT2Font_get_sfnt__doc__},
+ {"get_name_index", (PyCFunction)PyFT2Font_get_name_index, METH_VARARGS, PyFT2Font_get_name_index__doc__},
+ {"get_ps_font_info", (PyCFunction)PyFT2Font_get_ps_font_info, METH_NOARGS, PyFT2Font_get_ps_font_info__doc__},
+ {"get_sfnt_table", (PyCFunction)PyFT2Font_get_sfnt_table, METH_VARARGS, PyFT2Font_get_sfnt_table__doc__},
+ {"get_path", (PyCFunction)PyFT2Font_get_path, METH_NOARGS, PyFT2Font_get_path__doc__},
+ {"get_image", (PyCFunction)PyFT2Font_get_image, METH_NOARGS, PyFT2Font_get_image__doc__},
+ {NULL}
+ };
+
+ static PyBufferProcs buffer_procs;
+ buffer_procs.bf_getbuffer = (getbufferproc)PyFT2Font_get_buffer;
+
+ PyFT2FontType.tp_name = "matplotlib.ft2font.FT2Font";
+ PyFT2FontType.tp_doc = PyFT2Font_init__doc__;
+ PyFT2FontType.tp_basicsize = sizeof(PyFT2Font);
+ PyFT2FontType.tp_dealloc = (destructor)PyFT2Font_dealloc;
+ PyFT2FontType.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
+ PyFT2FontType.tp_methods = methods;
+ PyFT2FontType.tp_getset = getset;
+ PyFT2FontType.tp_new = PyFT2Font_new;
+ PyFT2FontType.tp_init = (initproc)PyFT2Font_init;
+ PyFT2FontType.tp_as_buffer = &buffer_procs;
+
+ return &PyFT2FontType;
+}
+
+static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "ft2font" };
+
+PyMODINIT_FUNC PyInit_ft2font(void)
+{
+ import_array();
+
+ if (FT_Init_FreeType(&_ft2Library)) { // initialize library
+ return PyErr_Format(
+ PyExc_RuntimeError, "Could not initialize the freetype2 library");
+ }
+ FT_Int major, minor, patch;
+ char version_string[64];
+ FT_Library_Version(_ft2Library, &major, &minor, &patch);
+ snprintf(version_string, sizeof(version_string), "%d.%d.%d", major, minor, patch);
+
+ PyObject *m;
+ if (!(m = PyModule_Create(&moduledef)) ||
+ prepare_and_add_type(PyFT2Image_init_type(), m) ||
+ prepare_and_add_type(PyFT2Font_init_type(), m) ||
+ // Glyph is not constructible from Python, thus not added to the module.
+ PyType_Ready(PyGlyph_init_type()) ||
+ PyModule_AddStringConstant(m, "__freetype_version__", version_string) ||
+ PyModule_AddStringConstant(m, "__freetype_build_type__", STRINGIFY(FREETYPE_BUILD_TYPE)) ||
+ PyModule_AddIntConstant(m, "SCALABLE", FT_FACE_FLAG_SCALABLE) ||
+ PyModule_AddIntConstant(m, "FIXED_SIZES", FT_FACE_FLAG_FIXED_SIZES) ||
+ PyModule_AddIntConstant(m, "FIXED_WIDTH", FT_FACE_FLAG_FIXED_WIDTH) ||
+ PyModule_AddIntConstant(m, "SFNT", FT_FACE_FLAG_SFNT) ||
+ PyModule_AddIntConstant(m, "HORIZONTAL", FT_FACE_FLAG_HORIZONTAL) ||
+ PyModule_AddIntConstant(m, "VERTICAL", FT_FACE_FLAG_VERTICAL) ||
+ PyModule_AddIntConstant(m, "KERNING", FT_FACE_FLAG_KERNING) ||
+ PyModule_AddIntConstant(m, "FAST_GLYPHS", FT_FACE_FLAG_FAST_GLYPHS) ||
+ PyModule_AddIntConstant(m, "MULTIPLE_MASTERS", FT_FACE_FLAG_MULTIPLE_MASTERS) ||
+ PyModule_AddIntConstant(m, "GLYPH_NAMES", FT_FACE_FLAG_GLYPH_NAMES) ||
+ PyModule_AddIntConstant(m, "EXTERNAL_STREAM", FT_FACE_FLAG_EXTERNAL_STREAM) ||
+ PyModule_AddIntConstant(m, "ITALIC", FT_STYLE_FLAG_ITALIC) ||
+ PyModule_AddIntConstant(m, "BOLD", FT_STYLE_FLAG_BOLD) ||
+ PyModule_AddIntConstant(m, "KERNING_DEFAULT", FT_KERNING_DEFAULT) ||
+ PyModule_AddIntConstant(m, "KERNING_UNFITTED", FT_KERNING_UNFITTED) ||
+ PyModule_AddIntConstant(m, "KERNING_UNSCALED", FT_KERNING_UNSCALED) ||
+ PyModule_AddIntConstant(m, "LOAD_DEFAULT", FT_LOAD_DEFAULT) ||
+ PyModule_AddIntConstant(m, "LOAD_NO_SCALE", FT_LOAD_NO_SCALE) ||
+ PyModule_AddIntConstant(m, "LOAD_NO_HINTING", FT_LOAD_NO_HINTING) ||
+ PyModule_AddIntConstant(m, "LOAD_RENDER", FT_LOAD_RENDER) ||
+ PyModule_AddIntConstant(m, "LOAD_NO_BITMAP", FT_LOAD_NO_BITMAP) ||
+ PyModule_AddIntConstant(m, "LOAD_VERTICAL_LAYOUT", FT_LOAD_VERTICAL_LAYOUT) ||
+ PyModule_AddIntConstant(m, "LOAD_FORCE_AUTOHINT", FT_LOAD_FORCE_AUTOHINT) ||
+ PyModule_AddIntConstant(m, "LOAD_CROP_BITMAP", FT_LOAD_CROP_BITMAP) ||
+ PyModule_AddIntConstant(m, "LOAD_PEDANTIC", FT_LOAD_PEDANTIC) ||
+ PyModule_AddIntConstant(m, "LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH", FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH) ||
+ PyModule_AddIntConstant(m, "LOAD_NO_RECURSE", FT_LOAD_NO_RECURSE) ||
+ PyModule_AddIntConstant(m, "LOAD_IGNORE_TRANSFORM", FT_LOAD_IGNORE_TRANSFORM) ||
+ PyModule_AddIntConstant(m, "LOAD_MONOCHROME", FT_LOAD_MONOCHROME) ||
+ PyModule_AddIntConstant(m, "LOAD_LINEAR_DESIGN", FT_LOAD_LINEAR_DESIGN) ||
+ PyModule_AddIntConstant(m, "LOAD_NO_AUTOHINT", (unsigned long)FT_LOAD_NO_AUTOHINT) ||
+ PyModule_AddIntConstant(m, "LOAD_TARGET_NORMAL", (unsigned long)FT_LOAD_TARGET_NORMAL) ||
+ PyModule_AddIntConstant(m, "LOAD_TARGET_LIGHT", (unsigned long)FT_LOAD_TARGET_LIGHT) ||
+ PyModule_AddIntConstant(m, "LOAD_TARGET_MONO", (unsigned long)FT_LOAD_TARGET_MONO) ||
+ PyModule_AddIntConstant(m, "LOAD_TARGET_LCD", (unsigned long)FT_LOAD_TARGET_LCD) ||
+ PyModule_AddIntConstant(m, "LOAD_TARGET_LCD_V", (unsigned long)FT_LOAD_TARGET_LCD_V)) {
+ FT_Done_FreeType(_ft2Library);
+ Py_XDECREF(m);
+ return NULL;
+ }
+
+ return m;
+}
diff --git a/contrib/python/matplotlib/py3/src/mplutils.h b/contrib/python/matplotlib/py3/src/mplutils.h
new file mode 100644
index 0000000000..6eb89899ca
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/mplutils.h
@@ -0,0 +1,99 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+/* Small utilities that are shared by most extension modules. */
+
+#ifndef MPLUTILS_H
+#define MPLUTILS_H
+#define PY_SSIZE_T_CLEAN
+
+#include <Python.h>
+#include <stdint.h>
+
+#ifdef _POSIX_C_SOURCE
+# undef _POSIX_C_SOURCE
+#endif
+#ifndef _AIX
+#ifdef _XOPEN_SOURCE
+# undef _XOPEN_SOURCE
+#endif
+#endif
+
+// Prevent multiple conflicting definitions of swab from stdlib.h and unistd.h
+#if defined(__sun) || defined(sun)
+#if defined(_XPG4)
+#undef _XPG4
+#endif
+#if defined(_XPG3)
+#undef _XPG3
+#endif
+#endif
+
+
+inline int mpl_round_to_int(double v)
+{
+ return (int)(v + ((v >= 0.0) ? 0.5 : -0.5));
+}
+
+inline double mpl_round(double v)
+{
+ return (double)mpl_round_to_int(v);
+}
+
+// 'kind' codes for paths.
+enum {
+ STOP = 0,
+ MOVETO = 1,
+ LINETO = 2,
+ CURVE3 = 3,
+ CURVE4 = 4,
+ CLOSEPOLY = 0x4f
+};
+
+const size_t NUM_VERTICES[] = { 1, 1, 1, 2, 3, 1 };
+
+inline int prepare_and_add_type(PyTypeObject *type, PyObject *module)
+{
+ if (PyType_Ready(type)) {
+ return -1;
+ }
+ char const* ptr = strrchr(type->tp_name, '.');
+ if (!ptr) {
+ PyErr_SetString(PyExc_ValueError, "tp_name should be a qualified name");
+ return -1;
+ }
+ if (PyModule_AddObject(module, ptr + 1, (PyObject *)type)) {
+ return -1;
+ }
+ return 0;
+}
+
+#ifdef __cplusplus // not for macosx.m
+// Check that array has shape (N, d1) or (N, d1, d2). We cast d1, d2 to longs
+// so that we don't need to access the NPY_INTP_FMT macro here.
+
+template<typename T>
+inline bool check_trailing_shape(T array, char const* name, long d1)
+{
+ if (array.dim(1) != d1) {
+ PyErr_Format(PyExc_ValueError,
+ "%s must have shape (N, %ld), got (%ld, %ld)",
+ name, d1, array.dim(0), array.dim(1));
+ return false;
+ }
+ return true;
+}
+
+template<typename T>
+inline bool check_trailing_shape(T array, char const* name, long d1, long d2)
+{
+ if (array.dim(1) != d1 || array.dim(2) != d2) {
+ PyErr_Format(PyExc_ValueError,
+ "%s must have shape (N, %ld, %ld), got (%ld, %ld, %ld)",
+ name, d1, d2, array.dim(0), array.dim(1), array.dim(2));
+ return false;
+ }
+ return true;
+}
+#endif
+
+#endif
diff --git a/contrib/python/matplotlib/py3/src/numpy_cpp.h b/contrib/python/matplotlib/py3/src/numpy_cpp.h
new file mode 100644
index 0000000000..36c763d158
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/numpy_cpp.h
@@ -0,0 +1,578 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+#ifndef MPL_NUMPY_CPP_H
+#define MPL_NUMPY_CPP_H
+#define PY_SSIZE_T_CLEAN
+/***************************************************************************
+ * This file is based on original work by Mark Wiebe, available at:
+ *
+ * http://github.com/mwiebe/numpy-cpp
+ *
+ * However, the needs of matplotlib wrappers, such as treating an
+ * empty array as having the correct dimensions, have made this rather
+ * matplotlib-specific, so it's no longer compatible with the
+ * original.
+ */
+
+#include "py_exceptions.h"
+
+#include <complex>
+
+#ifdef _POSIX_C_SOURCE
+# undef _POSIX_C_SOURCE
+#endif
+#ifndef _AIX
+#ifdef _XOPEN_SOURCE
+# undef _XOPEN_SOURCE
+#endif
+#endif
+
+// Prevent multiple conflicting definitions of swab from stdlib.h and unistd.h
+#if defined(__sun) || defined(sun)
+#if defined(_XPG4)
+#undef _XPG4
+#endif
+#if defined(_XPG3)
+#undef _XPG3
+#endif
+#endif
+
+#include <Python.h>
+#include <numpy/ndarrayobject.h>
+
+namespace numpy
+{
+
+// Type traits for the NumPy types
+template <typename T>
+struct type_num_of;
+
+/* Be careful with bool arrays as python has sizeof(npy_bool) == 1, but it is
+ * not always the case that sizeof(bool) == 1. Using the array_view_accessors
+ * is always fine regardless of sizeof(bool), so do this rather than using
+ * array.data() and pointer arithmetic which will not work correctly if
+ * sizeof(bool) != 1. */
+template <> struct type_num_of<bool>
+{
+ enum {
+ value = NPY_BOOL
+ };
+};
+template <>
+struct type_num_of<npy_byte>
+{
+ enum {
+ value = NPY_BYTE
+ };
+};
+template <>
+struct type_num_of<npy_ubyte>
+{
+ enum {
+ value = NPY_UBYTE
+ };
+};
+template <>
+struct type_num_of<npy_short>
+{
+ enum {
+ value = NPY_SHORT
+ };
+};
+template <>
+struct type_num_of<npy_ushort>
+{
+ enum {
+ value = NPY_USHORT
+ };
+};
+template <>
+struct type_num_of<npy_int>
+{
+ enum {
+ value = NPY_INT
+ };
+};
+template <>
+struct type_num_of<npy_uint>
+{
+ enum {
+ value = NPY_UINT
+ };
+};
+template <>
+struct type_num_of<npy_long>
+{
+ enum {
+ value = NPY_LONG
+ };
+};
+template <>
+struct type_num_of<npy_ulong>
+{
+ enum {
+ value = NPY_ULONG
+ };
+};
+template <>
+struct type_num_of<npy_longlong>
+{
+ enum {
+ value = NPY_LONGLONG
+ };
+};
+template <>
+struct type_num_of<npy_ulonglong>
+{
+ enum {
+ value = NPY_ULONGLONG
+ };
+};
+template <>
+struct type_num_of<npy_float>
+{
+ enum {
+ value = NPY_FLOAT
+ };
+};
+template <>
+struct type_num_of<npy_double>
+{
+ enum {
+ value = NPY_DOUBLE
+ };
+};
+#if NPY_LONGDOUBLE != NPY_DOUBLE
+template <>
+struct type_num_of<npy_longdouble>
+{
+ enum {
+ value = NPY_LONGDOUBLE
+ };
+};
+#endif
+template <>
+struct type_num_of<npy_cfloat>
+{
+ enum {
+ value = NPY_CFLOAT
+ };
+};
+template <>
+struct type_num_of<std::complex<npy_float> >
+{
+ enum {
+ value = NPY_CFLOAT
+ };
+};
+template <>
+struct type_num_of<npy_cdouble>
+{
+ enum {
+ value = NPY_CDOUBLE
+ };
+};
+template <>
+struct type_num_of<std::complex<npy_double> >
+{
+ enum {
+ value = NPY_CDOUBLE
+ };
+};
+#if NPY_CLONGDOUBLE != NPY_CDOUBLE
+template <>
+struct type_num_of<npy_clongdouble>
+{
+ enum {
+ value = NPY_CLONGDOUBLE
+ };
+};
+template <>
+struct type_num_of<std::complex<npy_longdouble> >
+{
+ enum {
+ value = NPY_CLONGDOUBLE
+ };
+};
+#endif
+template <>
+struct type_num_of<PyObject *>
+{
+ enum {
+ value = NPY_OBJECT
+ };
+};
+template <typename T>
+struct type_num_of<T &>
+{
+ enum {
+ value = type_num_of<T>::value
+ };
+};
+template <typename T>
+struct type_num_of<const T>
+{
+ enum {
+ value = type_num_of<T>::value
+ };
+};
+
+template <typename T>
+struct is_const
+{
+ enum {
+ value = false
+ };
+};
+template <typename T>
+struct is_const<const T>
+{
+ enum {
+ value = true
+ };
+};
+
+namespace detail
+{
+template <template <typename, int> class AV, typename T, int ND>
+class array_view_accessors;
+
+template <template <typename, int> class AV, typename T>
+class array_view_accessors<AV, T, 1>
+{
+ public:
+ typedef AV<T, 1> AVC;
+ typedef T sub_t;
+
+ T &operator()(npy_intp i)
+ {
+ AVC *self = static_cast<AVC *>(this);
+
+ return *reinterpret_cast<T *>(self->m_data + self->m_strides[0] * i);
+ }
+
+ const T &operator()(npy_intp i) const
+ {
+ const AVC *self = static_cast<const AVC *>(this);
+
+ return *reinterpret_cast<const T *>(self->m_data + self->m_strides[0] * i);
+ }
+
+ T &operator[](npy_intp i)
+ {
+ AVC *self = static_cast<AVC *>(this);
+
+ return *reinterpret_cast<T *>(self->m_data + self->m_strides[0] * i);
+ }
+
+ const T &operator[](npy_intp i) const
+ {
+ const AVC *self = static_cast<const AVC *>(this);
+
+ return *reinterpret_cast<const T *>(self->m_data + self->m_strides[0] * i);
+ }
+};
+
+template <template <typename, int> class AV, typename T>
+class array_view_accessors<AV, T, 2>
+{
+ public:
+ typedef AV<T, 2> AVC;
+ typedef AV<T, 1> sub_t;
+
+ T &operator()(npy_intp i, npy_intp j)
+ {
+ AVC *self = static_cast<AVC *>(this);
+
+ return *reinterpret_cast<T *>(self->m_data + self->m_strides[0] * i +
+ self->m_strides[1] * j);
+ }
+
+ const T &operator()(npy_intp i, npy_intp j) const
+ {
+ const AVC *self = static_cast<const AVC *>(this);
+
+ return *reinterpret_cast<const T *>(self->m_data + self->m_strides[0] * i +
+ self->m_strides[1] * j);
+ }
+
+ sub_t subarray(npy_intp i) const
+ {
+ const AVC *self = static_cast<const AVC *>(this);
+
+ return sub_t(self->m_arr,
+ self->m_data + self->m_strides[0] * i,
+ self->m_shape + 1,
+ self->m_strides + 1);
+ }
+};
+
+template <template <typename, int> class AV, typename T>
+class array_view_accessors<AV, T, 3>
+{
+ public:
+ typedef AV<T, 3> AVC;
+ typedef AV<T, 2> sub_t;
+
+ T &operator()(npy_intp i, npy_intp j, npy_intp k)
+ {
+ AVC *self = static_cast<AVC *>(this);
+
+ return *reinterpret_cast<T *>(self->m_data + self->m_strides[0] * i +
+ self->m_strides[1] * j + self->m_strides[2] * k);
+ }
+
+ const T &operator()(npy_intp i, npy_intp j, npy_intp k) const
+ {
+ const AVC *self = static_cast<const AVC *>(this);
+
+ return *reinterpret_cast<const T *>(self->m_data + self->m_strides[0] * i +
+ self->m_strides[1] * j + self->m_strides[2] * k);
+ }
+
+ sub_t subarray(npy_intp i) const
+ {
+ const AVC *self = static_cast<const AVC *>(this);
+
+ return sub_t(self->m_arr,
+ self->m_data + self->m_strides[0] * i,
+ self->m_shape + 1,
+ self->m_strides + 1);
+ }
+
+
+};
+
+// When adding instantiations of array_view_accessors, remember to add entries
+// to zeros[] below.
+
+}
+
+static npy_intp zeros[] = { 0, 0, 0 };
+
+template <typename T, int ND>
+class array_view : public detail::array_view_accessors<array_view, T, ND>
+{
+ friend class detail::array_view_accessors<numpy::array_view, T, ND>;
+
+ private:
+ // Copies of the array data
+ PyArrayObject *m_arr;
+ npy_intp *m_shape;
+ npy_intp *m_strides;
+ char *m_data;
+
+ public:
+ typedef T value_type;
+
+ enum {
+ ndim = ND
+ };
+
+ array_view() : m_arr(NULL), m_data(NULL)
+ {
+ m_shape = zeros;
+ m_strides = zeros;
+ }
+
+ array_view(PyObject *arr, bool contiguous = false) : m_arr(NULL), m_data(NULL)
+ {
+ if (!set(arr, contiguous)) {
+ throw py::exception();
+ }
+ }
+
+ array_view(const array_view &other) : m_arr(NULL), m_data(NULL)
+ {
+ m_arr = other.m_arr;
+ Py_XINCREF(m_arr);
+ m_data = other.m_data;
+ m_shape = other.m_shape;
+ m_strides = other.m_strides;
+ }
+
+ array_view(PyArrayObject *arr, char *data, npy_intp *shape, npy_intp *strides)
+ {
+ m_arr = arr;
+ Py_XINCREF(arr);
+ m_data = data;
+ m_shape = shape;
+ m_strides = strides;
+ }
+
+ array_view(PyArrayObject *arr)
+ {
+ m_arr = arr;
+ Py_XINCREF(arr);
+ m_shape = PyArray_DIMS(m_arr);
+ m_strides = PyArray_STRIDES(m_arr);
+ m_data = PyArray_BYTES(m_arr);
+ }
+
+ array_view(npy_intp shape[ND]) : m_arr(NULL), m_shape(NULL), m_strides(NULL), m_data(NULL)
+ {
+ PyObject *arr = PyArray_SimpleNew(ND, shape, type_num_of<T>::value);
+ if (arr == NULL) {
+ throw py::exception();
+ }
+ if (!set(arr, true)) {
+ Py_DECREF(arr);
+ throw py::exception();
+ }
+ Py_DECREF(arr);
+ }
+
+ ~array_view()
+ {
+ Py_XDECREF(m_arr);
+ }
+
+ array_view& operator=(const array_view &other)
+ {
+ if (this != &other)
+ {
+ Py_XDECREF(m_arr);
+ m_arr = other.m_arr;
+ Py_XINCREF(m_arr);
+ m_data = other.m_data;
+ m_shape = other.m_shape;
+ m_strides = other.m_strides;
+ }
+ return *this;
+ }
+
+ bool set(PyObject *arr, bool contiguous = false)
+ {
+ PyArrayObject *tmp;
+
+ if (arr == NULL || arr == Py_None) {
+ Py_XDECREF(m_arr);
+ m_arr = NULL;
+ m_data = NULL;
+ m_shape = zeros;
+ m_strides = zeros;
+ } else {
+ if (contiguous) {
+ tmp = (PyArrayObject *)PyArray_ContiguousFromAny(arr, type_num_of<T>::value, 0, ND);
+ } else {
+ tmp = (PyArrayObject *)PyArray_FromObject(arr, type_num_of<T>::value, 0, ND);
+ }
+ if (tmp == NULL) {
+ return false;
+ }
+
+ if (PyArray_NDIM(tmp) == 0 || PyArray_DIM(tmp, 0) == 0) {
+ Py_XDECREF(m_arr);
+ m_arr = NULL;
+ m_data = NULL;
+ m_shape = zeros;
+ m_strides = zeros;
+ if (PyArray_NDIM(tmp) == 0 && ND == 0) {
+ m_arr = tmp;
+ return true;
+ }
+ }
+ if (PyArray_NDIM(tmp) != ND) {
+ PyErr_Format(PyExc_ValueError,
+ "Expected %d-dimensional array, got %d",
+ ND,
+ PyArray_NDIM(tmp));
+ Py_DECREF(tmp);
+ return false;
+ }
+
+ /* Copy some of the data to the view object for faster access */
+ Py_XDECREF(m_arr);
+ m_arr = tmp;
+ m_shape = PyArray_DIMS(m_arr);
+ m_strides = PyArray_STRIDES(m_arr);
+ m_data = PyArray_BYTES(tmp);
+ }
+
+ return true;
+ }
+
+ npy_intp dim(size_t i) const
+ {
+ if (i >= ND) {
+ return 0;
+ }
+ return m_shape[i];
+ }
+
+ /*
+ In most cases, code should use size() instead of dim(0), since
+ size() == 0 when any dimension is 0.
+ */
+ size_t size() const
+ {
+ bool empty = (ND == 0);
+ for (size_t i = 0; i < ND; i++) {
+ if (m_shape[i] == 0) {
+ empty = true;
+ }
+ }
+ if (empty) {
+ return 0;
+ } else {
+ return (size_t)dim(0);
+ }
+ }
+
+ bool empty() const
+ {
+ return size() == 0;
+ }
+
+ // Do not use this for array_view<bool, ND>. See comment near top of file.
+ const T *data() const
+ {
+ return (const T *)m_data;
+ }
+
+ // Do not use this for array_view<bool, ND>. See comment near top of file.
+ T *data()
+ {
+ return (T *)m_data;
+ }
+
+ // Return a new reference.
+ PyObject *pyobj()
+ {
+ Py_XINCREF(m_arr);
+ return (PyObject *)m_arr;
+ }
+
+ // Steal a reference.
+ PyObject *pyobj_steal()
+ {
+ return (PyObject *)m_arr;
+ }
+
+ static int converter(PyObject *obj, void *arrp)
+ {
+ array_view<T, ND> *arr = (array_view<T, ND> *)arrp;
+
+ if (!arr->set(obj)) {
+ return 0;
+ }
+
+ return 1;
+ }
+
+ static int converter_contiguous(PyObject *obj, void *arrp)
+ {
+ array_view<T, ND> *arr = (array_view<T, ND> *)arrp;
+
+ if (!arr->set(obj, true)) {
+ return 0;
+ }
+
+ return 1;
+ }
+};
+
+} // namespace numpy
+
+
+#endif
diff --git a/contrib/python/matplotlib/py3/src/path_converters.h b/contrib/python/matplotlib/py3/src/path_converters.h
new file mode 100644
index 0000000000..8583d84855
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/path_converters.h
@@ -0,0 +1,1106 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+#ifndef MPL_PATH_CONVERTERS_H
+#define MPL_PATH_CONVERTERS_H
+
+#include <cmath>
+#include <stdint.h>
+#include "agg_path_storage.h"
+#include "agg_clip_liang_barsky.h"
+#include "mplutils.h"
+#include "agg_conv_segmentator.h"
+
+/*
+ This file contains a number of vertex converters that modify
+ paths. They all work as iterators, where the output is generated
+ on-the-fly, and don't require a copy of the full data.
+
+ Each class represents a discrete step in a "path-cleansing" pipeline.
+ They are currently applied in the following order in the Agg backend:
+
+ 1. Affine transformation (implemented in Agg, not here)
+
+ 2. PathNanRemover: skips over segments containing non-finite numbers
+ by inserting MOVETO commands
+
+ 3. PathClipper: Clips line segments to a given rectangle. This is
+ helpful for data reduction, and also to avoid a limitation in
+ Agg where coordinates cannot be larger than 24-bit signed
+ integers.
+
+ 4. PathSnapper: Rounds the path to the nearest center-pixels.
+ This makes rectilinear curves look much better.
+
+ 5. PathSimplifier: Removes line segments from highly dense paths
+ that would not have an impact on their appearance. Speeds up
+ rendering and reduces file sizes.
+
+ 6. curve-to-line-segment conversion (implemented in Agg, not here)
+
+ 7. stroking (implemented in Agg, not here)
+ */
+
+/************************************************************
+ This is a base class for vertex converters that need to queue their
+ output. It is designed to be as fast as possible vs. the STL's queue
+ which is more flexible.
+ */
+template <int QueueSize>
+class EmbeddedQueue
+{
+ protected:
+ EmbeddedQueue() : m_queue_read(0), m_queue_write(0)
+ {
+ // empty
+ }
+
+ struct item
+ {
+ item()
+ {
+ }
+
+ inline void set(const unsigned cmd_, const double x_, const double y_)
+ {
+ cmd = cmd_;
+ x = x_;
+ y = y_;
+ }
+ unsigned cmd;
+ double x;
+ double y;
+ };
+ int m_queue_read;
+ int m_queue_write;
+ item m_queue[QueueSize];
+
+ inline void queue_push(const unsigned cmd, const double x, const double y)
+ {
+ m_queue[m_queue_write++].set(cmd, x, y);
+ }
+
+ inline bool queue_nonempty()
+ {
+ return m_queue_read < m_queue_write;
+ }
+
+ inline bool queue_pop(unsigned *cmd, double *x, double *y)
+ {
+ if (queue_nonempty()) {
+ const item &front = m_queue[m_queue_read++];
+ *cmd = front.cmd;
+ *x = front.x;
+ *y = front.y;
+
+ return true;
+ }
+
+ m_queue_read = 0;
+ m_queue_write = 0;
+
+ return false;
+ }
+
+ inline void queue_clear()
+ {
+ m_queue_read = 0;
+ m_queue_write = 0;
+ }
+};
+
+/* Defines when path segment types have more than one vertex */
+static const size_t num_extra_points_map[] =
+ {0, 0, 0, 1,
+ 2, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0
+ };
+
+/* An implementation of a simple linear congruential random number
+ generator. This is a "classic" and fast RNG which works fine for
+ our purposes of sketching lines, but should not be used for things
+ that matter, like crypto. We are implementing this ourselves
+ rather than using the C stdlib so that the seed state is not shared
+ with other third-party code. There are recent C++ options, but we
+ still require nothing later than C++98 for compatibility
+ reasons. */
+class RandomNumberGenerator
+{
+private:
+ /* These are the same constants from MS Visual C++, which
+ has the nice property that the modulus is 2^32, thus
+ saving an explicit modulo operation
+ */
+ static const uint32_t a = 214013;
+ static const uint32_t c = 2531011;
+ uint32_t m_seed;
+
+public:
+ RandomNumberGenerator() : m_seed(0) {}
+ RandomNumberGenerator(int seed) : m_seed(seed) {}
+
+ void seed(int seed)
+ {
+ m_seed = seed;
+ }
+
+ double get_double()
+ {
+ m_seed = (a * m_seed + c);
+ return (double)m_seed / (double)(1LL << 32);
+ }
+};
+
+/*
+ PathNanRemover is a vertex converter that removes non-finite values
+ from the vertices list, and inserts MOVETO commands as necessary to
+ skip over them. If a curve segment contains at least one non-finite
+ value, the entire curve segment will be skipped.
+ */
+template <class VertexSource>
+class PathNanRemover : protected EmbeddedQueue<4>
+{
+ VertexSource *m_source;
+ bool m_remove_nans;
+ bool m_has_codes;
+ bool valid_segment_exists;
+ bool m_last_segment_valid;
+ bool m_was_broken;
+ double m_initX;
+ double m_initY;
+
+ public:
+ /* has_codes should be true if the path contains bezier curve segments, or
+ * closed loops, as this requires a slower algorithm to remove the NaNs.
+ * When in doubt, set to true.
+ */
+ PathNanRemover(VertexSource &source, bool remove_nans, bool has_codes)
+ : m_source(&source), m_remove_nans(remove_nans), m_has_codes(has_codes),
+ m_last_segment_valid(false), m_was_broken(false),
+ m_initX(nan("")), m_initY(nan(""))
+ {
+ // ignore all close/end_poly commands until after the first valid
+ // (nan-free) command is encountered
+ valid_segment_exists = false;
+ }
+
+ inline void rewind(unsigned path_id)
+ {
+ queue_clear();
+ m_source->rewind(path_id);
+ }
+
+ inline unsigned vertex(double *x, double *y)
+ {
+ unsigned code;
+
+ if (!m_remove_nans) {
+ return m_source->vertex(x, y);
+ }
+
+ if (m_has_codes) {
+ /* This is the slow method for when there might be curves or closed
+ * loops. */
+ if (queue_pop(&code, x, y)) {
+ return code;
+ }
+
+ bool needs_move_to = false;
+ while (true) {
+ /* The approach here is to push each full curve
+ segment into the queue. If any non-finite values
+ are found along the way, the queue is emptied, and
+ the next curve segment is handled. */
+ code = m_source->vertex(x, y);
+ /* The vertices attached to STOP and CLOSEPOLY are never used,
+ * so we leave them as-is even if NaN. */
+ if (code == agg::path_cmd_stop) {
+ return code;
+ } else if (code == (agg::path_cmd_end_poly |
+ agg::path_flags_close) &&
+ valid_segment_exists) {
+ /* However, CLOSEPOLY only makes sense if a valid MOVETO
+ * command has already been emitted. But if a NaN was
+ * removed in the path, then we cannot close it as it is no
+ * longer a loop. We must emulate that by inserting a
+ * LINETO instead. */
+ if (m_was_broken) {
+ if (m_last_segment_valid && (
+ std::isfinite(m_initX) &&
+ std::isfinite(m_initY))) {
+ /* Join to start if both ends are valid. */
+ queue_push(agg::path_cmd_line_to, m_initX, m_initY);
+ break;
+ } else {
+ /* Skip the close, in case there are additional
+ * subpaths. */
+ continue;
+ }
+ m_was_broken = false;
+ break;
+ } else {
+ return code;
+ }
+ } else if (code == agg::path_cmd_move_to) {
+ /* Save the initial point in order to produce the last
+ * segment closing a loop, *if* we broke the loop. */
+ m_initX = *x;
+ m_initY = *y;
+ m_was_broken = false;
+ }
+
+ if (needs_move_to) {
+ queue_push(agg::path_cmd_move_to, *x, *y);
+ }
+
+ size_t num_extra_points = num_extra_points_map[code & 0xF];
+ m_last_segment_valid = (std::isfinite(*x) && std::isfinite(*y));
+ queue_push(code, *x, *y);
+
+ /* Note: this test cannot be short-circuited, since we need to
+ advance through the entire curve no matter what */
+ for (size_t i = 0; i < num_extra_points; ++i) {
+ m_source->vertex(x, y);
+ m_last_segment_valid = m_last_segment_valid &&
+ (std::isfinite(*x) && std::isfinite(*y));
+ queue_push(code, *x, *y);
+ }
+
+ if (m_last_segment_valid) {
+ valid_segment_exists = true;
+ break;
+ }
+
+ m_was_broken = true;
+ queue_clear();
+
+ /* If the last point is finite, we use that for the
+ moveto, otherwise, we'll use the first vertex of
+ the next curve. */
+ if (std::isfinite(*x) && std::isfinite(*y)) {
+ queue_push(agg::path_cmd_move_to, *x, *y);
+ needs_move_to = false;
+ } else {
+ needs_move_to = true;
+ }
+ }
+
+ if (queue_pop(&code, x, y)) {
+ return code;
+ } else {
+ return agg::path_cmd_stop;
+ }
+ } else // !m_has_codes
+ {
+ /* This is the fast path for when we know we have no codes. */
+ code = m_source->vertex(x, y);
+
+ if (code == agg::path_cmd_stop ||
+ (code == (agg::path_cmd_end_poly | agg::path_flags_close) &&
+ valid_segment_exists)) {
+ return code;
+ }
+
+ if (!(std::isfinite(*x) && std::isfinite(*y))) {
+ do {
+ code = m_source->vertex(x, y);
+ if (code == agg::path_cmd_stop ||
+ (code == (agg::path_cmd_end_poly | agg::path_flags_close) &&
+ valid_segment_exists)) {
+ return code;
+ }
+ } while (!(std::isfinite(*x) && std::isfinite(*y)));
+ return agg::path_cmd_move_to;
+ }
+ valid_segment_exists = true;
+ return code;
+ }
+ }
+};
+
+/************************************************************
+ PathClipper uses the Liang-Barsky line clipping algorithm (as
+ implemented in Agg) to clip the path to a given rectangle. Lines
+ will never extend outside of the rectangle. Curve segments are not
+ clipped, but are always included in their entirety.
+ */
+template <class VertexSource>
+class PathClipper : public EmbeddedQueue<3>
+{
+ VertexSource *m_source;
+ bool m_do_clipping;
+ agg::rect_base<double> m_cliprect;
+ double m_lastX;
+ double m_lastY;
+ bool m_moveto;
+ double m_initX;
+ double m_initY;
+ bool m_has_init;
+ bool m_was_clipped;
+
+ public:
+ PathClipper(VertexSource &source, bool do_clipping, double width, double height)
+ : m_source(&source),
+ m_do_clipping(do_clipping),
+ m_cliprect(-1.0, -1.0, width + 1.0, height + 1.0),
+ m_lastX(nan("")),
+ m_lastY(nan("")),
+ m_moveto(true),
+ m_initX(nan("")),
+ m_initY(nan("")),
+ m_has_init(false),
+ m_was_clipped(false)
+ {
+ // empty
+ }
+
+ PathClipper(VertexSource &source, bool do_clipping, const agg::rect_base<double> &rect)
+ : m_source(&source),
+ m_do_clipping(do_clipping),
+ m_cliprect(rect),
+ m_lastX(nan("")),
+ m_lastY(nan("")),
+ m_moveto(true),
+ m_initX(nan("")),
+ m_initY(nan("")),
+ m_has_init(false),
+ m_was_clipped(false)
+ {
+ m_cliprect.x1 -= 1.0;
+ m_cliprect.y1 -= 1.0;
+ m_cliprect.x2 += 1.0;
+ m_cliprect.y2 += 1.0;
+ }
+
+ inline void rewind(unsigned path_id)
+ {
+ m_has_init = false;
+ m_was_clipped = false;
+ m_moveto = true;
+ m_source->rewind(path_id);
+ }
+
+ int draw_clipped_line(double x0, double y0, double x1, double y1,
+ bool closed=false)
+ {
+ unsigned moved = agg::clip_line_segment(&x0, &y0, &x1, &y1, m_cliprect);
+ // moved >= 4 - Fully clipped
+ // moved & 1 != 0 - First point has been moved
+ // moved & 2 != 0 - Second point has been moved
+ m_was_clipped = m_was_clipped || (moved != 0);
+ if (moved < 4) {
+ if (moved & 1 || m_moveto) {
+ queue_push(agg::path_cmd_move_to, x0, y0);
+ }
+ queue_push(agg::path_cmd_line_to, x1, y1);
+ if (closed && !m_was_clipped) {
+ // Close the path only if the end point hasn't moved.
+ queue_push(agg::path_cmd_end_poly | agg::path_flags_close,
+ x1, y1);
+ }
+
+ m_moveto = false;
+ return 1;
+ }
+
+ return 0;
+ }
+
+ unsigned vertex(double *x, double *y)
+ {
+ unsigned code;
+ bool emit_moveto = false;
+
+ if (!m_do_clipping) {
+ // If not doing any clipping, just pass along the vertices verbatim
+ return m_source->vertex(x, y);
+ }
+
+ /* This is the slow path where we actually do clipping */
+
+ if (queue_pop(&code, x, y)) {
+ return code;
+ }
+
+ while ((code = m_source->vertex(x, y)) != agg::path_cmd_stop) {
+ emit_moveto = false;
+
+ switch (code) {
+ case (agg::path_cmd_end_poly | agg::path_flags_close):
+ if (m_has_init) {
+ // Queue the line from last point to the initial point, and
+ // if never clipped, add a close code.
+ draw_clipped_line(m_lastX, m_lastY, m_initX, m_initY,
+ true);
+ } else {
+ // An empty path that is immediately closed.
+ queue_push(
+ agg::path_cmd_end_poly | agg::path_flags_close,
+ m_lastX, m_lastY);
+ }
+ // If paths were not clipped, then the above code queued
+ // something, and we should exit the loop. Otherwise, continue
+ // to the next point, as there may be a new subpath.
+ if (queue_nonempty()) {
+ goto exit_loop;
+ }
+ break;
+
+ case agg::path_cmd_move_to:
+
+ // was the last command a moveto (and we have
+ // seen at least one command ?
+ // if so, shove it in the queue if in clip box
+ if (m_moveto && m_has_init &&
+ m_lastX >= m_cliprect.x1 &&
+ m_lastX <= m_cliprect.x2 &&
+ m_lastY >= m_cliprect.y1 &&
+ m_lastY <= m_cliprect.y2) {
+ // push the last moveto onto the queue
+ queue_push(agg::path_cmd_move_to, m_lastX, m_lastY);
+ // flag that we need to emit it
+ emit_moveto = true;
+ }
+ // update the internal state for this moveto
+ m_initX = m_lastX = *x;
+ m_initY = m_lastY = *y;
+ m_has_init = true;
+ m_moveto = true;
+ m_was_clipped = false;
+ // if the last command was moveto exit the loop to emit the code
+ if (emit_moveto) {
+ goto exit_loop;
+ }
+ // else, break and get the next point
+ break;
+
+ case agg::path_cmd_line_to:
+ if (draw_clipped_line(m_lastX, m_lastY, *x, *y)) {
+ m_lastX = *x;
+ m_lastY = *y;
+ goto exit_loop;
+ }
+ m_lastX = *x;
+ m_lastY = *y;
+ break;
+
+ default:
+ if (m_moveto) {
+ queue_push(agg::path_cmd_move_to, m_lastX, m_lastY);
+ m_moveto = false;
+ }
+
+ queue_push(code, *x, *y);
+ m_lastX = *x;
+ m_lastY = *y;
+ goto exit_loop;
+ }
+ }
+
+ exit_loop:
+
+ if (queue_pop(&code, x, y)) {
+ return code;
+ }
+
+ if (m_moveto && m_has_init &&
+ m_lastX >= m_cliprect.x1 &&
+ m_lastX <= m_cliprect.x2 &&
+ m_lastY >= m_cliprect.y1 &&
+ m_lastY <= m_cliprect.y2) {
+ *x = m_lastX;
+ *y = m_lastY;
+ m_moveto = false;
+ return agg::path_cmd_move_to;
+ }
+
+ return agg::path_cmd_stop;
+ }
+};
+
+/************************************************************
+ PathSnapper rounds vertices to their nearest center-pixels. This
+ makes rectilinear paths (rectangles, horizontal and vertical lines
+ etc.) look much cleaner.
+*/
+enum e_snap_mode {
+ SNAP_AUTO,
+ SNAP_FALSE,
+ SNAP_TRUE
+};
+
+template <class VertexSource>
+class PathSnapper
+{
+ private:
+ VertexSource *m_source;
+ bool m_snap;
+ double m_snap_value;
+
+ static bool should_snap(VertexSource &path, e_snap_mode snap_mode, unsigned total_vertices)
+ {
+ // If this contains only straight horizontal or vertical lines, it should be
+ // snapped to the nearest pixels
+ double x0 = 0, y0 = 0, x1 = 0, y1 = 0;
+ unsigned code;
+
+ switch (snap_mode) {
+ case SNAP_AUTO:
+ if (total_vertices > 1024) {
+ return false;
+ }
+
+ code = path.vertex(&x0, &y0);
+ if (code == agg::path_cmd_stop) {
+ return false;
+ }
+
+ while ((code = path.vertex(&x1, &y1)) != agg::path_cmd_stop) {
+ switch (code) {
+ case agg::path_cmd_curve3:
+ case agg::path_cmd_curve4:
+ return false;
+ case agg::path_cmd_line_to:
+ if (fabs(x0 - x1) >= 1e-4 && fabs(y0 - y1) >= 1e-4) {
+ return false;
+ }
+ }
+ x0 = x1;
+ y0 = y1;
+ }
+
+ return true;
+ case SNAP_FALSE:
+ return false;
+ case SNAP_TRUE:
+ return true;
+ }
+
+ return false;
+ }
+
+ public:
+ /*
+ snap_mode should be one of:
+ - SNAP_AUTO: Examine the path to determine if it should be snapped
+ - SNAP_TRUE: Force snapping
+ - SNAP_FALSE: No snapping
+ */
+ PathSnapper(VertexSource &source,
+ e_snap_mode snap_mode,
+ unsigned total_vertices = 15,
+ double stroke_width = 0.0)
+ : m_source(&source)
+ {
+ m_snap = should_snap(source, snap_mode, total_vertices);
+
+ if (m_snap) {
+ int is_odd = mpl_round_to_int(stroke_width) % 2;
+ m_snap_value = (is_odd) ? 0.5 : 0.0;
+ }
+
+ source.rewind(0);
+ }
+
+ inline void rewind(unsigned path_id)
+ {
+ m_source->rewind(path_id);
+ }
+
+ inline unsigned vertex(double *x, double *y)
+ {
+ unsigned code;
+ code = m_source->vertex(x, y);
+ if (m_snap && agg::is_vertex(code)) {
+ *x = floor(*x + 0.5) + m_snap_value;
+ *y = floor(*y + 0.5) + m_snap_value;
+ }
+ return code;
+ }
+
+ inline bool is_snapping()
+ {
+ return m_snap;
+ }
+};
+
+/************************************************************
+ PathSimplifier reduces the number of vertices in a dense path without
+ changing its appearance.
+*/
+template <class VertexSource>
+class PathSimplifier : protected EmbeddedQueue<9>
+{
+ public:
+ /* Set simplify to true to perform simplification */
+ PathSimplifier(VertexSource &source, bool do_simplify, double simplify_threshold)
+ : m_source(&source),
+ m_simplify(do_simplify),
+ /* we square simplify_threshold so that we can compute
+ norms without doing the square root every step. */
+ m_simplify_threshold(simplify_threshold * simplify_threshold),
+
+ m_moveto(true),
+ m_after_moveto(false),
+ m_clipped(false),
+
+ // the x, y values from last iteration
+ m_lastx(0.0),
+ m_lasty(0.0),
+
+ // the dx, dy comprising the original vector, used in conjunction
+ // with m_currVecStart* to define the original vector.
+ m_origdx(0.0),
+ m_origdy(0.0),
+
+ // the squared norm of the original vector
+ m_origdNorm2(0.0),
+
+ // maximum squared norm of vector in forward (parallel) direction
+ m_dnorm2ForwardMax(0.0),
+ // maximum squared norm of vector in backward (anti-parallel) direction
+ m_dnorm2BackwardMax(0.0),
+
+ // was the last point the furthest from lastWritten in the
+ // forward (parallel) direction?
+ m_lastForwardMax(false),
+ // was the last point the furthest from lastWritten in the
+ // backward (anti-parallel) direction?
+ m_lastBackwardMax(false),
+
+ // added to queue when _push is called
+ m_nextX(0.0),
+ m_nextY(0.0),
+
+ // added to queue when _push is called if any backwards
+ // (anti-parallel) vectors were observed
+ m_nextBackwardX(0.0),
+ m_nextBackwardY(0.0),
+
+ // start of the current vector that is being simplified
+ m_currVecStartX(0.0),
+ m_currVecStartY(0.0)
+ {
+ // empty
+ }
+
+ inline void rewind(unsigned path_id)
+ {
+ queue_clear();
+ m_moveto = true;
+ m_source->rewind(path_id);
+ }
+
+ unsigned vertex(double *x, double *y)
+ {
+ unsigned cmd;
+
+ /* The simplification algorithm doesn't support curves or compound paths
+ so we just don't do it at all in that case... */
+ if (!m_simplify) {
+ return m_source->vertex(x, y);
+ }
+
+ /* idea: we can skip drawing many lines: we can combine
+ sequential parallel lines into a
+ single line instead of redrawing lines over the same
+ points. The loop below works a bit like a state machine,
+ where what it does depends on what it did in the last
+ looping. To test whether sequential lines are close to
+ parallel, I calculate the distance moved perpendicular to
+ the last line. Once it gets too big, the lines cannot be
+ combined. */
+
+ /* This code was originally written by Allan Haldane and I
+ have modified to work in-place -- meaning not creating an
+ entirely new path list each time. In order to do that
+ without too much additional code complexity, it keeps a
+ small queue around so that multiple points can be emitted
+ in a single call, and those points will be popped from the
+ queue in subsequent calls. The following block will empty
+ the queue before proceeding to the main loop below.
+ -- Michael Droettboom */
+
+ /* This code was originally written by Allan Haldane and
+ updated by Michael Droettboom. I have modified it to
+ handle anti-parallel vectors. This is done essentially
+ the same way as parallel vectors, but requires a little
+ additional book-keeping to track whether or not we have
+ observed an anti-parallel vector during the current run.
+ -- Kevin Rose */
+
+ if (queue_pop(&cmd, x, y)) {
+ return cmd;
+ }
+
+ /* The main simplification loop. The point is to consume only
+ as many points as necessary until something has been added
+ to the outbound queue, not to run through the entire path
+ in one go. This eliminates the need to allocate and fill
+ an entire additional path array on each draw. */
+ while ((cmd = m_source->vertex(x, y)) != agg::path_cmd_stop) {
+ /* if we are starting a new path segment, move to the first point
+ + init */
+
+ if (m_moveto || cmd == agg::path_cmd_move_to) {
+ /* m_moveto check is not generally needed because
+ m_source generates an initial moveto; but it is
+ retained for safety in case circumstances arise
+ where this is not true. */
+ if (m_origdNorm2 != 0.0 && !m_after_moveto) {
+ /* m_origdNorm2 is nonzero only if we have a
+ vector; the m_after_moveto check ensures we
+ push this vector to the queue only once. */
+ _push(x, y);
+ }
+ m_after_moveto = true;
+ m_lastx = *x;
+ m_lasty = *y;
+ m_moveto = false;
+ m_origdNorm2 = 0.0;
+ m_dnorm2BackwardMax = 0.0;
+ m_clipped = true;
+ if (queue_nonempty()) {
+ /* If we did a push, empty the queue now. */
+ break;
+ }
+ continue;
+ }
+ m_after_moveto = false;
+
+ /* NOTE: We used to skip this very short segments, but if
+ you have a lot of them cumulatively, you can miss
+ maxima or minima in the data. */
+
+ /* Don't render line segments less than one pixel long */
+ /* if (fabs(*x - m_lastx) < 1.0 && fabs(*y - m_lasty) < 1.0) */
+ /* { */
+ /* continue; */
+ /* } */
+
+ /* if we have no orig vector, set it to this vector and
+ continue. this orig vector is the reference vector we
+ will build up the line to */
+ if (m_origdNorm2 == 0.0) {
+ if (m_clipped) {
+ queue_push(agg::path_cmd_move_to, m_lastx, m_lasty);
+ m_clipped = false;
+ }
+
+ m_origdx = *x - m_lastx;
+ m_origdy = *y - m_lasty;
+ m_origdNorm2 = m_origdx * m_origdx + m_origdy * m_origdy;
+
+ // set all the variables to reflect this new orig vector
+ m_dnorm2ForwardMax = m_origdNorm2;
+ m_dnorm2BackwardMax = 0.0;
+ m_lastForwardMax = true;
+ m_lastBackwardMax = false;
+
+ m_currVecStartX = m_lastx;
+ m_currVecStartY = m_lasty;
+ m_nextX = m_lastx = *x;
+ m_nextY = m_lasty = *y;
+ continue;
+ }
+
+ /* If got to here, then we have an orig vector and we just got
+ a vector in the sequence. */
+
+ /* Check that the perpendicular distance we have moved
+ from the last written point compared to the line we are
+ building is not too much. If o is the orig vector (we
+ are building on), and v is the vector from the last
+ written point to the current point, then the
+ perpendicular vector is p = v - (o.v)o/(o.o)
+ (here, a.b indicates the dot product of a and b). */
+
+ /* get the v vector */
+ double totdx = *x - m_currVecStartX;
+ double totdy = *y - m_currVecStartY;
+
+ /* get the dot product o.v */
+ double totdot = m_origdx * totdx + m_origdy * totdy;
+
+ /* get the para vector ( = (o.v)o/(o.o)) */
+ double paradx = totdot * m_origdx / m_origdNorm2;
+ double parady = totdot * m_origdy / m_origdNorm2;
+
+ /* get the perp vector ( = v - para) */
+ double perpdx = totdx - paradx;
+ double perpdy = totdy - parady;
+
+ /* get the squared norm of perp vector ( = p.p) */
+ double perpdNorm2 = perpdx * perpdx + perpdy * perpdy;
+
+ /* If the perpendicular vector is less than
+ m_simplify_threshold pixels in size, then merge
+ current x,y with the current vector */
+ if (perpdNorm2 < m_simplify_threshold) {
+ /* check if the current vector is parallel or
+ anti-parallel to the orig vector. In either case,
+ test if it is the longest of the vectors
+ we are merging in that direction. If it is, then
+ update the current vector in that direction. */
+ double paradNorm2 = paradx * paradx + parady * parady;
+
+ m_lastForwardMax = false;
+ m_lastBackwardMax = false;
+ if (totdot > 0.0) {
+ if (paradNorm2 > m_dnorm2ForwardMax) {
+ m_lastForwardMax = true;
+ m_dnorm2ForwardMax = paradNorm2;
+ m_nextX = *x;
+ m_nextY = *y;
+ }
+ } else {
+ if (paradNorm2 > m_dnorm2BackwardMax) {
+ m_lastBackwardMax = true;
+ m_dnorm2BackwardMax = paradNorm2;
+ m_nextBackwardX = *x;
+ m_nextBackwardY = *y;
+ }
+ }
+
+ m_lastx = *x;
+ m_lasty = *y;
+ continue;
+ }
+
+ /* If we get here, then this vector was not similar enough to the
+ line we are building, so we need to draw that line and start the
+ next one. */
+
+ /* If the line needs to extend in the opposite direction from the
+ direction we are drawing in, move back to we start drawing from
+ back there. */
+ _push(x, y);
+
+ break;
+ }
+
+ /* Fill the queue with the remaining vertices if we've finished the
+ path in the above loop. */
+ if (cmd == agg::path_cmd_stop) {
+ if (m_origdNorm2 != 0.0) {
+ queue_push((m_moveto || m_after_moveto) ? agg::path_cmd_move_to
+ : agg::path_cmd_line_to,
+ m_nextX,
+ m_nextY);
+ if (m_dnorm2BackwardMax > 0.0) {
+ queue_push((m_moveto || m_after_moveto) ? agg::path_cmd_move_to
+ : agg::path_cmd_line_to,
+ m_nextBackwardX,
+ m_nextBackwardY);
+ }
+ m_moveto = false;
+ }
+ queue_push((m_moveto || m_after_moveto) ? agg::path_cmd_move_to : agg::path_cmd_line_to,
+ m_lastx,
+ m_lasty);
+ m_moveto = false;
+ queue_push(agg::path_cmd_stop, 0.0, 0.0);
+ }
+
+ /* Return the first item in the queue, if any, otherwise
+ indicate that we're done. */
+ if (queue_pop(&cmd, x, y)) {
+ return cmd;
+ } else {
+ return agg::path_cmd_stop;
+ }
+ }
+
+ private:
+ VertexSource *m_source;
+ bool m_simplify;
+ double m_simplify_threshold;
+
+ bool m_moveto;
+ bool m_after_moveto;
+ bool m_clipped;
+ double m_lastx, m_lasty;
+
+ double m_origdx;
+ double m_origdy;
+ double m_origdNorm2;
+ double m_dnorm2ForwardMax;
+ double m_dnorm2BackwardMax;
+ bool m_lastForwardMax;
+ bool m_lastBackwardMax;
+ double m_nextX;
+ double m_nextY;
+ double m_nextBackwardX;
+ double m_nextBackwardY;
+ double m_currVecStartX;
+ double m_currVecStartY;
+
+ inline void _push(double *x, double *y)
+ {
+ bool needToPushBack = (m_dnorm2BackwardMax > 0.0);
+
+ /* If we observed any backward (anti-parallel) vectors, then
+ we need to push both forward and backward vectors. */
+ if (needToPushBack) {
+ /* If the last vector seen was the maximum in the forward direction,
+ then we need to push the forward after the backward. Otherwise,
+ the last vector seen was the maximum in the backward direction,
+ or somewhere in between, either way we are safe pushing forward
+ before backward. */
+ if (m_lastForwardMax) {
+ queue_push(agg::path_cmd_line_to, m_nextBackwardX, m_nextBackwardY);
+ queue_push(agg::path_cmd_line_to, m_nextX, m_nextY);
+ } else {
+ queue_push(agg::path_cmd_line_to, m_nextX, m_nextY);
+ queue_push(agg::path_cmd_line_to, m_nextBackwardX, m_nextBackwardY);
+ }
+ } else {
+ /* If we did not observe any backwards vectors, just push forward. */
+ queue_push(agg::path_cmd_line_to, m_nextX, m_nextY);
+ }
+
+ /* If we clipped some segments between this line and the next line
+ we are starting, we also need to move to the last point. */
+ if (m_clipped) {
+ queue_push(agg::path_cmd_move_to, m_lastx, m_lasty);
+ } else if ((!m_lastForwardMax) && (!m_lastBackwardMax)) {
+ /* If the last line was not the longest line, then move
+ back to the end point of the last line in the
+ sequence. Only do this if not clipped, since in that
+ case lastx,lasty is not part of the line just drawn. */
+
+ /* Would be move_to if not for the artifacts */
+ queue_push(agg::path_cmd_line_to, m_lastx, m_lasty);
+ }
+
+ /* Now reset all the variables to get ready for the next line */
+ m_origdx = *x - m_lastx;
+ m_origdy = *y - m_lasty;
+ m_origdNorm2 = m_origdx * m_origdx + m_origdy * m_origdy;
+
+ m_dnorm2ForwardMax = m_origdNorm2;
+ m_lastForwardMax = true;
+ m_currVecStartX = m_queue[m_queue_write - 1].x;
+ m_currVecStartY = m_queue[m_queue_write - 1].y;
+ m_lastx = m_nextX = *x;
+ m_lasty = m_nextY = *y;
+ m_dnorm2BackwardMax = 0.0;
+ m_lastBackwardMax = false;
+
+ m_clipped = false;
+ }
+};
+
+template <class VertexSource>
+class Sketch
+{
+ public:
+ /*
+ scale: the scale of the wiggle perpendicular to the original
+ line (in pixels)
+
+ length: the base wavelength of the wiggle along the
+ original line (in pixels)
+
+ randomness: the factor that the sketch length will randomly
+ shrink and expand.
+ */
+ Sketch(VertexSource &source, double scale, double length, double randomness)
+ : m_source(&source),
+ m_scale(scale),
+ m_length(length),
+ m_randomness(randomness),
+ m_segmented(source),
+ m_last_x(0.0),
+ m_last_y(0.0),
+ m_has_last(false),
+ m_p(0.0),
+ m_rand(0)
+ {
+ rewind(0);
+ const double d_M_PI = 3.14159265358979323846;
+ m_p_scale = (2.0 * d_M_PI) / (m_length * m_randomness);
+ m_log_randomness = 2.0 * log(m_randomness);
+ }
+
+ unsigned vertex(double *x, double *y)
+ {
+ if (m_scale == 0.0) {
+ return m_source->vertex(x, y);
+ }
+
+ unsigned code = m_segmented.vertex(x, y);
+
+ if (code == agg::path_cmd_move_to) {
+ m_has_last = false;
+ m_p = 0.0;
+ }
+
+ if (m_has_last) {
+ // We want the "cursor" along the sine wave to move at a
+ // random rate.
+ double d_rand = m_rand.get_double();
+ // Original computation
+ // p += pow(k, 2*rand - 1)
+ // r = sin(p * c)
+ // x86 computes pow(a, b) as exp(b*log(a))
+ // First, move -1 out, so
+ // p' += pow(k, 2*rand)
+ // r = sin(p * c') where c' = c / k
+ // Next, use x86 logic (will not be worse on other platforms as
+ // the log is only computed once and pow and exp are, at worst,
+ // the same)
+ // So p+= exp(2*rand*log(k))
+ // lk = 2*log(k)
+ // p += exp(rand*lk)
+ m_p += exp(d_rand * m_log_randomness);
+ double den = m_last_x - *x;
+ double num = m_last_y - *y;
+ double len = num * num + den * den;
+ m_last_x = *x;
+ m_last_y = *y;
+ if (len != 0) {
+ len = sqrt(len);
+ double r = sin(m_p * m_p_scale) * m_scale;
+ double roverlen = r / len;
+ *x += roverlen * num;
+ *y -= roverlen * den;
+ }
+ } else {
+ m_last_x = *x;
+ m_last_y = *y;
+ }
+
+ m_has_last = true;
+
+ return code;
+ }
+
+ inline void rewind(unsigned path_id)
+ {
+ m_has_last = false;
+ m_p = 0.0;
+ if (m_scale != 0.0) {
+ m_rand.seed(0);
+ m_segmented.rewind(path_id);
+ } else {
+ m_source->rewind(path_id);
+ }
+ }
+
+ private:
+ VertexSource *m_source;
+ double m_scale;
+ double m_length;
+ double m_randomness;
+ agg::conv_segmentator<VertexSource> m_segmented;
+ double m_last_x;
+ double m_last_y;
+ bool m_has_last;
+ double m_p;
+ RandomNumberGenerator m_rand;
+ double m_p_scale;
+ double m_log_randomness;
+};
+
+#endif // MPL_PATH_CONVERTERS_H
diff --git a/contrib/python/matplotlib/py3/src/py_adaptors.h b/contrib/python/matplotlib/py3/src/py_adaptors.h
new file mode 100644
index 0000000000..7722137dc6
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/py_adaptors.h
@@ -0,0 +1,248 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+#ifndef MPL_PY_ADAPTORS_H
+#define MPL_PY_ADAPTORS_H
+#define PY_SSIZE_T_CLEAN
+/***************************************************************************
+ * This module contains a number of C++ classes that adapt Python data
+ * structures to C++ and Agg-friendly interfaces.
+ */
+
+#include <Python.h>
+
+#include "numpy/arrayobject.h"
+
+#include "py_exceptions.h"
+
+extern "C" {
+int convert_path(PyObject *obj, void *pathp);
+}
+
+namespace py
+{
+
+/************************************************************
+ * py::PathIterator acts as a bridge between Numpy and Agg. Given a
+ * pair of Numpy arrays, vertices and codes, it iterates over
+ * those vertices and codes, using the standard Agg vertex source
+ * interface:
+ *
+ * unsigned vertex(double* x, double* y)
+ */
+class PathIterator
+{
+ /* We hold references to the Python objects, not just the
+ underlying data arrays, so that Python reference counting
+ can work.
+ */
+ PyArrayObject *m_vertices;
+ PyArrayObject *m_codes;
+
+ unsigned m_iterator;
+ unsigned m_total_vertices;
+
+ /* This class doesn't actually do any simplification, but we
+ store the value here, since it is obtained from the Python
+ object.
+ */
+ bool m_should_simplify;
+ double m_simplify_threshold;
+
+ public:
+ inline PathIterator()
+ : m_vertices(NULL),
+ m_codes(NULL),
+ m_iterator(0),
+ m_total_vertices(0),
+ m_should_simplify(false),
+ m_simplify_threshold(1.0 / 9.0)
+ {
+ }
+
+ inline PathIterator(PyObject *vertices,
+ PyObject *codes,
+ bool should_simplify,
+ double simplify_threshold)
+ : m_vertices(NULL), m_codes(NULL), m_iterator(0)
+ {
+ if (!set(vertices, codes, should_simplify, simplify_threshold))
+ throw py::exception();
+ }
+
+ inline PathIterator(PyObject *vertices, PyObject *codes)
+ : m_vertices(NULL), m_codes(NULL), m_iterator(0)
+ {
+ if (!set(vertices, codes))
+ throw py::exception();
+ }
+
+ inline PathIterator(const PathIterator &other)
+ {
+ Py_XINCREF(other.m_vertices);
+ m_vertices = other.m_vertices;
+
+ Py_XINCREF(other.m_codes);
+ m_codes = other.m_codes;
+
+ m_iterator = 0;
+ m_total_vertices = other.m_total_vertices;
+
+ m_should_simplify = other.m_should_simplify;
+ m_simplify_threshold = other.m_simplify_threshold;
+ }
+
+ ~PathIterator()
+ {
+ Py_XDECREF(m_vertices);
+ Py_XDECREF(m_codes);
+ }
+
+ inline int
+ set(PyObject *vertices, PyObject *codes, bool should_simplify, double simplify_threshold)
+ {
+ m_should_simplify = should_simplify;
+ m_simplify_threshold = simplify_threshold;
+
+ Py_XDECREF(m_vertices);
+ m_vertices = (PyArrayObject *)PyArray_FromObject(vertices, NPY_DOUBLE, 2, 2);
+
+ if (!m_vertices || PyArray_DIM(m_vertices, 1) != 2) {
+ PyErr_SetString(PyExc_ValueError, "Invalid vertices array");
+ return 0;
+ }
+
+ Py_XDECREF(m_codes);
+ m_codes = NULL;
+
+ if (codes != NULL && codes != Py_None) {
+ m_codes = (PyArrayObject *)PyArray_FromObject(codes, NPY_UINT8, 1, 1);
+
+ if (!m_codes || PyArray_DIM(m_codes, 0) != PyArray_DIM(m_vertices, 0)) {
+ PyErr_SetString(PyExc_ValueError, "Invalid codes array");
+ return 0;
+ }
+ }
+
+ m_total_vertices = (unsigned)PyArray_DIM(m_vertices, 0);
+ m_iterator = 0;
+
+ return 1;
+ }
+
+ inline int set(PyObject *vertices, PyObject *codes)
+ {
+ return set(vertices, codes, false, 0.0);
+ }
+
+ inline unsigned vertex(double *x, double *y)
+ {
+ if (m_iterator >= m_total_vertices) {
+ *x = 0.0;
+ *y = 0.0;
+ return agg::path_cmd_stop;
+ }
+
+ const size_t idx = m_iterator++;
+
+ char *pair = (char *)PyArray_GETPTR2(m_vertices, idx, 0);
+ *x = *(double *)pair;
+ *y = *(double *)(pair + PyArray_STRIDE(m_vertices, 1));
+
+ if (m_codes != NULL) {
+ return (unsigned)(*(char *)PyArray_GETPTR1(m_codes, idx));
+ } else {
+ return idx == 0 ? agg::path_cmd_move_to : agg::path_cmd_line_to;
+ }
+ }
+
+ inline void rewind(unsigned path_id)
+ {
+ m_iterator = path_id;
+ }
+
+ inline unsigned total_vertices() const
+ {
+ return m_total_vertices;
+ }
+
+ inline bool should_simplify() const
+ {
+ return m_should_simplify;
+ }
+
+ inline double simplify_threshold() const
+ {
+ return m_simplify_threshold;
+ }
+
+ inline bool has_codes() const
+ {
+ return m_codes != NULL;
+ }
+
+ inline void *get_id()
+ {
+ return (void *)m_vertices;
+ }
+};
+
+class PathGenerator
+{
+ PyObject *m_paths;
+ Py_ssize_t m_npaths;
+
+ public:
+ typedef PathIterator path_iterator;
+
+ PathGenerator() : m_paths(NULL), m_npaths(0) {}
+
+ ~PathGenerator()
+ {
+ Py_XDECREF(m_paths);
+ }
+
+ int set(PyObject *obj)
+ {
+ if (!PySequence_Check(obj)) {
+ return 0;
+ }
+
+ Py_XDECREF(m_paths);
+ m_paths = obj;
+ Py_INCREF(m_paths);
+
+ m_npaths = PySequence_Size(m_paths);
+
+ return 1;
+ }
+
+ Py_ssize_t num_paths() const
+ {
+ return m_npaths;
+ }
+
+ Py_ssize_t size() const
+ {
+ return m_npaths;
+ }
+
+ path_iterator operator()(size_t i)
+ {
+ path_iterator path;
+ PyObject *item;
+
+ item = PySequence_GetItem(m_paths, i % m_npaths);
+ if (item == NULL) {
+ throw py::exception();
+ }
+ if (!convert_path(item, &path)) {
+ Py_DECREF(item);
+ throw py::exception();
+ }
+ Py_DECREF(item);
+ return path;
+ }
+};
+}
+
+#endif
diff --git a/contrib/python/matplotlib/py3/src/py_converters.cpp b/contrib/python/matplotlib/py3/src/py_converters.cpp
new file mode 100644
index 0000000000..04382c5f94
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/py_converters.cpp
@@ -0,0 +1,558 @@
+#define NO_IMPORT_ARRAY
+#define PY_SSIZE_T_CLEAN
+#include "py_converters.h"
+#include "numpy_cpp.h"
+
+#include "agg_basics.h"
+#include "agg_color_rgba.h"
+#include "agg_math_stroke.h"
+
+extern "C" {
+
+static int convert_string_enum(PyObject *obj, const char *name, const char **names, int *values, int *result)
+{
+ PyObject *bytesobj;
+ char *str;
+
+ if (obj == NULL || obj == Py_None) {
+ return 1;
+ }
+
+ if (PyUnicode_Check(obj)) {
+ bytesobj = PyUnicode_AsASCIIString(obj);
+ if (bytesobj == NULL) {
+ return 0;
+ }
+ } else if (PyBytes_Check(obj)) {
+ Py_INCREF(obj);
+ bytesobj = obj;
+ } else {
+ PyErr_Format(PyExc_TypeError, "%s must be str or bytes", name);
+ return 0;
+ }
+
+ str = PyBytes_AsString(bytesobj);
+ if (str == NULL) {
+ Py_DECREF(bytesobj);
+ return 0;
+ }
+
+ for ( ; *names != NULL; names++, values++) {
+ if (strncmp(str, *names, 64) == 0) {
+ *result = *values;
+ Py_DECREF(bytesobj);
+ return 1;
+ }
+ }
+
+ PyErr_Format(PyExc_ValueError, "invalid %s value", name);
+ Py_DECREF(bytesobj);
+ return 0;
+}
+
+int convert_from_method(PyObject *obj, const char *name, converter func, void *p)
+{
+ PyObject *value;
+
+ value = PyObject_CallMethod(obj, name, NULL);
+ if (value == NULL) {
+ if (!PyObject_HasAttrString(obj, name)) {
+ PyErr_Clear();
+ return 1;
+ }
+ return 0;
+ }
+
+ if (!func(value, p)) {
+ Py_DECREF(value);
+ return 0;
+ }
+
+ Py_DECREF(value);
+ return 1;
+}
+
+int convert_from_attr(PyObject *obj, const char *name, converter func, void *p)
+{
+ PyObject *value;
+
+ value = PyObject_GetAttrString(obj, name);
+ if (value == NULL) {
+ if (!PyObject_HasAttrString(obj, name)) {
+ PyErr_Clear();
+ return 1;
+ }
+ return 0;
+ }
+
+ if (!func(value, p)) {
+ Py_DECREF(value);
+ return 0;
+ }
+
+ Py_DECREF(value);
+ return 1;
+}
+
+int convert_double(PyObject *obj, void *p)
+{
+ double *val = (double *)p;
+
+ *val = PyFloat_AsDouble(obj);
+ if (PyErr_Occurred()) {
+ return 0;
+ }
+
+ return 1;
+}
+
+int convert_bool(PyObject *obj, void *p)
+{
+ bool *val = (bool *)p;
+ switch (PyObject_IsTrue(obj)) {
+ case 0: *val = false; break;
+ case 1: *val = true; break;
+ default: return 0; // errored.
+ }
+ return 1;
+}
+
+int convert_cap(PyObject *capobj, void *capp)
+{
+ const char *names[] = {"butt", "round", "projecting", NULL};
+ int values[] = {agg::butt_cap, agg::round_cap, agg::square_cap};
+ int result = agg::butt_cap;
+
+ if (!convert_string_enum(capobj, "capstyle", names, values, &result)) {
+ return 0;
+ }
+
+ *(agg::line_cap_e *)capp = (agg::line_cap_e)result;
+ return 1;
+}
+
+int convert_join(PyObject *joinobj, void *joinp)
+{
+ const char *names[] = {"miter", "round", "bevel", NULL};
+ int values[] = {agg::miter_join_revert, agg::round_join, agg::bevel_join};
+ int result = agg::miter_join_revert;
+
+ if (!convert_string_enum(joinobj, "joinstyle", names, values, &result)) {
+ return 0;
+ }
+
+ *(agg::line_join_e *)joinp = (agg::line_join_e)result;
+ return 1;
+}
+
+int convert_rect(PyObject *rectobj, void *rectp)
+{
+ agg::rect_d *rect = (agg::rect_d *)rectp;
+
+ if (rectobj == NULL || rectobj == Py_None) {
+ rect->x1 = 0.0;
+ rect->y1 = 0.0;
+ rect->x2 = 0.0;
+ rect->y2 = 0.0;
+ } else {
+ PyArrayObject *rect_arr = (PyArrayObject *)PyArray_ContiguousFromAny(
+ rectobj, NPY_DOUBLE, 1, 2);
+ if (rect_arr == NULL) {
+ return 0;
+ }
+
+ if (PyArray_NDIM(rect_arr) == 2) {
+ if (PyArray_DIM(rect_arr, 0) != 2 ||
+ PyArray_DIM(rect_arr, 1) != 2) {
+ PyErr_SetString(PyExc_ValueError, "Invalid bounding box");
+ Py_DECREF(rect_arr);
+ return 0;
+ }
+
+ } else { // PyArray_NDIM(rect_arr) == 1
+ if (PyArray_DIM(rect_arr, 0) != 4) {
+ PyErr_SetString(PyExc_ValueError, "Invalid bounding box");
+ Py_DECREF(rect_arr);
+ return 0;
+ }
+ }
+
+ double *buff = (double *)PyArray_DATA(rect_arr);
+ rect->x1 = buff[0];
+ rect->y1 = buff[1];
+ rect->x2 = buff[2];
+ rect->y2 = buff[3];
+
+ Py_DECREF(rect_arr);
+ }
+ return 1;
+}
+
+int convert_rgba(PyObject *rgbaobj, void *rgbap)
+{
+ agg::rgba *rgba = (agg::rgba *)rgbap;
+ PyObject *rgbatuple = NULL;
+ int success = 1;
+ if (rgbaobj == NULL || rgbaobj == Py_None) {
+ rgba->r = 0.0;
+ rgba->g = 0.0;
+ rgba->b = 0.0;
+ rgba->a = 0.0;
+ } else {
+ if (!(rgbatuple = PySequence_Tuple(rgbaobj))) {
+ success = 0;
+ goto exit;
+ }
+ rgba->a = 1.0;
+ if (!PyArg_ParseTuple(
+ rgbatuple, "ddd|d:rgba", &(rgba->r), &(rgba->g), &(rgba->b), &(rgba->a))) {
+ success = 0;
+ goto exit;
+ }
+ }
+exit:
+ Py_XDECREF(rgbatuple);
+ return success;
+}
+
+int convert_dashes(PyObject *dashobj, void *dashesp)
+{
+ Dashes *dashes = (Dashes *)dashesp;
+
+ double dash_offset = 0.0;
+ PyObject *dashes_seq = NULL;
+
+ if (!PyArg_ParseTuple(dashobj, "dO:dashes", &dash_offset, &dashes_seq)) {
+ return 0;
+ }
+
+ if (dashes_seq == Py_None) {
+ return 1;
+ }
+
+ if (!PySequence_Check(dashes_seq)) {
+ PyErr_SetString(PyExc_TypeError, "Invalid dashes sequence");
+ return 0;
+ }
+
+ Py_ssize_t nentries = PySequence_Size(dashes_seq);
+ // If the dashpattern has odd length, iterate through it twice (in
+ // accordance with the pdf/ps/svg specs).
+ Py_ssize_t dash_pattern_length = (nentries % 2) ? 2 * nentries : nentries;
+
+ for (Py_ssize_t i = 0; i < dash_pattern_length; ++i) {
+ PyObject *item;
+ double length;
+ double skip;
+
+ item = PySequence_GetItem(dashes_seq, i % nentries);
+ if (item == NULL) {
+ return 0;
+ }
+ length = PyFloat_AsDouble(item);
+ if (PyErr_Occurred()) {
+ Py_DECREF(item);
+ return 0;
+ }
+ Py_DECREF(item);
+
+ ++i;
+
+ item = PySequence_GetItem(dashes_seq, i % nentries);
+ if (item == NULL) {
+ return 0;
+ }
+ skip = PyFloat_AsDouble(item);
+ if (PyErr_Occurred()) {
+ Py_DECREF(item);
+ return 0;
+ }
+ Py_DECREF(item);
+
+ dashes->add_dash_pair(length, skip);
+ }
+
+ dashes->set_dash_offset(dash_offset);
+
+ return 1;
+}
+
+int convert_dashes_vector(PyObject *obj, void *dashesp)
+{
+ DashesVector *dashes = (DashesVector *)dashesp;
+
+ if (!PySequence_Check(obj)) {
+ return 0;
+ }
+
+ Py_ssize_t n = PySequence_Size(obj);
+
+ for (Py_ssize_t i = 0; i < n; ++i) {
+ PyObject *item;
+ Dashes subdashes;
+
+ item = PySequence_GetItem(obj, i);
+ if (item == NULL) {
+ return 0;
+ }
+
+ if (!convert_dashes(item, &subdashes)) {
+ Py_DECREF(item);
+ return 0;
+ }
+ Py_DECREF(item);
+
+ dashes->push_back(subdashes);
+ }
+
+ return 1;
+}
+
+int convert_trans_affine(PyObject *obj, void *transp)
+{
+ agg::trans_affine *trans = (agg::trans_affine *)transp;
+
+ /** If None assume identity transform. */
+ if (obj == NULL || obj == Py_None) {
+ return 1;
+ }
+
+ PyArrayObject *array = (PyArrayObject *)PyArray_ContiguousFromAny(obj, NPY_DOUBLE, 2, 2);
+ if (array == NULL) {
+ return 0;
+ }
+
+ if (PyArray_DIM(array, 0) == 3 && PyArray_DIM(array, 1) == 3) {
+ double *buffer = (double *)PyArray_DATA(array);
+ trans->sx = buffer[0];
+ trans->shx = buffer[1];
+ trans->tx = buffer[2];
+
+ trans->shy = buffer[3];
+ trans->sy = buffer[4];
+ trans->ty = buffer[5];
+
+ Py_DECREF(array);
+ return 1;
+ }
+
+ Py_DECREF(array);
+ PyErr_SetString(PyExc_ValueError, "Invalid affine transformation matrix");
+ return 0;
+}
+
+int convert_path(PyObject *obj, void *pathp)
+{
+ py::PathIterator *path = (py::PathIterator *)pathp;
+
+ PyObject *vertices_obj = NULL;
+ PyObject *codes_obj = NULL;
+ PyObject *should_simplify_obj = NULL;
+ PyObject *simplify_threshold_obj = NULL;
+ bool should_simplify;
+ double simplify_threshold;
+
+ int status = 0;
+
+ if (obj == NULL || obj == Py_None) {
+ return 1;
+ }
+
+ vertices_obj = PyObject_GetAttrString(obj, "vertices");
+ if (vertices_obj == NULL) {
+ goto exit;
+ }
+
+ codes_obj = PyObject_GetAttrString(obj, "codes");
+ if (codes_obj == NULL) {
+ goto exit;
+ }
+
+ should_simplify_obj = PyObject_GetAttrString(obj, "should_simplify");
+ if (should_simplify_obj == NULL) {
+ goto exit;
+ }
+ switch (PyObject_IsTrue(should_simplify_obj)) {
+ case 0: should_simplify = 0; break;
+ case 1: should_simplify = 1; break;
+ default: goto exit; // errored.
+ }
+
+ simplify_threshold_obj = PyObject_GetAttrString(obj, "simplify_threshold");
+ if (simplify_threshold_obj == NULL) {
+ goto exit;
+ }
+ simplify_threshold = PyFloat_AsDouble(simplify_threshold_obj);
+ if (PyErr_Occurred()) {
+ goto exit;
+ }
+
+ if (!path->set(vertices_obj, codes_obj, should_simplify, simplify_threshold)) {
+ goto exit;
+ }
+
+ status = 1;
+
+exit:
+ Py_XDECREF(vertices_obj);
+ Py_XDECREF(codes_obj);
+ Py_XDECREF(should_simplify_obj);
+ Py_XDECREF(simplify_threshold_obj);
+
+ return status;
+}
+
+int convert_pathgen(PyObject *obj, void *pathgenp)
+{
+ py::PathGenerator *paths = (py::PathGenerator *)pathgenp;
+ if (!paths->set(obj)) {
+ PyErr_SetString(PyExc_TypeError, "Not an iterable of paths");
+ return 0;
+ }
+ return 1;
+}
+
+int convert_clippath(PyObject *clippath_tuple, void *clippathp)
+{
+ ClipPath *clippath = (ClipPath *)clippathp;
+ py::PathIterator path;
+ agg::trans_affine trans;
+
+ if (clippath_tuple != NULL && clippath_tuple != Py_None) {
+ if (!PyArg_ParseTuple(clippath_tuple,
+ "O&O&:clippath",
+ &convert_path,
+ &clippath->path,
+ &convert_trans_affine,
+ &clippath->trans)) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+int convert_snap(PyObject *obj, void *snapp)
+{
+ e_snap_mode *snap = (e_snap_mode *)snapp;
+ if (obj == NULL || obj == Py_None) {
+ *snap = SNAP_AUTO;
+ } else {
+ switch (PyObject_IsTrue(obj)) {
+ case 0: *snap = SNAP_FALSE; break;
+ case 1: *snap = SNAP_TRUE; break;
+ default: return 0; // errored.
+ }
+ }
+ return 1;
+}
+
+int convert_sketch_params(PyObject *obj, void *sketchp)
+{
+ SketchParams *sketch = (SketchParams *)sketchp;
+
+ if (obj == NULL || obj == Py_None) {
+ sketch->scale = 0.0;
+ } else if (!PyArg_ParseTuple(obj,
+ "ddd:sketch_params",
+ &sketch->scale,
+ &sketch->length,
+ &sketch->randomness)) {
+ return 0;
+ }
+
+ return 1;
+}
+
+int convert_gcagg(PyObject *pygc, void *gcp)
+{
+ GCAgg *gc = (GCAgg *)gcp;
+
+ if (!(convert_from_attr(pygc, "_linewidth", &convert_double, &gc->linewidth) &&
+ convert_from_attr(pygc, "_alpha", &convert_double, &gc->alpha) &&
+ convert_from_attr(pygc, "_forced_alpha", &convert_bool, &gc->forced_alpha) &&
+ convert_from_attr(pygc, "_rgb", &convert_rgba, &gc->color) &&
+ convert_from_attr(pygc, "_antialiased", &convert_bool, &gc->isaa) &&
+ convert_from_attr(pygc, "_capstyle", &convert_cap, &gc->cap) &&
+ convert_from_attr(pygc, "_joinstyle", &convert_join, &gc->join) &&
+ convert_from_method(pygc, "get_dashes", &convert_dashes, &gc->dashes) &&
+ convert_from_attr(pygc, "_cliprect", &convert_rect, &gc->cliprect) &&
+ convert_from_method(pygc, "get_clip_path", &convert_clippath, &gc->clippath) &&
+ convert_from_method(pygc, "get_snap", &convert_snap, &gc->snap_mode) &&
+ convert_from_method(pygc, "get_hatch_path", &convert_path, &gc->hatchpath) &&
+ convert_from_method(pygc, "get_hatch_color", &convert_rgba, &gc->hatch_color) &&
+ convert_from_method(pygc, "get_hatch_linewidth", &convert_double, &gc->hatch_linewidth) &&
+ convert_from_method(pygc, "get_sketch_params", &convert_sketch_params, &gc->sketch))) {
+ return 0;
+ }
+
+ return 1;
+}
+
+int convert_face(PyObject *color, GCAgg &gc, agg::rgba *rgba)
+{
+ if (!convert_rgba(color, rgba)) {
+ return 0;
+ }
+
+ if (color != NULL && color != Py_None) {
+ if (gc.forced_alpha || PySequence_Size(color) == 3) {
+ rgba->a = gc.alpha;
+ }
+ }
+
+ return 1;
+}
+
+int convert_points(PyObject *obj, void *pointsp)
+{
+ numpy::array_view<double, 2> *points = (numpy::array_view<double, 2> *)pointsp;
+ if (obj == NULL || obj == Py_None) {
+ return 1;
+ }
+ if (!points->set(obj)
+ || (points->size() && !check_trailing_shape(*points, "points", 2))) {
+ return 0;
+ }
+ return 1;
+}
+
+int convert_transforms(PyObject *obj, void *transp)
+{
+ numpy::array_view<double, 3> *trans = (numpy::array_view<double, 3> *)transp;
+ if (obj == NULL || obj == Py_None) {
+ return 1;
+ }
+ if (!trans->set(obj)
+ || (trans->size() && !check_trailing_shape(*trans, "transforms", 3, 3))) {
+ return 0;
+ }
+ return 1;
+}
+
+int convert_bboxes(PyObject *obj, void *bboxp)
+{
+ numpy::array_view<double, 3> *bbox = (numpy::array_view<double, 3> *)bboxp;
+ if (obj == NULL || obj == Py_None) {
+ return 1;
+ }
+ if (!bbox->set(obj)
+ || (bbox->size() && !check_trailing_shape(*bbox, "bbox array", 2, 2))) {
+ return 0;
+ }
+ return 1;
+}
+
+int convert_colors(PyObject *obj, void *colorsp)
+{
+ numpy::array_view<double, 2> *colors = (numpy::array_view<double, 2> *)colorsp;
+ if (obj == NULL || obj == Py_None) {
+ return 1;
+ }
+ if (!colors->set(obj)
+ || (colors->size() && !check_trailing_shape(*colors, "colors", 4))) {
+ return 0;
+ }
+ return 1;
+}
+}
diff --git a/contrib/python/matplotlib/py3/src/py_converters.h b/contrib/python/matplotlib/py3/src/py_converters.h
new file mode 100644
index 0000000000..2c9dc6d1b8
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/py_converters.h
@@ -0,0 +1,48 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+#ifndef MPL_PY_CONVERTERS_H
+#define MPL_PY_CONVERTERS_H
+
+/***************************************************************************
+ * This module contains a number of conversion functions from Python types
+ * to C++ types. Most of them meet the Python "converter" signature:
+ *
+ * typedef int (*converter)(PyObject *, void *);
+ *
+ * and thus can be passed as conversion functions to PyArg_ParseTuple
+ * and friends.
+ */
+
+#include <Python.h>
+#include "_backend_agg_basic_types.h"
+
+extern "C" {
+typedef int (*converter)(PyObject *, void *);
+
+int convert_from_attr(PyObject *obj, const char *name, converter func, void *p);
+int convert_from_method(PyObject *obj, const char *name, converter func, void *p);
+
+int convert_double(PyObject *obj, void *p);
+int convert_bool(PyObject *obj, void *p);
+int convert_cap(PyObject *capobj, void *capp);
+int convert_join(PyObject *joinobj, void *joinp);
+int convert_rect(PyObject *rectobj, void *rectp);
+int convert_rgba(PyObject *rgbaocj, void *rgbap);
+int convert_dashes(PyObject *dashobj, void *gcp);
+int convert_dashes_vector(PyObject *obj, void *dashesp);
+int convert_trans_affine(PyObject *obj, void *transp);
+int convert_path(PyObject *obj, void *pathp);
+int convert_pathgen(PyObject *obj, void *pathgenp);
+int convert_clippath(PyObject *clippath_tuple, void *clippathp);
+int convert_snap(PyObject *obj, void *snapp);
+int convert_sketch_params(PyObject *obj, void *sketchp);
+int convert_gcagg(PyObject *pygc, void *gcp);
+int convert_points(PyObject *pygc, void *pointsp);
+int convert_transforms(PyObject *pygc, void *transp);
+int convert_bboxes(PyObject *pygc, void *bboxp);
+int convert_colors(PyObject *pygc, void *colorsp);
+
+int convert_face(PyObject *color, GCAgg &gc, agg::rgba *rgba);
+}
+
+#endif
diff --git a/contrib/python/matplotlib/py3/src/py_exceptions.h b/contrib/python/matplotlib/py3/src/py_exceptions.h
new file mode 100644
index 0000000000..c4accf2634
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/py_exceptions.h
@@ -0,0 +1,72 @@
+/* -*- mode: c++; c-basic-offset: 4 -*- */
+
+#ifndef MPL_PY_EXCEPTIONS_H
+#define MPL_PY_EXCEPTIONS_H
+
+#include <exception>
+#include <stdexcept>
+
+namespace py
+{
+class exception : public std::exception
+{
+ public:
+ const char *what() const throw()
+ {
+ return "python error has been set";
+ }
+};
+}
+
+#define CALL_CPP_FULL(name, a, cleanup, errorcode) \
+ try \
+ { \
+ a; \
+ } \
+ catch (const py::exception &) \
+ { \
+ { \
+ cleanup; \
+ } \
+ return (errorcode); \
+ } \
+ catch (const std::bad_alloc &) \
+ { \
+ PyErr_Format(PyExc_MemoryError, "In %s: Out of memory", (name)); \
+ { \
+ cleanup; \
+ } \
+ return (errorcode); \
+ } \
+ catch (const std::overflow_error &e) \
+ { \
+ PyErr_Format(PyExc_OverflowError, "In %s: %s", (name), e.what()); \
+ { \
+ cleanup; \
+ } \
+ return (errorcode); \
+ } \
+ catch (const std::runtime_error &e) \
+ { \
+ PyErr_Format(PyExc_RuntimeError, "In %s: %s", (name), e.what()); \
+ { \
+ cleanup; \
+ } \
+ return (errorcode); \
+ } \
+ catch (...) \
+ { \
+ PyErr_Format(PyExc_RuntimeError, "Unknown exception in %s", (name)); \
+ { \
+ cleanup; \
+ } \
+ return (errorcode); \
+ }
+
+#define CALL_CPP_CLEANUP(name, a, cleanup) CALL_CPP_FULL(name, a, cleanup, 0)
+
+#define CALL_CPP(name, a) CALL_CPP_FULL(name, a, , 0)
+
+#define CALL_CPP_INIT(name, a) CALL_CPP_FULL(name, a, , -1)
+
+#endif
diff --git a/contrib/python/matplotlib/py3/src/tri/_tri.cpp b/contrib/python/matplotlib/py3/src/tri/_tri.cpp
new file mode 100644
index 0000000000..2674a3140b
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/tri/_tri.cpp
@@ -0,0 +1,2074 @@
+/* This file contains liberal use of asserts to assist code development and
+ * debugging. Standard matplotlib builds disable asserts so they cause no
+ * performance reduction. To enable the asserts, you need to undefine the
+ * NDEBUG macro, which is achieved by adding the following
+ * undef_macros=['NDEBUG']
+ * to the appropriate make_extension call in setupext.py, and then rebuilding.
+ */
+#include "../mplutils.h"
+#include "_tri.h"
+
+#include <algorithm>
+#include <random>
+#include <set>
+
+
+TriEdge::TriEdge()
+ : tri(-1), edge(-1)
+{}
+
+TriEdge::TriEdge(int tri_, int edge_)
+ : tri(tri_), edge(edge_)
+{}
+
+bool TriEdge::operator<(const TriEdge& other) const
+{
+ if (tri != other.tri)
+ return tri < other.tri;
+ else
+ return edge < other.edge;
+}
+
+bool TriEdge::operator==(const TriEdge& other) const
+{
+ return tri == other.tri && edge == other.edge;
+}
+
+bool TriEdge::operator!=(const TriEdge& other) const
+{
+ return !operator==(other);
+}
+
+std::ostream& operator<<(std::ostream& os, const TriEdge& tri_edge)
+{
+ return os << tri_edge.tri << ' ' << tri_edge.edge;
+}
+
+
+
+XY::XY()
+{}
+
+XY::XY(const double& x_, const double& y_)
+ : x(x_), y(y_)
+{}
+
+double XY::angle() const
+{
+ return atan2(y, x);
+}
+
+double XY::cross_z(const XY& other) const
+{
+ return x*other.y - y*other.x;
+}
+
+bool XY::is_right_of(const XY& other) const
+{
+ if (x == other.x)
+ return y > other.y;
+ else
+ return x > other.x;
+}
+
+bool XY::operator==(const XY& other) const
+{
+ return x == other.x && y == other.y;
+}
+
+bool XY::operator!=(const XY& other) const
+{
+ return x != other.x || y != other.y;
+}
+
+XY XY::operator*(const double& multiplier) const
+{
+ return XY(x*multiplier, y*multiplier);
+}
+
+const XY& XY::operator+=(const XY& other)
+{
+ x += other.x;
+ y += other.y;
+ return *this;
+}
+
+const XY& XY::operator-=(const XY& other)
+{
+ x -= other.x;
+ y -= other.y;
+ return *this;
+}
+
+XY XY::operator+(const XY& other) const
+{
+ return XY(x + other.x, y + other.y);
+}
+
+XY XY::operator-(const XY& other) const
+{
+ return XY(x - other.x, y - other.y);
+}
+
+std::ostream& operator<<(std::ostream& os, const XY& xy)
+{
+ return os << '(' << xy.x << ' ' << xy.y << ')';
+}
+
+
+
+XYZ::XYZ(const double& x_, const double& y_, const double& z_)
+ : x(x_), y(y_), z(z_)
+{}
+
+XYZ XYZ::cross(const XYZ& other) const
+{
+ return XYZ(y*other.z - z*other.y,
+ z*other.x - x*other.z,
+ x*other.y - y*other.x);
+}
+
+double XYZ::dot(const XYZ& other) const
+{
+ return x*other.x + y*other.y + z*other.z;
+}
+
+XYZ XYZ::operator-(const XYZ& other) const
+{
+ return XYZ(x - other.x, y - other.y, z - other.z);
+}
+
+std::ostream& operator<<(std::ostream& os, const XYZ& xyz)
+{
+ return os << '(' << xyz.x << ' ' << xyz.y << ' ' << xyz.z << ')';
+}
+
+
+
+BoundingBox::BoundingBox()
+ : empty(true), lower(0.0, 0.0), upper(0.0, 0.0)
+{}
+
+void BoundingBox::add(const XY& point)
+{
+ if (empty) {
+ empty = false;
+ lower = upper = point;
+ } else {
+ if (point.x < lower.x) lower.x = point.x;
+ else if (point.x > upper.x) upper.x = point.x;
+
+ if (point.y < lower.y) lower.y = point.y;
+ else if (point.y > upper.y) upper.y = point.y;
+ }
+}
+
+void BoundingBox::expand(const XY& delta)
+{
+ if (!empty) {
+ lower -= delta;
+ upper += delta;
+ }
+}
+
+
+
+ContourLine::ContourLine()
+ : std::vector<XY>()
+{}
+
+void ContourLine::push_back(const XY& point)
+{
+ if (empty() || point != back())
+ std::vector<XY>::push_back(point);
+}
+
+void ContourLine::write() const
+{
+ std::cout << "ContourLine of " << size() << " points:";
+ for (const_iterator it = begin(); it != end(); ++it)
+ std::cout << ' ' << *it;
+ std::cout << std::endl;
+}
+
+
+
+void write_contour(const Contour& contour)
+{
+ std::cout << "Contour of " << contour.size() << " lines." << std::endl;
+ for (Contour::const_iterator it = contour.begin(); it != contour.end(); ++it)
+ it->write();
+}
+
+
+
+Triangulation::Triangulation(const CoordinateArray& x,
+ const CoordinateArray& y,
+ const TriangleArray& triangles,
+ const MaskArray& mask,
+ const EdgeArray& edges,
+ const NeighborArray& neighbors,
+ bool correct_triangle_orientations)
+ : _x(x),
+ _y(y),
+ _triangles(triangles),
+ _mask(mask),
+ _edges(edges),
+ _neighbors(neighbors)
+{
+ if (_x.ndim() != 1 || _y.ndim() != 1 || _x.shape(0) != _y.shape(0))
+ throw std::invalid_argument("x and y must be 1D arrays of the same length");
+
+ if (_triangles.ndim() != 2 || _triangles.shape(1) != 3)
+ throw std::invalid_argument("triangles must be a 2D array of shape (?,3)");
+
+ // Optional mask.
+ if (_mask.size() > 0 &&
+ (_mask.ndim() != 1 || _mask.shape(0) != _triangles.shape(0)))
+ throw std::invalid_argument(
+ "mask must be a 1D array with the same length as the triangles array");
+
+ // Optional edges.
+ if (_edges.size() > 0 &&
+ (_edges.ndim() != 2 || _edges.shape(1) != 2))
+ throw std::invalid_argument("edges must be a 2D array with shape (?,2)");
+
+ // Optional neighbors.
+ if (_neighbors.size() > 0 &&
+ (_neighbors.ndim() != 2 || _neighbors.shape() != _triangles.shape()))
+ throw std::invalid_argument(
+ "neighbors must be a 2D array with the same shape as the triangles array");
+
+ if (correct_triangle_orientations)
+ correct_triangles();
+}
+
+void Triangulation::calculate_boundaries()
+{
+ get_neighbors(); // Ensure _neighbors has been created.
+
+ // Create set of all boundary TriEdges, which are those which do not
+ // have a neighbor triangle.
+ typedef std::set<TriEdge> BoundaryEdges;
+ BoundaryEdges boundary_edges;
+ for (int tri = 0; tri < get_ntri(); ++tri) {
+ if (!is_masked(tri)) {
+ for (int edge = 0; edge < 3; ++edge) {
+ if (get_neighbor(tri, edge) == -1) {
+ boundary_edges.insert(TriEdge(tri, edge));
+ }
+ }
+ }
+ }
+
+ // Take any boundary edge and follow the boundary until return to start
+ // point, removing edges from boundary_edges as they are used. At the same
+ // time, initialise the _tri_edge_to_boundary_map.
+ while (!boundary_edges.empty()) {
+ // Start of new boundary.
+ BoundaryEdges::iterator it = boundary_edges.begin();
+ int tri = it->tri;
+ int edge = it->edge;
+ _boundaries.push_back(Boundary());
+ Boundary& boundary = _boundaries.back();
+
+ while (true) {
+ boundary.push_back(TriEdge(tri, edge));
+ boundary_edges.erase(it);
+ _tri_edge_to_boundary_map[TriEdge(tri, edge)] =
+ BoundaryEdge(_boundaries.size()-1, boundary.size()-1);
+
+ // Move to next edge of current triangle.
+ edge = (edge+1) % 3;
+
+ // Find start point index of boundary edge.
+ int point = get_triangle_point(tri, edge);
+
+ // Find next TriEdge by traversing neighbors until find one
+ // without a neighbor.
+ while (get_neighbor(tri, edge) != -1) {
+ tri = get_neighbor(tri, edge);
+ edge = get_edge_in_triangle(tri, point);
+ }
+
+ if (TriEdge(tri,edge) == boundary.front())
+ break; // Reached beginning of this boundary, so finished it.
+ else
+ it = boundary_edges.find(TriEdge(tri, edge));
+ }
+ }
+}
+
+void Triangulation::calculate_edges()
+{
+ assert(!has_edges() && "Expected empty edges array");
+
+ // Create set of all edges, storing them with start point index less than
+ // end point index.
+ typedef std::set<Edge> EdgeSet;
+ EdgeSet edge_set;
+ for (int tri = 0; tri < get_ntri(); ++tri) {
+ if (!is_masked(tri)) {
+ for (int edge = 0; edge < 3; edge++) {
+ int start = get_triangle_point(tri, edge);
+ int end = get_triangle_point(tri, (edge+1)%3);
+ edge_set.insert(start > end ? Edge(start,end) : Edge(end,start));
+ }
+ }
+ }
+
+ // Convert to python _edges array.
+ py::ssize_t dims[2] = {static_cast<py::ssize_t>(edge_set.size()), 2};
+ _edges = EdgeArray(dims);
+ auto edges = _edges.mutable_data();
+
+ int i = 0;
+ for (EdgeSet::const_iterator it = edge_set.begin(); it != edge_set.end(); ++it) {
+ edges[i++] = it->start;
+ edges[i++] = it->end;
+ }
+}
+
+void Triangulation::calculate_neighbors()
+{
+ assert(!has_neighbors() && "Expected empty neighbors array");
+
+ // Create _neighbors array with shape (ntri,3) and initialise all to -1.
+ py::ssize_t dims[2] = {get_ntri(), 3};
+ _neighbors = NeighborArray(dims);
+ auto* neighbors = _neighbors.mutable_data();
+
+ int tri, edge;
+ std::fill(neighbors, neighbors+3*get_ntri(), -1);
+
+ // For each triangle edge (start to end point), find corresponding neighbor
+ // edge from end to start point. Do this by traversing all edges and
+ // storing them in a map from edge to TriEdge. If corresponding neighbor
+ // edge is already in the map, don't need to store new edge as neighbor
+ // already found.
+ typedef std::map<Edge, TriEdge> EdgeToTriEdgeMap;
+ EdgeToTriEdgeMap edge_to_tri_edge_map;
+ for (tri = 0; tri < get_ntri(); ++tri) {
+ if (!is_masked(tri)) {
+ for (edge = 0; edge < 3; ++edge) {
+ int start = get_triangle_point(tri, edge);
+ int end = get_triangle_point(tri, (edge+1)%3);
+ EdgeToTriEdgeMap::iterator it =
+ edge_to_tri_edge_map.find(Edge(end,start));
+ if (it == edge_to_tri_edge_map.end()) {
+ // No neighbor edge exists in the edge_to_tri_edge_map, so
+ // add this edge to it.
+ edge_to_tri_edge_map[Edge(start,end)] = TriEdge(tri,edge);
+ } else {
+ // Neighbor edge found, set the two elements of _neighbors
+ // and remove edge from edge_to_tri_edge_map.
+ neighbors[3*tri + edge] = it->second.tri;
+ neighbors[3*it->second.tri + it->second.edge] = tri;
+ edge_to_tri_edge_map.erase(it);
+ }
+ }
+ }
+ }
+
+ // Note that remaining edges in the edge_to_tri_edge_map correspond to
+ // boundary edges, but the boundaries are calculated separately elsewhere.
+}
+
+Triangulation::TwoCoordinateArray Triangulation::calculate_plane_coefficients(
+ const CoordinateArray& z)
+{
+ if (z.ndim() != 1 || z.shape(0) != _x.shape(0))
+ throw std::invalid_argument(
+ "z must be a 1D array with the same length as the triangulation x and y arrays");
+
+ int dims[2] = {get_ntri(), 3};
+ Triangulation::TwoCoordinateArray planes_array(dims);
+ auto planes = planes_array.mutable_unchecked<2>();
+ auto triangles = _triangles.unchecked<2>();
+ auto x = _x.unchecked<1>();
+ auto y = _y.unchecked<1>();
+ auto z_ptr = z.unchecked<1>();
+
+ int point;
+ for (int tri = 0; tri < get_ntri(); ++tri) {
+ if (is_masked(tri)) {
+ planes(tri, 0) = 0.0;
+ planes(tri, 1) = 0.0;
+ planes(tri, 2) = 0.0;
+ }
+ else {
+ // Equation of plane for all points r on plane is r.normal = p
+ // where normal is vector normal to the plane, and p is a
+ // constant. Rewrite as
+ // r_x*normal_x + r_y*normal_y + r_z*normal_z = p
+ // and rearrange to give
+ // r_z = (-normal_x/normal_z)*r_x + (-normal_y/normal_z)*r_y +
+ // p/normal_z
+ point = triangles(tri, 0);
+ XYZ point0(x(point), y(point), z_ptr(point));
+ point = triangles(tri, 1);
+ XYZ side01 = XYZ(x(point), y(point), z_ptr(point)) - point0;
+ point = triangles(tri, 2);
+ XYZ side02 = XYZ(x(point), y(point), z_ptr(point)) - point0;
+
+ XYZ normal = side01.cross(side02);
+
+ if (normal.z == 0.0) {
+ // Normal is in x-y plane which means triangle consists of
+ // colinear points. To avoid dividing by zero, we use the
+ // Moore-Penrose pseudo-inverse.
+ double sum2 = (side01.x*side01.x + side01.y*side01.y +
+ side02.x*side02.x + side02.y*side02.y);
+ double a = (side01.x*side01.z + side02.x*side02.z) / sum2;
+ double b = (side01.y*side01.z + side02.y*side02.z) / sum2;
+ planes(tri, 0) = a;
+ planes(tri, 1) = b;
+ planes(tri, 2) = point0.z - a*point0.x - b*point0.y;
+ }
+ else {
+ planes(tri, 0) = -normal.x / normal.z; // x
+ planes(tri, 1) = -normal.y / normal.z; // y
+ planes(tri, 2) = normal.dot(point0) / normal.z; // constant
+ }
+ }
+ }
+
+ return planes_array;
+}
+
+void Triangulation::correct_triangles()
+{
+ auto triangles = _triangles.mutable_data();
+ auto neighbors = _neighbors.mutable_data();
+
+ for (int tri = 0; tri < get_ntri(); ++tri) {
+ XY point0 = get_point_coords(triangles[3*tri]);
+ XY point1 = get_point_coords(triangles[3*tri+1]);
+ XY point2 = get_point_coords(triangles[3*tri+2]);
+ if ( (point1 - point0).cross_z(point2 - point0) < 0.0) {
+ // Triangle points are clockwise, so change them to anticlockwise.
+ std::swap(triangles[3*tri+1], triangles[3*tri+2]);
+ if (has_neighbors())
+ std::swap(neighbors[3*tri+1], neighbors[3*tri+2]);
+ }
+ }
+}
+
+const Triangulation::Boundaries& Triangulation::get_boundaries() const
+{
+ if (_boundaries.empty())
+ const_cast<Triangulation*>(this)->calculate_boundaries();
+ return _boundaries;
+}
+
+void Triangulation::get_boundary_edge(const TriEdge& triEdge,
+ int& boundary,
+ int& edge) const
+{
+ get_boundaries(); // Ensure _tri_edge_to_boundary_map has been created.
+ TriEdgeToBoundaryMap::const_iterator it =
+ _tri_edge_to_boundary_map.find(triEdge);
+ assert(it != _tri_edge_to_boundary_map.end() &&
+ "TriEdge is not on a boundary");
+ boundary = it->second.boundary;
+ edge = it->second.edge;
+}
+
+int Triangulation::get_edge_in_triangle(int tri, int point) const
+{
+ assert(tri >= 0 && tri < get_ntri() && "Triangle index out of bounds");
+ assert(point >= 0 && point < get_npoints() && "Point index out of bounds.");
+
+ auto triangles = _triangles.data();
+
+ for (int edge = 0; edge < 3; ++edge) {
+ if (triangles[3*tri + edge] == point)
+ return edge;
+ }
+ return -1; // point is not in triangle.
+}
+
+Triangulation::EdgeArray& Triangulation::get_edges()
+{
+ if (!has_edges())
+ calculate_edges();
+ return _edges;
+}
+
+int Triangulation::get_neighbor(int tri, int edge) const
+{
+ assert(tri >= 0 && tri < get_ntri() && "Triangle index out of bounds");
+ assert(edge >= 0 && edge < 3 && "Edge index out of bounds");
+ if (!has_neighbors())
+ const_cast<Triangulation&>(*this).calculate_neighbors();
+ return _neighbors.data()[3*tri + edge];
+}
+
+TriEdge Triangulation::get_neighbor_edge(int tri, int edge) const
+{
+ int neighbor_tri = get_neighbor(tri, edge);
+ if (neighbor_tri == -1)
+ return TriEdge(-1,-1);
+ else
+ return TriEdge(neighbor_tri,
+ get_edge_in_triangle(neighbor_tri,
+ get_triangle_point(tri,
+ (edge+1)%3)));
+}
+
+Triangulation::NeighborArray& Triangulation::get_neighbors()
+{
+ if (!has_neighbors())
+ calculate_neighbors();
+ return _neighbors;
+}
+
+int Triangulation::get_npoints() const
+{
+ return _x.shape(0);
+}
+
+int Triangulation::get_ntri() const
+{
+ return _triangles.shape(0);
+}
+
+XY Triangulation::get_point_coords(int point) const
+{
+ assert(point >= 0 && point < get_npoints() && "Point index out of bounds.");
+ return XY(_x.data()[point], _y.data()[point]);
+}
+
+int Triangulation::get_triangle_point(int tri, int edge) const
+{
+ assert(tri >= 0 && tri < get_ntri() && "Triangle index out of bounds");
+ assert(edge >= 0 && edge < 3 && "Edge index out of bounds");
+ return _triangles.data()[3*tri + edge];
+}
+
+int Triangulation::get_triangle_point(const TriEdge& tri_edge) const
+{
+ return get_triangle_point(tri_edge.tri, tri_edge.edge);
+}
+
+bool Triangulation::has_edges() const
+{
+ return _edges.size() > 0;
+}
+
+bool Triangulation::has_mask() const
+{
+ return _mask.size() > 0;
+}
+
+bool Triangulation::has_neighbors() const
+{
+ return _neighbors.size() > 0;
+}
+
+bool Triangulation::is_masked(int tri) const
+{
+ assert(tri >= 0 && tri < get_ntri() && "Triangle index out of bounds.");
+ return has_mask() && _mask.data()[tri];
+}
+
+void Triangulation::set_mask(const MaskArray& mask)
+{
+ if (mask.size() > 0 &&
+ (mask.ndim() != 1 || mask.shape(0) != _triangles.shape(0)))
+ throw std::invalid_argument(
+ "mask must be a 1D array with the same length as the triangles array");
+
+ _mask = mask;
+
+ // Clear derived fields so they are recalculated when needed.
+ _edges = EdgeArray();
+ _neighbors = NeighborArray();
+ _boundaries.clear();
+}
+
+void Triangulation::write_boundaries() const
+{
+ const Boundaries& bs = get_boundaries();
+ std::cout << "Number of boundaries: " << bs.size() << std::endl;
+ for (Boundaries::const_iterator it = bs.begin(); it != bs.end(); ++it) {
+ const Boundary& b = *it;
+ std::cout << " Boundary of " << b.size() << " points: ";
+ for (Boundary::const_iterator itb = b.begin(); itb != b.end(); ++itb) {
+ std::cout << *itb << ", ";
+ }
+ std::cout << std::endl;
+ }
+}
+
+
+
+TriContourGenerator::TriContourGenerator(Triangulation& triangulation,
+ const CoordinateArray& z)
+ : _triangulation(triangulation),
+ _z(z),
+ _interior_visited(2*_triangulation.get_ntri()),
+ _boundaries_visited(0),
+ _boundaries_used(0)
+{
+ if (_z.ndim() != 1 || _z.shape(0) != _triangulation.get_npoints())
+ throw std::invalid_argument(
+ "z must be a 1D array with the same length as the x and y arrays");
+}
+
+void TriContourGenerator::clear_visited_flags(bool include_boundaries)
+{
+ // Clear _interiorVisited.
+ std::fill(_interior_visited.begin(), _interior_visited.end(), false);
+
+ if (include_boundaries) {
+ if (_boundaries_visited.empty()) {
+ const Boundaries& boundaries = get_boundaries();
+
+ // Initialise _boundaries_visited.
+ _boundaries_visited.reserve(boundaries.size());
+ for (Boundaries::const_iterator it = boundaries.begin();
+ it != boundaries.end(); ++it)
+ _boundaries_visited.push_back(BoundaryVisited(it->size()));
+
+ // Initialise _boundaries_used.
+ _boundaries_used = BoundariesUsed(boundaries.size());
+ }
+
+ // Clear _boundaries_visited.
+ for (BoundariesVisited::iterator it = _boundaries_visited.begin();
+ it != _boundaries_visited.end(); ++it)
+ std::fill(it->begin(), it->end(), false);
+
+ // Clear _boundaries_used.
+ std::fill(_boundaries_used.begin(), _boundaries_used.end(), false);
+ }
+}
+
+py::tuple TriContourGenerator::contour_line_to_segs_and_kinds(const Contour& contour)
+{
+ // Convert all of the lines generated by a call to create_contour() into
+ // their Python equivalents for return to the calling function.
+ // A line is either a closed line loop (in which case the last point is
+ // identical to the first) or an open line strip. Two NumPy arrays are
+ // created for each line:
+ // vertices is a double array of shape (npoints, 2) containing the (x, y)
+ // coordinates of the points in the line
+ // codes is a uint8 array of shape (npoints,) containing the 'kind codes'
+ // which are defined in the Path class
+ // and they are appended to the Python lists vertices_list and codes_list
+ // respectively for return to the Python calling function.
+
+ py::list vertices_list(contour.size());
+ py::list codes_list(contour.size());
+
+ for (Contour::size_type i = 0; i < contour.size(); ++i) {
+ const ContourLine& contour_line = contour[i];
+ py::ssize_t npoints = static_cast<py::ssize_t>(contour_line.size());
+
+ py::ssize_t segs_dims[2] = {npoints, 2};
+ CoordinateArray segs(segs_dims);
+ double* segs_ptr = segs.mutable_data();
+
+ py::ssize_t codes_dims[1] = {npoints};
+ CodeArray codes(codes_dims);
+ unsigned char* codes_ptr = codes.mutable_data();
+
+ for (ContourLine::const_iterator it = contour_line.begin();
+ it != contour_line.end(); ++it) {
+ *segs_ptr++ = it->x;
+ *segs_ptr++ = it->y;
+ *codes_ptr++ = (it == contour_line.begin() ? MOVETO : LINETO);
+ }
+
+ // Closed line loop has identical first and last (x, y) points.
+ if (contour_line.size() > 1 &&
+ contour_line.front() == contour_line.back())
+ *(codes_ptr-1) = CLOSEPOLY;
+
+ vertices_list[i] = segs;
+ codes_list[i] = codes;
+ }
+
+ return py::make_tuple(vertices_list, codes_list);
+}
+
+py::tuple TriContourGenerator::contour_to_segs_and_kinds(const Contour& contour)
+{
+ // Convert all of the polygons generated by a call to
+ // create_filled_contour() into their Python equivalents for return to the
+ // calling function. All of the polygons' points and kinds codes are
+ // combined into single NumPy arrays for each; this avoids having
+ // to determine which polygons are holes as this will be determined by the
+ // renderer. If there are ntotal points in all of the polygons, the two
+ // NumPy arrays created are:
+ // vertices is a double array of shape (ntotal, 2) containing the (x, y)
+ // coordinates of the points in the polygons
+ // codes is a uint8 array of shape (ntotal,) containing the 'kind codes'
+ // which are defined in the Path class
+ // and they are returned in the Python lists vertices_list and codes_list
+ // respectively.
+
+ Contour::const_iterator line;
+ ContourLine::const_iterator point;
+
+ // Find total number of points in all contour lines.
+ py::ssize_t n_points = 0;
+ for (line = contour.begin(); line != contour.end(); ++line)
+ n_points += static_cast<py::ssize_t>(line->size());
+
+ // Create segs array for point coordinates.
+ py::ssize_t segs_dims[2] = {n_points, 2};
+ TwoCoordinateArray segs(segs_dims);
+ double* segs_ptr = segs.mutable_data();
+
+ // Create kinds array for code types.
+ py::ssize_t codes_dims[1] = {n_points};
+ CodeArray codes(codes_dims);
+ unsigned char* codes_ptr = codes.mutable_data();
+
+ for (line = contour.begin(); line != contour.end(); ++line) {
+ for (point = line->begin(); point != line->end(); point++) {
+ *segs_ptr++ = point->x;
+ *segs_ptr++ = point->y;
+ *codes_ptr++ = (point == line->begin() ? MOVETO : LINETO);
+ }
+
+ if (line->size() > 1)
+ *(codes_ptr-1) = CLOSEPOLY;
+ }
+
+ py::list vertices_list(1);
+ vertices_list[0] = segs;
+
+ py::list codes_list(1);
+ codes_list[0] = codes;
+
+ return py::make_tuple(vertices_list, codes_list);
+}
+
+py::tuple TriContourGenerator::create_contour(const double& level)
+{
+ clear_visited_flags(false);
+ Contour contour;
+
+ find_boundary_lines(contour, level);
+ find_interior_lines(contour, level, false, false);
+
+ return contour_line_to_segs_and_kinds(contour);
+}
+
+py::tuple TriContourGenerator::create_filled_contour(const double& lower_level,
+ const double& upper_level)
+{
+ if (lower_level >= upper_level)
+ throw std::invalid_argument("filled contour levels must be increasing");
+
+ clear_visited_flags(true);
+ Contour contour;
+
+ find_boundary_lines_filled(contour, lower_level, upper_level);
+ find_interior_lines(contour, lower_level, false, true);
+ find_interior_lines(contour, upper_level, true, true);
+
+ return contour_to_segs_and_kinds(contour);
+}
+
+XY TriContourGenerator::edge_interp(int tri, int edge, const double& level)
+{
+ return interp(_triangulation.get_triangle_point(tri, edge),
+ _triangulation.get_triangle_point(tri, (edge+1)%3),
+ level);
+}
+
+void TriContourGenerator::find_boundary_lines(Contour& contour,
+ const double& level)
+{
+ // Traverse boundaries to find starting points for all contour lines that
+ // intersect the boundaries. For each starting point found, follow the
+ // line to its end before continuing.
+ const Triangulation& triang = _triangulation;
+ const Boundaries& boundaries = get_boundaries();
+ for (Boundaries::const_iterator it = boundaries.begin();
+ it != boundaries.end(); ++it) {
+ const Boundary& boundary = *it;
+ bool startAbove, endAbove = false;
+ for (Boundary::const_iterator itb = boundary.begin();
+ itb != boundary.end(); ++itb) {
+ if (itb == boundary.begin())
+ startAbove = get_z(triang.get_triangle_point(*itb)) >= level;
+ else
+ startAbove = endAbove;
+ endAbove = get_z(triang.get_triangle_point(itb->tri,
+ (itb->edge+1)%3)) >= level;
+ if (startAbove && !endAbove) {
+ // This boundary edge is the start point for a contour line,
+ // so follow the line.
+ contour.push_back(ContourLine());
+ ContourLine& contour_line = contour.back();
+ TriEdge tri_edge = *itb;
+ follow_interior(contour_line, tri_edge, true, level, false);
+ }
+ }
+ }
+}
+
+void TriContourGenerator::find_boundary_lines_filled(Contour& contour,
+ const double& lower_level,
+ const double& upper_level)
+{
+ // Traverse boundaries to find starting points for all contour lines that
+ // intersect the boundaries. For each starting point found, follow the
+ // line to its end before continuing.
+ const Triangulation& triang = _triangulation;
+ const Boundaries& boundaries = get_boundaries();
+ for (Boundaries::size_type i = 0; i < boundaries.size(); ++i) {
+ const Boundary& boundary = boundaries[i];
+ for (Boundary::size_type j = 0; j < boundary.size(); ++j) {
+ if (!_boundaries_visited[i][j]) {
+ // z values of start and end of this boundary edge.
+ double z_start = get_z(triang.get_triangle_point(boundary[j]));
+ double z_end = get_z(triang.get_triangle_point(
+ boundary[j].tri, (boundary[j].edge+1)%3));
+
+ // Does this boundary edge's z increase through upper level
+ // and/or decrease through lower level?
+ bool incr_upper = (z_start < upper_level && z_end >= upper_level);
+ bool decr_lower = (z_start >= lower_level && z_end < lower_level);
+
+ if (decr_lower || incr_upper) {
+ // Start point for contour line, so follow it.
+ contour.push_back(ContourLine());
+ ContourLine& contour_line = contour.back();
+ TriEdge start_tri_edge = boundary[j];
+ TriEdge tri_edge = start_tri_edge;
+
+ // Traverse interior and boundaries until return to start.
+ bool on_upper = incr_upper;
+ do {
+ follow_interior(contour_line, tri_edge, true,
+ on_upper ? upper_level : lower_level, on_upper);
+ on_upper = follow_boundary(contour_line, tri_edge,
+ lower_level, upper_level, on_upper);
+ } while (tri_edge != start_tri_edge);
+
+ // Close polygon.
+ contour_line.push_back(contour_line.front());
+ }
+ }
+ }
+ }
+
+ // Add full boundaries that lie between the lower and upper levels. These
+ // are boundaries that have not been touched by an internal contour line
+ // which are stored in _boundaries_used.
+ for (Boundaries::size_type i = 0; i < boundaries.size(); ++i) {
+ if (!_boundaries_used[i]) {
+ const Boundary& boundary = boundaries[i];
+ double z = get_z(triang.get_triangle_point(boundary[0]));
+ if (z >= lower_level && z < upper_level) {
+ contour.push_back(ContourLine());
+ ContourLine& contour_line = contour.back();
+ for (Boundary::size_type j = 0; j < boundary.size(); ++j)
+ contour_line.push_back(triang.get_point_coords(
+ triang.get_triangle_point(boundary[j])));
+
+ // Close polygon.
+ contour_line.push_back(contour_line.front());
+ }
+ }
+ }
+}
+
+void TriContourGenerator::find_interior_lines(Contour& contour,
+ const double& level,
+ bool on_upper,
+ bool filled)
+{
+ const Triangulation& triang = _triangulation;
+ int ntri = triang.get_ntri();
+ for (int tri = 0; tri < ntri; ++tri) {
+ int visited_index = (on_upper ? tri+ntri : tri);
+
+ if (_interior_visited[visited_index] || triang.is_masked(tri))
+ continue; // Triangle has already been visited or is masked.
+
+ _interior_visited[visited_index] = true;
+
+ // Determine edge via which to leave this triangle.
+ int edge = get_exit_edge(tri, level, on_upper);
+ assert(edge >= -1 && edge < 3 && "Invalid exit edge");
+ if (edge == -1)
+ continue; // Contour does not pass through this triangle.
+
+ // Found start of new contour line loop.
+ contour.push_back(ContourLine());
+ ContourLine& contour_line = contour.back();
+ TriEdge tri_edge = triang.get_neighbor_edge(tri, edge);
+ follow_interior(contour_line, tri_edge, false, level, on_upper);
+
+ // Close line loop
+ contour_line.push_back(contour_line.front());
+ }
+}
+
+bool TriContourGenerator::follow_boundary(ContourLine& contour_line,
+ TriEdge& tri_edge,
+ const double& lower_level,
+ const double& upper_level,
+ bool on_upper)
+{
+ const Triangulation& triang = _triangulation;
+ const Boundaries& boundaries = get_boundaries();
+
+ // Have TriEdge to start at, need equivalent boundary edge.
+ int boundary, edge;
+ triang.get_boundary_edge(tri_edge, boundary, edge);
+ _boundaries_used[boundary] = true;
+
+ bool stop = false;
+ bool first_edge = true;
+ double z_start, z_end = 0;
+ while (!stop)
+ {
+ assert(!_boundaries_visited[boundary][edge] && "Boundary already visited");
+ _boundaries_visited[boundary][edge] = true;
+
+ // z values of start and end points of boundary edge.
+ if (first_edge)
+ z_start = get_z(triang.get_triangle_point(tri_edge));
+ else
+ z_start = z_end;
+ z_end = get_z(triang.get_triangle_point(tri_edge.tri,
+ (tri_edge.edge+1)%3));
+
+ if (z_end > z_start) { // z increasing.
+ if (!(!on_upper && first_edge) &&
+ z_end >= lower_level && z_start < lower_level) {
+ stop = true;
+ on_upper = false;
+ } else if (z_end >= upper_level && z_start < upper_level) {
+ stop = true;
+ on_upper = true;
+ }
+ } else { // z decreasing.
+ if (!(on_upper && first_edge) &&
+ z_start >= upper_level && z_end < upper_level) {
+ stop = true;
+ on_upper = true;
+ } else if (z_start >= lower_level && z_end < lower_level) {
+ stop = true;
+ on_upper = false;
+ }
+ }
+
+ first_edge = false;
+
+ if (!stop) {
+ // Move to next boundary edge, adding point to contour line.
+ edge = (edge+1) % (int)boundaries[boundary].size();
+ tri_edge = boundaries[boundary][edge];
+ contour_line.push_back(triang.get_point_coords(
+ triang.get_triangle_point(tri_edge)));
+ }
+ }
+
+ return on_upper;
+}
+
+void TriContourGenerator::follow_interior(ContourLine& contour_line,
+ TriEdge& tri_edge,
+ bool end_on_boundary,
+ const double& level,
+ bool on_upper)
+{
+ int& tri = tri_edge.tri;
+ int& edge = tri_edge.edge;
+
+ // Initial point.
+ contour_line.push_back(edge_interp(tri, edge, level));
+
+ while (true) {
+ int visited_index = tri;
+ if (on_upper)
+ visited_index += _triangulation.get_ntri();
+
+ // Check for end not on boundary.
+ if (!end_on_boundary && _interior_visited[visited_index])
+ break; // Reached start point, so return.
+
+ // Determine edge by which to leave this triangle.
+ edge = get_exit_edge(tri, level, on_upper);
+ assert(edge >= 0 && edge < 3 && "Invalid exit edge");
+
+ _interior_visited[visited_index] = true;
+
+ // Append new point to point set.
+ assert(edge >= 0 && edge < 3 && "Invalid triangle edge");
+ contour_line.push_back(edge_interp(tri, edge, level));
+
+ // Move to next triangle.
+ TriEdge next_tri_edge = _triangulation.get_neighbor_edge(tri,edge);
+
+ // Check if ending on a boundary.
+ if (end_on_boundary && next_tri_edge.tri == -1)
+ break;
+
+ tri_edge = next_tri_edge;
+ assert(tri_edge.tri != -1 && "Invalid triangle for internal loop");
+ }
+}
+
+const TriContourGenerator::Boundaries& TriContourGenerator::get_boundaries() const
+{
+ return _triangulation.get_boundaries();
+}
+
+int TriContourGenerator::get_exit_edge(int tri,
+ const double& level,
+ bool on_upper) const
+{
+ assert(tri >= 0 && tri < _triangulation.get_ntri() &&
+ "Triangle index out of bounds.");
+
+ unsigned int config =
+ (get_z(_triangulation.get_triangle_point(tri, 0)) >= level) |
+ (get_z(_triangulation.get_triangle_point(tri, 1)) >= level) << 1 |
+ (get_z(_triangulation.get_triangle_point(tri, 2)) >= level) << 2;
+
+ if (on_upper) config = 7-config;
+
+ switch (config) {
+ case 0: return -1;
+ case 1: return 2;
+ case 2: return 0;
+ case 3: return 2;
+ case 4: return 1;
+ case 5: return 1;
+ case 6: return 0;
+ case 7: return -1;
+ default: assert(0 && "Invalid config value"); return -1;
+ }
+}
+
+const double& TriContourGenerator::get_z(int point) const
+{
+ assert(point >= 0 && point < _triangulation.get_npoints() &&
+ "Point index out of bounds.");
+ return _z.data()[point];
+}
+
+XY TriContourGenerator::interp(int point1,
+ int point2,
+ const double& level) const
+{
+ assert(point1 >= 0 && point1 < _triangulation.get_npoints() &&
+ "Point index 1 out of bounds.");
+ assert(point2 >= 0 && point2 < _triangulation.get_npoints() &&
+ "Point index 2 out of bounds.");
+ assert(point1 != point2 && "Identical points");
+ double fraction = (get_z(point2) - level) / (get_z(point2) - get_z(point1));
+ return _triangulation.get_point_coords(point1)*fraction +
+ _triangulation.get_point_coords(point2)*(1.0 - fraction);
+}
+
+
+
+TrapezoidMapTriFinder::TrapezoidMapTriFinder(Triangulation& triangulation)
+ : _triangulation(triangulation),
+ _points(0),
+ _tree(0)
+{}
+
+TrapezoidMapTriFinder::~TrapezoidMapTriFinder()
+{
+ clear();
+}
+
+bool
+TrapezoidMapTriFinder::add_edge_to_tree(const Edge& edge)
+{
+ std::vector<Trapezoid*> trapezoids;
+ if (!find_trapezoids_intersecting_edge(edge, trapezoids))
+ return false;
+ assert(!trapezoids.empty() && "No trapezoids intersect edge");
+
+ const Point* p = edge.left;
+ const Point* q = edge.right;
+ Trapezoid* left_old = 0; // old trapezoid to the left.
+ Trapezoid* left_below = 0; // below trapezoid to the left.
+ Trapezoid* left_above = 0; // above trapezoid to the left.
+
+ // Iterate through trapezoids intersecting edge from left to right.
+ // Replace each old trapezoid with 2+ new trapezoids, and replace its
+ // corresponding nodes in the search tree with new nodes.
+ size_t ntraps = trapezoids.size();
+ for (size_t i = 0; i < ntraps; ++i) {
+ Trapezoid* old = trapezoids[i]; // old trapezoid to replace.
+ bool start_trap = (i == 0);
+ bool end_trap = (i == ntraps-1);
+ bool have_left = (start_trap && edge.left != old->left);
+ bool have_right = (end_trap && edge.right != old->right);
+
+ // Old trapezoid is replaced by up to 4 new trapezoids: left is to the
+ // left of the start point p, below/above are below/above the edge
+ // inserted, and right is to the right of the end point q.
+ Trapezoid* left = 0;
+ Trapezoid* below = 0;
+ Trapezoid* above = 0;
+ Trapezoid* right = 0;
+
+ // There are 4 different cases here depending on whether the old
+ // trapezoid in question is the start and/or end trapezoid of those
+ // that intersect the edge inserted. There is some code duplication
+ // here but it is much easier to understand this way rather than
+ // interleave the 4 different cases with many more if-statements.
+ if (start_trap && end_trap) {
+ // Edge intersects a single trapezoid.
+ if (have_left)
+ left = new Trapezoid(old->left, p, old->below, old->above);
+ below = new Trapezoid(p, q, old->below, edge);
+ above = new Trapezoid(p, q, edge, old->above);
+ if (have_right)
+ right = new Trapezoid(q, old->right, old->below, old->above);
+
+ // Set pairs of trapezoid neighbours.
+ if (have_left) {
+ left->set_lower_left(old->lower_left);
+ left->set_upper_left(old->upper_left);
+ left->set_lower_right(below);
+ left->set_upper_right(above);
+ }
+ else {
+ below->set_lower_left(old->lower_left);
+ above->set_upper_left(old->upper_left);
+ }
+
+ if (have_right) {
+ right->set_lower_right(old->lower_right);
+ right->set_upper_right(old->upper_right);
+ below->set_lower_right(right);
+ above->set_upper_right(right);
+ }
+ else {
+ below->set_lower_right(old->lower_right);
+ above->set_upper_right(old->upper_right);
+ }
+ }
+ else if (start_trap) {
+ // Old trapezoid is the first of 2+ trapezoids that the edge
+ // intersects.
+ if (have_left)
+ left = new Trapezoid(old->left, p, old->below, old->above);
+ below = new Trapezoid(p, old->right, old->below, edge);
+ above = new Trapezoid(p, old->right, edge, old->above);
+
+ // Set pairs of trapezoid neighbours.
+ if (have_left) {
+ left->set_lower_left(old->lower_left);
+ left->set_upper_left(old->upper_left);
+ left->set_lower_right(below);
+ left->set_upper_right(above);
+ }
+ else {
+ below->set_lower_left(old->lower_left);
+ above->set_upper_left(old->upper_left);
+ }
+
+ below->set_lower_right(old->lower_right);
+ above->set_upper_right(old->upper_right);
+ }
+ else if (end_trap) {
+ // Old trapezoid is the last of 2+ trapezoids that the edge
+ // intersects.
+ if (left_below->below == old->below) {
+ below = left_below;
+ below->right = q;
+ }
+ else
+ below = new Trapezoid(old->left, q, old->below, edge);
+
+ if (left_above->above == old->above) {
+ above = left_above;
+ above->right = q;
+ }
+ else
+ above = new Trapezoid(old->left, q, edge, old->above);
+
+ if (have_right)
+ right = new Trapezoid(q, old->right, old->below, old->above);
+
+ // Set pairs of trapezoid neighbours.
+ if (have_right) {
+ right->set_lower_right(old->lower_right);
+ right->set_upper_right(old->upper_right);
+ below->set_lower_right(right);
+ above->set_upper_right(right);
+ }
+ else {
+ below->set_lower_right(old->lower_right);
+ above->set_upper_right(old->upper_right);
+ }
+
+ // Connect to new trapezoids replacing prevOld.
+ if (below != left_below) {
+ below->set_upper_left(left_below);
+ if (old->lower_left == left_old)
+ below->set_lower_left(left_below);
+ else
+ below->set_lower_left(old->lower_left);
+ }
+
+ if (above != left_above) {
+ above->set_lower_left(left_above);
+ if (old->upper_left == left_old)
+ above->set_upper_left(left_above);
+ else
+ above->set_upper_left(old->upper_left);
+ }
+ }
+ else { // Middle trapezoid.
+ // Old trapezoid is neither the first nor last of the 3+ trapezoids
+ // that the edge intersects.
+ if (left_below->below == old->below) {
+ below = left_below;
+ below->right = old->right;
+ }
+ else
+ below = new Trapezoid(old->left, old->right, old->below, edge);
+
+ if (left_above->above == old->above) {
+ above = left_above;
+ above->right = old->right;
+ }
+ else
+ above = new Trapezoid(old->left, old->right, edge, old->above);
+
+ // Connect to new trapezoids replacing prevOld.
+ if (below != left_below) { // below is new.
+ below->set_upper_left(left_below);
+ if (old->lower_left == left_old)
+ below->set_lower_left(left_below);
+ else
+ below->set_lower_left(old->lower_left);
+ }
+
+ if (above != left_above) { // above is new.
+ above->set_lower_left(left_above);
+ if (old->upper_left == left_old)
+ above->set_upper_left(left_above);
+ else
+ above->set_upper_left(old->upper_left);
+ }
+
+ below->set_lower_right(old->lower_right);
+ above->set_upper_right(old->upper_right);
+ }
+
+ // Create new nodes to add to search tree. Below and above trapezoids
+ // may already have owning trapezoid nodes, in which case reuse them.
+ Node* new_top_node = new Node(
+ &edge,
+ below == left_below ? below->trapezoid_node : new Node(below),
+ above == left_above ? above->trapezoid_node : new Node(above));
+ if (have_right)
+ new_top_node = new Node(q, new_top_node, new Node(right));
+ if (have_left)
+ new_top_node = new Node(p, new Node(left), new_top_node);
+
+ // Insert new_top_node in correct position or positions in search tree.
+ Node* old_node = old->trapezoid_node;
+ if (old_node == _tree)
+ _tree = new_top_node;
+ else
+ old_node->replace_with(new_top_node);
+
+ // old_node has been removed from all of its parents and is no longer
+ // needed.
+ assert(old_node->has_no_parents() && "Node should have no parents");
+ delete old_node;
+
+ // Clearing up.
+ if (!end_trap) {
+ // Prepare for next loop.
+ left_old = old;
+ left_above = above;
+ left_below = below;
+ }
+ }
+
+ return true;
+}
+
+void
+TrapezoidMapTriFinder::clear()
+{
+ delete [] _points;
+ _points = 0;
+
+ _edges.clear();
+
+ delete _tree;
+ _tree = 0;
+}
+
+TrapezoidMapTriFinder::TriIndexArray
+TrapezoidMapTriFinder::find_many(const CoordinateArray& x,
+ const CoordinateArray& y)
+{
+ if (x.ndim() != 1 || x.shape(0) != y.shape(0))
+ throw std::invalid_argument(
+ "x and y must be array-like with same shape");
+
+ // Create integer array to return.
+ auto n = x.shape(0);
+ TriIndexArray tri_indices_array(n);
+ auto tri_indices = tri_indices_array.mutable_unchecked<1>();
+ auto x_data = x.data();
+ auto y_data = y.data();
+
+ // Fill returned array.
+ for (py::ssize_t i = 0; i < n; ++i)
+ tri_indices(i) = find_one(XY(x_data[i], y_data[i]));
+
+ return tri_indices_array;
+}
+
+int
+TrapezoidMapTriFinder::find_one(const XY& xy)
+{
+ const Node* node = _tree->search(xy);
+ assert(node != 0 && "Search tree for point returned null node");
+ return node->get_tri();
+}
+
+bool
+TrapezoidMapTriFinder::find_trapezoids_intersecting_edge(
+ const Edge& edge,
+ std::vector<Trapezoid*>& trapezoids)
+{
+ // This is the FollowSegment algorithm of de Berg et al, with some extra
+ // checks to deal with simple colinear (i.e. invalid) triangles.
+ trapezoids.clear();
+ Trapezoid* trapezoid = _tree->search(edge);
+ if (trapezoid == 0) {
+ assert(trapezoid != 0 && "search(edge) returns null trapezoid");
+ return false;
+ }
+
+ trapezoids.push_back(trapezoid);
+ while (edge.right->is_right_of(*trapezoid->right)) {
+ int orient = edge.get_point_orientation(*trapezoid->right);
+ if (orient == 0) {
+ if (edge.point_below == trapezoid->right)
+ orient = +1;
+ else if (edge.point_above == trapezoid->right)
+ orient = -1;
+ else {
+ assert(0 && "Unable to deal with point on edge");
+ return false;
+ }
+ }
+
+ if (orient == -1)
+ trapezoid = trapezoid->lower_right;
+ else if (orient == +1)
+ trapezoid = trapezoid->upper_right;
+
+ if (trapezoid == 0) {
+ assert(0 && "Expected trapezoid neighbor");
+ return false;
+ }
+ trapezoids.push_back(trapezoid);
+ }
+
+ return true;
+}
+
+py::list
+TrapezoidMapTriFinder::get_tree_stats()
+{
+ NodeStats stats;
+ _tree->get_stats(0, stats);
+
+ py::list ret(7);
+ ret[0] = stats.node_count;
+ ret[1] = stats.unique_nodes.size(),
+ ret[2] = stats.trapezoid_count,
+ ret[3] = stats.unique_trapezoid_nodes.size(),
+ ret[4] = stats.max_parent_count,
+ ret[5] = stats.max_depth,
+ ret[6] = stats.sum_trapezoid_depth / stats.trapezoid_count;
+ return ret;
+}
+
+void
+TrapezoidMapTriFinder::initialize()
+{
+ clear();
+ const Triangulation& triang = _triangulation;
+
+ // Set up points array, which contains all of the points in the
+ // triangulation plus the 4 corners of the enclosing rectangle.
+ int npoints = triang.get_npoints();
+ _points = new Point[npoints + 4];
+ BoundingBox bbox;
+ for (int i = 0; i < npoints; ++i) {
+ XY xy = triang.get_point_coords(i);
+ // Avoid problems with -0.0 values different from 0.0
+ if (xy.x == -0.0)
+ xy.x = 0.0;
+ if (xy.y == -0.0)
+ xy.y = 0.0;
+ _points[i] = Point(xy);
+ bbox.add(xy);
+ }
+
+ // Last 4 points are corner points of enclosing rectangle. Enclosing
+ // rectangle made slightly larger in case corner points are already in the
+ // triangulation.
+ if (bbox.empty) {
+ bbox.add(XY(0.0, 0.0));
+ bbox.add(XY(1.0, 1.0));
+ }
+ else {
+ const double small = 0.1; // Any value > 0.0
+ bbox.expand( (bbox.upper - bbox.lower)*small );
+ }
+ _points[npoints ] = Point(bbox.lower); // SW point.
+ _points[npoints+1] = Point(bbox.upper.x, bbox.lower.y); // SE point.
+ _points[npoints+2] = Point(bbox.lower.x, bbox.upper.y); // NW point.
+ _points[npoints+3] = Point(bbox.upper); // NE point.
+
+ // Set up edges array.
+ // First the bottom and top edges of the enclosing rectangle.
+ _edges.push_back(Edge(&_points[npoints], &_points[npoints+1],-1,-1,0,0));
+ _edges.push_back(Edge(&_points[npoints+2],&_points[npoints+3],-1,-1,0,0));
+
+ // Add all edges in the triangulation that point to the right. Do not
+ // explicitly include edges that point to the left as the neighboring
+ // triangle will supply that, unless there is no such neighbor.
+ int ntri = triang.get_ntri();
+ for (int tri = 0; tri < ntri; ++tri) {
+ if (!triang.is_masked(tri)) {
+ for (int edge = 0; edge < 3; ++edge) {
+ Point* start = _points + triang.get_triangle_point(tri,edge);
+ Point* end = _points +
+ triang.get_triangle_point(tri,(edge+1)%3);
+ Point* other = _points +
+ triang.get_triangle_point(tri,(edge+2)%3);
+ TriEdge neighbor = triang.get_neighbor_edge(tri,edge);
+ if (end->is_right_of(*start)) {
+ const Point* neighbor_point_below = (neighbor.tri == -1) ?
+ 0 : _points + triang.get_triangle_point(
+ neighbor.tri, (neighbor.edge+2)%3);
+ _edges.push_back(Edge(start, end, neighbor.tri, tri,
+ neighbor_point_below, other));
+ }
+ else if (neighbor.tri == -1)
+ _edges.push_back(Edge(end, start, tri, -1, other, 0));
+
+ // Set triangle associated with start point if not already set.
+ if (start->tri == -1)
+ start->tri = tri;
+ }
+ }
+ }
+
+ // Initial trapezoid is enclosing rectangle.
+ _tree = new Node(new Trapezoid(&_points[npoints], &_points[npoints+1],
+ _edges[0], _edges[1]));
+ _tree->assert_valid(false);
+
+ // Randomly shuffle all edges other than first 2.
+ std::mt19937 rng(1234);
+ std::shuffle(_edges.begin()+2, _edges.end(), rng);
+
+ // Add edges, one at a time, to tree.
+ size_t nedges = _edges.size();
+ for (size_t index = 2; index < nedges; ++index) {
+ if (!add_edge_to_tree(_edges[index]))
+ throw std::runtime_error("Triangulation is invalid");
+ _tree->assert_valid(index == nedges-1);
+ }
+}
+
+void
+TrapezoidMapTriFinder::print_tree()
+{
+ assert(_tree != 0 && "Null Node tree");
+ _tree->print();
+}
+
+TrapezoidMapTriFinder::Edge::Edge(const Point* left_,
+ const Point* right_,
+ int triangle_below_,
+ int triangle_above_,
+ const Point* point_below_,
+ const Point* point_above_)
+ : left(left_),
+ right(right_),
+ triangle_below(triangle_below_),
+ triangle_above(triangle_above_),
+ point_below(point_below_),
+ point_above(point_above_)
+{
+ assert(left != 0 && "Null left point");
+ assert(right != 0 && "Null right point");
+ assert(right->is_right_of(*left) && "Incorrect point order");
+ assert(triangle_below >= -1 && "Invalid triangle below index");
+ assert(triangle_above >= -1 && "Invalid triangle above index");
+}
+
+int
+TrapezoidMapTriFinder::Edge::get_point_orientation(const XY& xy) const
+{
+ double cross_z = (xy - *left).cross_z(*right - *left);
+ return (cross_z > 0.0) ? +1 : ((cross_z < 0.0) ? -1 : 0);
+}
+
+double
+TrapezoidMapTriFinder::Edge::get_slope() const
+{
+ // Divide by zero is acceptable here.
+ XY diff = *right - *left;
+ return diff.y / diff.x;
+}
+
+double
+TrapezoidMapTriFinder::Edge::get_y_at_x(const double& x) const
+{
+ if (left->x == right->x) {
+ // If edge is vertical, return lowest y from left point.
+ assert(x == left->x && "x outside of edge");
+ return left->y;
+ }
+ else {
+ // Equation of line: left + lambda*(right - left) = xy.
+ // i.e. left.x + lambda(right.x - left.x) = x and similar for y.
+ double lambda = (x - left->x) / (right->x - left->x);
+ assert(lambda >= 0 && lambda <= 1.0 && "Lambda out of bounds");
+ return left->y + lambda*(right->y - left->y);
+ }
+}
+
+bool
+TrapezoidMapTriFinder::Edge::has_point(const Point* point) const
+{
+ assert(point != 0 && "Null point");
+ return (left == point || right == point);
+}
+
+bool
+TrapezoidMapTriFinder::Edge::operator==(const Edge& other) const
+{
+ return this == &other;
+}
+
+void
+TrapezoidMapTriFinder::Edge::print_debug() const
+{
+ std::cout << "Edge " << *this << " tri_below=" << triangle_below
+ << " tri_above=" << triangle_above << std::endl;
+}
+
+TrapezoidMapTriFinder::Node::Node(const Point* point, Node* left, Node* right)
+ : _type(Type_XNode)
+{
+ assert(point != 0 && "Invalid point");
+ assert(left != 0 && "Invalid left node");
+ assert(right != 0 && "Invalid right node");
+ _union.xnode.point = point;
+ _union.xnode.left = left;
+ _union.xnode.right = right;
+ left->add_parent(this);
+ right->add_parent(this);
+}
+
+TrapezoidMapTriFinder::Node::Node(const Edge* edge, Node* below, Node* above)
+ : _type(Type_YNode)
+{
+ assert(edge != 0 && "Invalid edge");
+ assert(below != 0 && "Invalid below node");
+ assert(above != 0 && "Invalid above node");
+ _union.ynode.edge = edge;
+ _union.ynode.below = below;
+ _union.ynode.above = above;
+ below->add_parent(this);
+ above->add_parent(this);
+}
+
+TrapezoidMapTriFinder::Node::Node(Trapezoid* trapezoid)
+ : _type(Type_TrapezoidNode)
+{
+ assert(trapezoid != 0 && "Null Trapezoid");
+ _union.trapezoid = trapezoid;
+ trapezoid->trapezoid_node = this;
+}
+
+TrapezoidMapTriFinder::Node::~Node()
+{
+ switch (_type) {
+ case Type_XNode:
+ if (_union.xnode.left->remove_parent(this))
+ delete _union.xnode.left;
+ if (_union.xnode.right->remove_parent(this))
+ delete _union.xnode.right;
+ break;
+ case Type_YNode:
+ if (_union.ynode.below->remove_parent(this))
+ delete _union.ynode.below;
+ if (_union.ynode.above->remove_parent(this))
+ delete _union.ynode.above;
+ break;
+ case Type_TrapezoidNode:
+ delete _union.trapezoid;
+ break;
+ }
+}
+
+void
+TrapezoidMapTriFinder::Node::add_parent(Node* parent)
+{
+ assert(parent != 0 && "Null parent");
+ assert(parent != this && "Cannot be parent of self");
+ assert(!has_parent(parent) && "Parent already in collection");
+ _parents.push_back(parent);
+}
+
+void
+TrapezoidMapTriFinder::Node::assert_valid(bool tree_complete) const
+{
+#ifndef NDEBUG
+ // Check parents.
+ for (Parents::const_iterator it = _parents.begin();
+ it != _parents.end(); ++it) {
+ Node* parent = *it;
+ assert(parent != this && "Cannot be parent of self");
+ assert(parent->has_child(this) && "Parent missing child");
+ }
+
+ // Check children, and recurse.
+ switch (_type) {
+ case Type_XNode:
+ assert(_union.xnode.left != 0 && "Null left child");
+ assert(_union.xnode.left->has_parent(this) && "Incorrect parent");
+ assert(_union.xnode.right != 0 && "Null right child");
+ assert(_union.xnode.right->has_parent(this) && "Incorrect parent");
+ _union.xnode.left->assert_valid(tree_complete);
+ _union.xnode.right->assert_valid(tree_complete);
+ break;
+ case Type_YNode:
+ assert(_union.ynode.below != 0 && "Null below child");
+ assert(_union.ynode.below->has_parent(this) && "Incorrect parent");
+ assert(_union.ynode.above != 0 && "Null above child");
+ assert(_union.ynode.above->has_parent(this) && "Incorrect parent");
+ _union.ynode.below->assert_valid(tree_complete);
+ _union.ynode.above->assert_valid(tree_complete);
+ break;
+ case Type_TrapezoidNode:
+ assert(_union.trapezoid != 0 && "Null trapezoid");
+ assert(_union.trapezoid->trapezoid_node == this &&
+ "Incorrect trapezoid node");
+ _union.trapezoid->assert_valid(tree_complete);
+ break;
+ }
+#endif
+}
+
+void
+TrapezoidMapTriFinder::Node::get_stats(int depth,
+ NodeStats& stats) const
+{
+ stats.node_count++;
+ if (depth > stats.max_depth)
+ stats.max_depth = depth;
+ bool new_node = stats.unique_nodes.insert(this).second;
+ if (new_node)
+ stats.max_parent_count = std::max(stats.max_parent_count,
+ static_cast<long>(_parents.size()));
+
+ switch (_type) {
+ case Type_XNode:
+ _union.xnode.left->get_stats(depth+1, stats);
+ _union.xnode.right->get_stats(depth+1, stats);
+ break;
+ case Type_YNode:
+ _union.ynode.below->get_stats(depth+1, stats);
+ _union.ynode.above->get_stats(depth+1, stats);
+ break;
+ default: // Type_TrapezoidNode:
+ stats.unique_trapezoid_nodes.insert(this);
+ stats.trapezoid_count++;
+ stats.sum_trapezoid_depth += depth;
+ break;
+ }
+}
+
+int
+TrapezoidMapTriFinder::Node::get_tri() const
+{
+ switch (_type) {
+ case Type_XNode:
+ return _union.xnode.point->tri;
+ case Type_YNode:
+ if (_union.ynode.edge->triangle_above != -1)
+ return _union.ynode.edge->triangle_above;
+ else
+ return _union.ynode.edge->triangle_below;
+ default: // Type_TrapezoidNode:
+ assert(_union.trapezoid->below.triangle_above ==
+ _union.trapezoid->above.triangle_below &&
+ "Inconsistent triangle indices from trapezoid edges");
+ return _union.trapezoid->below.triangle_above;
+ }
+}
+
+bool
+TrapezoidMapTriFinder::Node::has_child(const Node* child) const
+{
+ assert(child != 0 && "Null child node");
+ switch (_type) {
+ case Type_XNode:
+ return (_union.xnode.left == child || _union.xnode.right == child);
+ case Type_YNode:
+ return (_union.ynode.below == child ||
+ _union.ynode.above == child);
+ default: // Type_TrapezoidNode:
+ return false;
+ }
+}
+
+bool
+TrapezoidMapTriFinder::Node::has_no_parents() const
+{
+ return _parents.empty();
+}
+
+bool
+TrapezoidMapTriFinder::Node::has_parent(const Node* parent) const
+{
+ return (std::find(_parents.begin(), _parents.end(), parent) !=
+ _parents.end());
+}
+
+void
+TrapezoidMapTriFinder::Node::print(int depth /* = 0 */) const
+{
+ for (int i = 0; i < depth; ++i) std::cout << " ";
+ switch (_type) {
+ case Type_XNode:
+ std::cout << "XNode " << *_union.xnode.point << std::endl;
+ _union.xnode.left->print(depth + 1);
+ _union.xnode.right->print(depth + 1);
+ break;
+ case Type_YNode:
+ std::cout << "YNode " << *_union.ynode.edge << std::endl;
+ _union.ynode.below->print(depth + 1);
+ _union.ynode.above->print(depth + 1);
+ break;
+ case Type_TrapezoidNode:
+ std::cout << "Trapezoid ll="
+ << _union.trapezoid->get_lower_left_point() << " lr="
+ << _union.trapezoid->get_lower_right_point() << " ul="
+ << _union.trapezoid->get_upper_left_point() << " ur="
+ << _union.trapezoid->get_upper_right_point() << std::endl;
+ break;
+ }
+}
+
+bool
+TrapezoidMapTriFinder::Node::remove_parent(Node* parent)
+{
+ assert(parent != 0 && "Null parent");
+ assert(parent != this && "Cannot be parent of self");
+ Parents::iterator it = std::find(_parents.begin(), _parents.end(), parent);
+ assert(it != _parents.end() && "Parent not in collection");
+ _parents.erase(it);
+ return _parents.empty();
+}
+
+void
+TrapezoidMapTriFinder::Node::replace_child(Node* old_child, Node* new_child)
+{
+ switch (_type) {
+ case Type_XNode:
+ assert((_union.xnode.left == old_child ||
+ _union.xnode.right == old_child) && "Not a child Node");
+ assert(new_child != 0 && "Null child node");
+ if (_union.xnode.left == old_child)
+ _union.xnode.left = new_child;
+ else
+ _union.xnode.right = new_child;
+ break;
+ case Type_YNode:
+ assert((_union.ynode.below == old_child ||
+ _union.ynode.above == old_child) && "Not a child node");
+ assert(new_child != 0 && "Null child node");
+ if (_union.ynode.below == old_child)
+ _union.ynode.below = new_child;
+ else
+ _union.ynode.above = new_child;
+ break;
+ case Type_TrapezoidNode:
+ assert(0 && "Invalid type for this operation");
+ break;
+ }
+ old_child->remove_parent(this);
+ new_child->add_parent(this);
+}
+
+void
+TrapezoidMapTriFinder::Node::replace_with(Node* new_node)
+{
+ assert(new_node != 0 && "Null replacement node");
+ // Replace child of each parent with new_node. As each has parent has its
+ // child replaced it is removed from the _parents collection.
+ while (!_parents.empty())
+ _parents.front()->replace_child(this, new_node);
+}
+
+const TrapezoidMapTriFinder::Node*
+TrapezoidMapTriFinder::Node::search(const XY& xy)
+{
+ switch (_type) {
+ case Type_XNode:
+ if (xy == *_union.xnode.point)
+ return this;
+ else if (xy.is_right_of(*_union.xnode.point))
+ return _union.xnode.right->search(xy);
+ else
+ return _union.xnode.left->search(xy);
+ case Type_YNode: {
+ int orient = _union.ynode.edge->get_point_orientation(xy);
+ if (orient == 0)
+ return this;
+ else if (orient < 0)
+ return _union.ynode.above->search(xy);
+ else
+ return _union.ynode.below->search(xy);
+ }
+ default: // Type_TrapezoidNode:
+ return this;
+ }
+}
+
+TrapezoidMapTriFinder::Trapezoid*
+TrapezoidMapTriFinder::Node::search(const Edge& edge)
+{
+ switch (_type) {
+ case Type_XNode:
+ if (edge.left == _union.xnode.point)
+ return _union.xnode.right->search(edge);
+ else {
+ if (edge.left->is_right_of(*_union.xnode.point))
+ return _union.xnode.right->search(edge);
+ else
+ return _union.xnode.left->search(edge);
+ }
+ case Type_YNode:
+ if (edge.left == _union.ynode.edge->left) {
+ // Coinciding left edge points.
+ if (edge.get_slope() == _union.ynode.edge->get_slope()) {
+ if (_union.ynode.edge->triangle_above ==
+ edge.triangle_below)
+ return _union.ynode.above->search(edge);
+ else if (_union.ynode.edge->triangle_below ==
+ edge.triangle_above)
+ return _union.ynode.below->search(edge);
+ else {
+ assert(0 &&
+ "Invalid triangulation, common left points");
+ return 0;
+ }
+ }
+ if (edge.get_slope() > _union.ynode.edge->get_slope())
+ return _union.ynode.above->search(edge);
+ else
+ return _union.ynode.below->search(edge);
+ }
+ else if (edge.right == _union.ynode.edge->right) {
+ // Coinciding right edge points.
+ if (edge.get_slope() == _union.ynode.edge->get_slope()) {
+ if (_union.ynode.edge->triangle_above ==
+ edge.triangle_below)
+ return _union.ynode.above->search(edge);
+ else if (_union.ynode.edge->triangle_below ==
+ edge.triangle_above)
+ return _union.ynode.below->search(edge);
+ else {
+ assert(0 &&
+ "Invalid triangulation, common right points");
+ return 0;
+ }
+ }
+ if (edge.get_slope() > _union.ynode.edge->get_slope())
+ return _union.ynode.below->search(edge);
+ else
+ return _union.ynode.above->search(edge);
+ }
+ else {
+ int orient =
+ _union.ynode.edge->get_point_orientation(*edge.left);
+ if (orient == 0) {
+ // edge.left lies on _union.ynode.edge
+ if (_union.ynode.edge->point_above != 0 &&
+ edge.has_point(_union.ynode.edge->point_above))
+ orient = -1;
+ else if (_union.ynode.edge->point_below != 0 &&
+ edge.has_point(_union.ynode.edge->point_below))
+ orient = +1;
+ else {
+ assert(0 && "Invalid triangulation, point on edge");
+ return 0;
+ }
+ }
+ if (orient < 0)
+ return _union.ynode.above->search(edge);
+ else
+ return _union.ynode.below->search(edge);
+ }
+ default: // Type_TrapezoidNode:
+ return _union.trapezoid;
+ }
+}
+
+TrapezoidMapTriFinder::Trapezoid::Trapezoid(const Point* left_,
+ const Point* right_,
+ const Edge& below_,
+ const Edge& above_)
+ : left(left_), right(right_), below(below_), above(above_),
+ lower_left(0), lower_right(0), upper_left(0), upper_right(0),
+ trapezoid_node(0)
+{
+ assert(left != 0 && "Null left point");
+ assert(right != 0 && "Null right point");
+ assert(right->is_right_of(*left) && "Incorrect point order");
+}
+
+void
+TrapezoidMapTriFinder::Trapezoid::assert_valid(bool tree_complete) const
+{
+#ifndef NDEBUG
+ assert(left != 0 && "Null left point");
+ assert(right != 0 && "Null right point");
+
+ if (lower_left != 0) {
+ assert(lower_left->below == below &&
+ lower_left->lower_right == this &&
+ "Incorrect lower_left trapezoid");
+ assert(get_lower_left_point() == lower_left->get_lower_right_point() &&
+ "Incorrect lower left point");
+ }
+
+ if (lower_right != 0) {
+ assert(lower_right->below == below &&
+ lower_right->lower_left == this &&
+ "Incorrect lower_right trapezoid");
+ assert(get_lower_right_point() == lower_right->get_lower_left_point() &&
+ "Incorrect lower right point");
+ }
+
+ if (upper_left != 0) {
+ assert(upper_left->above == above &&
+ upper_left->upper_right == this &&
+ "Incorrect upper_left trapezoid");
+ assert(get_upper_left_point() == upper_left->get_upper_right_point() &&
+ "Incorrect upper left point");
+ }
+
+ if (upper_right != 0) {
+ assert(upper_right->above == above &&
+ upper_right->upper_left == this &&
+ "Incorrect upper_right trapezoid");
+ assert(get_upper_right_point() == upper_right->get_upper_left_point() &&
+ "Incorrect upper right point");
+ }
+
+ assert(trapezoid_node != 0 && "Null trapezoid_node");
+
+ if (tree_complete) {
+ assert(below.triangle_above == above.triangle_below &&
+ "Inconsistent triangle indices from trapezoid edges");
+ }
+#endif
+}
+
+XY
+TrapezoidMapTriFinder::Trapezoid::get_lower_left_point() const
+{
+ double x = left->x;
+ return XY(x, below.get_y_at_x(x));
+}
+
+XY
+TrapezoidMapTriFinder::Trapezoid::get_lower_right_point() const
+{
+ double x = right->x;
+ return XY(x, below.get_y_at_x(x));
+}
+
+XY
+TrapezoidMapTriFinder::Trapezoid::get_upper_left_point() const
+{
+ double x = left->x;
+ return XY(x, above.get_y_at_x(x));
+}
+
+XY
+TrapezoidMapTriFinder::Trapezoid::get_upper_right_point() const
+{
+ double x = right->x;
+ return XY(x, above.get_y_at_x(x));
+}
+
+void
+TrapezoidMapTriFinder::Trapezoid::print_debug() const
+{
+ std::cout << "Trapezoid " << this
+ << " left=" << *left
+ << " right=" << *right
+ << " below=" << below
+ << " above=" << above
+ << " ll=" << lower_left
+ << " lr=" << lower_right
+ << " ul=" << upper_left
+ << " ur=" << upper_right
+ << " node=" << trapezoid_node
+ << " llp=" << get_lower_left_point()
+ << " lrp=" << get_lower_right_point()
+ << " ulp=" << get_upper_left_point()
+ << " urp=" << get_upper_right_point() << std::endl;
+}
+
+void
+TrapezoidMapTriFinder::Trapezoid::set_lower_left(Trapezoid* lower_left_)
+{
+ lower_left = lower_left_;
+ if (lower_left != 0)
+ lower_left->lower_right = this;
+}
+
+void
+TrapezoidMapTriFinder::Trapezoid::set_lower_right(Trapezoid* lower_right_)
+{
+ lower_right = lower_right_;
+ if (lower_right != 0)
+ lower_right->lower_left = this;
+}
+
+void
+TrapezoidMapTriFinder::Trapezoid::set_upper_left(Trapezoid* upper_left_)
+{
+ upper_left = upper_left_;
+ if (upper_left != 0)
+ upper_left->upper_right = this;
+}
+
+void
+TrapezoidMapTriFinder::Trapezoid::set_upper_right(Trapezoid* upper_right_)
+{
+ upper_right = upper_right_;
+ if (upper_right != 0)
+ upper_right->upper_left = this;
+}
diff --git a/contrib/python/matplotlib/py3/src/tri/_tri.h b/contrib/python/matplotlib/py3/src/tri/_tri.h
new file mode 100644
index 0000000000..c176b4c0e8
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/tri/_tri.h
@@ -0,0 +1,799 @@
+/*
+ * Unstructured triangular grid functions, particularly contouring.
+ *
+ * There are two main classes: Triangulation and TriContourGenerator.
+ *
+ * Triangulation
+ * -------------
+ * Triangulation is an unstructured triangular grid with npoints and ntri
+ * triangles. It consists of point x and y coordinates, and information about
+ * the triangulation stored in an integer array of shape (ntri,3) called
+ * triangles. Each triangle is represented by three point indices (in the
+ * range 0 to npoints-1) that comprise the triangle, ordered anticlockwise.
+ * There is an optional mask of length ntri which can be used to mask out
+ * triangles and has the same result as removing those triangles from the
+ * 'triangles' array.
+ *
+ * A particular edge of a triangulation is termed a TriEdge, which is a
+ * triangle index and an edge index in the range 0 to 2. TriEdge(tri,edge)
+ * refers to the edge that starts at point index triangles(tri,edge) and ends
+ * at point index triangles(tri,(edge+1)%3).
+ *
+ * Various derived fields are calculated when they are first needed. The
+ * triangle connectivity is stored in a neighbors array of shape (ntri,3) such
+ * that neighbors(tri,edge) is the index of the triangle that adjoins the
+ * TriEdge(tri,edge), or -1 if there is no such neighbor.
+ *
+ * A triangulation has one or more boundaries, each of which is a 1D array of
+ * the TriEdges that comprise the boundary, in order following the boundary
+ * with non-masked triangles on the left.
+ *
+ * TriContourGenerator
+ * -------------------
+ * A TriContourGenerator generates contours for a particular Triangulation.
+ * The process followed is different for non-filled and filled contours, with
+ * one and two contour levels respectively. In both cases boundary contour
+ * lines are found first, then interior lines.
+ *
+ * Boundary lines start and end on a boundary. They are found by traversing
+ * the triangulation boundary edges until a suitable start point is found, and
+ * then the contour line is followed across the interior of the triangulation
+ * until it ends on another boundary edge. For a non-filled contour this
+ * completes a line, whereas a filled contour continues by following the
+ * boundary around until either another boundary start point is found or the
+ * start of the contour line is reached. Filled contour generation stores
+ * boolean flags to indicate which boundary edges have already been traversed
+ * so that they are not dealt with twice. Similar flags are used to indicate
+ * which triangles have been used when following interior lines.
+ *
+ * Interior lines do not intersect any boundaries. They are found by
+ * traversing all triangles that have not yet been visited until a suitable
+ * starting point is found, and then the contour line is followed across the
+ * interior of the triangulation until it returns to the start point. For
+ * filled contours this process is repeated for both lower and upper contour
+ * levels, and the direction of traversal is reversed for upper contours.
+ *
+ * Working out in which direction a contour line leaves a triangle uses the
+ * a lookup table. A triangle has three points, each of which has a z-value
+ * which is either less than the contour level or not. Hence there are 8
+ * configurations to deal with, 2 of which do not have a contour line (all
+ * points below or above (including the same as) the contour level) and 6 that
+ * do. See the function get_exit_edge for details.
+ */
+#ifndef MPL_TRI_H
+#define MPL_TRI_H
+
+#include <pybind11/pybind11.h>
+#include <pybind11/numpy.h>
+
+#include <iostream>
+#include <list>
+#include <map>
+#include <set>
+#include <vector>
+
+namespace py = pybind11;
+
+
+/* An edge of a triangle consisting of an triangle index in the range 0 to
+ * ntri-1 and an edge index in the range 0 to 2. Edge i goes from the
+ * triangle's point i to point (i+1)%3. */
+struct TriEdge
+{
+ TriEdge();
+ TriEdge(int tri_, int edge_);
+ bool operator<(const TriEdge& other) const;
+ bool operator==(const TriEdge& other) const;
+ bool operator!=(const TriEdge& other) const;
+ friend std::ostream& operator<<(std::ostream& os, const TriEdge& tri_edge);
+
+ int tri, edge;
+};
+
+// 2D point with x,y coordinates.
+struct XY
+{
+ XY();
+ XY(const double& x_, const double& y_);
+ double angle() const; // Angle in radians with respect to x-axis.
+ double cross_z(const XY& other) const; // z-component of cross product.
+ bool is_right_of(const XY& other) const; // Compares x then y.
+ bool operator==(const XY& other) const;
+ bool operator!=(const XY& other) const;
+ XY operator*(const double& multiplier) const;
+ const XY& operator+=(const XY& other);
+ const XY& operator-=(const XY& other);
+ XY operator+(const XY& other) const;
+ XY operator-(const XY& other) const;
+ friend std::ostream& operator<<(std::ostream& os, const XY& xy);
+
+ double x, y;
+};
+
+// 3D point with x,y,z coordinates.
+struct XYZ
+{
+ XYZ(const double& x_, const double& y_, const double& z_);
+ XYZ cross(const XYZ& other) const;
+ double dot(const XYZ& other) const;
+ XYZ operator-(const XYZ& other) const;
+ friend std::ostream& operator<<(std::ostream& os, const XYZ& xyz);
+
+ double x, y, z;
+};
+
+// 2D bounding box, which may be empty.
+class BoundingBox
+{
+public:
+ BoundingBox();
+ void add(const XY& point);
+ void expand(const XY& delta);
+
+ // Consider these member variables read-only.
+ bool empty;
+ XY lower, upper;
+};
+
+/* A single line of a contour, which may be a closed line loop or an open line
+ * strip. Identical adjacent points are avoided using push_back(), and a closed
+ * line loop should also not have identical first and last points. */
+class ContourLine : public std::vector<XY>
+{
+public:
+ ContourLine();
+ void push_back(const XY& point);
+ void write() const;
+};
+
+// A Contour is a collection of zero or more ContourLines.
+typedef std::vector<ContourLine> Contour;
+
+// Debug contour writing function.
+void write_contour(const Contour& contour);
+
+
+
+
+/* Triangulation with npoints points and ntri triangles. Derived fields are
+ * calculated when they are first needed. */
+class Triangulation
+{
+public:
+ typedef py::array_t<double, py::array::c_style | py::array::forcecast> CoordinateArray;
+ typedef py::array_t<double, py::array::c_style | py::array::forcecast> TwoCoordinateArray;
+ typedef py::array_t<int, py::array::c_style | py::array::forcecast> TriangleArray;
+ typedef py::array_t<bool, py::array::c_style | py::array::forcecast> MaskArray;
+ typedef py::array_t<int, py::array::c_style | py::array::forcecast> EdgeArray;
+ typedef py::array_t<int, py::array::c_style | py::array::forcecast> NeighborArray;
+
+ /* A single boundary is a vector of the TriEdges that make up that boundary
+ * following it around with unmasked triangles on the left. */
+ typedef std::vector<TriEdge> Boundary;
+ typedef std::vector<Boundary> Boundaries;
+
+ /* Constructor with optional mask, edges and neighbors. The latter two
+ * are calculated when first needed.
+ * x: double array of shape (npoints) of points' x-coordinates.
+ * y: double array of shape (npoints) of points' y-coordinates.
+ * triangles: int array of shape (ntri,3) of triangle point indices.
+ * Those ordered clockwise are changed to be anticlockwise.
+ * mask: Optional bool array of shape (ntri) indicating which triangles
+ * are masked.
+ * edges: Optional int array of shape (?,2) of start and end point
+ * indices, each edge (start,end and end,start) appearing only
+ * once.
+ * neighbors: Optional int array of shape (ntri,3) indicating which
+ * triangles are the neighbors of which TriEdges, or -1 if
+ * there is no such neighbor.
+ * correct_triangle_orientations: Whether or not should correct triangle
+ * orientations so that vertices are
+ * ordered anticlockwise. */
+ Triangulation(const CoordinateArray& x,
+ const CoordinateArray& y,
+ const TriangleArray& triangles,
+ const MaskArray& mask,
+ const EdgeArray& edges,
+ const NeighborArray& neighbors,
+ bool correct_triangle_orientations);
+
+ /* Calculate plane equation coefficients for all unmasked triangles from
+ * the point (x,y) coordinates and point z-array of shape (npoints) passed
+ * in via the args. Returned array has shape (npoints,3) and allows
+ * z-value at (x,y) coordinates in triangle tri to be calculated using
+ * z = array[tri,0]*x + array[tri,1]*y + array[tri,2]. */
+ TwoCoordinateArray calculate_plane_coefficients(const CoordinateArray& z);
+
+ // Return the boundaries collection, creating it if necessary.
+ const Boundaries& get_boundaries() const;
+
+ // Return which boundary and boundary edge the specified TriEdge is.
+ void get_boundary_edge(const TriEdge& triEdge,
+ int& boundary,
+ int& edge) const;
+
+ /* Return the edges array, creating it if necessary. */
+ EdgeArray& get_edges();
+
+ /* Return the triangle index of the neighbor of the specified triangle
+ * edge. */
+ int get_neighbor(int tri, int edge) const;
+
+ /* Return the TriEdge that is the neighbor of the specified triangle edge,
+ * or TriEdge(-1,-1) if there is no such neighbor. */
+ TriEdge get_neighbor_edge(int tri, int edge) const;
+
+ /* Return the neighbors array, creating it if necessary. */
+ NeighborArray& get_neighbors();
+
+ // Return the number of points in this triangulation.
+ int get_npoints() const;
+
+ // Return the number of triangles in this triangulation.
+ int get_ntri() const;
+
+ /* Return the index of the point that is at the start of the specified
+ * triangle edge. */
+ int get_triangle_point(int tri, int edge) const;
+ int get_triangle_point(const TriEdge& tri_edge) const;
+
+ // Return the coordinates of the specified point index.
+ XY get_point_coords(int point) const;
+
+ // Indicates if the specified triangle is masked or not.
+ bool is_masked(int tri) const;
+
+ /* Set or clear the mask array. Clears various derived fields so they are
+ * recalculated when next needed.
+ * mask: bool array of shape (ntri) indicating which triangles are
+ * masked, or an empty array to clear mask. */
+ void set_mask(const MaskArray& mask);
+
+ // Debug function to write boundaries.
+ void write_boundaries() const;
+
+private:
+ // An edge of a triangulation, composed of start and end point indices.
+ struct Edge
+ {
+ Edge() : start(-1), end(-1) {}
+ Edge(int start_, int end_) : start(start_), end(end_) {}
+ bool operator<(const Edge& other) const {
+ return start != other.start ? start < other.start : end < other.end;
+ }
+ int start, end;
+ };
+
+ /* An edge of a boundary of a triangulation, composed of a boundary index
+ * and an edge index within that boundary. Used to index into the
+ * boundaries collection to obtain the corresponding TriEdge. */
+ struct BoundaryEdge
+ {
+ BoundaryEdge() : boundary(-1), edge(-1) {}
+ BoundaryEdge(int boundary_, int edge_)
+ : boundary(boundary_), edge(edge_) {}
+ int boundary, edge;
+ };
+
+ /* Calculate the boundaries collection. Should normally be accessed via
+ * get_boundaries(), which will call this function if necessary. */
+ void calculate_boundaries();
+
+ /* Calculate the edges array. Should normally be accessed via
+ * get_edges(), which will call this function if necessary. */
+ void calculate_edges();
+
+ /* Calculate the neighbors array. Should normally be accessed via
+ * get_neighbors(), which will call this function if necessary. */
+ void calculate_neighbors();
+
+ /* Correct each triangle so that the vertices are ordered in an
+ * anticlockwise manner. */
+ void correct_triangles();
+
+ /* Determine which edge index (0,1 or 2) the specified point index is in
+ * the specified triangle, or -1 if the point is not in the triangle. */
+ int get_edge_in_triangle(int tri, int point) const;
+
+ bool has_edges() const;
+
+ bool has_mask() const;
+
+ bool has_neighbors() const;
+
+
+ // Variables shared with python, always set.
+ CoordinateArray _x, _y; // double array (npoints).
+ TriangleArray _triangles; // int array (ntri,3) of triangle point indices,
+ // ordered anticlockwise.
+
+ // Variables shared with python, may be unset (size == 0).
+ MaskArray _mask; // bool array (ntri).
+
+ // Derived variables shared with python, may be unset (size == 0).
+ // If unset, are recalculated when needed.
+ EdgeArray _edges; // int array (?,2) of start & end point indices.
+ NeighborArray _neighbors; // int array (ntri,3), neighbor triangle indices
+ // or -1 if no neighbor.
+
+ // Variables internal to C++ only.
+ Boundaries _boundaries;
+
+ // Map used to look up BoundaryEdges from TriEdges. Normally accessed via
+ // get_boundary_edge().
+ typedef std::map<TriEdge, BoundaryEdge> TriEdgeToBoundaryMap;
+ TriEdgeToBoundaryMap _tri_edge_to_boundary_map;
+};
+
+
+
+// Contour generator for a triangulation.
+class TriContourGenerator
+{
+public:
+ typedef Triangulation::CoordinateArray CoordinateArray;
+ typedef Triangulation::TwoCoordinateArray TwoCoordinateArray;
+ typedef py::array_t<unsigned char> CodeArray;
+
+ /* Constructor.
+ * triangulation: Triangulation to generate contours for.
+ * z: Double array of shape (npoints) of z-values at triangulation
+ * points. */
+ TriContourGenerator(Triangulation& triangulation,
+ const CoordinateArray& z);
+
+ /* Create and return a non-filled contour.
+ * level: Contour level.
+ * Returns new python list [segs0, segs1, ...] where
+ * segs0: double array of shape (?,2) of point coordinates of first
+ * contour line, etc. */
+ py::tuple create_contour(const double& level);
+
+ /* Create and return a filled contour.
+ * lower_level: Lower contour level.
+ * upper_level: Upper contour level.
+ * Returns new python tuple (segs, kinds) where
+ * segs: double array of shape (n_points,2) of all point coordinates,
+ * kinds: ubyte array of shape (n_points) of all point code types. */
+ py::tuple create_filled_contour(const double& lower_level,
+ const double& upper_level);
+
+private:
+ typedef Triangulation::Boundary Boundary;
+ typedef Triangulation::Boundaries Boundaries;
+
+ /* Clear visited flags.
+ * include_boundaries: Whether to clear boundary flags or not, which are
+ * only used for filled contours. */
+ void clear_visited_flags(bool include_boundaries);
+
+ /* Convert a non-filled Contour from C++ to Python.
+ * Returns new python tuple ([segs0, segs1, ...], [kinds0, kinds1...])
+ * where
+ * segs0: double array of shape (n_points,2) of point coordinates of first
+ * contour line, etc.
+ * kinds0: ubyte array of shape (n_points) of kinds codes of first contour
+ * line, etc. */
+ py::tuple contour_line_to_segs_and_kinds(const Contour& contour);
+
+ /* Convert a filled Contour from C++ to Python.
+ * Returns new python tuple ([segs], [kinds]) where
+ * segs: double array of shape (n_points,2) of all point coordinates,
+ * kinds: ubyte array of shape (n_points) of all point code types. */
+ py::tuple contour_to_segs_and_kinds(const Contour& contour);
+
+ /* Return the point on the specified TriEdge that intersects the specified
+ * level. */
+ XY edge_interp(int tri, int edge, const double& level);
+
+ /* Find and follow non-filled contour lines that start and end on a
+ * boundary of the Triangulation.
+ * contour: Contour to add new lines to.
+ * level: Contour level. */
+ void find_boundary_lines(Contour& contour,
+ const double& level);
+
+ /* Find and follow filled contour lines at either of the specified contour
+ * levels that start and end of a boundary of the Triangulation.
+ * contour: Contour to add new lines to.
+ * lower_level: Lower contour level.
+ * upper_level: Upper contour level. */
+ void find_boundary_lines_filled(Contour& contour,
+ const double& lower_level,
+ const double& upper_level);
+
+ /* Find and follow lines at the specified contour level that are
+ * completely in the interior of the Triangulation and hence do not
+ * intersect any boundary.
+ * contour: Contour to add new lines to.
+ * level: Contour level.
+ * on_upper: Whether on upper or lower contour level.
+ * filled: Whether contours are filled or not. */
+ void find_interior_lines(Contour& contour,
+ const double& level,
+ bool on_upper,
+ bool filled);
+
+ /* Follow contour line around boundary of the Triangulation from the
+ * specified TriEdge to its end which can be on either the lower or upper
+ * levels. Only used for filled contours.
+ * contour_line: Contour line to append new points to.
+ * tri_edge: On entry, TriEdge to start from. On exit, TriEdge that is
+ * finished on.
+ * lower_level: Lower contour level.
+ * upper_level: Upper contour level.
+ * on_upper: Whether starts on upper level or not.
+ * Return true if finishes on upper level, false if lower. */
+ bool follow_boundary(ContourLine& contour_line,
+ TriEdge& tri_edge,
+ const double& lower_level,
+ const double& upper_level,
+ bool on_upper);
+
+ /* Follow contour line across interior of Triangulation.
+ * contour_line: Contour line to append new points to.
+ * tri_edge: On entry, TriEdge to start from. On exit, TriEdge that is
+ * finished on.
+ * end_on_boundary: Whether this line ends on a boundary, or loops back
+ * upon itself.
+ * level: Contour level to follow.
+ * on_upper: Whether following upper or lower contour level. */
+ void follow_interior(ContourLine& contour_line,
+ TriEdge& tri_edge,
+ bool end_on_boundary,
+ const double& level,
+ bool on_upper);
+
+ // Return the Triangulation boundaries.
+ const Boundaries& get_boundaries() const;
+
+ /* Return the edge by which the a level leaves a particular triangle,
+ * which is 0, 1 or 2 if the contour passes through the triangle or -1
+ * otherwise.
+ * tri: Triangle index.
+ * level: Contour level to follow.
+ * on_upper: Whether following upper or lower contour level. */
+ int get_exit_edge(int tri, const double& level, bool on_upper) const;
+
+ // Return the z-value at the specified point index.
+ const double& get_z(int point) const;
+
+ /* Return the point at which the a level intersects the line connecting the
+ * two specified point indices. */
+ XY interp(int point1, int point2, const double& level) const;
+
+
+
+ // Variables shared with python, always set.
+ Triangulation _triangulation;
+ CoordinateArray _z; // double array (npoints).
+
+ // Variables internal to C++ only.
+ typedef std::vector<bool> InteriorVisited; // Size 2*ntri
+ typedef std::vector<bool> BoundaryVisited;
+ typedef std::vector<BoundaryVisited> BoundariesVisited;
+ typedef std::vector<bool> BoundariesUsed;
+
+ InteriorVisited _interior_visited;
+ BoundariesVisited _boundaries_visited; // Only used for filled contours.
+ BoundariesUsed _boundaries_used; // Only used for filled contours.
+};
+
+
+
+/* TriFinder class implemented using the trapezoid map algorithm from the book
+ * "Computational Geometry, Algorithms and Applications", second edition, by
+ * M. de Berg, M. van Kreveld, M. Overmars and O. Schwarzkopf.
+ *
+ * The domain of interest is composed of vertical-sided trapezoids that are
+ * bounded to the left and right by points of the triangulation, and below and
+ * above by edges of the triangulation. Each triangle is represented by 1 or
+ * more of these trapezoids. Edges are inserted one a time in a random order.
+ *
+ * As the trapezoid map is created, a search tree is also created which allows
+ * fast lookup O(log N) of the trapezoid containing the point of interest.
+ * There are 3 types of node in the search tree: all leaf nodes represent
+ * trapezoids and all branch nodes have 2 child nodes and are either x-nodes or
+ * y-nodes. X-nodes represent points in the triangulation, and their 2 children
+ * refer to those parts of the search tree to the left and right of the point.
+ * Y-nodes represent edges in the triangulation, and their 2 children refer to
+ * those parts of the search tree below and above the edge.
+ *
+ * Nodes can be repeated throughout the search tree, and each is reference
+ * counted through the multiple parent nodes it is a child of.
+ *
+ * The algorithm is only intended to work with valid triangulations, i.e. it
+ * must not contain duplicate points, triangles formed from colinear points, or
+ * overlapping triangles. It does have some tolerance to triangles formed from
+ * colinear points but only in the simplest of cases. No explicit testing of
+ * the validity of the triangulation is performed as this is a computationally
+ * more complex task than the trifinding itself. */
+class TrapezoidMapTriFinder
+{
+public:
+ typedef Triangulation::CoordinateArray CoordinateArray;
+ typedef py::array_t<int, py::array::c_style | py::array::forcecast> TriIndexArray;
+
+ /* Constructor. A separate call to initialize() is required to initialize
+ * the object before use.
+ * triangulation: Triangulation to find triangles in. */
+ TrapezoidMapTriFinder(Triangulation& triangulation);
+
+ ~TrapezoidMapTriFinder();
+
+ /* Return an array of triangle indices. Takes 1D arrays x and y of
+ * point coordinates, and returns an array of the same size containing the
+ * indices of the triangles at those points. */
+ TriIndexArray find_many(const CoordinateArray& x, const CoordinateArray& y);
+
+ /* Return a reference to a new python list containing the following
+ * statistics about the tree:
+ * 0: number of nodes (tree size)
+ * 1: number of unique nodes (number of unique Node objects in tree)
+ * 2: number of trapezoids (tree leaf nodes)
+ * 3: number of unique trapezoids
+ * 4: maximum parent count (max number of times a node is repeated in
+ * tree)
+ * 5: maximum depth of tree (one more than the maximum number of
+ * comparisons needed to search through the tree)
+ * 6: mean of all trapezoid depths (one more than the average number of
+ * comparisons needed to search through the tree) */
+ py::list get_tree_stats();
+
+ /* Initialize this object before use. May be called multiple times, if,
+ * for example, the triangulation is changed by setting the mask. */
+ void initialize();
+
+ // Print the search tree as text to stdout; useful for debug purposes.
+ void print_tree();
+
+private:
+ /* A Point consists of x,y coordinates as well as the index of a triangle
+ * associated with the point, so that a search at this point's coordinates
+ * can return a valid triangle index. */
+ struct Point : XY
+ {
+ Point() : XY(), tri(-1) {}
+ Point(const double& x, const double& y) : XY(x,y), tri(-1) {}
+ explicit Point(const XY& xy) : XY(xy), tri(-1) {}
+
+ int tri;
+ };
+
+ /* An Edge connects two Points, left and right. It is always true that
+ * right->is_right_of(*left). Stores indices of triangles below and above
+ * the Edge which are used to map from trapezoid to triangle index. Also
+ * stores pointers to the 3rd points of the below and above triangles,
+ * which are only used to disambiguate triangles with colinear points. */
+ struct Edge
+ {
+ Edge(const Point* left_,
+ const Point* right_,
+ int triangle_below_,
+ int triangle_above_,
+ const Point* point_below_,
+ const Point* point_above_);
+
+ // Return -1 if point to left of edge, 0 if on edge, +1 if to right.
+ int get_point_orientation(const XY& xy) const;
+
+ // Return slope of edge, even if vertical (divide by zero is OK here).
+ double get_slope() const;
+
+ /* Return y-coordinate of point on edge with specified x-coordinate.
+ * x must be within the x-limits of this edge. */
+ double get_y_at_x(const double& x) const;
+
+ // Return true if the specified point is either of the edge end points.
+ bool has_point(const Point* point) const;
+
+ bool operator==(const Edge& other) const;
+
+ friend std::ostream& operator<<(std::ostream& os, const Edge& edge)
+ {
+ return os << *edge.left << "->" << *edge.right;
+ }
+
+ void print_debug() const;
+
+
+ const Point* left; // Not owned.
+ const Point* right; // Not owned.
+ int triangle_below; // Index of triangle below (to right of) Edge.
+ int triangle_above; // Index of triangle above (to left of) Edge.
+ const Point* point_below; // Used only for resolving ambiguous cases;
+ const Point* point_above; // is 0 if corresponding triangle is -1
+ };
+
+ class Node; // Forward declaration.
+
+ // Helper structure used by TrapezoidMapTriFinder::get_tree_stats.
+ struct NodeStats
+ {
+ NodeStats()
+ : node_count(0), trapezoid_count(0), max_parent_count(0),
+ max_depth(0), sum_trapezoid_depth(0.0)
+ {}
+
+ long node_count, trapezoid_count, max_parent_count, max_depth;
+ double sum_trapezoid_depth;
+ std::set<const Node*> unique_nodes, unique_trapezoid_nodes;
+ };
+
+ struct Trapezoid; // Forward declaration.
+
+ /* Node of the trapezoid map search tree. There are 3 possible types:
+ * Type_XNode, Type_YNode and Type_TrapezoidNode. Data members are
+ * represented using a union: an XNode has a Point and 2 child nodes
+ * (left and right of the point), a YNode has an Edge and 2 child nodes
+ * (below and above the edge), and a TrapezoidNode has a Trapezoid.
+ * Each Node has multiple parents so it can appear in the search tree
+ * multiple times without having to create duplicate identical Nodes.
+ * The parent collection acts as a reference count to the number of times
+ * a Node occurs in the search tree. When the parent count is reduced to
+ * zero a Node can be safely deleted. */
+ class Node
+ {
+ public:
+ Node(const Point* point, Node* left, Node* right);// Type_XNode.
+ Node(const Edge* edge, Node* below, Node* above); // Type_YNode.
+ Node(Trapezoid* trapezoid); // Type_TrapezoidNode.
+
+ ~Node();
+
+ void add_parent(Node* parent);
+
+ /* Recurse through the search tree and assert that everything is valid.
+ * Reduces to a no-op if NDEBUG is defined. */
+ void assert_valid(bool tree_complete) const;
+
+ // Recurse through the tree to return statistics about it.
+ void get_stats(int depth, NodeStats& stats) const;
+
+ // Return the index of the triangle corresponding to this node.
+ int get_tri() const;
+
+ bool has_child(const Node* child) const;
+ bool has_no_parents() const;
+ bool has_parent(const Node* parent) const;
+
+ /* Recurse through the tree and print a textual representation to
+ * stdout. Argument depth used to indent for readability. */
+ void print(int depth = 0) const;
+
+ /* Remove a parent from this Node. Return true if no parents remain
+ * so that this Node can be deleted. */
+ bool remove_parent(Node* parent);
+
+ void replace_child(Node* old_child, Node* new_child);
+
+ // Replace this node with the specified new_node in all parents.
+ void replace_with(Node* new_node);
+
+ /* Recursive search through the tree to find the Node containing the
+ * specified XY point. */
+ const Node* search(const XY& xy);
+
+ /* Recursive search through the tree to find the Trapezoid containing
+ * the left endpoint of the specified Edge. Return 0 if fails, which
+ * can only happen if the triangulation is invalid. */
+ Trapezoid* search(const Edge& edge);
+
+ /* Copy constructor and assignment operator defined but not implemented
+ * to prevent objects being copied. */
+ Node(const Node& other);
+ Node& operator=(const Node& other);
+
+ private:
+ typedef enum {
+ Type_XNode,
+ Type_YNode,
+ Type_TrapezoidNode
+ } Type;
+ Type _type;
+
+ union {
+ struct {
+ const Point* point; // Not owned.
+ Node* left; // Owned.
+ Node* right; // Owned.
+ } xnode;
+ struct {
+ const Edge* edge; // Not owned.
+ Node* below; // Owned.
+ Node* above; // Owned.
+ } ynode;
+ Trapezoid* trapezoid; // Owned.
+ } _union;
+
+ typedef std::list<Node*> Parents;
+ Parents _parents; // Not owned.
+ };
+
+ /* A Trapezoid is bounded by Points to left and right, and Edges below and
+ * above. Has up to 4 neighboring Trapezoids to lower/upper left/right.
+ * Lower left neighbor is Trapezoid to left that shares the below Edge, or
+ * is 0 if there is no such Trapezoid (and similar for other neighbors).
+ * To obtain the index of the triangle corresponding to a particular
+ * Trapezoid, use the Edge member variables below.triangle_above or
+ * above.triangle_below. */
+ struct Trapezoid
+ {
+ Trapezoid(const Point* left_,
+ const Point* right_,
+ const Edge& below_,
+ const Edge& above_);
+
+ /* Assert that this Trapezoid is valid. Reduces to a no-op if NDEBUG
+ * is defined. */
+ void assert_valid(bool tree_complete) const;
+
+ /* Return one of the 4 corner points of this Trapezoid. Only used for
+ * debugging purposes. */
+ XY get_lower_left_point() const;
+ XY get_lower_right_point() const;
+ XY get_upper_left_point() const;
+ XY get_upper_right_point() const;
+
+ void print_debug() const;
+
+ /* Set one of the 4 neighbor trapezoids and the corresponding reverse
+ * Trapezoid of the new neighbor (if it is not 0), so that they are
+ * consistent. */
+ void set_lower_left(Trapezoid* lower_left_);
+ void set_lower_right(Trapezoid* lower_right_);
+ void set_upper_left(Trapezoid* upper_left_);
+ void set_upper_right(Trapezoid* upper_right_);
+
+ /* Copy constructor and assignment operator defined but not implemented
+ * to prevent objects being copied. */
+ Trapezoid(const Trapezoid& other);
+ Trapezoid& operator=(const Trapezoid& other);
+
+
+ const Point* left; // Not owned.
+ const Point* right; // Not owned.
+ const Edge& below;
+ const Edge& above;
+
+ // 4 neighboring trapezoids, can be 0, not owned.
+ Trapezoid* lower_left; // Trapezoid to left that shares below
+ Trapezoid* lower_right; // Trapezoid to right that shares below
+ Trapezoid* upper_left; // Trapezoid to left that shares above
+ Trapezoid* upper_right; // Trapezoid to right that shares above
+
+ Node* trapezoid_node; // Node that owns this Trapezoid.
+ };
+
+
+ // Add the specified Edge to the search tree, returning true if successful.
+ bool add_edge_to_tree(const Edge& edge);
+
+ // Clear all memory allocated by this object.
+ void clear();
+
+ // Return the triangle index at the specified point, or -1 if no triangle.
+ int find_one(const XY& xy);
+
+ /* Determine the trapezoids that the specified Edge intersects, returning
+ * true if successful. */
+ bool find_trapezoids_intersecting_edge(const Edge& edge,
+ std::vector<Trapezoid*>& trapezoids);
+
+
+
+ // Variables shared with python, always set.
+ Triangulation& _triangulation;
+
+ // Variables internal to C++ only.
+ Point* _points; // Array of all points in triangulation plus corners of
+ // enclosing rectangle. Owned.
+
+ typedef std::vector<Edge> Edges;
+ Edges _edges; // All Edges in triangulation plus bottom and top Edges of
+ // enclosing rectangle.
+
+ Node* _tree; // Root node of the trapezoid map search tree. Owned.
+};
+
+#endif
diff --git a/contrib/python/matplotlib/py3/src/tri/_tri_wrapper.cpp b/contrib/python/matplotlib/py3/src/tri/_tri_wrapper.cpp
new file mode 100644
index 0000000000..1b0c3d7555
--- /dev/null
+++ b/contrib/python/matplotlib/py3/src/tri/_tri_wrapper.cpp
@@ -0,0 +1,58 @@
+#include "_tri.h"
+
+PYBIND11_MODULE(_tri, m) {
+ py::class_<Triangulation>(m, "Triangulation")
+ .def(py::init<const Triangulation::CoordinateArray&,
+ const Triangulation::CoordinateArray&,
+ const Triangulation::TriangleArray&,
+ const Triangulation::MaskArray&,
+ const Triangulation::EdgeArray&,
+ const Triangulation::NeighborArray&,
+ bool>(),
+ py::arg("x"),
+ py::arg("y"),
+ py::arg("triangles"),
+ py::arg("mask"),
+ py::arg("edges"),
+ py::arg("neighbors"),
+ py::arg("correct_triangle_orientations"),
+ "Create a new C++ Triangulation object.\n"
+ "This should not be called directly, use the python class\n"
+ "matplotlib.tri.Triangulation instead.\n")
+ .def("calculate_plane_coefficients", &Triangulation::calculate_plane_coefficients,
+ "Calculate plane equation coefficients for all unmasked triangles.")
+ .def("get_edges", &Triangulation::get_edges,
+ "Return edges array.")
+ .def("get_neighbors", &Triangulation::get_neighbors,
+ "Return neighbors array.")
+ .def("set_mask", &Triangulation::set_mask,
+ "Set or clear the mask array.");
+
+ py::class_<TriContourGenerator>(m, "TriContourGenerator")
+ .def(py::init<Triangulation&,
+ const TriContourGenerator::CoordinateArray&>(),
+ py::arg("triangulation"),
+ py::arg("z"),
+ "Create a new C++ TriContourGenerator object.\n"
+ "This should not be called directly, use the functions\n"
+ "matplotlib.axes.tricontour and tricontourf instead.\n")
+ .def("create_contour", &TriContourGenerator::create_contour,
+ "Create and return a non-filled contour.")
+ .def("create_filled_contour", &TriContourGenerator::create_filled_contour,
+ "Create and return a filled contour.");
+
+ py::class_<TrapezoidMapTriFinder>(m, "TrapezoidMapTriFinder")
+ .def(py::init<Triangulation&>(),
+ py::arg("triangulation"),
+ "Create a new C++ TrapezoidMapTriFinder object.\n"
+ "This should not be called directly, use the python class\n"
+ "matplotlib.tri.TrapezoidMapTriFinder instead.\n")
+ .def("find_many", &TrapezoidMapTriFinder::find_many,
+ "Find indices of triangles containing the point coordinates (x, y).")
+ .def("get_tree_stats", &TrapezoidMapTriFinder::get_tree_stats,
+ "Return statistics about the tree used by the trapezoid map.")
+ .def("initialize", &TrapezoidMapTriFinder::initialize,
+ "Initialize this object, creating the trapezoid map from the triangulation.")
+ .def("print_tree", &TrapezoidMapTriFinder::print_tree,
+ "Print the search tree as text to stdout; useful for debug purposes.");
+}