Repository URL to install this package:
Version:
6.0.0 ▾
|
chaco
/
pdf_graphics_context.py
|
---|
# (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