import re
import zlib
from ..font_loader import FontLoader
from pdffont import PDFFont
from ttfonts import TTFontFile

class PDFTTFont(PDFFont):
    def __init__(self, session, family='arial', style=None, size=20):
        self.session = session

        self.FAMILIES = FontLoader.families
        self.TTFONTS = FontLoader.font_dict

        self.is_set = False
        self.font_size = None
        self.subset = []

        self.path = None
        self.diffs = None
        self.diff_number = None
        self.encoded = None
        self.descriptors = {}

        self._set_font(family, style, size)
        self._cache_text("""abcdefghijklmnopqrstuvwxyz
                        ABCDEFGHIJKLMNOPQRSTUVWXYZ
                        1234567890-,.<>/?;:\'\"\\[]{}
                        =+_!@#$%^&*()~`""")

    @staticmethod
    def available_fonts():
        return FontLoader.font_dict

    def _set_family(self, family):
        if family is not None:
            family = family.lower()
            if family not in self.TTFONTS:
                raise Exception("%s not a valid font name" % family)
            self.family = family
        else:
            self.family = 'arial'

    def _set_name(self):
        self.name = self.font_key + self.style

    def _set_character_widths(self):
        pass

    def _set_font(self, family=None, style=None, size=None):
        """Select a font; size given in points"""
        if style is not None:
            if 'B' in style:
                family += '_bold'
            if 'I' in style:
                family += '_italic'

        self._set_family(family)
        self._get_diffs()
        self._set_style(style)
        self._set_metrics()
        self._set_size(size)
        self._set_font_key()

    def _set_metrics(self):
        self.path = self.TTFONTS[self.family]

        self.info_object = TTFontFile()
        ttf = self.info_object
        ttf.getMetrics(self.path)
        self.descriptors['Ascent'] = int(round(ttf.ascent))
        self.descriptors['Descent'] = int(round(ttf.descent))
        self.descriptors['CapHeight'] = int(round(ttf.capHeight))
        self.descriptors['Flags'] = ttf.flags
        self.descriptors['FontBBox'] = '[%s %s %s %s]' % (
                                       int(round(ttf.bbox[0])),
                                       int(round(ttf.bbox[1])),
                                       int(round(ttf.bbox[2])),
                                       int(round(ttf.bbox[3])))
        self.descriptors['ItalicAngle'] = int(ttf.italicAngle)
        self.descriptors['StemV'] = int(round(ttf.stemV))
        self.descriptors['MissingWidth'] = int(round(ttf.defaultWidth))
        self.name = re.sub('[ ()]', '', ttf.fullName)
        self.type = 'TTF'
        self.character_widths = ttf.charWidths

    def _get_diffs(self):
        pass

    def _string_width(self, s):
        """Get width of a string in the current font"""
        s = str(s)
        w = 0
        for char in s:
            char = ord(char)
            w += self.character_widths[char]
        return w * self.font_size / 1000.0

    def _output(self):
        self.session._out('<</Type /Font')
        self.session._out('/BaseFont /' + self.name)
        self.session._out('/Subtype /TrueType')
        self.session._out('/FirstChar 32 /LastChar 255')
        self.session._out('/Widths %s 0 R' % (self.number + 1))
        self.session._out('/FontDescriptor %s 0 R' % (self.number + 2))

        if self.diff_number is not None:
            self.session._out('/Encoding %s 0 R' % (self.diff_number + self.session.get_saved_number))
        else:
            self.session._out('/Encoding /WinAnsiEncoding')
        self.session._out('>>')
        self.session._out('endobj')

        self._output_character_widths()
        self._output_descriptors()
        self._output_file()

    def _output_character_widths(self):
        #Widths
        self.session._add_object()
        cw = self.character_widths
        s = '['
        for i in xrange(32, 256):
            try:
                s += str(cw[i]) + ' '
            except KeyError:
                s += '0 '
        self.session._out(s + ']')
        self.session._out('endobj')

    def _output_descriptors(self):
        #Descriptor
        obj = self.session._add_object()
        self.session._out('<</Type /FontDescriptor /FontName /%s' % self.name)
        s = ''
        for k in self.descriptors:
            s += ' /%s %s' % (k, self.descriptors[k])

        s += ' /FontFile2 %s 0 R' % (obj.id + 1)
        self.session._out(s + '>>')
        self.session._out('endobj')

    def _output_file(self):
        self.file = self.info_object.makeSubset(self.path, self.subset)
        self.length1 = len(self.file)
        self.file = zlib.compress(self.file)
        self.length = len(self.file)

        self.session._add_object()
        self.session._out('<</Length ' + str(self.length))
        self.session._out('/Filter /FlateDecode')
        self.session._out('/Length1 ' + str(self.length1))
        self.session._out('>>')
        self.session._put_stream(self.file)
        self.session._out('endobj')

    def _cache_text(self, text):
        txt_unicode = [ord(c) for c in text]
        for uni in txt_unicode:
            if uni not in self.subset:
                self.subset.append(uni)


# -*- coding: utf-8 -*-
import os
import string
from sys import platform as _platform
import itertools

MAC_SEARCH_PATH = '/Library/Fonts/'
WINDOWS_SEARCH_PATH = "C:\\Windows\\Fonts"
LINUX_SEARCH_PATH = '/usr/share/fonts'

english = 'abcdefghijklmnopqrstuvwxyz'

class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class FontLoader(object):
    __metaclass__ = Singleton

    def __init__(self, search_path=None):
        self.search_path = search_path

    def get_ttf(self):
        """ Given a search path, find file with requested extension """
        font_dict = {}
        families = []
        rootdirlist = string.split(self.search_path, os.pathsep)

        #for rootdir in rootdirlist:
        #    rootdir = os.path.expanduser(rootdir)
        for dirName, subdirList, filelist in itertools.chain.from_iterable(os.walk(path) for path in rootdirlist):
            for item in filelist:
                root, ext = os.path.splitext(item)
                if ext == '.ttf':
                    if root[0].lower() in english:
                        source = os.path.join(dirName, item)
                        name = root.lower().replace('_', ' ')
                        if ' bold' in name:
                            name = name.replace(' bold', '_bold')
                            if ' italic' in name:
                                name = name.replace(' italic', '_italic')
                        elif 'bold' in name:
                            name = name.replace('bold', '_bold')
                            if 'italic' in name:
                                name = name.replace('italic', '_italic')
                        elif ' italic' in name:
                            name = name.replace(' italic', '_italic')
                        elif 'italic' in name:
                            name = name.replace('italic', '_italic')
                        elif 'oblique' in name:
                            name = name.replace('oblique', '_italic')
                        else:
                            families.append(name)
                        font_dict[name] = source
                    else:
                        source = os.path.join(dirName, item)
                        name = root.lower().replace('_', ' ')
                        font_dict[name] = source
                        families.append(name)
        self.font_dict = font_dict
        self.families = families

    def load_fonts(self):
        if self.search_path is None:
            if _platform == "linux" or _platform == "linux2":
                self.search_path = LINUX_SEARCH_PATH
            elif _platform == "darwin":
                self.search_path = MAC_SEARCH_PATH
            elif _platform == "win32":
                self.search_path = WINDOWS_SEARCH_PATH

        self.get_ttf()

    def load_from_dir(self, directory):
        self.search_path = directory
        self.load_fonts()

    def load_from_list(self, filelist):
        self.search_path = filelist[0]
        for ffile in filelist[1:]:
            self.search_path += os.pathsep + ffile
        self.load_fonts()

FontLoader = FontLoader()


import os
from pdfobjects.pdfobject import _PDFObject

class _Session(object):
    """ Manages the storing of the
        text output for the PDF objects
        as they are generated by the pdfobject
        classes. Has a centralized count of the
        objects, and some helper functions (putStream,
        compression, etc.)

        One and only one session object should be
        instantiated per PDF file. The object should be
        passed by-reference to objects that will need
        to generate output (either PDF objects or to streams).

    """

    def __init__(self, parent):
        self.parent = parent

        # Central buffer for storing PDF code not in pages.
        self.buffer = ''

        # Offest is used to calculate the binary lengths in the cross-reference
        # section
        self.offset = 0

        self.objects = []

        # Creates the placeholders in the object list to maintain proper count.
        self._create_placeholder_objects()
        self.compression = False
        self.drawn_color = None
        self.color = None
        self.project_dir = os.path.dirname(__file__)

        if '/modules' in self.project_dir:  # pypdflite is a submodule
            self.project_dir, other = self.project_dir.split('/modules')

    # Compression
    def _set_compression(self, value):
        """ May be used to compress PDF files. Code is more readable
            for testing and inspection if not compressed. Requires a boolean. """
        if isinstance(value, bool):
            self.compression = value
        else:
            raise Exception(
                TypeError, "%s is not a valid option for compression" % value)

    # Objects
    def _create_placeholder_objects(self):
        """ PDF objects #1 through #3 are typically saved for the
            Zeroth, Catalog, and Pages Objects. This program will save
            the numbers, but outputs the individual Page and Content objects
            first. The actual Catalog and Pages objects are calculated after.

        """
        self.objects.append("Zeroth")
        self.objects.append("Catalog")
        self.objects.append("Pages")

    def _add_object(self, flag=None):
        """ The flag is a simple integer to force the placement
            of the object into position in the object array.
            Used for overwriting the placeholder objects.

        """
        self.offset = len(self.buffer)
        if flag is None:
            objnum = len(self.objects)
            obj = _PDFObject(objnum, self.offset)
            self.objects.append(obj)
        else:
            objnum = flag
            obj = _PDFObject(objnum, self.offset)
            self.objects[flag] = obj
        self._out(str(objnum) + ' 0 obj')
        return obj

    def _save_object_number(self):
        self.saved = len(self.objects)

    def _get_saved_number(self):
        return self.saved

    # Inputs
    def _out(self, stream, page=None):
        """ Stores the pdf code in a buffer. If it is page related,
            provide the page object.

        """
        if page is not None:
            page.buffer += str(stream) + "\n"
        else:
            self.buffer += str(stream) + "\n"

    def _put_stream(self, stream):
        """ Creates a PDF text stream sandwich.

        """
        self._out('stream')
        self._out(stream)
        self._out('endstream')

    def _add_page(self, text):
        """ Helper function for PDFText, to have the document
            add a page, and retry adding a large block of
            text that would otherwise have been to long for the
            page.

        """
        save_cursor = self.parent.document.page.cursor.copy()
        save_cursor.x_reset()
        save_cursor.y_reset()
        self.parent.document.add_page()
        self.parent.document.set_cursor(save_cursor)
        self.parent.document.add_text(text)

    # Strings
    @staticmethod
    def _UTF8toUTF16(utf8, setbom=True):
        result = ''
        if setbom:
            result = '\xFE\xFF'
        if not isinstance(utf8, unicode):
            utf8 = utf8.decode('UTF-8')
        result += utf8.encode('UTF-16BE')
        return result

    # Colors
    def _save_color(self, color):
        if color.color_type == 'd':
            self.drawn_color = color
        else:
            self.color = color

    def _compare_color(self, newcolor):
        if newcolor._is_equal(self.drawn_color):
            return True
        elif newcolor._is_equal(self.color):
            return True
        else:
            return False

    def _reset_colors(self):
        self.drawn_color = None
        self.color = None


import os
import re
from pdfobjects.pdffont import PDFFont, CORE_FONTS
from pdfobjects.pdfpage import PDFPage
from pdfobjects.pdftext import PDFText
from pdfobjects.pdfcursor import PDFCursor
from pdfobjects.pdfline import PDFLine
from pdfobjects.pdfcolor import PDFColor
from pdfobjects.pdfrectangle import PDFRectangle
from pdfobjects.pdftable import PDFTable
from pdfobjects.pdfimage import PDFImage
from pdfobjects.pdfpng import PDFPNG
from pdfobjects.pdfjpg import PDFJPG
from pdfobjects.pdfttfonts import PDFTTFont
from pdfobjects.pdfmargin import PDFMargin
from pdfobjects.pdfcellformat import PDFCellFormat
from pdfobjects.pdfhtml import PDFHtml
from pdfobjects.pdfellipse import PDFEllipse
from pdfobjects.pdfarc import PDFArc
from pdfobjects.pdflinegraph import PDFLineGraph
from pdfobjects.pdfxyscatter import PDFXYScatter
from pdfobjects.pdfpiechart import PDFPieChart
from pdfobjects.pdfbarchart import PDFBarChart, PDFMultiBarChart
from pdfobjects.pdfbezier import PDFBezier

class PDFDocument(object):
    """ The Document object is the base class that
        is used to add and manage the content of the
        pdf file.

        Document maintains lists of pages and other
        resources, provides for default selections,
        which can be changed through the convinence
        methods.

        Note that page and text objects do not maintain
        fonts themselves. If they must use them (for
        calculating widths or such), they must have
        the current font passed to them.

    """

    def __init__(self, session, orientation, layout):
        """Sets up a standard default document."""
        self.session = session
        self.pages = []
        self.fonts = []
        self.fontkeys = []               # array of used fonts

        self.images = []
        self.imagekeys = []
        self.image_filter = None

        self.margins = None
        self.orientation_default = orientation
        self.layout_default = layout
        self.justification = 'left'
        self.double_spacing = None
        self.diffs = []

        self._set_defaults()

    # Set Defaults
    def _set_defaults(self):
        """Set color scheme & font to defaults."""
        self._set_color_scheme()
        self._set_default_font()
        self.add_page()     # add first page

    def _set_color_scheme(self, draw_color=None, fill_color=None, text_color=None):
        """ Default color object is black letters
            & black lines."""
        if draw_color is None:
            draw_color = PDFColor()
            draw_color._set_type('d')
        if fill_color is None:
            fill_color = PDFColor()
            fill_color._set_type('f')
        if text_color is None:
            text_color = PDFColor()
            text_color._set_type('t')

        self.draw_color = draw_color
        self.fill_color = fill_color
        self.text_color = text_color

    def set_text_color(self, color):
        if isinstance(color, PDFColor):
            self.text_color = color
        else:
            raise ValueError("Not a PDFColor instance")

    def set_fill_color(self, color):
        if isinstance(color, PDFColor):
            self.fill_color = color
        else:
            raise ValueError("Not a PDFColor instance")

    def set_draw_color(self, color):
        if isinstance(color, PDFColor):
            self.draw_color = color
        else:
            raise ValueError("Not a PDFColor instance")

    def _set_default_font(self):
        """ Internal method to set the initial default font. Change
            the font using set_font method."""
        self.font = PDFFont(self.session)
        self.font._set_index()
        self.fonts.append(self.font)
        self.fontkeys.append(self.font.font_key)

    @staticmethod
    def get_color():
        return PDFColor()

    # Public methods, main interface
    # Pages
    def add_page(self, page=None):
        """ May generate and add a PDFPage separately, or use this to generate
            a default page."""
        if page is None:
            self.page = PDFPage(self.orientation_default, self.layout_default, self.margins)
        else:
            self.page = page
        self.page._set_index(len(self.pages))
        self.pages.append(self.page)
        currentfont = self.font
        self.set_font(font=currentfont)
        self.session._reset_colors()

    def get_page(self):
        """Returns reference to current page object."""
        return self.page

    def set_margins(self, left, top=None, right=None, bottom=None):
        if isinstance(left, PDFMargin):
            self.margins = left
        else:
            self.margins = PDFMargin(left, top, right, bottom)
        if self.page is not None:
            self.page.set_margins(self.margins)

    def set_justification(self, value='left'):
        if value in ['left', 'right', 'center']:
            self.justification = value
        else:
            raise ValueError("Incorrect value for justification %s" % value)

    def add_page_numbers(self, location='right', font=None, cursor=None, color=None, text1="Page %s", text2=None):
        self.page_numbers = location
        self.page_numbers_font = font
        self.page_numbers_cursor = cursor
        self.page_numbers_color = color
        self.page_numbers_text1 = text1
        self.page_numbers_text2 = text2

    def change_page_orientation(self):
        self.page._change_orientation()

    # Cursor
    def get_new_cursor(self):
        """ Returns a new default cursor """
        return PDFCursor()

    def get_cursor(self):
        return self.page.cursor.copy()

    def set_cursor(self, x=None, y=None):
        if x is not None and isinstance(x, PDFCursor):
            self.page.cursor = x
        elif x is not None and y is not None:
            self.page.cursor = PDFCursor(x, y, True)
        else:
            raise Exception("Invalid cursor input")

    # Font
    def set_font(self, family=None, style='', size=None, font=None):
        """ Set the document font object, size given in points. If family, style, and/or size is given, generates
            a new Font object, checks to see if it is already in use, and selects it. """
        if font:
            testfont = font
        elif isinstance(family, PDFFont):
            testfont = family
        else:
            # If size is not defined, keep the last size.
            if size is None:
                size = self.font.font_size

            # Create a font from givens to test its key
            if family in CORE_FONTS:
                testfont = PDFFont(self.session, family, style, size)
            else:
                testfont = PDFTTFont(self.session, family, style, size)

        testkey = testfont.font_key

        if testkey in self.fontkeys:
            index = self.fontkeys.index(testkey)
            self.font = self.fonts[index]
            if size != self.font.font_size:
                self.font._set_size(size)
            if style != self.font.style:
                self.font._set_style(style)
        else:
            self.font = testfont
            self._register_new_font(self.font)

        self.font.is_set = False

        if self.page.index > 0:
                self.session._out('BT /F%d %.2f Tf ET' %
                                  (self.font.index, self.font.font_size),
                                  self.page)
                self.font.is_set = True
        return self.font

    def get_font(self):
        """ Get the current font object. Useful for storing
            in variables, and switching between styles. """
        return self.font

    def set_font_size(self, size):
        """Convenience method for just changing font size."""
        if self.font.font_size == size:
            pass
        else:
            self.font._set_size(size)

    def get_font_size(self):
        return self.font.font_size

    def print_available_fonts(self):
        print PDFTTFont.available_fonts()

    # Writing
    def add_text(self, text, cursor=None, justification=None):
        """ Input text, short or long. Writes in order, within the defined page boundaries. Sequential add_text commands will print without
            additional whitespace. """
        if cursor is None:
            cursor = self.page.cursor

        text = re.sub("\s\s+" , " ", text)

        if justification is None:
            justification = self.justification

        if '\n' in text:
            text_list = text.split('\n')
            for text in text_list:
                PDFText(self.session, self.page, text, self.font, self.text_color, cursor, justification, self.double_spacing)
                self.add_newline()
        else:
            PDFText(self.session, self.page, text, self.font, self.text_color, cursor, justification, self.double_spacing)

    def draw_text(self, *args, **kwargs):
        self.add_text(*args, **kwargs)

    def text(self, *args, **kwargs):
        self.add_text(*args, **kwargs)

    def double_space(self, multiplier=1):
        self.double_spacing = multiplier

    def add_newline(self, number=1):
        """ Starts over again at the new line. If number is specified,
            it will leave multiple lines."""
        if isinstance(number, int):
            try:
                self.page._add_newline(self.font, number, self.double_spacing)
            except ValueError:
                self.add_page()
        else:
            raise TypeError("Number of newlines must be an integer.")

    def newline(self, *args, **kwargs):
        self.add_newline(*args, **kwargs)

    def add_indent(self, spaces=4):
        """ Adds a standard tab of 4 spaces.
        """
        self.page._add_indent(self.font, spaces)

    def indent(self, *args, **kwargs):
        self.add_indent(*args, **kwargs)

    def start_block_indent(self, px=20):
        self.px = px
        self.page.cursor.x_plus(self.px)
        self.page.cursor.xmin = self.page.cursor.x

    def end_block_indent(self):
        self.page.cursor.xmin -= self.px
        self.page.cursor.x_plus(-self.px)

    def add_list(self, *args, **kwargs):
        bullet_code = 149
        if 'bullet' in kwargs:
            if kwargs['bullet'] == 1:
                bullet_code = 149
            elif kwargs['bullet'] == 2:
                bullet_code = 186
            elif kwargs['bullet'] == 3:
                bullet_code = 150

        char = chr(bullet_code)

        if 'force' in kwargs and kwargs['force'] is True:
            saved_font = self.get_font()
            for arg in args:
                self.set_font(family='helvetica', size=saved_font.font_size)
                self.add_text(char)
                self.set_font(saved_font)
                self.add_text(' %s' % arg)
                self.add_newline(2)
        else:
            for arg in args:
                if isinstance(arg, list):
                    for item in arg:
                        self.add_text(char)
                        self.add_text(' %s' % item)
                        self.add_newline(1)
                else:
                    self.add_text(char)
                    self.add_text(' %s' % arg)
                    self.add_newline(2)

    def list(self, *args, **kwargs):
        self.add_list(*args, **kwargs)

    def add_html(self, htmltext, context=None, formats=None, cursor=None):
        if cursor is not None:
            self.set_cursor(cursor)

        if hasattr(htmltext, 'write'):
            htmltext = htmltext.read()

        PDFHtml(self, self.session, htmltext, formats, context)

    def html(self, *args, **kwargs):
        self.add_html(*args, **kwargs)

    # Shapes
    def add_line(self, x1=None, y1=None, x2=None, y2=None, cursor1=None, cursor2=None, stroke="solid"):
        if cursor1 is not None:
            if cursor2 is not None:
                pass
            elif x2 is not None and y2 is not None:
                cursor2 = self.page.cursor.copy()
                cursor2.x = x2
                cursor2.y = y2
            else:
                raise Exception("Line not fully specified")
        elif x1 is not None and y1 is not None:
            cursor1 = self.page.cursor.copy()
            cursor1.x = x1
            cursor1.y = y1
            if cursor2 is not None:
                pass
            elif x2 is not None and y2 is not None:
                cursor2 = self.page.cursor.copy()
                cursor2.x = x2
                cursor2.y = y2
            else:
                raise Exception("Line not fully specified")
        else:
            raise Exception("Line not specified")

        myline = PDFLine(self.session, self.page, cursor1, cursor2, self.draw_color, stroke)
        myline._draw()

    def draw_line(self, *args, **kwargs):
        self.add_line(*args, **kwargs)

    def line(self, *args, **kwargs):
        self.add_line(*args, **kwargs)

    def draw_horizontal_line(self):
        end_cursor = self.page.cursor.copy()
        end_cursor.x = end_cursor.xmax

        myline = PDFLine(self.session, self.page, self.page.cursor, end_cursor, self.draw_color, None)
        myline._draw()

    def add_horizontal_line(self):
        self.draw_horizontal_line()

    def horizontal_line(self):
        self.draw_horizontal_line()

    def draw_rectangle(self, x1=None, y1=None, x2=None, y2=None, width=None, height=None, cursor1=None, cursor2=None,
                       style='S', stroke="solid", size=1):
        if cursor1 is not None:
            if cursor2 is not None:
                pass
            elif width is not None and height is not None:
                dims = PDFCursor(width, height)
                cursor2 = cursor1 + dims
            elif x2 is not None and y2 is not None:
                cursor2 = PDFCursor(x2, y2)
            else:
                raise Exception("Rectangle not defined")
        else:
            if x1 is not None and y1 is not None:
                cursor1 = PDFCursor(x1, y1)
                if x2 is not None and y2 is not None:
                    cursor2 = PDFCursor(x2, y2)
                elif width is not None and height is not None:
                    dims = PDFCursor(width, height)
                    cursor2 = cursor1 + dims
                elif cursor2 is not None:
                    pass
                else:
                    raise Exception("Rectangle not defined")
            else:
                raise Exception("Rectangle not defined")

        rectangle = PDFRectangle(self.session, self.page, cursor1, cursor2, self.draw_color, self.fill_color, style, stroke, size)

        rectangle._draw()

    def add_rectangle(self, *args, **kwargs):
        self.draw_rectangle(*args, **kwargs)

    def rectangle(self, *args, **kwargs):
        self.draw_rectangle(*args, **kwargs)

    def draw_circle(self, center_cursor, radius, border_color=None, fill_color=None, style="S", stroke='solid', size=1):
        self.draw_ellipse(center_cursor, radius, radius, border_color, fill_color, style, stroke, size)

    def draw_ellipse(self, center_cursor, x_radius, y_radius, border_color=None, fill_color=None, style="S", stroke='solid', size=1):
        radius_cursor = PDFCursor(x_radius, y_radius)
        if isinstance(border_color, PDFColor):
            border_color = border_color
        else:
            border_color = self.draw_color
        if isinstance(fill_color, PDFColor):
            fill_color = fill_color
        else:
            fill_color = self.fill_color

        circle = PDFEllipse(self.session, self.page, center_cursor, radius_cursor, border_color, fill_color, style, stroke, size)
        circle._draw()

    def draw_arc(self, center_cursor, radius, starting_angle, arc_angle, inverted=False, end_angle=None, border_color=None, fill_color=None, style='S', stroke='solid', size=1):
        if isinstance(border_color, PDFColor):
            border_color = border_color
        else:
            border_color = self.draw_color
        if isinstance(fill_color, PDFColor):
            fill_color = fill_color
        else:
            fill_color = self.fill_color

        arc = PDFArc(self.session, self.page, center_cursor, radius, starting_angle, arc_angle, inverted, end_angle, border_color, fill_color, style, stroke, size)
        arc._draw()

    def draw_bezier_lines(self, points, color=None, style="S", stroke="solid"):
        save_draw_color = self.draw_color

        line = PDFBezier(self.session, self.page, points, color, style, stroke)

        self.set_draw_color(save_draw_color)

    # Graphs
    def add_line_graph(self, data, cursor, width, height, title=None, x_axis_limits=None, y_axis_limits=None, frequency=None, axis_titles=None, axis_labels=None, axis_font_size=None, line_colors=None,
                       background=None, legend=None, dots=None, style="S", axis=True):
        save_draw_color = self.draw_color
        save_fill_color = self.fill_color
        save_font_size = self.get_font_size()
        if axis_font_size is None:
            self.set_font_size(8)
        else:
            self.set_font_size(axis_font_size)

        graph = PDFLineGraph(self.session, self.page, cursor, data, width, height, title, x_axis_limits, y_axis_limits, frequency, axis_titles, axis_labels, line_colors, background, legend, dots, style, axis)

        self.set_font_size(save_font_size)
        self.set_draw_color(save_draw_color)
        self.set_fill_color(save_fill_color)

    def add_xy_scatter(self, data, cursor, width, height, title=None, x_axis_limits=None, y_axis_limits=None, frequency=None, axis_titles=None, axis_labels=None, axis_font_size=None, line_colors=None,
                       background=None, legend=None, dots=None, linear_regression=None, linear_regression_equation=None):

        save_draw_color = self.draw_color
        save_fill_color = self.fill_color
        save_font_size = self.get_font_size()
        if axis_font_size is None:
            self.set_font_size(8)
        else:
            self.set_font_size(axis_font_size)

        graph = PDFXYScatter(self.session, self.page, cursor, data, width, height, title, x_axis_limits, y_axis_limits, frequency, axis_titles, axis_labels, line_colors, background, legend, dots, linear_regression, linear_regression_equation)

        self.set_font_size(save_font_size)
        self.set_draw_color(save_draw_color)
        self.set_fill_color(save_fill_color)

    def add_simple_bar_chart(self, data, cursor, width, height, title=None, axis_titles=None, axis_font_size=None, y_axis_limits=None, y_axis_frequency=None, bar_style="B", bar_padding=0.1, bar_border_colors=None, bar_fill_colors=None,
                             background=None):

        save_draw_color = self.draw_color
        save_fill_color = self.fill_color
        save_font_size = self.get_font_size()

        if axis_font_size is None:
            self.set_font_size(8)
        else:
            self.set_font_size(axis_font_size)

        graph = PDFBarChart(self.session, self.page, data, cursor, width, height, title, axis_titles, y_axis_limits, y_axis_frequency, bar_style, bar_padding, bar_border_colors, bar_fill_colors,
                            background)

        self.set_font_size(save_font_size)
        self.set_draw_color(save_draw_color)
        self.set_fill_color(save_fill_color)

    def add_multi_bar_chart(self, data, cursor, width, height, title=None, axis_titles=None, axis_font_size=None, y_axis_limits=None, y_axis_frequency=None, bar_style="B", bar_padding=0.1, bar_border_colors=None, bar_fill_colors=None,
                            background=None, legend=None):
        save_draw_color = self.draw_color
        save_fill_color = self.fill_color
        save_font_size = self.get_font_size()

        if axis_font_size is None:
            self.set_font_size(8)
        else:
            self.set_font_size(axis_font_size)

        graph = PDFMultiBarChart(self.session, self.page, data, cursor, width, height, title, axis_titles, y_axis_limits, y_axis_frequency, bar_style, bar_padding, bar_border_colors, bar_fill_colors,
                                 background, legend)

        self.set_font_size(save_font_size)
        self.set_draw_color(save_draw_color)
        self.set_fill_color(save_fill_color)

    def add_pie_chart(self, data, cursor, width, height, title=None, data_type="raw", fill_colors=None, labels=False, background=None, legend=None):
        """ Data type may be "raw" or "percent" """
        save_draw_color = self.draw_color
        save_fill_color = self.fill_color

        chart = PDFPieChart(self.session, self.page, data, cursor, width, height, title, data_type, fill_colors, labels, background, legend)

        self.set_draw_color(save_draw_color)
        self.set_fill_color(save_fill_color)

    # Tables
    def add_table(self, rows, columns, cursor=None):
        if cursor is None:
            cursor = self.page.cursor

        table = PDFTable(self.session, self.page, rows, columns, cursor, self.font)

        return table

    def draw_table(self, table):
        if isinstance(table, PDFTable):
            table._draw()
            self.page.cursor = table.cursor
        else:
            raise Exception("Invalid Table")

    def add_cell_format(self, data=None, cell_font=None):
        if cell_font is None:
            cell_font = self.font
        if data is None:
            data = {}
        format = PDFCellFormat(data, cell_font)
        return format

    # Images
    def add_image(self, image, name=None):
        if isinstance(image, PDFImage):  # It's an image object
            myimage = self._get_image(image.name)
            if not myimage:
                self._register_new_image(image)
            myimage = image

        elif isinstance(image, str):  # Name or path
            image_string = image
            if image_string.find('.') == -1:  # Not a path, treat as name
                myimage = self._get_image(image_string)
                if not myimage:
                    raise Exception('Not a proper image path')
            else:  # Is a path
                if not name:  # But it doesn't have a name specified.
                    name = os.path.splitext(image_string)[0]  # Specify it
                myimage = self._get_image(name)
                if not myimage:  # New image
                    extension = os.path.splitext(image_string)[1]
                    if extension == '.png':
                        myimage = PDFPNG(self.session, image_string, name)
                    elif extension == '.jpg':
                        myimage = PDFJPG(self.session, image_string, name)
                    else:
                        raise Exception("Image format %s not supported" % extension)
                    self._register_new_image(myimage)

        return myimage

    def draw_image(self, image, cursor=None, width=None, height=None):
        if isinstance(image, PDFImage):  # It's an image object
            myimage = self._get_image(image.name)
        else:
            try:
                myimage = self._get_image(image)
            except:
                raise Exception("%s is an invalid image. Add it first.")

        if not cursor:
            imagecursor = self.page.cursor
        else:
            imagecursor = cursor
        myimage._set_cursor(imagecursor)
        myimage._set_size(width, height)
        myimage._draw(self.page)

        # If a cursor was not specified, place at the end
        if not cursor:
            self.page.cursor = myimage.cursor

    def set_background_image(self, image):
        background_cursor = PDFCursor(0, 0)
        background_cursor.set_bounds(xmin=0, ymin=0, xmax=self.page.width, ymax=self.page.height, ymaxmax=self.page.height)
        myimage = self.add_image(image)
        self.draw_image(myimage, background_cursor, width=self.page.width)

    # Private methods for outputting document
    def _get_image(self, image_name):
        if image_name in self.imagekeys:
            index = self.imagekeys.index(image_name)
            myimage = self.images[index]
            return myimage
        else:
            return False

    def _output_pages(self):
        """ Called by the PDFLite object to prompt creating
            the page objects."""
        if not self.orientation_changes:
            self._get_orientation_changes()

        # Page
        for page in self.pages:
            obj = self.session._add_object()
            self.session._out('<</Type /Page')
            self.session._out('/Parent 1 0 R')
            if self.orientation_changes:
                self.session._out('/MediaBox [0 0 %.2f %.2f]' % (page.width, page.height))
            self.session._out('/Resources 2 0 R')
            self.session._out('/Group <</Type /Group /S /Transparency /CS /DeviceRGB>>')
            self.session._out('/Contents %s 0 R>>' % (obj.id + 1))
            self.session._out('endobj')

            # Page content
            self.session._add_object()
            if self.session.compression is True:
                textfilter = ' /Filter /FlateDecode '
                page._compress()
            else:
                textfilter = ''
            self.session._out('<<%s/Length %s >>' % (textfilter, len(page.buffer)))
            self.session._put_stream(page.buffer)
            self.session._out('endobj')

    def _set_page_numbers(self):
        if hasattr(self, 'page_numbers'):
            for page in self.pages:
                self._add_page_number(page, len(self.pages))

    def _add_page_number(self, page, number_of_pages):
        if self.page_numbers_font is not None:
            fs = 'BT /F%d %.2f Tf ET' % (self.page_numbers_font.index, self.page_numbers_font.font_size)
            self.session._out(fs, page)

        if self.page_numbers_color is not None:
            self.page_numbers_color._set_type('t')
            self.session._out(self.page_numbers_color._get_color_string(), page)

        text_string = self.page_numbers_text1 % (page.index + 1)
        if self.page_numbers_text2 is not None:
            text_string += self.page_numbers_text2 % number_of_pages

        if self.page_numbers_cursor is None:
            sw = self.font._string_width(text_string)
            y = page.page_size[1]
            y = y - self.page.margin.bottom

            if self.page_numbers == 'left':
                x = self.page.margin.left
            elif self.page_numbers == 'right':
                x = page.page_size[0] - page.margin.right - sw
            else:
                x = page.page_size[0] / 2 - int(sw / 2.0)
            cursor = PDFCursor(x, y)
        else:
            cursor = self.page_numbers_cursor

        text_string = '(%s)' % text_string
        s = 'BT %.2f %.2f Td %s Tj ET' % (cursor.x, cursor.y_prime, text_string)
        self.session._out(s, page)

    def _get_orientation_changes(self):
        """ Returns a list of the pages that have
            orientation changes."""
        self.orientation_changes = []
        for page in self.pages:
            if page.orientation_change is True:
                self.orientation_changes.append(page.index)
            else:
                pass
        return self.orientation_changes

    def _register_new_font(self, font):
        font._set_index(len(self.fonts) + 1)
        self.fonts.append(self.font)
        self.fontkeys.append(self.font.font_key)
        if hasattr(font, 'diffs') and font.diffs is not None:
            try:
                index_of_diff = self.diffs.index(font.diffs)
            except AttributeError:
                index_of_diff = len(self.diffs) + 1
                self.diffs.append(font.diffs)
            font.diff_number = index_of_diff

    def _output_fonts(self):
        """ Called by the PDFLite object to prompt creating
            the font objects."""
        self.session._save_object_number()
        self._output_encoding_diffs()
        self._output_font_files()

        for font in self.fonts:
            obj = self.session._add_object()
            font._set_number(obj.id)
            font._output()

    def _output_font_files(self):
        for font in self.fonts:
            if hasattr(font, 'file'):
                font.output_file()

    def _output_encoding_diffs(self):
        if self.diffs:
            for _ in self.diffs:
                self.session._add_object()
                self.session._out('<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences [%s]>>')
                self.session._out('endobj')

    def _register_new_image(self, image):
        image._set_index(len(self.images) + 1)
        self.images.append(image)
        self.imagekeys.append(image.name)

    def _output_images(self):
        """ Creates reference images, that can be
            drawn throughout the document."""
        for image in self.images:
            obj = self.session._add_object()
            image._set_number(obj.id)
            image._output()


import os
import struct
import urllib
import zlib
import re

class PDFImage(object):

    def __init__(self, session, path, name):

        self.pal = None
        self.palette_length = None
        self.session = session
        self.path = path
        self.name = name
        self.cursor = None
        self.width = -1
        self.height = -1
        self.size = None  # in bits

        self.number = None  # Object number

        self.bits_per_component = None
        self.palette = None
        self.colorspace = None  # 'Indexed', 'DeviceCMYK',
        self.transparent = None

        self.soft_mask = None
        self.filter = None
        self.decode = None

        self._get_metrics()

    def _set_number(self, number):
        """ Object number

        """
        self.number = number

    def _set_index(self, index=1):
        """ Image number

        """
        self.index = index

    def _set_cursor(self, cursor):
        self.cursor = cursor

    def _set_size(self, width=None, height=None):
        if width is not None and height is None:
            self.scale = width / float(self.width)
            self._set_scale()
        elif height is not None and width is None:
            self.scale = height / float(self.height)
            self._set_scale()
        elif width is None and height is None:
            self.scale = 1
            self._set_scale()
        else:
            self.scale_width = int(width)
            self.scale_height = int(height)

    def _set_scale(self):
        self.scale_width = int(self.width * self.scale)
        self.scale_height = int(self.height * self.scale)

    def _open_file(self):
        if self.path.startswith("http://") or self.path.startswith("https://"):
            self.file = urllib.urlopen(self.path)
        else:
            try:
                self.file = open(self.path, 'rb')
            except IOError:
                try:
                    path = os.path.join(self.session.project_dir, self.path)
                    self.file = open(path, 'rb')
                    self.path = path
                except:
                    path = os.path.join(self.session.project_dir, self.path)
                    raise Exception("Can't open path: %s" % path)

    def _initialize(self):
        self._open_file()

        self.initial_data = str(self.file.read())
        self.file.close()
        if not self.initial_data:
            raise Exception("Can't open image file: ", self.path)

    def _read(self):
        pass

    def _get_metrics(self):
        self._initialize()
        self._read()
        #self._set_scale()

    def _parse_image(self):
        self.transparent = None
        self.palette = None
        image_data = ''

        self.file = None
        self._open_file()
        f = self.file

        f.read(12)
        if f.read(4) != 'IHDR':
            raise Exception('Image is broken')
        w = struct.unpack('>HH', f.read(4))[1]
        h = struct.unpack('>HH', f.read(4))[1]
        self.width = int(w)
        self.height = int(h)
        #self._set_scale()
        # Find bits per component
        self.bits_per_component = ord(f.read(1))
        if self.bits_per_component > 8:
            raise Exception('16 bit not supported')

        # Find ct
        self.ct = ord(f.read(1))
        if self.ct == 0 or self.ct == 4:
            self.colorspace = 'DeviceGray'
            coord = 1
        elif self.ct == 2 or self.ct == 6:
            self.colorspace = 'DeviceRGB'
            coord = 3
        elif self.ct == 3:
            self.colorspace = 'Indexed'
            coord = 1
        else:
            raise Exception('Unknown color type: %s' % self.ct)
        f.read(4)

        self.decode = '/Predictor 15 /Colors %s /BitsPerComponent %s /Columns %s' \
                      % (coord, self.bits_per_component, self.width)

        test_n = 1
        while True:
            last_pos = f.tell()
            header = f.read(4)
            if header in ('PLTE', 'tRNS', 'IDAT', 'IEND'):
                f.seek(last_pos - 4)
                test_n = struct.unpack('>HH', f.read(4))[1]
                f.read(4)
            if header == 'IHDR':
                pass
            elif header == 'PLTE':
                self.pallet = f.read(test_n)
                f.read(4)
            elif header == 'tRNS':
                # Simple transparancy
                t = f.read(test_n)
                if self.ct == 0:
                    self.transparent = [ord(t[1:2]), ]
                elif self.ct == 2:
                    self.transparent = [ord(t[1:2]), ord(t[3:4]), ord(t[5:6])]
                else:
                    pos = t.find('\x00')
                    if pos != -1:
                        self.transparent = [pos, ]
                f.read(4)
            elif header == 'IDAT':
                image_data += f.read(test_n)
                f.read(4)
            elif header == 'IEND':
                break
            else:
                f.seek(last_pos + 1)
                pass

        if self.colorspace == 'Indexed' and not self.palette:
            raise Exception('Missing Palette')
        self.file.close()

        if self.ct >= 4:
            #print "Color Type >= 4, splitting"
            image_data = zlib.decompress(image_data)
            color = ''
            alpha = ''
            if self.ct == 4:
                # Grey
                length = 2 * self.width
                for i in range(self.height):
                    pos = (1 + length) * i
                    color += image_data[pos]
                    alpha += image_data[pos]
                    line = image_data[pos + 1: pos + 1 + length]
                    color += re.sub('(.).', lambda m: m.group(1), line, flags=re.DOTALL)
                    alpha += re.sub('.(.)', lambda m: m.group(1), line, flags=re.DOTALL)
            else:
                # RGB image
                length = 4 * self.width
                for i in range(self.height):
                    pos = (1 + length) * i
                    color += image_data[pos]
                    alpha += image_data[pos]
                    line = image_data[pos + 1: pos + 1 + length]
                    color += re.sub('(.{3}).', lambda m: m.group(1), line, flags=re.DOTALL)
                    alpha += re.sub('.{3}(.)', lambda m: m.group(1), line, flags=re.DOTALL)

            image_data = zlib.compress(color)
            smdata = zlib.compress(alpha)
        else:
            smdata = None

        self.image_data = image_data
        self.size = len(self.image_data)
        self.filter = 'FlateDecode'

        if self.transparent is not None:
            self.transparent_string = ''
            for i in xrange(0, len(self.transparent)):
                self.transparent_string += '%s %s ' % (self.transparent[i], self.transparent[i])

        if smdata is not None:
            self.soft_mask = PDFSoftMask(self.session, self.width, self.height, smdata, self.filter)

    def _draw(self, page):
        self.session._out('q %s 0 0 %s %s %s cm /I%d Do Q' %
                          (self.scale_width, self.scale_height,
                           self.cursor.x, (self.cursor.y_prime - self.scale_height),
                          self.index), page)
        self.cursor.x_plus(self.scale_width)
        self.cursor.y_plus(self.scale_height)

    def _output(self):
        """ Prompts the creating of image objects.

        """
        self.session._out('<</Type /XObject')
        self.session._out('/Subtype /Image')
        self.session._out('/Width %s' % self.width)
        self.session._out('/Height %s' % self.height)

        if self.colorspace is 'Indexed':
            self.session._out('/ColorSpace [/Indexed /DeviceRGB %s %s 0 R' %
                              (self.pal, self.number + 1))
        else:
            self.session._out('/ColorSpace /%s' % self.colorspace)
            if self.colorspace is 'DeviceCMYK':
                self.session._out('/Decode [1 0 1 0 1 0 1 0]')

        self.session._out('/BitsPerComponent %s' % self.bits_per_component)

        if self.filter:
            self.session._out('/Filter /%s' % self.filter)
        if self.decode:
            self.session._out('/DecodeParms << %s >>' % self.decode)
        if self.transparent:
            self.session._out('/Mask [%s]' % self.transparent_string)
        if self.soft_mask:
            self.session._out('/SMask %s 0 R' % (self.number + 1))
        self.session._out('/Length %s >>' % self.size)
        self.session._put_stream(self.image_data)
        self.session._out('endobj')

        if self.colorspace is 'Indexed':
            self.session._out('<<%s /Length %s >>' % (self.palette_filter, self.palette_length))
            self.session._put_stream(self.palette)
            self.session._out('endobj')

        if isinstance(self.soft_mask, PDFImage):
            obj = self.session._add_object()
            self.soft_mask._set_number(obj.id)
            self.soft_mask._output()

class PDFSoftMask(PDFImage):
    def __init__(self, session, width, height, data, imfilter):
        self.session = session
        self.width = width
        self.height = height
        self.image_data = data
        self.bits_per_component = 8
        self.colorspace = 'DeviceGray'
        self.filter = imfilter
        self.decode = '/Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns %s' % self.width
        self.path = None
        self.size = len(self.image_data)

    def draw(self):
        pass

    def parse_image(self):
        pass

    def _initialize(self):
        pass

    def _output(self):
        self.session._out('<</Type /XObject')
        self.session._out('/Subtype /Image')
        self.session._out('/Width %s' % self.width)
        self.session._out('/Height %s' % self.height)
        self.session._out('/ColorSpace /%s' % self.colorspace)
        self.session._out('/BitsPerComponent %s' % self.bits_per_component)

        if self.filter:
            self.session._out('/Filter /%s' % self.filter)

        self.session._out('/DecodeParms << %s >>' % self.decode)
        self.session._out('/Length %s >>' % self.size)
        self.session._put_stream(self.image_data)
        self.session._out('endobj')


"""
Created on Mar 16, 2014

@author: tjoneslo
"""

from math import cos, tan, sin, pi

class PDFTransform(object):
    """
    classdocs
    """

    def __init__(self, session, page):
        """
        Constructor
        """
        self.session = session
        self.page = page

        self._currentMatrix = (1., 0., 0., 1., 0., 0.)
        self._textMatrix = (1., 0., 0., 1., 0., 0.)

    def save_state(self):
        """Save the current graphics state to be restored later by restoreState.
        This saves/restores only the current graphics state on the PDF Page,
        not in the internal state of the PyPDFLite.

        For example:
            canvas.setFont("Helvetica", 20)
            canvas.saveState()
            ...
            canvas.setFont("Courier", 9)
            ...
            canvas.restoreState()
            # if the save/restore pairs match then font is Helvetica 20 again.
        """
        self.session._out('q', self.page)

    def restore_state(self):
        """restore the graphics state to the matching saved state (see saveState)."""
        self.session._out('Q', self.page)

    def transform(self, a, b, c, d, e, f):
        """ Adjust the current transformation state of the current graphics state
        matrix. Not recommended for the faint of heart.
        """
        a0, b0, c0, d0, e0, f0 = self._currentMatrix
        self._currentMatrix = (a0 * a + c0 * b, b0 * a + d0 * b,
                               a0 * c + c0 * d, b0 * c + d0 * d,
                               a0 * e + c0 * f + e0, b0 * e + d0 * f + f0)
        a1, b1, c1, d1, e1, f1 = self._currentMatrix
        self.session._out('%.2f %.2f %.2f %.2f %.2f %.2f cm' % (a1, b1, c1, d1, e1, f1), self.page)

    def absolute_position(self, x, y):
        """return the absolute position of x,y in user space w.r.t. default user space"""
        (a, b, c, d, e, f) = self._currentMatrix
        xp = a * x + c * y + e
        yp = b * x + d * y + f
        return xp, yp

    def translate(self, dx, dy):
        """move the origin from the current (0,0) point to the (dx,dy) point
           (with respect to the current graphics state)."""
        self.transform(1, 0, 0, 1, dx, dy)

    def scale(self, x, y):
        """Scale the horizontal dimension by x and the vertical by y
           (with respect to the current graphics state).
           For example canvas.scale(2.0, 0.5) will make everything short and fat."""
        self.transform(x, 0, 0, y, 0, 0)

    def rotate(self, theta):
        """Rotate current graphic state by the angle theta (in degrees)."""
        c = cos(theta * pi / 180)
        s = sin(theta * pi / 180)
        self.transform(c, s, -s, c, 0, 0)

    def skew(self, alpha, beta):
        tan_alpha = tan(alpha * pi / 180)
        tan_beta = tan(beta * pi / 180)
        self.transform(1, tan_alpha, tan_beta, 1, 0, 0)


from fontref import pdf_character_widths
import copy

CORE_FONTS = {
    'courier': 'Courier',
    'courier_bold': 'Courier-Bold',
    'courier_italic': 'Courier-Oblique',
    'courier_bold_italic': 'Courier-BoldOblique',
    'helvetica': 'Helvetica',
    'helvetica_bold': 'Helvetica-Bold',
    'helvetica_italic': 'Helvetica-Oblique',
    'helvetica_bold_italic': 'Helvetica-BoldOblique',
    'times': 'Times-Roman',
    'times_bold': 'Times-Bold',
    'times_italic': 'Times-Italic',
    'times_bold_italic': 'Times-BoldItalic',
    'symbol': 'Symbol', 'zapfdingbats': 'ZapfDingbats'}

class PDFFont(object):

    def __init__(self, session, family='helvetica', style='', size=12):
        self.session = session

        self.is_set = False
        self.font_size = None
        self._set_font(family, style, size)
        self.type = 'Core'

    def __repr__(self):
        return self.font_key

    def _copy(self):
        new_font = copy.deepcopy(self)
        new_font.is_set = False
        return new_font

    def _set_family(self, family):
        if family is not None:
            family = family.lower()
            if family not in CORE_FONTS:
                raise Exception("%s is not a valid core font" % family)
            self.family = family
        else:
            self.family = "helvetica"

    def _set_style(self, style=None):
        """ Style should be a string, containing the letters 'B' for bold,
        'U' for underline, or 'I' for italic, or should be '', for no style.
        Symbol will not be underlined. The underline style can further be
        modified by specifying the underline thickness and position.

        """
        if style is None:
            self.style = ''
            self.underline = False
        # No syling for symbol
        elif self.family == ('symbol' or 'zapfdingbats'):
            self.style = ''
            self.underline = False

        self.style = style.upper()
            # SetUnderline

        if 'U' in self.style or self.style == 'U':
            self.underline = True
        else:
            self.underline = False

    def _set_underline_params(self):
        # Does a pretty good job visually representing an underline
        if 'B' in self.style:
            multiplier = 1.5
        else:
            multiplier = 1

        self.underline_thickness = int(multiplier * self.font_size / 12.0)
        if self.underline_thickness < 1:
            self.underline_thickness = 1
        self.underline_position = int(multiplier * self.font_size / 7.0)

    def _set_size(self, size=None):
        if size is not None:
            if self.font_size != size:
                self.font_size = float(size)
                self.line_size = self.font_size * 1.2
                self._set_underline_params()
                self.is_set = False

    def _set_font_key(self):
        self.font_key = self.family
        if 'B' in self.style:
            self.font_key += '_bold'
        if 'I' in self.style:
            self.font_key += '_italic'
        self.font_id = self.font_key
        self.font_key += ":%s" % self.font_size

    def _set_name(self):
        self.name = CORE_FONTS[self.font_id]

    def _set_character_widths(self):
        self.character_widths = pdf_character_widths[self.font_id]

    def _set_font(self, family=None, style=None, size=None):
        """Select a font; size given in points"""
        self._set_family(family)
        self._set_style(style)
        self._set_size(size)
        self._set_font_key()
        self._set_name()
        self._set_character_widths()

    def _set_index(self, index=1):
        """ Index is the number of the font, not the same as
            object number, both are used and set in document.

        """
        self.index = index

    def _set_number(self, value):
        """This is the font pdf object number."""
        self.number = value

    def _equals(self, font):
        if (font.family == self.family) and\
           (font.font_size == self.font_size) and\
           (font.style == self.style):
            ans = True
        else:
            ans = False
        return ans

    def _string_width(self, s):
        """Get width of a string in the current font"""
        s = str(s)
        w = 0
        for i in s:
            w += self.character_widths[i]
        return w * self.font_size / 1000.0

    def _output(self):
            self.session._out('<</Type /Font')
            self.session._out('/BaseFont /' + self.name)
            self.session._out('/Subtype /Type1')
            if self.name != 'Symbol' and self.name != 'ZapfDingbats':
                self.session._out('/Encoding /WinAnsiEncoding')
            self.session._out('>>')
            self.session._out('endobj')


#!/usr/bin/python3
# coding=utf8
#
# Copyright (c) 2016-2017 - Luís Moreira de Sousa
# Licenced under EUPL 1.1. Please consult the LICENCE file for details.
#
# Class for HexASCII raster - an ASCII encoded cartographic hexagonal grid [0].
#
# Author: Luís Moreira de Sousa (luis.de.sousa[@]protonmail.ch)
# Date: 31-03-2016
#
# [0] https://github.com/ldesousa/HexAsciiBNF

import math
from hex_utils.raster import Raster

class HASC (Raster):

    _key_ncols  = "ncols"
    _key_nrows  = "nrows"
    _key_xll    = "xll"
    _key_yll    = "yll"
    _key_side   = "side"
    _key_nodata = "no_data"
    _key_angle  = "angle"

    _side    = 0
    _angle   = None
    _hexPerp = 0

    _angle_rd = 0

    __value_field = "value"

    @property
    def side(self):
        return self._side

    @property
    def angle(self):
        return self._angle

    @property
    def hexPerp(self):
        return self._hexPerp

    @property
    def valueField(self):
        return self.__value_field

    def _set_side(self, side):

        if (side <= 0):
            raise ValueError('Invalid cell side')
        self._side = side

    def init(self, ncols, nrows, xll, yll, side, nodata = "", angle = None):

        Raster.init(self, ncols, nrows, xll, yll, nodata)
        self._set_side(side)
        self._angle    = angle
        self._hexPerp  = math.sqrt(3) * self._side / 2.0
        if angle != None:
            self._angle_rd = math.radians(angle)

    def initWithExtent(self, side, xll, yll, xtr, ytr, nodata = "", angle = None):

        # Calculate hexagonal cell geometry
        self._side = side
        self._hexPerp = math.sqrt(3) * self._side / 2.0

        # Calculate mesh span - in Python 2 math.ceil returns a float
        self._set_nrows(int(math.ceil((ytr - yll) / (2 * self._hexPerp))))
        self._set_ncols(int(math.ceil((xtr - xll) / (3 * self._side / 2))))

        # Position first hexagon
        # yy position tries to minimise the area of square cells outside the given extent
        self._xll = xll + self._side / 2
        self._yll = ytr - self._nrows * 2 * self._hexPerp + self._hexPerp

        self._nodata = nodata
        if angle != None:
            self._angle = angle
            self._angle_rd = math.radians(angle)
        self._mesh = [[None for x in range(self._nrows)] for y in range(self._ncols)]

    def cellArea(self):

        return self._side * self.hexPerp * 3

    def _loadHeader(self):

        # Mandatory header
        ncols = self._loadHeaderLine(self._file.readline(), self._key_ncols,  type(1))
        nrows = self._loadHeaderLine(self._file.readline(), self._key_nrows,  type(1))
        xll   = self._loadHeaderLine(self._file.readline(), self._key_xll,    type(1.0))
        yll   = self._loadHeaderLine(self._file.readline(), self._key_yll,    type(1.0))
        side  = self._loadHeaderLine(self._file.readline(), self._key_side,   type(1.0))
        # Optional headers
        self._nextLine = self._file.readline()
        nodata = self._loadHeaderLine(self._nextLine, self._key_nodata, type("a"), True)
        if nodata != "" and nodata != None:
            self._nextLine = self._file.readline()
        angle = self._loadHeaderLine(self._nextLine, self._key_angle, type(1.0),  True)
        if angle != None :
            self._nextLine =  self._file.readline()

        self.init(ncols, nrows, xll, yll, side, nodata, angle)

    def _saveHeader(self, f):

        f.write(self._key_ncols + "\t" + str(self._ncols) + "\n")
        f.write(self._key_nrows + "\t" + str(self._nrows) + "\n")
        f.write(self._key_xll + "\t" + str(self._xll) + "\n")
        f.write(self._key_yll + "\t" + str(self._yll) + "\n")
        f.write(self._key_side + "\t" + str(self._side) + "\n")
        if self._nodata != "" :
            f.write(self._key_nodata + "\t" + str(self._nodata) + "\n")
        if self._angle != None :
            f.write(self._key_angle + "\t" + str(self._angle) + "\n")

    def getCellCentroidCoords(self, i, j):

        baseCoords = self._getUnrotatedCellCentroidCoords(i, j)
        return self.rotatePoint(baseCoords[0], baseCoords[1])

    def _getUnrotatedCellCentroidCoords(self, i, j):

        x = self._xll + i * 3.0 * self._side / 2.0
        y = self._yll + (self._nrows - 1.0 - j) * 2.0 * self._hexPerp + (i % 2) * self._hexPerp

        return (x, y)

    def saveAsGML(self, outputFilePath):

        try:
            from osgeo import ogr
        except ImportError:
            raise ImportError(""" ERROR: Could not find the GDAL/OGR Python library.
                       On Debian based systems you can install it with this command:
                       apt install python-gdal""")

        driver = ogr.GetDriverByName("GML")
        outSource = driver.CreateDataSource(
            outputFilePath,
            ["XSISCHEMAURI=http://schemas.opengis.net/gml/2.1.2/feature.xsd"])
        outLayer = outSource.CreateLayer("output", None, ogr.wkbUnknown)

        newField = ogr.FieldDefn(self.__value_field, ogr.OFTReal)
        outLayer.GetLayerDefn().AddFieldDefn(newField)

        for j in range(self._nrows):
            for i in range(self._ncols):

                cellVertexes = self.getCellVertexes(i, j)

                polygon = ogr.CreateGeometryFromWkt("POLYGON ((" +
                    str(cellVertexes[0][0]) + " " + str(cellVertexes[0][1]) + ", " +
                    str(cellVertexes[1][0]) + " " + str(cellVertexes[1][1]) + ", " +
                    str(cellVertexes[2][0]) + " " + str(cellVertexes[2][1]) + ", " +
                    str(cellVertexes[3][0]) + " " + str(cellVertexes[3][1]) + ", " +
                    str(cellVertexes[4][0]) + " " + str(cellVertexes[4][1]) + ", " +
                    str(cellVertexes[5][0]) + " " + str(cellVertexes[5][1]) + ", " +
                    str(cellVertexes[0][0]) + " " + str(cellVertexes[0][1]) + "))")

                outFeature = ogr.Feature(feature_def=outLayer.GetLayerDefn())
                outFeature.SetGeometryDirectly(polygon)
                outFeature.SetField(self.__value_field, str(self._mesh[i][j]))
                outLayer.CreateFeature(outFeature)

    def saveAsGeoJSON(self, outputFilePath):

        try:
            from geojson import Feature, Polygon, FeatureCollection, dump
        except ImportError:
            raise ImportError(""" ERROR: Could not find the GeoJSON Python library.""")

        collection = FeatureCollection([])

        for j in range(self._nrows):
            for i in range(self._ncols):

                cellVertexes = self.getCellVertexes(i, j)

                collection.features.append(
                    Feature(
                        geometry = Polygon([[
                            cellVertexes[0],
                            cellVertexes[1],
                            cellVertexes[2],
                            cellVertexes[3],
                            cellVertexes[4],
                            cellVertexes[5],
                            cellVertexes[0]
                           ]]),
                        properties = {self.__value_field: str(self._mesh[i][j])}))

        with open(outputFilePath, 'w') as fp:
            dump(collection, fp)

    def getCellVertexes(self, i, j):
        """
         Edge coordinates of an hexagon centered in (x,y) having a side of d:

                   [x-d/2, y+sqrt(3)*d/2]   [x+d/2, y+sqrt(3)*d/2]

          [x-d, y]                                                 [x+d, y]

                   [x-d/2, y-sqrt(3)*d/2]   [x+d/2, y-sqrt(3)*d/2]
        """

        # Using unrotated centroid coordinates to avoid an extra computation
        x,y = self._getUnrotatedCellCentroidCoords(i, j)

        return [
            self.rotatePoint(x - self._side,       y                 ),
            self.rotatePoint(x - self._side / 2.0, y - self._hexPerp ),
            self.rotatePoint(x + self._side / 2.0, y - self._hexPerp ),
            self.rotatePoint(x + self._side,       y                 ),
            self.rotatePoint(x + self._side / 2.0, y + self._hexPerp ),
            self.rotatePoint(x - self._side / 2.0, y + self._hexPerp ),
            ]

    def rotatePoint(self, pointX, pointY):
        """
        Rotates a point relative to the mesh origin by the angle specified in the angle property.
        Uses the angle formed between the segment linking the point of interest to the origin and
        the parallel intersecting the origin. This angle is called beta in the code.
        """
        if(self.angle == 0 or self.angle == None):
            return(pointX, pointY)

        # 1. Compute the segment length
        length = math.sqrt((pointX - self.xll) ** 2 + (pointY - self.yll) ** 2)

        # 2. Compute beta
        beta = math.acos((pointX - self.xll) / length)
        if(pointY < self.yll):
            beta = math.pi * 2 - beta

        # 3. Compute offsets
        offsetX = math.cos(beta) * length - math.cos(self._angle_rd + beta) * length
        offsetY = math.sin(self._angle_rd + beta) * length - math.sin(beta) * length
        return (pointX - offsetX, pointY + offsetY)


import hashlib
from datetime import datetime
from font_loader import FontLoader
from session import _Session
from pdfdocument import PDFDocument

class PDFLite(object):

    """ PDF generator, this class creates a document,
        session object, and the PDF outline.

        There are some overall pdf options to set, like
        the meta-data in information (this won't print
        anywhere in the document, but can be seen in
        Properties, in Adobe reader.)

        When using this module, start by creating an
        instance of PDFLite, then request the document
        object with get_document. Make your inputs to that
        object, and finish by closing the PDFLite.

    """

    def __init__(self, filepath, orientation="P", layout="letter", font_list=None, font_dir=None):
        if font_dir is not None:
            FontLoader.load_from_dir(font_dir)
        elif font_list is not None:
            FontLoader.load_from_list(font_list)
        else:
            FontLoader.load_fonts()

        self.filepath = filepath
        self.destination = None

        if hasattr(self.filepath, 'write'):
            self.destination = self.filepath
        elif self.filepath == 'string':
            self.destination = 'string'

        # Create session and document objects
        self.session = _Session(self)
        self.document = PDFDocument(self.session, orientation, layout)

        # Full width display mode default
        self.set_display_mode()
        # Set default PDF version number
        self.pdf_version = '1.7'

        # Initialize PDF information
        self.set_information()
        self.set_compression()

    def set_compression(self, value=True):
        # False is easier to read with a text editor.
        self.session._set_compression(value)

    def get_document(self):
        return self.document

    def set_information(self, title=None, subject=None, author=None, keywords=None, creator=None):
        """ Convenience function to add property info, can set any attribute and leave the others blank, it won't over-write
            previously set items. """
        info_dict = {"title": title, "subject": subject,
                     "author": author, "keywords": keywords,
                     "creator": creator}

        for att, value in info_dict.iteritems():
            if hasattr(self, att):
                if value:
                    setattr(self, att, value)
            else:
                setattr(self, att, None)

    def set_display_mode(self, zoom='fullpage', layout='continuous'):
        """ Set the default viewing options. """
        self.zoom_options = ["fullpage", "fullwidth", "real", "default"]
        self.layout_options = ["single", "continuous", "two", "default"]

        if zoom in self.zoom_options or (isinstance(zoom, int) and 0 < zoom <= 100):
            self.zoom_mode = zoom
        else:
            raise Exception('Incorrect zoom display mode: ' + zoom)

        if layout in self.layout_options:
            self.layout_mode = layout
        else:
            raise Exception('Incorrect layout display mode: ' + layout)

    def close(self):
        """ Prompt the objects to output pdf code, and save to file. """
        self.document._set_page_numbers()
        # Places header, pages, page content first.
        self._put_header()
        self._put_pages()
        self._put_resources()
        # Information object
        self._put_information()
        # Catalog object
        self._put_catalog()
        # Cross-reference object
        #self._put_cross_reference()
        # Trailer object
        self._put_trailer()

        if hasattr(self.destination, "write"):
            output = self._output_to_io()
        elif self.destination == 'string':
            output = self._output_to_string()
        else:
            self._output_to_file()
            output = None
        return output

    # Private Methods for building the PDF
    def _put_header(self):
        """ Standard first line in a PDF. """
        self.session._out('%%PDF-%s' % self.pdf_version)
        if self.session.compression:
            self.session.buffer += '%' + chr(235) + chr(236) + chr(237) + chr(238) + "\n"

    def _put_pages(self):
        """ First, the Document object does the heavy-lifting for the
            individual page objects and content.

            Then, the overall "Pages" object is generated.

        """
        self.document._get_orientation_changes()
        self.document._output_pages()

        # Pages Object, provides reference to page objects (Kids list).
        self.session._add_object(1)
        self.session._out('<</Type /Pages')
        kids = '/Kids ['
        for i in xrange(0, len(self.document.pages)):
            kids += str(3 + 2 * i) + ' 0 R '
        self.session._out(kids + ']')
        self.session._out('/Count %s' % len(self.document.pages))

        # Overall size of the default PDF page
        self.session._out('/MediaBox [0 0 %.2f %.2f]' %
                          (self.document.page.width,
                           self.document.page.height))
        self.session._out('>>')
        self.session._out('endobj')

    def _put_resources(self):
        """ Resource objects can be used several times throughout the document,
        but the pdf code defining them are all defined here.

        """
        self._put_fonts()
        self._put_images()

        # Resource dictionary
        self._put_resource_dict()

    def _put_fonts(self):
        """ Fonts definitions objects.

        """
        self.document._output_fonts()

    def _put_images(self):
        """ Image definition objects.

        """
        self.document._output_images()

    def _put_resource_dict(self):
        """ Creates PDF reference to resource objects.

        """
        self.session._add_object(2)
        self.session._out('<<')
        self.session._out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]')
        self.session._out('/Font <<')
        for font in self.document.fonts:
            self.session._out('/F%s %s 0 R' % (font.index, font.number))
        self.session._out('>>')
        if self.document.images:
            self.session._out('/XObject <<')
            for image in self.document.images:
                self.session._out('/I%s %s 0 R' % (image.index, image.number))
            self.session._out('>>')
        self.session._out('>>')
        self.session._out('endobj')

    def _put_information(self):
        """PDF Information object."""
        self.session._add_object()
        self.session._out('<<')
        self.session._out('/Producer ' + self._text_to_string(
            'PDFLite, https://github.com/katerina7479'))
        if self.title:
            self.session._out('/Title ' + self._text_to_string(self.title))
        if self.subject:
            self.session._out('/Subject ' + self._text_to_string(self.subject))
        if self.author:
            self.session._out('/Author ' + self._text_to_string(self.author))
        if self.keywords:
            self.session._out('/Keywords ' +
                              self._text_to_string(self.keywords))
        if self.creator:
            self.session._out('/Creator ' + self._text_to_string(self.creator))
        self.session._out('/CreationDate ' + self._text_to_string(
            'D:' + datetime.now().strftime('%Y%m%d%H%M%S')))
        self.session._out('>>')
        self.session._out('endobj')

    def _put_catalog(self):
        """Catalog object."""
        self.session._add_object()
        self.session._out('<<')

        self.session._out('/Type /Catalog')
        self.session._out('/Pages 1 0 R')
        if self.zoom_mode == 'fullpage':
            self.session._out('/OpenAction [3 0 R /Fit]')
        elif self.zoom_mode == 'fullwidth':
            self.session._out('/OpenAction [3 0 R /FitH null]')
        elif self.zoom_mode == 'real':
            self.session._out('/OpenAction [3 0 R /XYZ null null 1]')
        elif not isinstance(self.zoom_mode, basestring):
            self.session._out(
                '/OpenAction [3 0 R /XYZ null null ' +
                (self.zoom_mode / 100) + ']')

        if self.layout_mode == 'single':
            self.session._out('/PageLayout /SinglePage')
        elif self.layout_mode == 'continuous':
            self.session._out('/PageLayout /OneColumn')
        elif self.layout_mode == 'two':
            self.session._out('/PageLayout /TwoColumnLeft')
        self.session._out('>>')
        self.session._out('endobj')

    def _put_cross_reference(self):
        """ Cross Reference Object, calculates
            the position in bytes to the start
            (first number) of each object in
            order by number (zero is special)
            from the beginning of the file.

        """
        self.session._out('xref')
        self.session._out('0 %s' % len(self.session.objects))
        self.session._out('0000000000 65535 f ')
        for obj in self.session.objects:
            if isinstance(obj, basestring):
                pass
            else:
                self.session._out('%010d 00000 n ' % obj.offset)

    def _put_trailer(self):
        """ Final Trailer calculations, and end-of-file
            reference.

        """
        startxref = len(self.session.buffer)

        self._put_cross_reference()

        md5 = hashlib.md5()
        md5.update(datetime.now().strftime('%Y%m%d%H%M%S'))
        try:
            md5.update(self.filepath)
        except TypeError:
            pass
        if self.title:
            md5.update(self.title)
        if self.subject:
            md5.update(self.subject)
        if self.author:
            md5.update(self.author)
        if self.keywords:
            md5.update(self.keywords)
        if self.creator:
            md5.update(self.creator)

        objnum = len(self.session.objects)
        self.session._out('trailer')
        self.session._out('<<')
        self.session._out('/Size %s' % objnum)
        self.session._out('/Root %s 0 R' % (objnum - 1))
        self.session._out('/Info %s 0 R' % (objnum - 2))
        self.session._out('/ID [ <%s> <%s>]' % (md5.hexdigest(),md5.hexdigest()))
        self.session._out('>>')
        self.session._out('startxref')
        self.session._out(startxref)
        self.session._out('%%EOF')

    def _output_to_file(self):
        """ Save to filepath specified on
            init. (Will throw an error if
            the document is already open).

        """
        f = open(self.filepath, 'wb')
        if not f:
            raise Exception('Unable to create output file: ', self.filepath)
        f.write(self.session.buffer)
        f.close()

    def _output_to_string(self):
        return self.session.buffer

    def _output_to_io(self):
        self.destination.write(self.session.buffer)
        return self.destination

    def _text_to_string(self, text):
        """ Provides for escape characters and converting to
            pdf text object (pdf strings are in parantheses).
            Mainly for use in the information block here, this
            functionality is also present in the text object.

        """
        if text:
            for i,j in [("\\","\\\\"),(")","\\)"),("(", "\\(")]:
                text = text.replace(i, j)
            text = "(%s)" % text
        else:
            text = 'None'
        return text


"""A collection of cycle detectors.

Cycle detectors are classes of algorithms solve the halting problem
for finite state autonoma; specifically they will tell if a pure
function x = F(x') that maps from one value to the other within a
finite range of values |x| ever reaches a defined "stop" state, or
continues to cycle forever.  For this function `F(x)`; the only state
it may consider in making its decision is X.

Cycles in finite state machines are exclusively of the form
<x0, x1, x2 ... > <y0, y1, y2, y3, ... yn> where

So for example the sequence 1, 2, 3, 4, 5 would be an example of a
terminating sequence.  This will be correctly detected as being finite
by these algorithms.

The sequence 1, 2, 3, 4, 5, 3, 4, 5, 3, 4, 5 ... is an infinite
sequence repeating 3, 4, 5 after an initial 1, and 2.  This sequence
will eventually by identified by all algorithms in this module as

The sequence 1, 2, 2, 2, 2, 3 is an example of a sequence that is
finite, but not unique.  The behavior of these algorithms on this
example are undefined.

The sequence 1, 2, 3, 4, 5, 3, 4, 5, 6, 3, 4, 5, 6, 7, ... where the
repeated section grows in length by one with each pass is not
generate by a finite state machine; this requires a machine with
unbounded memory.  The behavior of these algorithms on this class of
sequences is undefined because the set of values in the function's
range is not finite; this is not a finite state autonoma.

All cycle detection algorithms in this module share a common API; they
accept either a pair of named parameters `f` and `start` or an
algorithm specific number of instances of iterable instances, each
separately returning the same sequence.

If named parameters `f` and `start` are provided, the function `f` is
used to compute the transition from any state `X_n` to `X_(n+1)` and
`start` contains the initial state.

If positional parameters are provided, each must contain an
independent but identical implementation of the sequence being worked
with.  The number that must be provided is specific to the algorithm.

In all cases, the function will generate items from the sequence up to
the point the sequence terminates or a cycle is detected.

If a cycle is detected `CycleDetected` will be raised; if called with
`f` and `start` instead of multiple iterators, attributes `period` and
`first` containing the period of the cycle and the position of the first
repetition respectively.

"""

import functools

class CycleDetected(Exception):

    def __init__(self, period=None, first=None, *args, **kwargs):
        self.period = period
        self.first = first
        super(CycleDetected, self).__init__(*args, **kwargs)

def f_generator(f, value):
    try:
        while value is not None:
            yield value
            value = f(value)
    except StopIteration:
        pass

def _remove_kwargs_from_args(name, args, kwargs):
    try:
        value = kwargs.pop(name)
        try:
            args.remove(value)
        except ValueError:
            pass
    except LookupError:
        value = None
    return value

def _convert_f_to_seqs(n, variable=False):
    def __convert_f_t_seqs(wrapped):
        @functools.wraps(wrapped)
        def wrapper(*seqs, **kwargs):
            seqs = list(seqs)
            f, start, m = (None, None, None)
            if 'f' in kwargs and 'start' in kwargs:
                f = _remove_kwargs_from_args('f', seqs, kwargs)
                start = _remove_kwargs_from_args('start', seqs, kwargs)
                m = _remove_kwargs_from_args('m', seqs, kwargs)
            key = _remove_kwargs_from_args(
                'key', seqs, kwargs) or (lambda x: x)

            for kwarg in kwargs:
                raise TypeError(
                    "%s() got an unexpected keyword argument %r" % (f.func_name, kwarg,))

            if seqs:
                if f is not None:
                    raise TypeError(
                        ('You may call {f.func_name} with positional parameters or '
                         'kwargs f and start, but not both').format(f=wrapped))
                if len(seqs) != (m or n):
                    raise TypeError(
                        ('{f.func_name}(*seqs=..., m={n}) must be either `m` '
                         'items in length, and all elements must independently '
                         'generate the same sequence.  *seqs={seqs}').format(
                             f=wrapped, n=m or n, seqs=seqs))
            else:
                if not f:
                    raise TypeError(
                        ('You must call {f.func_name} with either positional '
                         'parameaters or kwargs f and start').format(f=wrapped))

                seqs = [f_generator(f, start) for _ in xrange(m or n)]
            if m is None:
                return wrapped(seqs, f=f, start=start, key=key)
            return wrapped(seqs, f=f, start=start, m=m or n, key=key)
        return wrapper
    return __convert_f_t_seqs

@_convert_f_to_seqs(2)
def floyd(seqs, f=None, start=None, key=lambda x: x):
    """Floyd's Cycle Detector.

    See help(cycle_detector) for more context.

    Args:

      *args: Two iterators issueing the exact same sequence:
      -or-
      f, start: Function and starting state for finite state machine

    Yields:

      Values yielded by sequence_a if it terminates, undefined if a
      cycle is found.

    Raises:

      CycleFound if exception is found; if called with f and `start`,
      the parametres `first` and `period` will be defined indicating
      the offset of start of the cycle and the cycle's period.
    """

    tortise, hare = seqs

    yield hare.next()
    tortise_value = tortise.next()
    hare_value = hare.next()
    while hare_value != tortise_value:
        yield hare_value
        yield hare.next()
        hare_value = hare.next()
        tortise_value = tortise.next()

    if f is None:
        raise CycleDetected()

    hare_value = f(hare_value)
    first = 0
    tortise_value = start
    while key(tortise_value) != key(hare_value):
        tortise_value = f(tortise_value)
        hare_value = f(hare_value)
        first += 1

    period = 1
    hare_value = f(tortise_value)
    while key(tortise_value) != key(hare_value):
        hare_value = f(hare_value)
        period += 1

    raise CycleDetected(period=period, first=first)

@_convert_f_to_seqs(1)
def naive(seqs, f=None, start=None, key=lambda x: x):
    """Naive cycle detector

    See help(cycle_detector) for more context.

    Args:

      sequence: A sequence to detect cyles in.

      f, start: Function and starting state for finite state machine

    Yields:

      Values yielded by sequence_a if it terminates, undefined if a
      cycle is found.

    Raises:

      CycleFound if exception is found.  Will always generate a first
      and period value no matter which of the `seqs` or `f` interface
      is used.
    """
    history = {}
    for step, value in enumerate(seqs[0]):
        keyed = key(value)
        yield value
        if keyed in history:
            raise CycleDetected(
                first=history[keyed], period=step - history[keyed])
        history[keyed] = step

@_convert_f_to_seqs(1)
def gosper(seqs, f=None, start=None, key=lambda x: x):
    """Gosper's cycle detector

    See help(cycle_detector) for more context.

    Args:

      sequence: A sequence to detect cyles in.

      f, start: Function and starting state for finite state machine

    Yields:

      Values yielded by sequence_a if it terminates, undefined if a
      cycle is found.

    Raises:

      CycleFound if exception is found.  Unlike Floyd and Brent's,
      Gosper's can only detect period of a cycle.  It cannot
      compute the first position
    """

    tab = []
    for c, value in enumerate(seqs[0], start=1):
        yield value
        try:
            e = tab.index(key(value))
            raise CycleDetected(
                period=c - ((((c >> e) - 1) | 1) << e))
        except ValueError:
            try:
                tab[(c ^ (c - 1)).bit_length() - 1] = key(value)
            except IndexError:
                tab.append(value)

@_convert_f_to_seqs(2)
def brent(seqs, f=None, start=None, key=lambda x: x):
    """Brent's Cycle Detector.

    See help(cycle_detector) for more context.

    Args:

      *args: Two iterators issueing the exact same sequence:
      -or-
      f, start: Function and starting state for finite state machine

    Yields:

      Values yielded by sequence_a if it terminates, undefined if a
      cycle is found.

    Raises:

      CycleFound if exception is found; if called with f and `start`,
      the parametres `first` and `period` will be defined indicating
      the offset of start of the cycle and the cycle's period.
    """

    power = period = 1
    tortise, hare = seqs

    yield hare.next()
    tortise_value = tortise.next()
    hare_value = hare.next()
    while key(tortise_value) != key(hare_value):
        yield hare_value
        if power == period:
            power *= 2
            period = 0
            if f:
                tortise = f_generator(f, hare_value)
                tortise_value = tortise.next()
            else:
                while tortise_value != hare_value:
                    tortise_value = tortise.next()
        hare_value = hare.next()
        period += 1

    if f is None:
        raise CycleDetected()

    first = 0
    tortise_value = hare_value = start
    for _ in xrange(period):
        hare_value = f(hare_value)

    while key(tortise_value) != key(hare_value):
        tortise_value = f(tortise_value)
        hare_value = f(hare_value)
        first += 1
    raise CycleDetected(period=period, first=first)


class PDFCursor(object):

    def __init__(self, x=20, y=20, boundary_flag=False):
        """ Cursor object. Initialize with placement of cursor.
            This cursor places the origin (0,) at the upper left hand
            side of the page. X increases horizontally, and y increases
            vertically down the page. Note: This is different from the way
            Adobe sets the origin. They place the origin at the lower
            left hand corner, and y increases from the bottom to the
            top of the page. I feel that their way required more explanation,
            and was less intuitive. Therefore, I have provided the y_prime
            property for use in outputting the cursor data at document
            generation.

        """
        self._x = 0
        self._y = 0

        # Call to set default boundaries and increments.
        if boundary_flag is True:
            self.set_bounds(xmin=x, ymin=y)
        else:
            self.set_bounds()
        self.set_deltas()

        self.x = x
        self.y = y

    def __repr__(self):
        return "(%s, %s)" % (self.x, self.y)

    # May be used to change boundaries, and small offsets.
    def set_bounds(self, xmin=0, ymin=0, xmax=612, ymax=792, ymaxmax=792):
        self.xmin = xmin
        self.ymin = ymin
        self.xmax = xmax
        self.ymax = ymax
        self.ymaxmax = ymaxmax

        # Setter called in case original placement
        # of cursor was out of newly set bounds
        self.x = self.x
        self.y = self.y

    def set_deltas(self, dx=2, dy=2):
        self.dx = dx
        self.dy = dy

    # Setters / getters for x & y
    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value=0):
        # If in left margin, sets to minimum value.
        if value <= self.xmin:
            self._x = self.xmin
        else:
            self._x = value

    @property
    def y(self):
        return self._y

    @y.setter
    def y(self, value=0):
        if value <= self.ymin:
            self._y = self.ymin
        else:
            self._y = value

    @property
    def y_prime(self):
        return self.ymaxmax - self.y

    def x_fit(self, test_length):
        """ Test to see if the line can has enough space for the given length. """
        if (self.x + test_length) >= self.xmax:
            return False
        else:
            return True

    def y_fit(self, test_length):
        """ Test to see if the page has enough space for the given text height. """
        if (self.y + test_length) >= self.ymax:
            return False
        else:
            return True

    @property
    def x_left(self):
        """ Space remaining on line. """
        return self.xmax - self.x

    @property
    def y_left(self):
        """ Space remaining on page. """
        return self.ymax - self.y

    # Equality methods
    @staticmethod
    def _is_coordinate(test_ordinate):
        """ Test to see if object is a proper PDFCursor. """
        if isinstance(test_ordinate, PDFCursor):
            return True
        else:
            raise Exception("Not a valid PDFCursor object")

    def x_is_greater_than(self, test_ordinate):
        """ Comparison for x coordinate"""
        self._is_coordinate(test_ordinate)
        if self.x > test_ordinate.x:
            return True
        else:
            return False

    def y_is_greater_than(self, test_ordinate):
        """Comparison for y coordinate"""
        self._is_coordinate(test_ordinate)
        if self.y > test_ordinate.y:
            return True
        else:
            return False

    def __gt__(self, test_ordinate):
        """ Comparison for both ordinates, prioritizing the y direction. """
        self._is_coordinate(test_ordinate)
        if self.y > test_ordinate.y:
            return True
        elif self.y == test_ordinate.y:
            if self.x > test_ordinate.x:
                return True
        else:
            return False

    def __eq__(self, test_ordinate):
        """ Test to see if coordinates are equal to each other. """
        self._is_coordinate(test_ordinate)
        if self.y == test_ordinate.y:
            if self.x == test_ordinate.x:
                return True
        else:
            return False

    def __lt__(self, test_ordinate):
        """ Comparison for both ordinates, prioritizing the y direction."""
        self._is_coordinate(test_ordinate)
        if self.y < test_ordinate.y:
            return True
        elif self.y == test_ordinate.y:
            if self.x < test_ordinate.x:
                return True
        else:
            return False

    # Returns a new cursor
    def copy(self):
        """ Create a copy, and return it."""
        new_cursor = self.__class__(self.x, self.y)
        new_cursor.set_bounds(self.xmin, self.ymin, self.xmax, self.ymax, self.ymaxmax)
        new_cursor.set_deltas(self.dx, self.dy)
        return new_cursor

    def __add__(self, add_ordinate):
        """ Add two coordinates to each other, return new coordinate. """
        self._is_coordinate(add_ordinate)
        x = self.x + add_ordinate.x
        y = self.y + add_ordinate.y
        return self.__class__(x, y)

    def __sub__(self, sub_ordinate):
        """ Subtract two coordinates from each other, suppress negatives. """
        self._is_coordinate(sub_ordinate)
        x = abs(self.x - sub_ordinate.x)
        y = abs(self.y - sub_ordinate.y)
        return self.__class__(x, y)

    def __mul__(self, scale):
        """ Return scaled coordinate"""
        scale = float(scale)
        x = self.x * scale
        y = self.y * scale
        return self.__class__(x, y)

    def invert(self):
        """ Return inverse coordinate. """
        return self.__class__(self.y, self.x)

    # Changes this cursor
    def x_plus(self, dx=None):
        """ Mutable x addition. Defaults to set delta value. """
        if dx is None:
            self.x += self.dx
        else:
            self.x = self.x + dx

    def y_plus(self, dy=None):
        """ Mutable y addition. Defaults to set delta value. """
        if dy is None:
            self.y += self.dy
        else:
            self.y = self.y + dy

    def x_reset(self):
        """ Resets x to xmin."""
        self.x = self.xmin

    def y_reset(self):
        """ Resets y to ymin."""
        self.y = self.ymin

    def x_shift_left(self, delta):
        self.xmin = self.xmin + delta
        self.x = self.x + delta