Создание печатного календаря с Python - PullRequest
4 голосов
/ 02 сентября 2011

Как я могу создать файл PDF для печати (размером с букву США), чтобы каждая страница представляла месяц и была разделена таким образом, чтобы каждый день месяца получал коробку одинакового размера? Что если я хочу пропустить выходные и просто отобразить будние дни?

Какие модули Python я бы использовал для достижения следующих целей? :

  1. Создание изображения с разрешением буквы США
  2. Итерация по каждому дню месяца с возможностью пропуска определенных дней (например, всех выходных)
  3. Разделение изображения таким образом, чтобы каждый день месяца указывался в рамке фиксированного размера
  4. Повторение шагов 2-3 для всех месяцев данного года
  5. Создание PDF в качестве вывода

Ответы [ 3 ]

7 голосов
/ 15 сентября 2011

Вы можете сделать это с 3 пакетами.Reportlab для создания PDF, календарь для получения месяца в виде списков списков и привязка Python для Ghostscript для преобразования PDF в PNG.

Вы начнете получать данные спакет календаря, использующий Reportlab для создания страницы с размером букв США.Таблицей можно манипулировать, чтобы иметь сетку, иметь каждую ячейку в блоке одинакового размера и изменять шрифт, размер и выравнивание текста.

Вы можете оставить это при этом, если вы просто хотите PDF, илиВы можете преобразовать этот PDF-файл в изображение, используя привязки Python Ghostscript.Или, если хотите, вы можете просто запустить 'Ghostscript', используя system ('gs ...').Также Ghostscript должен быть установлен для работы пакета Python 'Ghostscript.

Если вы хотите отфильтровать все выходные, вы можете использовать для этого старые добрые манипуляции со списком в календарных данных.

Вот пример того, как вы могли бы создать PDF.Я не собираюсь делать целый год, только один месяц, и я не собираюсь фильтровать нули.

from reportlab.lib.units import inch
from reportlab.lib import colors
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle
from reportlab.graphics.shapes import Drawing

import calendar

doc = SimpleDocTemplate('calendar.pdf', pagesize=letter)

cal = [['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']]
cal.extend(calendar.monthcalendar(2011,9))


table = Table(cal, 7*[inch], len(cal) * [inch])

table.setStyle(TableStyle([
        ('FONT', (0, 0), (-1, -1), 'Helvetica'),
        ('FONT', (0, 0), (-1, 0), 'Helvetica-Bold'),
        ('FONTSIZE', (0, 0), (-1, -1), 8),
        ('INNERGRID', (0, 0), (-1, -1), 0.25, colors.black),
        ('BOX', (0, 0), (-1, -1), 0.25, colors.green),
        ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
        ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
    ]))

#create the pdf with this
doc.build([table])

Если вы хотите, чтобы другая страница добавила PageBreak (), а затем следующуюкалендарь в список передается в doc.build ().PageBreak является частью reportlab.platypus.

И для преобразования pdf в png

import ghostscript

args = ['gs', #it seems not to matter what is put in 1st position
        '-dSAFER',
        '-dBATCH',
        '-dNOPAUSE',
        '-sDEVICE=png16m',
        '-r100',
        '-sOutputFile=calendar.png',
        'calendar.pdf']

ghostscript.Ghostscript(*args)

Пакеты reportlab и ghostscript доступны посредством использования pip.Я создал это в среде virtualenv.

ReportLab http://www.reportlab.com/software/opensource/rl-toolkit/

Привязки Python Ghostscript https://bitbucket.org/htgoebel/python-ghostscript

Календарь является частью стандартной библиотеки Python.

2 голосов
/ 25 мая 2016

Для тех, кто заходит в Google, сотрудник по имени Билл Милл написал модуль общественного достояния , который упрощает создание календаря с использованием reportlab, как текст этого примера.

from pdf_calendar import createCalendar
#create a December, 2005 PDF
c = createCalendar(12, 2005, filename="blog_calendar.pdf")
#now add January, 2006 to the end
createCalendar(1, 2006, canvas=c)
c.save()

Есть также пример выходных данных по предоставленной мною ссылке, и, хотя он простой и спартанский, он выглядит прилично (аналогично тому, что вы получаете из таких вещей, как скрипт "make calendar" для Scribus) и станет отличной отправной точкой для будущие улучшения.

Полный код:

#!/usr/bin/env python
"""Create a PDF calendar.

This script requires Python and Reportlab
( http://reportlab.org/rl_toolkit.html ). Tested only with Python 2.4 and
Reportlab 1.2.

See bottom of file for an example of usage. No command-line interface has been
added, but it would be trivial to do so. Furthermore, this script is pretty
hacky, and could use some refactoring, but it works for what it's intended
to do.

Created by Bill Mill on 11/16/05, this script is in the public domain. There
are no express warranties, so if you mess stuff up with this script, it's not
my fault.

If you have questions or comments or bugfixes or flames, please drop me a line 
at bill.mill@gmail.com .
"""
from reportlab.lib import pagesizes
from reportlab.pdfgen.canvas import Canvas
import calendar, time, datetime
from math import floor

NOW = datetime.datetime.now()
SIZE = pagesizes.landscape(pagesizes.letter)

class NoCanvasError(Exception): pass

def nonzero(row):
    return len([x for x in row if x!=0])

def createCalendar(month, year=NOW.year, canvas=None, filename=None, \
                   size=SIZE):
    """
    Create a one-month pdf calendar, and return the canvas

    month: can be an integer (1=Jan, 12=Dec) or a month abbreviation (Jan, Feb,
            etc.
    year: year in which month falls. Defaults to current year.
    canvas: you may pass in a canvas to add a calendar page to the end.
    filename: String containing the file to write the calendar to
    size: size, in points of the canvas to write on
    """
    if type(month) == type(''):
        month = time.strptime(month, "%b")[1]
    if canvas is None and filename is not None:
        canvas = Canvas(filename, size)
    elif canvas is None and filename is None:
        raise NoCanvasError
    monthname = time.strftime("%B", time.strptime(str(month), "%m"))
    cal = calendar.monthcalendar(year, month)

    width, height = size

    #draw the month title
    title = monthname + ' ' + str(year)
    canvas.drawCentredString(width / 2, height - 27, title)
    height = height - 40

    #margins
    wmar, hmar = width/50, height/50

    #set up constants
    width, height = width - (2*wmar), height - (2*hmar)
    rows, cols = len(cal), 7
    lastweek = nonzero(cal[-1])
    firstweek = nonzero(cal[0])
    weeks = len(cal)
    rowheight = floor(height / rows)
    boxwidth = floor(width/7)

    #draw the bottom line
    canvas.line(wmar, hmar, wmar+(boxwidth*lastweek), hmar)
    #now, for all complete rows, draw the bottom line
    for row in range(1, len(cal[1:-1]) + 1):
        y = hmar + (row * rowheight)
        canvas.line(wmar, y, wmar + (boxwidth * 7), y)
    #now draw the top line of the first full row
    y = hmar + ((rows-1) * rowheight)
    canvas.line(wmar, y, wmar + (boxwidth * 7), y)
    #and, then the top line of the first row
    startx = wmar + (boxwidth * (7-firstweek))
    endx = startx + (boxwidth * firstweek)
    y = y + rowheight
    canvas.line(startx, y, endx, y)

    #now draw the vert lines
    for col in range(8):
        #1 = don't draw line to first or last; 0 = do draw
        last, first = 1, 1
        if col <= lastweek: last = 0
        if col >= 7 - firstweek: first = 0
        x = wmar + (col * boxwidth)
        starty = hmar + (last * rowheight)
        endy = hmar + (rows * rowheight) - (first * rowheight)
        canvas.line(x, starty, x, endy)

    #now fill in the day numbers and any data
    x = wmar + 6
    y = hmar + (rows * rowheight) - 15
    for week in cal:
        for day in week:
            if day:
                canvas.drawString(x, y, str(day))
            x = x + boxwidth
        y = y - rowheight
        x = wmar + 6

    #finish this page
    canvas.showPage()

    return canvas

if __name__ == "__main__":
    #create a December, 2005 PDF
    c = createCalendar(12, 2005, filename="blog_calendar.pdf")
    #now add January, 2006 to the end
    createCalendar(1, 2006, canvas=c)
    c.save()

РЕДАКТИРОВАТЬ 2017-11-25: Мне пришлось реорганизовать это для собственного использования, поэтому я решил поделиться этим здесь. Самая новая версия всегда будет в этом GitHub Gist , но ниже я включаю последнюю ревизию, прежде чем она получит зависимость от PyEphem для вычисления таких вещей, как фазы луны:

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""Generate a printable calendar in PDF format, suitable for embedding
into another document.

Tested with Python 2.7.

Dependencies:
- Python
- Reportlab

Resources Used:
- https://stackoverflow.com/a/37443801/435253
  (Originally present at http://billmill.org/calendar )
- https://www.reportlab.com/docs/reportlab-userguide.pdf

Originally created by Bill Mill on 11/16/05, this script is in the public
domain. There are no express warranties, so if you mess stuff up with this
script, it's not my fault.

Refactored and improved 2017-11-23 by Stephan Sokolow (http://ssokolow.com/).

TODO:
- Implement diagonal/overlapped cells for months which touch six weeks to avoid
  wasting space on six rows.
"""

from __future__ import (absolute_import, division, print_function,
                        with_statement, unicode_literals)

__author__ = "Bill Mill; Stephan Sokolow (deitarion/SSokolow)"
__license__ = "CC0-1.0"  # https://creativecommons.org/publicdomain/zero/1.0/

import calendar, collections, datetime
from contextlib import contextmanager

from reportlab.lib import pagesizes
from reportlab.pdfgen.canvas import Canvas

# Supporting languages like French should be as simple as editing this
ORDINALS = {
    1: 'st', 2: 'nd', 3: 'rd',
    21: 'st', 22: 'nd', 23: 'rd',
    31: 'st',
    None: 'th'}

# Something to help make code more readable
Font = collections.namedtuple('Font', ['name', 'size'])
Geom = collections.namedtuple('Geom', ['x', 'y', 'width', 'height'])
Size = collections.namedtuple('Size', ['width', 'height'])

@contextmanager
def save_state(canvas):
    """Simple context manager to tidy up saving and restoring canvas state"""
    canvas.saveState()
    yield
    canvas.restoreState()

def add_calendar_page(canvas, rect, datetime_obj, cell_cb,
                      first_weekday=calendar.SUNDAY):
    """Create a one-month pdf calendar, and return the canvas

    @param rect: A C{Geom} or 4-item iterable of floats defining the shape of
        the calendar in points with any margins already applied.
    @param datetime_obj: A Python C{datetime} object specifying the month
        the calendar should represent.
    @param cell_cb: A callback taking (canvas, day, rect, font) as arguments
        which will be called to render each cell.
        (C{day} will be 0 for empty cells.)

    @type canvas: C{reportlab.pdfgen.canvas.Canvas}
    @type rect: C{Geom}
    @type cell_cb: C{function(Canvas, int, Geom, Font)}
    """
    calendar.setfirstweekday(first_weekday)
    cal = calendar.monthcalendar(datetime_obj.year, datetime_obj.month)
    rect = Geom(*rect)

    # set up constants
    scale_factor = min(rect.width, rect.height)
    line_width = scale_factor * 0.0025
    font = Font('Helvetica', scale_factor * 0.028)
    rows = len(cal)

    # Leave room for the stroke width around the outermost cells
    rect = Geom(rect.x + line_width,
                rect.y + line_width,
                rect.width - (line_width * 2),
                rect.height - (line_width * 2))
    cellsize = Size(rect.width / 7, rect.height / rows)

    # now fill in the day numbers and any data
    for row, week in enumerate(cal):
        for col, day in enumerate(week):
            # Give each call to cell_cb a known canvas state
            with save_state(canvas):

                # Set reasonable default drawing parameters
                canvas.setFont(*font)
                canvas.setLineWidth(line_width)


                cell_cb(canvas, day, Geom(
                    x=rect.x + (cellsize.width * col),
                    y=rect.y + ((rows - row) * cellsize.height),
                    width=cellsize.width, height=cellsize.height),
                    font, scale_factor)

    # finish this page
    canvas.showPage()
    return canvas

def draw_cell(canvas, day, rect, font, scale_factor):
    """Draw a calendar cell with the given characteristics

    @param day: The date in the range 0 to 31.
    @param rect: A Geom(x, y, width, height) tuple defining the shape of the
        cell in points.
    @param scale_factor: A number which can be used to calculate sizes which
        will remain proportional to the size of the entire calendar.
        (Currently the length of the shortest side of the full calendar)

    @type rect: C{Geom}
    @type font: C{Font}
    @type scale_factor: C{float}
    """
    # Skip drawing cells that don't correspond to a date in this month
    if not day:
        return

    margin = Size(font.size * 0.5, font.size * 1.3)

    # Draw the cell border
    canvas.rect(rect.x, rect.y - rect.height, rect.width, rect.height)

    day = str(day)
    ordinal_str = ORDINALS.get(int(day), ORDINALS[None])

    # Draw the number
    text_x = rect.x + margin.width
    text_y = rect.y - margin.height
    canvas.drawString(text_x, text_y, day)

    # Draw the lifted ordinal number suffix
    number_width = canvas.stringWidth(day, font.name, font.size)
    canvas.drawString(text_x + number_width,
                      text_y + (margin.height * 0.1),
                      ordinal_str)

def generate_pdf(datetime_obj, outfile, size, first_weekday=calendar.SUNDAY):
    """Helper to apply add_calendar_page to save a ready-to-print file to disk.

    @param datetime_obj: A Python C{datetime} object specifying the month
        the calendar should represent.
    @param outfile: The path to which to write the PDF file.
    @param size: A (width, height) tuple (specified in points) representing
        the target page size.
    """
    size = Size(*size)
    canvas = Canvas(outfile, size)

    # margins
    wmar, hmar = size.width / 50, size.height / 50
    size = Size(size.width - (2 * wmar), size.height - (2 * hmar))

    add_calendar_page(canvas,
                      Geom(wmar, hmar, size.width, size.height),
                      datetime_obj, draw_cell, first_weekday).save()

if __name__ == "__main__":
    generate_pdf(datetime.datetime.now(), 'calendar.pdf',
                 pagesizes.landscape(pagesizes.letter))

Реорганизованный код обладает следующими преимуществами:

  • Функция рисования календаря не рисует ничего, кроме самих полей без полей, поэтому она полезна для встраивания вывода в более крупные создания.
  • Код для отрисовки отдельных ячеек был преобразован в обратный вызов, который каждый раз получает только что обновленное состояние холста.
  • Теперь все это хорошо задокументировано. (Правда, в разметке ePydoc я еще не запускал ePyDoc)
  • Код для рисования верхних порядковых суффиксов на числах
  • PEP-8-совместимый стиль кода и правильные метаданные.
1 голос
/ 02 сентября 2011

Некоторое время назад у меня была похожая проблема - я использовал отличную утилиту для работы с компьютером. Это не Python, но даже будучи фанатом Python, я обнаружил серьезные ограничения на получение надежных печатаемых PDF-файлов из Python - мой LaTeX был недостаточно хорош

http://www.itmanagerscookbook.com/Workstation/power-user/calendar.html

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...