Python library for building print-ready PDFs by layering transparent PNGs and JPGs. Supports image cropping, page fills, lines for crop marks, and region copying for tiling cards across a sheet. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5.2 KiB
Spork — Usage Guide
Spork is a Python library for building print-ready PDFs by layering images together. It was designed for compositing trading cards and tiling them onto pages for printing.
Requirements
pip install reportlab Pillow
Quick Start
from spork import *
outfile = NewPDF(8.5, 11)
art = ReadPNG("mushroom.png")
frame = ReadPNG("PictureFrame.png")
outfile.layer(art, 0.5, 0.5, 3.5, 3.5)
outfile.layer(frame, 0.5, 0.5, 3.5, 3.5)
outfile.save("output.pdf")
All coordinates and dimensions are in inches. The origin (0, 0) is the top-left corner of the page.
Loading Images
img = ReadPNG("card_art.png")
img = ReadJPG("photo.jpg")
Both return a LoadedImage that can be cropped before layering.
Cropping Images
All crop methods modify the image in place and return self, so they can be chained.
Remove pixels from an edge
img.CropPixelsL(100) # remove 100px from the left
img.CropPixelsR(50) # remove 50px from the right
img.CropPixelsT(80) # remove 80px from the top
img.CropPixelsB(30) # remove 30px from the bottom
Keep only pixels from an edge
img.KeepPixelsT(500) # keep the top 500px, discard the rest
img.KeepPixelsB(500) # keep the bottom 500px
img.KeepPixelsL(500) # keep the left 500px
img.KeepPixelsR(500) # keep the right 500px
Make it square
img.CropSquare() # trim the longer dimension equally from both sides
img.KeepSquare(400) # keep only the center 400x400 pixels
Chaining
art = ReadPNG("wide_photo.png").CropPixelsL(100).CropPixelsR(100).CropSquare()
Creating a PDF
outfile = NewPDF(8.5, 11) # width, height in inches (this is US Letter)
layer(img, x, y, w, h)
Place an image on the page. Images are scaled to fit the given width and height. Layers stack in the order they are added — later layers draw on top of earlier ones.
outfile.layer(background, 0, 0, 8.5, 11) # full-page background
outfile.layer(art, 0.25, 0.25, 2.0, 2.8) # card art
outfile.layer(frame, 0.25, 0.25, 2.0, 2.8) # transparent frame on top
PNG transparency is preserved — this is how you composite a card from separate art and frame layers.
fill(color)
Fill the entire page with a solid color. Typically used first, as a background.
outfile.fill(BLACK)
line(color, x0, y0, x1, y1, width=1)
Draw a straight line between two points. The width parameter is in pixels (at 300 DPI, so 1px is a hairline). Useful for crop marks.
outfile.line(BLACK, 0.25, 0.0, 0.25, 0.15) # vertical crop mark
outfile.line(BLACK, 0.0, 0.25, 0.15, 0.25) # horizontal crop mark
outfile.line(RED, 0, 4.0, 8.5, 4.0, width=3) # thicker red guide line
copy(x0, y0, x1, y1, x2, y2)
Copy a rectangular region of the page and paste it at a new position. The rectangle from (x0, y0) to (x1, y1) is pasted with its top-left corner at (x2, y2).
This is how you tile a card across a page — compose it once, then copy it to fill the sheet.
# Compose a 2.5 x 3.5 inch card at (0.25, 0.25)
outfile.layer(art, 0.25, 0.25, 2.5, 3.5)
outfile.layer(frame, 0.25, 0.25, 2.5, 3.5)
# Tile it: 3 columns x 3 rows
cw = 2.5 # card width
ch = 3.5 # card height
for row in range(3):
for col in range(3):
if row == 0 and col == 0:
continue # skip the original
outfile.copy(0.25, 0.25, 0.25 + cw, 0.25 + ch,
0.25 + col * cw, 0.25 + row * ch)
save(filename)
Render everything and write the PDF. Output is 300 DPI.
outfile.save("cards.pdf")
Colors
Built-in color constants (all are (R, G, B) tuples):
| Name | Value |
|---|---|
BLACK |
(0, 0, 0) |
WHITE |
(255, 255, 255) |
RED |
(255, 0, 0) |
GREEN |
(0, 128, 0) |
BLUE |
(0, 0, 255) |
YELLOW |
(255, 255, 0) |
CYAN |
(0, 255, 255) |
MAGENTA |
(255, 0, 255) |
GRAY |
(128, 128, 128) |
LIGHT_GRAY |
(192, 192, 192) |
DARK_GRAY |
(64, 64, 64) |
You can also pass any (R, G, B) tuple directly:
outfile.fill((30, 30, 30))
outfile.line((255, 128, 0), 0, 0, 8.5, 11) # orange diagonal
Full Example: Trading Card Sheet
from spork import *
# Load and prep the art
art = ReadJPG("dragon.jpg").CropSquare()
frame = ReadPNG("card_frame.png")
# Create a letter-size page
page = NewPDF(8.5, 11)
page.fill(WHITE)
# Compose one card at top-left (standard poker size: 2.5 x 3.5)
page.layer(art, 0.25, 0.25, 2.5, 3.5)
page.layer(frame, 0.25, 0.25, 2.5, 3.5)
# Tile into a 3x3 grid
cw, ch = 2.5, 3.5
for row in range(3):
for col in range(3):
if row == 0 and col == 0:
continue
page.copy(0.25, 0.25, 0.25 + cw, 0.25 + ch,
0.25 + col * cw, 0.25 + row * ch)
# Add crop marks at each card corner
for row in range(4):
for col in range(4):
x = 0.25 + col * cw
y = 0.25 + row * ch
page.line(BLACK, x, y - 0.15, x, y + 0.15)
page.line(BLACK, x - 0.15, y, x + 0.15, y)
page.save("dragon_sheet.pdf")