Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix PDF clipping and add an add_page() method. #109

Merged
merged 3 commits into from May 21, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
120 changes: 79 additions & 41 deletions chaco/pdf_graphics_context.py
@@ -1,5 +1,4 @@


# Major library imports
import warnings

Expand All @@ -15,19 +14,22 @@

from kiva.pdf import GraphicsContext


PAGE_DPI = 72.0

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

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

if Canvas is not None:
class PdfPlotGraphicsContext(GraphicsContext):
Expand All @@ -42,18 +44,18 @@ class PdfPlotGraphicsContext(GraphicsContext):
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;
# 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.
# 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:
Expand All @@ -65,17 +67,30 @@ def __init__(self, pdf_canvas=None, filename=None, pagesize=None,
if dest_box_units:
self.dest_box_units = dest_box_units

if pdf_canvas == None:
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.
""" Erases the current contents of the graphics context and renders
the given component at the maximum possible scaling while
preserving aspect ratio.

Parameters
----------
Expand All @@ -84,22 +99,26 @@ def render_component(self, component, container_coords=False,
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)
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)
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.
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
Expand All @@ -108,8 +127,8 @@ def render_component(self, component, container_coords=False,
y = -y
width, height = component.outer_bounds

# Compute the correct scaling to fit the component into the available
# canvas space while preserving aspect ratio.
# 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]

Expand Down Expand Up @@ -157,7 +176,7 @@ def render_component(self, component, container_coords=False,

self.translate_ctm(trans_x, trans_y)
self.scale_ctm(scale, scale)
self.clip_to_rect(0, 0, width, height)
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))
Expand All @@ -168,30 +187,49 @@ 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]
units = UNITS_MAP[self.dest_box_units]
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]

width = pagesize[0] * units * inch / 72.0
height = pagesize[1] * units * inch / 72.0
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 += width
w += pagesize[0] * units * inch / PAGE_DPI
if h < 0:
h += height
h += pagesize[1] * units * inch / PAGE_DPI

if w < 0 or h < 0:
warnings.warn("Margins exceed page dimensions.")
self.gc = None
return

gc.translate(x,y)
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)
return gc

self._page_initialized = True
37 changes: 25 additions & 12 deletions examples/demo/noninteractive.py
Expand Up @@ -4,14 +4,14 @@
file on disk.
"""
# Standard library imports
import os, sys
import os
import sys

# Major library imports
from numpy import fabs, linspace, pi, sin
from numpy import linspace
from scipy.special import jn

# Enthought library imports
from traits.api import false
from traits.etsconfig.api import ETSConfig

# Chaco imports
Expand All @@ -24,6 +24,7 @@
# with the GraphicsContext's CTM.
dpi_scale = DPI / 72.0


def create_plot():
numpoints = 100
low = -5
Expand All @@ -32,17 +33,18 @@ def create_plot():
pd = ArrayPlotData(index=x)
p = Plot(pd, bgcolor="oldlace", padding=50, border_visible=True)
for i in range(10):
pd.set_data("y" + str(i), jn(i,x))
pd.set_data("y" + str(i), jn(i, x))
p.plot(("index", "y" + str(i)), color=tuple(COLOR_PALETTE[i]),
width = 2.0 * dpi_scale)
width=2.0 * dpi_scale)
p.x_grid.visible = True
p.x_grid.line_width *= dpi_scale
p.y_grid.visible = True
p.y_grid.line_width *= dpi_scale
p.legend.visible = True
return p

def draw_plot(filename, size=(800,600)):

def draw_plot(filename, size=(800, 600)):
container = create_plot()
container.outer_bounds = list(size)
container.do_layout(force=True)
Expand All @@ -51,7 +53,8 @@ def draw_plot(filename, size=(800,600)):
gc.save(filename)
return

def draw_svg(filename, size=(800,600)):

def draw_svg(filename, size=(800, 600)):
from chaco.svg_graphics_context import SVGGraphicsContext
container = create_plot()
container.bounds = list(size)
Expand All @@ -60,15 +63,25 @@ def draw_svg(filename, size=(800,600)):
gc.render_component(container)
gc.save(filename)

def draw_pdf(filename, size=(800,600)):

def draw_pdf(filename, size=(800, 600)):
from chaco.pdf_graphics_context import PdfPlotGraphicsContext
container = create_plot()
container.bounds = list(size)
container.outer_bounds = list(size)
container.do_layout(force=True)
gc = PdfPlotGraphicsContext(filename=filename, dest_box = (0.5, 0.5, 5.0, 5.0))
gc.render_component(container)
gc = PdfPlotGraphicsContext(filename=filename,
dest_box=(0.5, 0.5, 5.0, 5.0))

for i in range(2):
# draw the plot
gc.render_component(container)

#Start a new page for subsequent draw commands.
gc.add_page()

gc.save()


def get_directory(filename):
print 'Please enter a path in which to place generated plots.'
print 'Press <ENTER> to generate in the current directory.'
Expand All @@ -89,7 +102,7 @@ def get_directory(filename):
if __name__ == "__main__":
if ETSConfig.kiva_backend == 'svg':
# Render the plot as a SVG
draw_svg(get_directory('noninteractive.svg'), size=(800,600))
draw_svg(get_directory('noninteractive.svg'), size=(800, 600))
elif ETSConfig.kiva_backend == 'pdf':
# Render the plot as a PDF, requires on ReportLab
draw_pdf(get_directory('noninteractive.pdf'))
Expand Down