Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
chaco / pdf_graphics_context.py
Size: Mime:
# (C) Copyright 2005-2021 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!

# Major library imports
import warnings

try:
    # PDF imports from reportlab
    from reportlab.pdfgen.canvas import Canvas
    from reportlab.lib.pagesizes import letter, A4, landscape
    from reportlab.lib.units import inch, cm, mm, pica

except ImportError:
    Canvas = None
    PdfPlotGraphicsContext = None

from kiva.pdf import GraphicsContext


PAGE_DPI = 72.0

PAGE_SIZE_MAP = {
    "letter": letter,
    "A4": A4,
    "landscape_letter": landscape(letter),
    "landscape_A4": landscape(A4),
}

UNITS_MAP = {
    "inch": inch,
    "cm": cm,
    "mm": mm,
    "pica": pica,
}

if Canvas is not None:

    class PdfPlotGraphicsContext(GraphicsContext):
        """A convenience class for rendering PlotComponents onto PDF"""

        # The name of the file that this graphics context will use when
        # gc.save() is called without a filename being supplied.
        filename = "saved_plot.pdf"

        # The page size of the generated PDF
        pagesize = "letter"  # Enum("letter", "A4")

        # A tuple (x, y, width, height) specifying the box into which the plot
        # should be rendered.  **x** and **y** correspond to the lower-left
        # hand coordinates of the box in the coordinates of the page
        # (i.e. 0,0 is at the lower left).  **width** and **height** can be
        # positive or negative;
        # if they are positive, they are interpreted as distances from (x,y);
        # if they are negative, they are interpreted as distances from the
        # right and top of the page, respectively.
        dest_box = (0.5, 0.5, -0.5, -0.5)

        # The units of the values in dest_box
        dest_box_units = "inch"  # Enum("inch", "cm", "mm", "pica")

        def __init__(
            self,
            pdf_canvas=None,
            filename=None,
            pagesize=None,
            dest_box=None,
            dest_box_units=None,
        ):
            if filename:
                self.filename = filename
            if pagesize:
                self.pagesize = pagesize
            if dest_box:
                self.dest_box = dest_box
            if dest_box_units:
                self.dest_box_units = dest_box_units

            if pdf_canvas is None:
                pdf_canvas = self._create_new_canvas()

            GraphicsContext.__init__(self, pdf_canvas)

        def add_page(self):
            """Adds a new page to the PDF canvas and makes that the current
            drawing target.
            """
            if self.gc is None:
                warnings.warn("PDF Canvas has not been created yet.")
                return

            # Add the new page
            self.gc.showPage()

            # We'll need to call _initialize_page() before drawing
            self._page_initialized = False

        def render_component(
            self,
            component,
            container_coords=False,
            halign="center",
            valign="top",
        ):
            """Erases the current contents of the graphics context and renders
            the given component at the maximum possible scaling while
            preserving aspect ratio.

            Parameters
            ----------
            component : Component
                The component to be rendered.
            container_coords : Boolean
                Whether to use coordinates of the component's container
            halign : "center", "left", "right"
                Determines the position of the component if it is narrower than
                the graphics context area (after scaling)
            valign : "center", "top", "bottom"
                Determiens the position of the component if it is shorter than
                the graphics context area (after scaling)

            Description
            -----------
            If *container_coords* is False, then the (0,0) coordinate of this
            graphics context corresponds to the lower-left corner of the
            component's **outer_bounds**. If *container_coords* is True, then
            the method draws the component as it appears inside its container,
            i.e., it treats (0, 0) of the graphics context as the lower-left
            corner of the container's outer bounds.
            """

            if not self._page_initialized:
                # Make sure the origin is set up as before.
                self._initialize_page(self.gc)

            x, y = component.outer_position
            if container_coords:
                width, height = component.container.bounds
            else:
                x = -x
                y = -y
                width, height = component.outer_bounds

            # Compute the correct scaling to fit the component into the
            # available canvas space while preserving aspect ratio.
            units = UNITS_MAP[self.dest_box_units]
            pagesize = PAGE_SIZE_MAP[self.pagesize]

            full_page_width = pagesize[0]
            full_page_height = pagesize[1]
            page_offset_x = self.dest_box[0] * units
            page_offset_y = self.dest_box[1] * units
            page_width = self.dest_box[2] * units
            page_height = self.dest_box[3] * units

            if page_width < 0:
                page_width += full_page_width - page_offset_x
            if page_height < 0:
                page_height += full_page_height - page_offset_y

            aspect = float(width) / float(height)

            if aspect >= page_width / page_height:
                # We are limited in width, so use widths to compute the scale
                # factor between pixels to page units.  (We want square pixels,
                # so we re-use this scale for the vertical dimension.)
                scale = float(page_width) / float(width)
                trans_width = page_width

                trans_height = height * scale
                trans_x = x * scale
                trans_y = y * scale
                if valign == "top":
                    trans_y += page_height - trans_height
                elif valign == "center":
                    trans_y += (page_height - trans_height) / 2.0

            else:
                # We are limited in height
                scale = page_height / height
                trans_height = page_height

                trans_width = width * scale
                trans_x = x * scale
                trans_y = y * scale
                if halign == "right":
                    trans_x += page_width - trans_width
                elif halign == "center":
                    trans_x += (page_width - trans_width) / 2.0

            self.translate_ctm(trans_x, trans_y)
            self.scale_ctm(scale, scale)
            self.clip_to_rect(-x, -y, width, height)
            old_bb_setting = component.use_backbuffer
            component.use_backbuffer = False
            component.draw(self, view_bounds=(0, 0, width, height))
            component.use_backbuffer = old_bb_setting

        def save(self, filename=None):
            self.gc.save()

        def _create_new_canvas(self):
            """Create the PDF canvas context."""
            x, y, w, h = self._get_bounding_box()
            if w < 0 or h < 0:
                self.gc = None
                return

            pagesize = PAGE_SIZE_MAP[self.pagesize]
            gc = Canvas(filename=self.filename, pagesize=pagesize)
            self._initialize_page(gc)

            return gc

        def _get_bounding_box(self):
            """Compute the bounding rect of a page."""
            pagesize = PAGE_SIZE_MAP[self.pagesize]
            units = UNITS_MAP[self.dest_box_units]

            x = self.dest_box[0] * units
            y = self.dest_box[1] * units
            w = self.dest_box[2] * units
            h = self.dest_box[3] * units

            if w < 0:
                w += pagesize[0] * units * inch / PAGE_DPI
            if h < 0:
                h += pagesize[1] * units * inch / PAGE_DPI

            if w < 0 or h < 0:
                warnings.warn("Margins exceed page dimensions.")

            return x, y, w, h

        def _initialize_page(self, gc):
            """Make sure the origin is set to something consistent."""
            x, y, w, h = self._get_bounding_box()

            gc.translate(x, y)

            path = gc.beginPath()
            path.rect(0, 0, w, h)
            gc.clipPath(path, stroke=0, fill=0)

            self._page_initialized = True