##############################################################################
#
# Copyright (c) 2007 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Page Drawing Related Element Processing
"""
import reportlab.pdfgen.canvas  # noqa: F401 imported but unused

from z3c.rml import attr
from z3c.rml import chart
from z3c.rml import directive
from z3c.rml import flowable
from z3c.rml import form
from z3c.rml import interfaces
from z3c.rml import occurence
from z3c.rml import page
from z3c.rml import special
from z3c.rml import stylesheet  # noqa: F401 imported but unused


class IShape(interfaces.IRMLDirectiveSignature):
    """A shape to be drawn on the canvas."""

    x = attr.Measurement(
        title='X-Coordinate',
        description=('The X-coordinate of the lower-left position of the '
                     'shape.'),
        required=True)

    y = attr.Measurement(
        title='Y-Coordinate',
        description=('The Y-coordinate of the lower-left position of the '
                     'shape.'),
        required=True)

    fill = attr.Boolean(
        title='Fill',
        description='A flag to specify whether the shape should be filled.',
        required=False)

    stroke = attr.Boolean(
        title='Stroke',
        description=("A flag to specify whether the shape's outline should "
                     "be drawn."),
        required=False)


class CanvasRMLDirective(directive.RMLDirective):
    callable = None
    attrMapping = None

    def process(self):
        kwargs = dict(self.getAttributeValues(attrMapping=self.attrMapping))
        canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
        getattr(canvas, self.callable)(**kwargs)


class ISaveState(interfaces.IRMLDirectiveSignature):
    """Saves the current canvas state."""


class SaveState(CanvasRMLDirective):
    signature = ISaveState
    callable = 'saveState'


class IRestoreState(interfaces.IRMLDirectiveSignature):
    """Saves the current canvas state."""


class RestoreState(CanvasRMLDirective):
    signature = IRestoreState
    callable = 'restoreState'


class IDrawString(interfaces.IRMLDirectiveSignature):
    """Draws a simple string (left aligned) onto the canvas at the specified
    location."""

    x = attr.Measurement(
        title='X-Coordinate',
        description=('The X-coordinate of the lower-left position of the '
                     'string.'),
        required=True)

    y = attr.Measurement(
        title='Y-Coordinate',
        description=('The Y-coordinate of the lower-left position of the '
                     'string.'),
        required=True)

    text = attr.RawXMLContent(
        title='Text',
        description=('The string/text that is put onto the canvas.'),
        required=True)


class DrawString(CanvasRMLDirective, special.TextFlowables):
    signature = IDrawString
    callable = 'drawString'

    def process(self):
        canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
        kwargs = dict(self.getAttributeValues(attrMapping=self.attrMapping))
        kwargs['text'] = self._getText(self.element, canvas).strip()
        getattr(canvas, self.callable)(**kwargs)


class IDrawRightString(IDrawString):
    """Draws a simple string (right aligned) onto the canvas at the specified
    location."""


class DrawRightString(DrawString):
    signature = IDrawRightString
    callable = 'drawRightString'


class IDrawCenteredString(IDrawString):
    """Draws a simple string (centered aligned) onto the canvas at the
    specified location."""


class DrawCenteredString(DrawString):
    signature = IDrawCenteredString
    callable = 'drawCentredString'


class IDrawAlignedString(IDrawString):
    """Draws a simple string (aligned to the pivot character) onto the canvas
    at the specified location."""

    pivotChar = attr.Text(
        title='Text',
        description=('The string/text that is put onto the canvas.'),
        min_length=1,
        max_length=1,
        default='.',
        required=True)


class DrawAlignedString(DrawString):
    signature = IDrawAlignedString
    callable = 'drawAlignedString'


class IEllipse(IShape):
    """Draws an ellipse on the canvas."""

    width = attr.Measurement(
        title='Width',
        description='The width of the ellipse.',
        required=True)

    height = attr.Measurement(
        title='Height',
        description='The height of the ellipse.',
        required=True)


class Ellipse(CanvasRMLDirective):
    signature = IEllipse
    callable = 'ellipse'
    attrMapping = {'x': 'x1', 'y': 'y1'}

    def process(self):
        kwargs = dict(self.getAttributeValues(attrMapping=self.attrMapping))
        canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
        # Convert width and height to end locations
        kwargs['x2'] = kwargs['x1'] + kwargs['width']
        del kwargs['width']
        kwargs['y2'] = kwargs['y1'] + kwargs['height']
        del kwargs['height']
        getattr(canvas, self.callable)(**kwargs)


class ICircle(IShape):
    """Draws a circle on the canvas."""

    radius = attr.Measurement(
        title='Radius',
        description='The radius of the circle.',
        required=True)


class Circle(CanvasRMLDirective):
    signature = ICircle
    callable = 'circle'
    attrMapping = {'x': 'x_cen', 'y': 'y_cen', 'radius': 'r'}


class IRectangle(IShape):
    """Draws an ellipse on the canvas."""

    width = attr.Measurement(
        title='Width',
        description='The width of the rectangle.',
        required=True)

    height = attr.Measurement(
        title='Height',
        description='The height of the rectangle.',
        required=True)

    round = attr.Measurement(
        title='Corner Radius',
        description='The radius of the rounded corners.',
        required=False)

    href = attr.Text(
        title='Link URL',
        description='When specified, the rectangle becomes a link to that'
                    ' URL.',
        required=False)

    destination = attr.Text(
        title='Link Destination',
        description=('When specified, the rectangle becomes a link to that '
                     'destination.'),
        required=False)


class Rectangle(CanvasRMLDirective):
    signature = IRectangle
    callable = 'rect'
    attrMapping = {'round': 'radius'}

    def process(self):
        if 'round' in self.element.keys():
            self.callable = 'roundRect'
        kwargs = dict(self.getAttributeValues(attrMapping=self.attrMapping))
        canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
        # Create a link
        url = kwargs.pop('href', None)
        if url:
            canvas.linkURL(
                url,
                (kwargs['x'],
                 kwargs['y'],
                 kwargs['x'] + kwargs['width'],
                 kwargs['y'] + kwargs['height']))
        dest = kwargs.pop('destination', None)
        if dest:
            canvas.linkRect(
                '',
                dest,
                (kwargs['x'],
                 kwargs['y'],
                 kwargs['x'] + kwargs['width'],
                 kwargs['y'] + kwargs['height']))

        # Render the rectangle
        getattr(canvas, self.callable)(**kwargs)


class IGrid(interfaces.IRMLDirectiveSignature):
    """A shape to be drawn on the canvas."""

    xs = attr.Sequence(
        title='X-Coordinates',
        description=('A sequence x-coordinates that represent the vertical '
                     'line positions.'),
        value_type=attr.Measurement(),
        required=True)

    ys = attr.Sequence(
        title='Y-Coordinates',
        description=('A sequence y-coordinates that represent the horizontal '
                     'line positions.'),
        value_type=attr.Measurement(),
        required=True)


class Grid(CanvasRMLDirective):
    signature = IGrid
    callable = 'grid'
    attrMapping = {'xs': 'xlist', 'ys': 'ylist'}


class ILines(interfaces.IRMLDirectiveSignature):
    """A path of connected lines drawn on the canvas."""

    linelist = attr.TextNodeGrid(
        title='Line List',
        description=('A list of lines coordinates to draw.'),
        value_type=attr.Measurement(),
        columns=4,
        required=True)


class Lines(CanvasRMLDirective):
    signature = ILines
    callable = 'lines'


class ICurves(interfaces.IRMLDirectiveSignature):
    """A path of connected bezier curves drawn on the canvas."""

    curvelist = attr.TextNodeGrid(
        title='Curve List',
        description=('A list of curve coordinates to draw.'),
        value_type=attr.Measurement(),
        columns=8,
        required=True)


class Curves(CanvasRMLDirective):
    signature = ICurves
    callable = 'bezier'

    def process(self):
        argset = self.getAttributeValues(valuesOnly=True)[0]
        canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
        for args in argset:
            getattr(canvas, self.callable)(*args)


class IImage(interfaces.IRMLDirectiveSignature):
    """Draws an external image on the canvas."""

    file = attr.Image(
        title='File',
        description=('Reference to the external file of the iamge.'),
        required=True)

    x = attr.Measurement(
        title='X-Coordinate',
        description=('The X-coordinate of the lower-left position of the '
                     'shape.'),
        required=True)

    y = attr.Measurement(
        title='Y-Coordinate',
        description=('The Y-coordinate of the lower-left position of the '
                     'shape.'),
        required=True)

    width = attr.Measurement(
        title='Width',
        description='The width of the image.',
        required=False)

    height = attr.Measurement(
        title='Height',
        description='The height of the image.',
        required=False)

    showBoundary = attr.Boolean(
        title='Show Boundary',
        description=('A flag determining whether a border should be drawn '
                     'around the image.'),
        default=False,
        required=False)

    preserveAspectRatio = attr.Boolean(
        title='Preserve Aspect Ratio',
        description=("A flag determining whether the image's aspect ration "
                     "should be conserved under any circumstances."),
        default=False,
        required=False)

    mask = attr.Color(
        title='Mask',
        description='The color mask used to render the image, or "auto" to use'
                    ' the alpha channel if available.',
        default='auto',
        required=False,
        acceptAuto=True)

    anchor = attr.Choice(
        title='Text Anchor',
        description='The position in the text to which the coordinates refer.',
        choices='''nw   n   ne
                w    c    e
                sw   s   se'''.split(),
        required=False)


class Image(CanvasRMLDirective):
    signature = IImage
    callable = 'drawImage'
    attrMapping = {'file': 'image'}

    def process(self):
        kwargs = dict(self.getAttributeValues(attrMapping=self.attrMapping))
        show = kwargs.pop('showBoundary')

        canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
        getattr(canvas, self.callable)(**kwargs)

        if show:
            width = kwargs.get('width', kwargs['image'].getSize()[0])
            height = kwargs.get('height', kwargs['image'].getSize()[1])
            canvas.rect(kwargs['x'], kwargs['y'], width, height)


class IPlace(interfaces.IRMLDirectiveSignature):
    """Draws a set of flowables on the canvas within a given region."""

    x = attr.Measurement(
        title='X-Coordinate',
        description=('The X-coordinate of the lower-left position of the '
                     'place.'),
        required=True)

    y = attr.Measurement(
        title='Y-Coordinate',
        description=('The Y-coordinate of the lower-left position of the '
                     'place.'),
        required=True)

    width = attr.Measurement(
        title='Width',
        description='The width of the place.',
        required=False)

    height = attr.Measurement(
        title='Height',
        description='The height of the place.',
        required=False)


class Place(CanvasRMLDirective):
    signature = IPlace

    def process(self):
        x, y, width, height = self.getAttributeValues(
            select=('x', 'y', 'width', 'height'), valuesOnly=True)
        y += height

        flows = flowable.Flow(self.element, self.parent)
        flows.process()

        canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
        for flow in flows.flow:
            flow.canv = canvas
            flowWidth, flowHeight = flow.wrap(width, height)
            if flowWidth <= width and flowHeight <= height:
                y -= flowHeight
                flow.drawOn(canvas, x, y)
                height -= flowHeight
            else:
                raise ValueError("Not enough space")


class IParam(interfaces.IRMLDirectiveSignature):
    """Sets one paramter for the text annotation."""

    name = attr.Text(
        title='Name',
        description='The name of the paramter.',
        required=True)

    value = attr.TextNode(
        title='Value',
        description=('The parameter value.'),
        required=True)


class Param(directive.RMLDirective):
    signature = IParam

    def process(self):
        args = dict(self.getAttributeValues())
        self.parent.params[args['name']] = args['value']


class ITextAnnotation(interfaces.IRMLDirectiveSignature):
    """Writes a low-level text annotation into the PDF."""
    occurence.containing(
        occurence.ZeroOrMore('param', IParam))

    contents = attr.FirstLevelTextNode(
        title='Contents',
        description='The PDF commands that are inserted as annotation.',
        required=True)


class TextAnnotation(CanvasRMLDirective):
    signature = ITextAnnotation
    factories = {'param': Param}

    paramTypes = {'escape': attr.Integer()}

    def process(self):
        contents = self.getAttributeValues(valuesOnly=True)[0]
        self.params = {}
        self.processSubDirectives()
        for name, type in self.paramTypes.items():
            if name in self.params:
                bound = type.bind(self)
                self.params[name] = bound.fromUnicode(self.params[name])
        canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
        canvas.textAnnotation(contents, **self.params)


class IMoveTo(interfaces.IRMLDirectiveSignature):
    """Move the path cursor to the specified location."""

    position = attr.TextNodeSequence(
        title='Position',
        description='Position to which the path pointer is moved to.',
        value_type=attr.Measurement(),
        min_length=2,
        max_length=2,
        required=True)


class MoveTo(directive.RMLDirective):
    signature = IMoveTo

    def process(self):
        args = self.getAttributeValues(valuesOnly=True)
        self.parent.path.moveTo(*args[0])


class ICurveTo(interfaces.IRMLDirectiveSignature):
    """Create a bezier curve from the current location to the specified one."""

    curvelist = attr.TextNodeGrid(
        title='Curve Specification',
        description='Describes the end position and the curve properties.',
        value_type=attr.Measurement(),
        columns=6,
        required=True)


class CurveTo(directive.RMLDirective):
    signature = ICurveTo

    def process(self):
        argset = self.getAttributeValues(valuesOnly=True)[0]
        for args in argset:
            self.parent.path.curveTo(*args)


class ICurvesTo(ICurveTo):
    pass


directive.DeprecatedDirective(
    ICurvesTo,
    'Available for ReportLab RML compatibility. Please use the "curveto" '
    'directive instead.')


class IPath(IShape):
    """Create a line path."""
    occurence.containing(
        occurence.ZeroOrMore('moveto', IMoveTo),
        occurence.ZeroOrMore('curveto', ICurveTo),
        occurence.ZeroOrMore('curvesto', ICurvesTo),
    )

    points = attr.TextNodeGrid(
        title='Points',
        description=('A list of coordinate points that define th path.'),
        value_type=attr.Measurement(),
        columns=2,
        required=True)

    close = attr.Boolean(
        title='Close Path',
        description=("A flag specifying whether the path should be closed."),
        default=False,
        required=False)

    clip = attr.Boolean(
        title='Clip Path',
        description=("A flag specifying whether the path should clip "
                     "overlapping elements."),
        default=False,
        required=False)


class Path(CanvasRMLDirective):
    signature = IPath
    factories = {
        'moveto': MoveTo,
        'curveto': CurveTo,
        'curvesto': CurveTo
    }

    def processPoints(self, text):
        if text.strip() == '':
            return
        bound = self.signature['points'].bind(self)
        for coords in bound.fromUnicode(text):
            self.path.lineTo(*coords)

    def process(self):
        kwargs = dict(self.getAttributeValues(ignore=('points',)))

        # Start the path and set the cursor to the start location.
        canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
        self.path = canvas.beginPath()
        self.path.moveTo(kwargs.pop('x'), kwargs.pop('y'))

        # Process the text before the first sub-directive.
        if self.element.text is not None:
            self.processPoints(self.element.text)
        # Handle each sub-directive.
        for directive_ in self.element.getchildren():
            if directive_.tag in self.factories:
                self.factories[directive_.tag](directive_, self).process()
            # If there is more text after sub-directive, process it.
            if directive_.tail is not None:
                self.processPoints(directive_.tail)

        if kwargs.pop('close', False):
            self.path.close()

        if kwargs.pop('clip', False):
            canvas.clipPath(self.path, **kwargs)
        else:
            canvas.drawPath(self.path, **kwargs)


class IFill(interfaces.IRMLDirectiveSignature):
    """Set the fill color."""

    color = attr.Color(
        title='Color',
        description=('The color value to be set.'),
        required=True)


class Fill(CanvasRMLDirective):
    signature = IFill
    callable = 'setFillColor'
    attrMapping = {'color': 'aColor'}


class IStroke(interfaces.IRMLDirectiveSignature):
    """Set the stroke/line color."""

    color = attr.Color(
        title='Color',
        description=('The color value to be set.'),
        required=True)


class Stroke(CanvasRMLDirective):
    signature = IStroke
    callable = 'setStrokeColor'
    attrMapping = {'color': 'aColor'}


class ISetFont(interfaces.IRMLDirectiveSignature):
    """Set the font name and/or size."""

    name = attr.Text(
        title='Font Name',
        description=('The name of the font as it was registered.'),
        required=True)

    size = attr.Measurement(
        title='Size',
        description=('The font size.'),
        required=True)

    leading = attr.Measurement(
        title='Leading',
        description=('The font leading.'),
        required=False)


class SetFont(CanvasRMLDirective):
    signature = ISetFont
    callable = 'setFont'
    attrMapping = {'name': 'psfontname'}


class ISetFontSize(interfaces.IRMLDirectiveSignature):
    """Set the font size."""

    size = attr.Measurement(
        title='Size',
        description=('The font size.'),
        required=True)

    leading = attr.Measurement(
        title='Leading',
        description=('The font leading.'),
        required=False)


class SetFontSize(CanvasRMLDirective):
    signature = ISetFontSize
    callable = 'setFontSize'


class IScale(interfaces.IRMLDirectiveSignature):
    """Scale the drawing using x and y scaling factors."""

    sx = attr.Float(
        title='X-Scaling-Factor',
        description=('The scaling factor applied on x-coordinates.'),
        default=1,
        required=False)

    sy = attr.Float(
        title='Y-Scaling-Factor',
        description=('The scaling factor applied on y-coordinates.'),
        default=1,
        required=False)


class Scale(CanvasRMLDirective):
    signature = IScale
    callable = 'scale'
    attrMapping = {'sx': 'x', 'sy': 'y'}


class ITranslate(interfaces.IRMLDirectiveSignature):
    """Translate the drawing coordinates by the specified x and y offset."""

    dx = attr.Measurement(
        title='X-Offset',
        description=('The amount to move the drawing to the right.'),
        required=True)

    dy = attr.Measurement(
        title='Y-Offset',
        description=('The amount to move the drawing upward.'),
        required=True)


class Translate(CanvasRMLDirective):
    signature = ITranslate
    callable = 'translate'


class IRotate(interfaces.IRMLDirectiveSignature):
    """Rotate the drawing counterclockwise."""

    degrees = attr.Measurement(
        title='Angle',
        description=('The angle in degrees.'),
        required=True)


class Rotate(CanvasRMLDirective):
    signature = IRotate
    callable = 'rotate'
    attrMapping = {'degrees': 'theta'}


class ISkew(interfaces.IRMLDirectiveSignature):
    """Skew the drawing."""

    alpha = attr.Measurement(
        title='Alpha',
        description=('The amount to skew the drawing in the horizontal.'),
        required=True)

    beta = attr.Measurement(
        title='Beta',
        description=('The amount to skew the drawing in the vertical.'),
        required=True)


class Skew(CanvasRMLDirective):
    signature = ISkew
    callable = 'skew'


class ITransform(interfaces.IRMLDirectiveSignature):
    """A full 2-D matrix transformation"""

    matrix = attr.TextNodeSequence(
        title='Matrix',
        description='The transformation matrix.',
        value_type=attr.Float(),
        min_length=6,
        max_length=6,
        required=True)


class Transform(CanvasRMLDirective):
    signature = ITransform

    def process(self):
        args = self.getAttributeValues(valuesOnly=True)
        canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
        canvas.transform(*args[0])


class ILineMode(interfaces.IRMLDirectiveSignature):
    """Set the line mode for the following graphics elements."""

    width = attr.Measurement(
        title='Width',
        description=('The line width.'),
        required=False)

    dash = attr.Sequence(
        title='Dash-Pattern',
        description=('The dash-pattern of a line.'),
        value_type=attr.Measurement(),
        required=False)

    miterLimit = attr.Measurement(
        title='Miter Limit',
        description=('The ???.'),
        required=False)

    join = attr.Choice(
        title='Join',
        description='The way lines are joined together.',
        choices=interfaces.JOIN_CHOICES,
        required=False)

    cap = attr.Choice(
        title='Cap',
        description='The cap is the desciption of how the line-endings look.',
        choices=interfaces.CAP_CHOICES,
        required=False)


class LineMode(CanvasRMLDirective):
    signature = ILineMode

    def process(self):
        kw = dict(self.getAttributeValues())
        canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
        if 'width' in kw:
            canvas.setLineWidth(kw['width'])
        if 'join' in kw:
            canvas.setLineJoin(kw['join'])
        if 'cap' in kw:
            canvas.setLineCap(kw['cap'])
        if 'miterLimit' in kw:
            canvas.setMiterLimit(kw['miterLimit'])
        if 'dash' in kw:
            canvas.setDash(kw['dash'])


class IBookmark(interfaces.IRMLDirectiveSignature):
    """
    This creates a bookmark to the current page which can be referred to with
    the given key elsewhere. (Used inside a page drawing.)
    """

    name = attr.Text(
        title='Name',
        description='The name of the bookmark.',
        required=True)

    fit = attr.Choice(
        title='Fit',
        description='The Fit Type.',
        choices=('XYZ', 'Fit', 'FitH', 'FitV', 'FitR'),
        required=False)

    zoom = attr.Float(
        title='Zoom',
        description='The zoom level when clicking on the bookmark.',
        required=False)

    x = attr.Measurement(
        title='X-Position',
        description='The x-position.',
        required=False)

    y = attr.Measurement(
        title='Y-Position',
        description='The y-position.',
        required=False)


class Bookmark(CanvasRMLDirective):
    signature = IBookmark

    def process(self):
        args = dict(self.getAttributeValues())
        canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
        args['left'], args['top'] = canvas.absolutePosition(
            args['x'], args['y'])
        canvas.bookmarkPage(**args)


class IPlugInGraphic(interfaces.IRMLDirectiveSignature):
    """Inserts a custom graphic developed in Python."""

    module = attr.Text(
        title='Module',
        description='The Python module in which the flowable is located.',
        required=True)

    function = attr.Text(
        title='Function',
        description=('The name of the factory function within the module '
                     'that returns the custom flowable.'),
        required=True)

    params = attr.TextNode(
        title='Parameters',
        description=('A list of parameters encoded as a long string.'),
        required=False)


class PlugInGraphic(CanvasRMLDirective):
    signature = IPlugInGraphic

    def process(self):
        modulePath, functionName, params = self.getAttributeValues(
            valuesOnly=True)
        module = __import__(modulePath, {}, {}, [modulePath])
        function = getattr(module, functionName)
        canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
        function(canvas, params)


class IDrawing(interfaces.IRMLDirectiveSignature):
    """A container directive for all directives that draw directly on the
    cnavas."""
    occurence.containing(
        # State Manipulation
        occurence.ZeroOrMore('saveState', ISaveState),
        occurence.ZeroOrMore('restoreState', IRestoreState),
        # String Drawing
        occurence.ZeroOrMore('drawString', IDrawString),
        occurence.ZeroOrMore('drawRightString', IDrawRightString),
        occurence.ZeroOrMore('drawCenteredString', IDrawCenteredString),
        occurence.ZeroOrMore('drawCentredString', IDrawCenteredString),
        occurence.ZeroOrMore('drawAlignedString', IDrawAlignedString),
        # Drawing Operations
        occurence.ZeroOrMore('ellipse', IEllipse),
        occurence.ZeroOrMore('circle', ICircle),
        occurence.ZeroOrMore('rect', IRectangle),
        occurence.ZeroOrMore('grid', IGrid),
        occurence.ZeroOrMore('lines', ILines),
        occurence.ZeroOrMore('curves', ICurves),
        occurence.ZeroOrMore('image', IImage),
        occurence.ZeroOrMore('place', IPlace),
        occurence.ZeroOrMore('textAnnotation', ITextAnnotation),
        occurence.ZeroOrMore('path', IPath),
        # State Change Operations
        occurence.ZeroOrMore('fill', IFill),
        occurence.ZeroOrMore('stroke', IStroke),
        occurence.ZeroOrMore('setFont', ISetFont),
        occurence.ZeroOrMore('setFontSize', ISetFontSize),
        occurence.ZeroOrMore('scale', IScale),
        occurence.ZeroOrMore('translate', ITranslate),
        occurence.ZeroOrMore('rotate', IRotate),
        occurence.ZeroOrMore('skew', ISkew),
        occurence.ZeroOrMore('transform', ITransform),
        occurence.ZeroOrMore('lineMode', ILineMode),
        # Form Field Elements
        occurence.ZeroOrMore('barCode', form.IBarCode),
        occurence.ZeroOrMore('textField', form.ITextField),
        occurence.ZeroOrMore('buttonField', form.IButtonField),
        occurence.ZeroOrMore('selectField', form.ISelectField),
        # Charts
        occurence.ZeroOrMore('barChart', chart.IBarChart),
        occurence.ZeroOrMore('barChart3D', chart.IBarChart3D),
        occurence.ZeroOrMore('linePlot', chart.ILinePlot),
        occurence.ZeroOrMore('linePlot3D', chart.ILinePlot3D),
        occurence.ZeroOrMore('pieChart', chart.IPieChart),
        occurence.ZeroOrMore('pieChart3D', chart.IPieChart3D),
        occurence.ZeroOrMore('spiderChart', chart.ISpiderChart),
        # Misc
        occurence.ZeroOrMore('bookmark', IBookmark),
        occurence.ZeroOrMore('plugInGraphic', IPlugInGraphic),
    )


class Drawing(directive.RMLDirective):
    signature = IDrawing

    factories = {
        # State Management
        'saveState': SaveState,
        'restoreState': RestoreState,
        # Drawing Strings
        'drawString': DrawString,
        'drawRightString': DrawRightString,
        'drawCenteredString': DrawCenteredString,
        'drawCentredString': DrawCenteredString,
        'drawAlignedString': DrawAlignedString,
        # Drawing Operations
        'ellipse': Ellipse,
        'circle': Circle,
        'rect': Rectangle,
        'grid': Grid,
        'lines': Lines,
        'curves': Curves,
        'image': Image,
        'place': Place,
        'textAnnotation': TextAnnotation,
        'path': Path,
        # Form Field Elements
        'barCode': form.BarCode,
        'textField': form.TextField,
        'buttonField': form.ButtonField,
        'selectField': form.SelectField,
        # State Change Operations
        'fill': Fill,
        'stroke': Stroke,
        'setFont': SetFont,
        'setFontSize': SetFontSize,
        'scale': Scale,
        'translate': Translate,
        'rotate': Rotate,
        'skew': Skew,
        'transform': Transform,
        'lineMode': LineMode,
        # Charts
        'barChart': chart.BarChart,
        'barChart3D': chart.BarChart3D,
        'linePlot': chart.LinePlot,
        'linePlot3D': chart.LinePlot3D,
        'pieChart': chart.PieChart,
        'pieChart3D': chart.PieChart3D,
        'spiderChart': chart.SpiderChart,
        # Misc
        'bookmark': Bookmark,
        'plugInGraphic': PlugInGraphic,
    }


class IPageDrawing(IDrawing):
    """Draws directly on the content of one page's canvas. Every call of this
    directive creates a new page."""

    occurence.containing(
        # 'mergePage': IMergePage,
        *IDrawing.getTaggedValue('directives'))


class PageDrawing(Drawing):
    signature = IDrawing

    factories = Drawing.factories.copy()
    factories.update({
        'mergePage': page.MergePage
    })

    def process(self):
        super(Drawing, self).process()
        canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
        canvas.showPage()


class IPageInfo(interfaces.IRMLDirectiveSignature):
    """Set's up page-global settings."""

    pageSize = attr.PageSize(
        title='Page Size',
        description=('The page size of all pages within this document.'),
        required=True)


class PageInfo(CanvasRMLDirective):
    signature = IPageInfo
    callable = 'setPageSize'
    attrMapping = {'pageSize': 'size'}
