| name | pdf-calendar-parsing |
| description | Use this skill when you need to extract calendar/schedule information from a PDF file, including measuring visual positions of appointment blocks, identifying colors of blocks, and determining time slots based on grid lines. Specifically useful for parsing visual calendars where time is encoded by position relative to horizontal grid lines. |
PDF Calendar Parsing
Approach Overview
To extract schedule data from a visual PDF calendar, you need to:
- Convert the PDF to an image or parse its vector/text content
- Identify the time axis and grid lines
- Detect colored blocks representing appointments
- Map pixel positions to actual times
Tools and Libraries
Python libraries:
pdfplumber — extracts text, lines, and rectangles from PDFs with precise coordinates
PyMuPDF (fitz) — renders PDF pages to images, extracts drawings and text with positions
pdf2image + Pillow — converts PDF to images for pixel-level analysis
tabula-py or camelot — for table extraction (less useful for visual calendars)
Extracting Horizontal Lines with pdfplumber
import pdfplumber
with pdfplumber.open("/root/calendar.pdf") as pdf:
page = pdf.pages[0]
lines = page.lines
horizontal_lines = [l for l in lines if abs(l['top'] - l['bottom']) < 2]
horizontal_lines.sort(key=lambda l: l['top'])
Extracting Rectangles/Blocks with pdfplumber
rects = page.rects
for rect in rects:
print(rect)
Extracting Colored Blocks with PyMuPDF
import fitz
doc = fitz.open("/root/calendar.pdf")
page = doc[0]
drawings = page.get_drawings()
for d in drawings:
for item in d["items"]:
pass
fill_color = d.get("fill")
rect = d.get("rect")
Identifying Blue Blocks
Blue blocks typically have RGB fill values where:
- Blue channel is dominant (close to 1.0)
- Red and Green channels are low
def is_blue(color):
if color is None:
return False
r, g, b = color[:3]
return b > 0.5 and r < 0.5 and g < 0.5
Mapping Positions to Times
Given that the space between two adjacent horizontal lines = 15 minutes:
line_positions = sorted(set(l['top'] for l in horizontal_lines))
start_time = datetime.strptime("08:00 AM", "%I:%M %p")
def y_to_time(y, line_positions, start_time):
"""Convert a y-coordinate to a time based on grid lines."""
from datetime import timedelta
for i in range(len(line_positions) - 1):
if line_positions[i] <= y <= line_positions[i + 1]:
frac = (y - line_positions[i]) / (line_positions[i + 1] - line_positions[i])
minutes = (i + frac) * 15
return start_time + timedelta(minutes=minutes)
idx = len(line_positions) - 1
extra = (y - line_positions[-1]) / (line_positions[1] - line_positions[0])
minutes = (idx + extra) * 15
return start_time + timedelta(minutes=minutes)
Extracting Text Labels
words = page.extract_words()
for w in words:
print(w['text'], w['top'], w['x0'])
Determining Day Columns
If the calendar has multiple days (columns):
- Extract day/date headers from text at the top
- Identify column boundaries from vertical lines or header positions
- Map each block's x-position to the appropriate day column
vertical_lines = [l for l in lines if abs(l['x0'] - l['x1']) < 2]
vertical_lines.sort(key=lambda l: l['x0'])
column_boundaries = [l['x0'] for l in vertical_lines]
def x_to_day(x, column_boundaries, day_labels):
for i in range(len(column_boundaries) - 1):
if column_boundaries[i] <= x < column_boundaries[i + 1]:
return day_labels[i]
return day_labels[-1]
Image-Based Color Detection (Fallback)
If vector extraction doesn't yield colors reliably:
from pdf2image import convert_from_path
from PIL import Image
import numpy as np
images = convert_from_path("/root/calendar.pdf", dpi=200)
img = np.array(images[0])
blue_mask = (img[:,:,2] > 150) & (img[:,:,0] < 100) & (img[:,:,1] < 100)
Tips
- PDF coordinate systems typically have origin at bottom-left, but
pdfplumber uses top-left
- Always verify coordinate system by cross-referencing text positions with known labels
- Account for small floating-point differences when comparing positions
- Group nearby horizontal lines that might be duplicates (borders vs grid lines)