#! /usr/bin/env python
# -*- coding: utf-8 -*-

# Taken from https://github.com/baruchel/txt2pdf
# Published under MIT license

# Copyright (c) 2014 Thomas Baruchel
# Copyright (c) 2017 Fredrik de Vibe
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.

import argparse
import reportlab.lib.pagesizes
from reportlab.pdfgen.canvas import Canvas
from reportlab.lib import units
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
import re
import sys
import os


class Margins(object):
    def __init__(self, right, left, top, bottom):
        self._right = right
        self._left = left
        self._top = top
        self._bottom = bottom

    @property
    def right(self):
        return self._right * units.cm

    @property
    def left(self):
        return self._left * units.cm

    @property
    def top(self):
        return self._top * units.cm

    @property
    def bottom(self):
        return self._bottom * units.cm

    def adjustLeft(self, width):
        self._left -= width / units.cm


class PDFCreator(object):
    appName = "txt2pdf (version 1.0)"

    def __init__(self, args, margins):
        pageWidth, pageHeight = reportlab.lib.pagesizes.__dict__[args.media]
        if args.landscape:
            pageWidth, pageHeight = reportlab.lib.pagesizes.landscape(
                (pageWidth, pageHeight))
        self.author = args.author
        self.title = args.title
        self.keywords = args.keywords
        self.subject = args.subject
        self.canvas = Canvas(args.output, pagesize=(pageWidth, pageHeight))
        self.canvas.setCreator(self.appName)
        if len(args.author) > 0:
            self.canvas.setAuthor(args.author)
        if len(args.title) > 0:
            self.canvas.setTitle(args.title)
        if len(args.subject) > 0:
            self.canvas.setSubject(args.subject)
        if len(args.keywords) > 0:
            self.canvas.setKeywords(args.keywords)
        self.fontSize = args.font_size
        if args.font not in ('Courier'):
            self.font = 'myFont'
            pdfmetrics.registerFont(TTFont('myFont', args.font))
        else:
            self.font = args.font
        self.kerning = args.kerning
        self.margins = margins
        self.leading = (args.extra_vertical_space + 1.2) * self.fontSize
        self.linesPerPage = int(
            (self.leading + pageHeight
             - margins.top - margins.bottom - self.fontSize) / self.leading)
        self.lppLen = len(str(self.linesPerPage))
        fontWidth = self.canvas.stringWidth(
            ".", fontName=self.font, fontSize=self.fontSize)
        self.lineNumbering = args.line_numbers
        if self.lineNumbering:
            margins.adjustLeft(fontWidth * (self.lppLen + 2))
        contentWidth = pageWidth - margins.left - margins.right
        self.charsPerLine = int(
            (contentWidth + self.kerning) / (fontWidth + self.kerning))
        self.top = pageHeight - margins.top - self.fontSize
        self.filename = args.filename
        self.verbose = not args.quiet
        self.breakOnBlanks = args.break_on_blanks
        self.encoding = args.encoding
        self.pageNumbering = args.page_numbers
        if self.pageNumbering:
            self.pageNumberPlacement = \
               (pageWidth / 2, margins.bottom / 2)

    def _process(self, data):
        flen = os.fstat(data.fileno()).st_size
        lineno = 0
        read = 0
        for line in data:
            lineno += 1
            if sys.version_info.major == 2:
                read += len(line)
                yield flen == \
                    read, lineno, line.decode(self.encoding).rstrip('\r\n')
            else:
                read += len(line.encode(self.encoding))
                yield flen == read, lineno, line.rstrip('\r\n')

    def _readDocument(self):
        with open(self.filename, 'r') as data:
            for done, lineno, line in self._process(data):
                if len(line) > self.charsPerLine:
                    self._scribble(
                        "Warning: wrapping line %d in %s" %
                        (lineno + 1, self.filename))
                    while len(line) > self.charsPerLine:
                        yield done, line[:self.charsPerLine]
                        line = line[self.charsPerLine:]
                yield done, line

    def _newpage(self):
        textobject = self.canvas.beginText()
        textobject.setFont(self.font, self.fontSize, leading=self.leading)
        textobject.setTextOrigin(self.margins.left, self.top)
        textobject.setCharSpace(self.kerning)
        if self.pageNumbering:
            self.canvas.drawString(
                self.pageNumberPlacement[0],
                self.pageNumberPlacement[1],
                str(self.canvas.getPageNumber()))
        return textobject

    def _scribble(self, text):
        if self.verbose:
            sys.stderr.write(text + os.linesep)

    def generate(self):
        self._scribble(
            "Writing '%s' with %d characters per "
            "line and %d lines per page..." %
            (self.filename, self.charsPerLine, self.linesPerPage)
        )
        if self.breakOnBlanks:
            pageno = self._generateBob(self._readDocument())
        else:
            pageno = self._generatePlain(self._readDocument())
        self._scribble("PDF document: %d pages" % pageno)

    def _generatePlain(self, data):
        pageno = 1
        lineno = 0
        page = self._newpage()
        for _, line in data:
            lineno += 1

            # Handle form feed characters.
            (line, pageBreakCount) = re.subn(r'\f', r'', line)
            if pageBreakCount > 0 and lineno >= args.minimum_page_length:
                for _ in range(pageBreakCount):
                    self.canvas.drawText(page)
                    self.canvas.showPage()
                    lineno = 0
                    pageno += 1
                    page = self._newpage()
                    if args.minimum_page_length > 0:
                        break

            page.textLine(line)

            if lineno == self.linesPerPage:
                self.canvas.drawText(page)
                self.canvas.showPage()
                lineno = 0
                pageno += 1
                page = self._newpage()
        if lineno > 0:
            self.canvas.drawText(page)
        else:
            pageno -= 1
        self.canvas.save()
        return pageno

    def _writeChunk(self, page, chunk, lineno):
        if self.lineNumbering:
            formatstr = '%%%dd: %%s' % self.lppLen
            for index, line in enumerate(chunk):
                page.textLine(
                    formatstr % (lineno - len(chunk) + index + 1, line))
        else:
            for line in chunk:
                page.textLine(line)

    def _generateBob(self, data):
        pageno = 1
        lineno = 0
        page = self._newpage()
        chunk = list()
        for last, line in data:
            if lineno == self.linesPerPage:
                self.canvas.drawText(page)
                self.canvas.showPage()
                lineno = len(chunk)
                pageno += 1
                page = self._newpage()
            lineno += 1
            chunk.append(line)
            if last or len(line.strip()) == 0:
                self._writeChunk(page, chunk, lineno)
                chunk = list()
        if lineno > 0:
            self.canvas.drawText(page)
            self.canvas.showPage()
        else:
            pageno -= 1
        if len(chunk) > 0:
            page = self._newpage()
            self.canvas.drawText(page)
            self.canvas.showPage()
            pageno += 1
        self.canvas.save()
        return pageno


parser = argparse.ArgumentParser()
parser.add_argument('filename')
parser.add_argument(
    '--font',
    '-f',
    default='Courier',
    help='Select a font (True Type format) by its full path')
parser.add_argument(
    '--font-size',
    '-s',
    type=float,
    default=10.0,
    help='Size of the font')
parser.add_argument(
    '--extra-vertical-space',
    '-v',
    type=float,
    default=0.0,
    help='Extra vertical space between lines')
parser.add_argument(
    '--kerning',
    '-k',
    type=float,
    default=0.0,
    help='Extra horizontal space between characters')
parser.add_argument(
    '--media',
    '-m',
    default='A4',
    help='Select the size of the page (A4, A3, etc.)')
parser.add_argument(
    '--minimum-page-length',
    '-M',
    type=int,
    default=10,
    help='The minimum number of lines before a form feed character will change the page')
parser.add_argument(
    '--landscape',
    '-l',
    action="store_true",
    default=False,
    help='Select landscape mode')
parser.add_argument(
    '--margin-left',
    '-L',
    type=float,
    default=2.0,
    help='Left margin (in cm unit)')
parser.add_argument(
    '--margin-right',
    '-R',
    type=float,
    default=2.0,
    help='Right margin (in cm unit)')
parser.add_argument(
    '--margin-top',
    '-T',
    type=float,
    default=2.0,
    help='Top margin (in cm unit)')
parser.add_argument(
    '--margin-bottom',
    '-B',
    type=float,
    default=2.0,
    help='Bottom margin (in cm unit)')
parser.add_argument(
    '--output',
    '-o',
    default='output.pdf',
    help='Output file')
parser.add_argument(
    '--author',
    default='',
    help='Author of the PDF document')
parser.add_argument(
    '--title',
    default='',
    help='Title of the PDF document')
parser.add_argument(
    '--quiet',
    '-q',
    action='store_true',
    default=False,
    help='Hide detailed information')
parser.add_argument('--subject',default='',help='Subject of the PDF document')
parser.add_argument('--keywords',default='',help='Keywords of the PDF document')
parser.add_argument(
    '--break-on-blanks',
    '-b',
    action='store_true',
    default=False,
    help='Only break page on blank lines')
parser.add_argument(
    '--encoding',
    '-e',
    type=str,
    default='utf8',
    help='Input encoding')
parser.add_argument(
    '--page-numbers',
    '-n',
    action='store_true',
    help='Add page numbers')
parser.add_argument(
    '--line-numbers',
    action='store_true',
    help='Add line numbers')

args = parser.parse_args()

PDFCreator(args, Margins(
    args.margin_right,
    args.margin_left,
    args.margin_top,
    args.margin_bottom)).generate()