8/2/2014 - 2:57 PM

Markdown to PDF Conversion for Pythonista

Markdown to PDF Conversion for Pythonista

# Markdown to PDF Conversion
# This script converts Markdown markup to PDF directly on your iOS device.
# Please see the "Requirements" section in the docstring below for required 
# dependencies and the "Installation" section for instructions on how to
# install them.
# Run this script directly or put it into the Pythonista editor action menu 
# with one of the following options as the first and sole argument:
# -e     Use the text of the current editor buffer as input
# -c     Use the file currently opened in the editor as input
# These two options will use different input if the current editor buffer
# contains unsaved changes.
# You can also pass the path of a file to use as input directly as a command
# line argument. Called without arguments, the script will present a dialog
# where you can choose either to convert the text from the clipboard, from
# a URL or the README. 
"""Markdown to PDF Conversion

**Markdown** is a plain text formatting language invented by [John Gruber] [1].


*Yes, it is possible:* convert Markdown markup text to PDF files on iOS with
Python - without the use of external apps or a web service. The conversion is
done *purely* in Python, which means it can be done *offline* and without
requiring additional paid apps (you need [Pythonista] [2], of course).

The Markdown text is first converted to HTML with [markdown2] [4] and then its
HTML output is converted to PDF with [xhtml2pdf] [4] and written to a file with
the given name.


Here is the most basic code to convert some Markdown text:

    from markdown2pdf import markdown2pdf
    markdown2pdf('This is a *test*', 'test.pdf')

The Markdown to HTML conversion supports standard Markdown syntax with the
following extras enabled by default (see the [markdown2] [3] web site for
more information on these extras):

* fenced-code-blocks
* footnotes
* metadata
* pyshell
* smarty-pants
* tag-friendly
* wiki-tables

To use additional extras or disable some, pass a list of extra names via the
`extras` keyword argument, which will override the default extras list:

    markdown2pdf(text, 'test.pdf', extras=['code-friendly', 'smarty-pants'])

The appearance and layout of the PDF output is determined by the default CSS
definitions used by `xhtml2pdf`. You can overwrite the default CSS by passing
in a string of CSS definitions via the `css` keyword argument. If you only
want to change certain styles, you can import the default CSS from `xhtml2pdf`
and add to it:

    from xhtml2pdf.default import DEFAULT_CSS

    DEFAULT_CSS += \"""
    html {
        font-family: Arial, Helvetica, sans-serif;
        font-size: 12pt;

    markdown2pdf(text, 'test.pdf', css=DEFAULT_CSS)

See the [xhtml2pdf usage guide] [5] for more information on the supported CSS
properties and page layout directives.


The `markdown2pdf` module requires the following Python packages:

* [markdown2] [3] \*
* [html5lib]( \*
* PIL or [Pillow]( (optional) \*
* [PyPDF2]( (optional)
* [ReportLab](
* [xhtml2pdf] [4]

Packages marked with an asterisk are already included with Pythonista. All
these packages, except PIL / Pillow, are pure-Python code or the included
C extensions are optional (ReportLab).


I have created a bundle of all the above libraries, which are not already
included in Pythonista, as a Zip archive. You have to extract this archive into
the `site-packages` sub-directory of your Pythonista document folder. You can
use the following code in the Pythonista console to download and extract the
Zip archive:

    import os, requests, zipfile

    ZIPFN = ''
    ZIPURL = '' + ZIPFN

    with open(ZIPFN, 'wb') as f:

    with zipfile.ZipFile(ZIPFN) as z:


You can also download a version of the above script, which also checks the
integrity of the downloaded Zip file, from this Gist:




__author__  = 'Christopher Arndt'
__version__ = '1.1'

import argparse
import logging
import os
import sys
import tempfile

from os.path import basename, join, splitext

import console
import editor

from markdown2pdf import markdown2pdf

def make_pdf_filename(fn):
    return splitext(basename(fn))[0] + '.pdf'

def main(args=None):
    ap = argparse.ArgumentParser()
    ap.add_argument('-c', '--current-file', action='store_true',
        help='Use file currently opened in editor as input')
    ap.add_argument('-e', '--edit-buffer', action='store_true',
        help='Use content of current editor buffer as input')
    ap.add_argument('infile', nargs='?', help='Input file name')
    args = ap.parse_args(args if args is not None else sys.argv[1:])

    if args.edit_buffer or args.current_file:
        pdf_bn = make_pdf_filename(editor.get_path())

    if args.current_file:
        with open(editor.get_path()) as f:
            md =
    elif args.edit_buffer:
        md = editor.get_text()
    elif args.infile:
        pfd_bn = make_pdf_filename(args.infile)
        with open(args.infile) as f:
            md =
        pdf_bn = 'markdown2pdf.pdf'

            choice = console.alert('Markdown to PDF', '',
                'Show README', 'Convert Clipboard', 'Convert URL')
        except KeyboardInterrupt:

        if choice == 1:
            md = __doc__
        elif choice == 2:
            import clipboard
            md = clipboard.get()
        elif choice == 3:
            import re
            import clipboard
                cb = clipboard.get().strip()
                if not'^(ht|f)tps?://', cb):
                    cb = ''
                url = console.input_alert('Enter URL', 'Download Markdown from URL:',
                    cb, 'Download')
            except KeyboardInterrupt:
                import urllib2
                import urlparse
                    r = urllib2.urlopen(url)
                except urllib2.URLError as exc:
                    console.hud_alert("Download error (see console)", 'error')
                    md =

                url = urlparse.urlparse(r.geturl())
                fn = make_pdf_filename(url.path)
                if fn:
                    pdf_bn = fn

    if not md:

    tempdir = tempfile.mkdtemp()
    pdf_path = join(tempdir, pdf_bn)
    status = markdown2pdf(md, pdf_path)

        choice = console.alert('Select Ouput', '',
            'Save to file...', 'Open in...', 'View')
    except KeyboardInterrupt:

    if choice == 1:
            filename = console.input_alert("Filename",
                "Enter PDF output filename\n(will overwrite existing files!):",
                pdf_bn, 'Save')
            os.rename(pdf_path, filename)
        except KeyboardInterrupt:
        except (IOError, OSError) as exc:
            console.alert("Error", "Error writing PDF file:\n\n%s" % exc)
            return 1
    elif choice == 2:
    elif choice == 3:

    except: pass

if __name__ == '__main__':