CGI um PDF auf Serverseite zu rendern

Code-Stücke können hier veröffentlicht werden.
Antworten
BlackJack

Freitag 8. August 2014, 09:29

Ein CGI-Skript das mit Hilfe von ``pdfinfo`` (Xpdf oder poppler-utils) und ImageMagick Seiten aus PDF-Dateien auf dem Server rendert und als Bilder ausliefert:

Code: Alles auswählen

#!/usr/bin/env python
import cgi
import cgitb; cgitb.enable()
import os
from itertools import imap
from subprocess import check_output

PDFINFO = '/usr/bin/pdfinfo'
CONVERT = '/usr/bin/convert'
DOC_ROOT = '/home/bj/Documents'

BASE_TEMPLATE = (
    'Content-type: text/html\n\n'
    '<html><head><title>{title}</title></head><body>{body}</body></html>'
)
PDF_PAGE_TEMPLATE = (
    '<h1>{filename}</h1>'
    '<p>{prev_link} {page}/{page_count} {next_link}</p>'
    '<p><img src="{image_url}" style="border: solid thin gray;"></p>'
)

SCRIPT_NAME = os.environ['SCRIPT_NAME']


def create_page_url(filename, page_number, type_):
    return '{0}?file={1}&page={2}&type={3}'.format(
        cgi.escape(SCRIPT_NAME, True),
        cgi.escape(filename, True),
        page_number,
        type_
    )


def create_page_link(text, filename, page_number):
    text = cgi.escape(text)
    if page_number is None:
        return '<span style="color: gray;">{0}</span>'.format(text)
    else:
        return '<a href="{0}">{1}</a>'.format(
            create_page_url(filename, page_number, 'html'), text
        )


def get_page_count(filename):

    def parse_line(line):
        key, _, value = line.partition(':')
        return key, value.strip()

    info = dict(
        imap(parse_line, check_output([PDFINFO, filename]).splitlines())
    )
    return int(info['Pages'])


def get_page(filename, page_index):
    return check_output(
        [
            CONVERT,
            '-density', '96',
            '{0}[{1}]'.format(filename, page_index),
            'jpg:-'
        ]
    )


def send_error(message):
    print BASE_TEMPLATE.format(
        title='Error', body='<h1>Error</h1>{0}'.format(message)
    )


def send_page_html(_pdf_path, filename, page_number, page_count):
    body = PDF_PAGE_TEMPLATE.format(
        filename=cgi.escape(filename),
        page=page_number,
        page_count=page_count,
        image_url=create_page_url(filename, page_number, 'jpg'),
        prev_link=create_page_link(
            '<<', filename, page_number - 1 if page_number > 1 else None
        ),
        next_link=create_page_link(
            '>>',
            filename,
            page_number + 1 if page_number < page_count else None
        )
    )
    print BASE_TEMPLATE.format(title='PDF', body=body)


def send_page_image(pdf_path, _filename, page_number, _page_count):
    image_data = get_page(pdf_path, page_number - 1)
    print 'Content-type: image/jpg'
    print 'Content-Length:', len(image_data)
    print
    print image_data


TYPE2SEND_FUNCTION = {
    'html': send_page_html,
    'jpg': send_page_image,
}


def main():
    form = cgi.FieldStorage()
    filename = form.getfirst('file')
    page_number = int(form.getfirst('page', 1))
    type_ = form.getfirst('type', 'html')

    pdf_path = os.path.abspath(os.path.join(DOC_ROOT, filename))
    if os.path.exists(pdf_path) and pdf_path.startswith(DOC_ROOT):
        page_count = get_page_count(pdf_path)
        page_number = min(max(1, page_number), page_count)
        TYPE2SEND_FUNCTION[type_](pdf_path, filename, page_number, page_count)
    else:
        send_error(
            '<p>PDF file <em>{0!r}</em> not found.</p>'.format(
                cgi.escape(filename)
            )
        )


main()
Ist im Rahmen einer Antwort auf StackOverflow entstanden und hat noch Verbesserungspotential. Mit den Python-Anbindungen an `libpoppler` und `libcairo` könnte man sicher die Abhängigkeiten zu externen Programmen loswerden. Cachen der gerenderten Seiten wäre eventuell eine gute Idee. Und mit `libpoppler` könnte man wahrscheinlich auch HTML-Imagemaps für Links innerhalb einer PDF-Seite erstellen.
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Samstag 23. August 2014, 07:50

ABer warum nicht als WSGI-Programm? Dann kann man immer noch CGI via flup nutzen wenn man will, aber man hat mehr Freiheit wie man das nutzt.
My god, it's full of CARs! | Leonidasvoice vs Modvoice
BlackJack

Samstag 23. August 2014, 14:28

@Leonidas: Stackoverflow halt, der Fragesteller wollte auf keinen Fall von CGI abrücken. Ohne weitere Begründung. Ich schrob ja dass das Verbesserungspotential hat. :-)
Antworten