# Sketch - A Python-based interactive drawing program
# Copyright (C) 1999, 2000 by Bernhard Herzog
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307	USA

###Sketch Config
#type = Import
#class_name = 'CMXLoader'
#rx_magic = '(?s)RIFF....CMX1'
#tk_file_type = ('Corel CMX', '.cmx')
#format_name = 'CMX1'
#unload = 1
#standard_messages = 1
###End

# The regular expression must allow . to match all characters including
# newline.

#
#       Import Filter for CMX files
#
# Status:
#
# The filter has support for 16 bit (generated by Corel Draw 5) and 32
# bit (generated by Corel Draw 6 and later) CMX files. Only little
# endian byte order is implemented.
#
# Supported for both variants:
#
# - Multipath Polybezier curves
# - Uniform fills, gradient fills (linear for both, linear, radial and
#   conical for 32bit)
# - Dashes
# - Layers and groups
# - Color models: RGB, Gray, CMYK, CMYK255
#
# Supported only for 32bit files:
#
# - Images, incomplete.
# - Containers a.k.a Powerclip in Corel Draw
# - Arrowheads, incomplete
# - Additional fills: color bitmap, monochrome bitmap (both incomplete)
#
# Unsupported as yet:
#
# - Color models: Pantone, HSB, and others
# - Lenses. (there is some experimental support for magnifying lenses
#   but that's deactivated)
# - Fills: texture, postscript pattern
# - Text
#


#
# The code here is really ugly. This is mostly due to Corel's CMX
# documentation, which is very incomplete and in some cases incorrect,
# so I have to reverse engineer some aspects of CMX files. Therefore the
# code in here is still quite experimental and propably buggy.
#


import sys, types
from math import pi
from struct import unpack, calcsize
import struct

from streamfilter import BinaryInput

from Sketch import CreatePath, ContSmooth, ContAngle, ContSymmetrical, \
     SolidPattern, EmptyPattern, LinearGradient, RadialGradient, \
     ConicalGradient, MultiGradient,\
     CreateRGBColor, CreateCMYKColor, Trafo, Point, Polar, Translation, \
     Scale, StandardColors, ImageTilePattern, ImageData, MaskGroup, \
     Arrow

from Sketch.warn import INTERNAL, warn_tb
from Sketch.load import GenericLoader, SketchLoadError, EmptyCompositeError
from Sketch.Lib import units


#
#	Generic RIFF stuff.
#
# Might be a good idea to put it into a separate module
#

RIFF = 'RIFF'
LIST = 'LIST'
compound_chunks = (RIFF, LIST)

class RiffEOF(Exception):
    pass

class ChunkHeader:

    def __init__(self, filepos, chunk_type, length, sub_type = ''):
        self.filepos = filepos
        self.chunk_type = chunk_type
        self.length = length
        self.sub_type = sub_type
        if sub_type:
            self.headersize = 12
            self.data_length = self.length - 4
        else:
            self.headersize = 8
            self.data_length = self.length
        self.data_start = self.filepos + self.headersize
        self.has_subchunks = self.sub_type != ''
        self.subchunks = None

    def SetSubchunks(self, chunks):
        self.subchunks = chunks


def read_chunk_header(file):
    # read the riff chunk header at the current position in file and
    # return a ChunkHeader instance containing the header data
    filepos = file.tell()
    data = file.read(8)
    if len(data) < 8:
        raise RiffEOF
    chunk_type, length = unpack('<4si', data)
    if length % 2 != 0:
        length = length + 1
    if chunk_type in compound_chunks:
        sub_type = file.read(4)
        if len(sub_type) < 4:
            raise RiffEOF
    else:
        sub_type = ''
    return ChunkHeader(filepos, chunk_type, length, sub_type)

#
#	CMX specific stuff
#


struct_cmxheader_start = ('32s'	# Id
                          '16s'	# OS
                          '4s'	# ByteOrder, 2 little, 4 big endian
                          '2s'	# coord size, 2 = 16bit, 4 = 32bit
                          '4s'	# major version
                          '4s'	# minor version
                          )
struct_cmxheader_end = ('<'
                        'H'	# Unit, 35 = mm, 64 = inch
                        'd'	# factor
                        'xxxx'	# option, unused
                        'xxxx'	# foreign key, unused
                        'xxxx'	# capability, unused
                        'l'	# index section, offset
                        'l'	# info section, offset
                        'l'	# thumbnail, offset (the docs differ here)
                        'l'	# bb_left
                        'l'	# bb_top
                        'l'	# bb_right
                        'l'	# bb_bottom
                        'l'	# tally
                        '64x'	# reserved
                        )

color_models = ('Invalid', 'Pantone', 'CMYK', 'CMYK255', 'CMY', 'RGB',
                'HSB', 'HLS', 'BW', 'Gray', 'YIQ255', 'LAB')
color_bytes = (0, 4, 4, 4, 4, 3, 4, 4, 1, 4, 1, 4)
color_palettes = ('Invalid', 'Truematch', 'PantoneProcess', 'PantoneSpot',
                  'Image', 'User', 'CustomFixed')

cmx_commands = {
    88: 'AddClippingRegion',
    94: 'AddGlobalTransform',
    22: 'BeginEmbedded',
    13: 'BeginGroup',
    11: 'BeginLayer',
    9:	'BeginPage',
    99: 'BeginParagraph',
    17: 'BeginProcedure',
    72: 'BeginTextGroup',
    70: 'BeginTextObject',
    20: 'BeginTextStream',
    101:'CharInfo',
    102:'Characters',
    90: 'ClearClipping',
    2:	'Comment',
    69: 'DrawImage',
    65: 'DrawChars',
    66: 'Ellipse',
    23: 'EndEmbedded',
    14: 'EndGroup',
    12: 'EndLayer',
    10: 'EndPage',
    100:'EndParagraph',
    18: 'EndSection',
    73: 'EndTextGroup',
    71: 'EndTextObject',
    21: 'EndTextStream',
    111:'JumpAbsolute',
    67: 'PolyCurve',
    92: 'PopMappingMode',
    104:'PopTint',
    91: 'PushMappingMode',
    103:'PushTint',
    68: 'Rectangle',
    89: 'RemoveLastClippingRegion',
    95: 'RestoreLastGlobalTransfo',
    85: 'SetCharStyle',
    93: 'SetGlobalTransfo',
    86: 'SimpleWideText',
    98: 'TextFrame'
}



class Outline:

    def __init__(self, style, screen, color, arrowheads, pen, dashes):
        self.style = style
        self.screen = screen
        self.color = color
        self.arrowheads = arrowheads
        self.pen = pen
        self.dashes = dashes


class CMXFile:

    def __init__(self, loader, file):
        self.file = file
        self.loader = loader
        self.tagged = 0
        self.colors = []
        self.screens = []
        self.dashes = []
        self.pens = []
        self.line_styles = []
        self.procindex = [None,]
        self.bitmapindex = [None,]
        self.embeddedindex = [None,]
        self.arrowindex = [None,]
        self.verbosity = 0
        self.angle_factor = 1
        self.pages = []

    def warn(self, message):
        self.loader.add_message(message)

    def _print(self, format, *args, **kw):
        if self.verbosity:
            if kw:
                text = format % kw
            elif args:
                text = format % args
            else:
                text = format
            sys.stderr.write(text)

    def read_header(self):
        self.file.seek(0)
        self.riff_header = h = read_chunk_header(self.file)
        self._print('%6d %s %s %d\n',
                    h.filepos, h.chunk_type, h.sub_type, h.data_length)

    def read_subchunks(self, header, indent = 1):
        bytesread = 0
        chunks = []
        self.file.seek(header.data_start)
        while bytesread < header.data_length:
            subheader = read_chunk_header(self.file)
            bytesread = bytesread + subheader.headersize
            self._print('%6d %s%s %s %d\n',
                        subheader.filepos, indent * ' ', subheader.chunk_type,
                        subheader.sub_type, subheader.data_length)
            if subheader.sub_type != '':
                subchunks = self.read_subchunks(subheader, indent + 1)
                subheader.SetSubchunks(subchunks)
            self.file.seek(subheader.data_start + subheader.data_length)
            bytesread = bytesread + subheader.data_length
            chunks.append(subheader)
        return chunks

    def read_16(self):
        read = self.file.read
        lo, hi = read(2)
        value = ord(lo) + 256 * ord(hi)
        if value > 0x7FFF:
            value = value - 0x010000
        return value

    def read_32(self):
        data = self.file.read(4)
        return int(unpack('<i', data)[0])

    def read_angle(self):
        angle = self.read_32()
        return angle * self.angle_factor

    def read_matrix(self):
        type = self.read_16()
        if type == 1: # identity
            return (1, 0, 0, 1, 0, 0)
        data = self.file.read(48)
        matrix = unpack('<dddddd', data)
        return tuple(matrix)

    def read_string(self):
        count = self.read_16()
        return self.read(count)

    def get_rectangle(data):
        return	unpack('<hhhh', self.file.read(8))

    def read_tag(self, supported):
        read = self.file.read
        tag = -1; data = ''
        while tag != 255 and tag not in supported:
            tag = read(1); 
            tag = ord(tag)
            if tag == 255:
                break
            size = ord(read(1)) + 256 * ord(read(1))
            data = read(size - 3)
        return tag, data

    def read_cont(self, chunk):
        self.file.seek(chunk.data_start)
        data = self.file.read(chunk.data_length)
        start_size = struct.calcsize(struct_cmxheader_start)
        end_size = struct.calcsize(struct_cmxheader_end)
        self.file_id, self.platform, byte_order, coord_size, major, minor \
            = unpack(struct_cmxheader_start, data[:start_size])
        self.byte_order = int(byte_order[:1])
        self.coord_size = int(coord_size[:1])
        self.version = float(major[:1])
        self.unit, self.coord_factor, index, info, thumbnail, \
            left, top, right, bottom, tally \
            = unpack(struct_cmxheader_end, data[start_size:])
        self.unit = int(self.unit) # unpack makes unit a long
                                    # (fixed in python 1.5.2)
        self.bbox = left, top, right, bottom
        if self.version == 2.0:
            self.tagged = 1
            self.angle_factor = pi / 180000000.0
        else:
            self.angle_factor = pi / 1800.0

        self._print('\nHeader\n------\n')
        fmt = '% 10s: %s\n'
        fmt2 = '% 10s: '
        self._print(fmt, 'ID', self.file_id)
        self._print(fmt, 'platform', self.platform)
        self._print(fmt2 + '%s %s\n', 'byte order',
                    (0, 0, 'little', '', 'big')[self.byte_order], 'endian')
        self._print(fmt2 + '%d %s\n', 'coord size',
                    (0, 0, 16, 0, 32)[self.coord_size], 'bit')
        self._print(fmt, 'version', self.version)
        self._print(fmt, 'unit', self.unit == 35 and 'mm' or 'inch')
        self._print(fmt, 'factor', self.coord_factor)
        self._print(fmt, 'bound.box', (left, top, right, bottom))
        self._print('\n')

    def append_color(self, colors, model, data):
        if model == 2: # CMYK
            c, m, y, k = map(ord, data)
            self._print(`c, m, y, k`)
            colors.append(CreateCMYKColor(c / 100.0, m / 100.0, y / 100.0,
                                          k /100.0))
        elif model == 3: # CMYK 255
            c, m, y, k = map(ord, data)
            self._print(`c, m, y, k`)
            colors.append(CreateCMYKColor(c / 255.0, m / 255.0, y / 255.0,
                                          k /255.0))
        elif model == 5: # RGB
            r, g, b = map(ord, data)
            self._print(`r, g, b`)
            colors.append(CreateRGBColor(r / 255.0, g / 255.0, b / 255.0))
        elif model == 9:
            g = ord(data)
            self._print('gray %d\n', g)
            colors.append(CreateRGBColor(g / 255.0, g / 255.0, g / 255.0))
        else:
            #XXX warn
            self.warn(_("Color model %s not implemented. Using black")
                      % color_models[model])
            colors.append(StandardColors.black)
            self._print(`data`)

    def read_colors(self, chunk):
        self._print('Colors\n------\n')
        colors = [None,]
        self.file.seek(chunk.data_start)
        count = self.read_16()
        self._print('%d %s\n', count, 'colors')
        if self.tagged:
            for i in range(count):
                tag = -1
                while tag != 255:
                    tag, data = self.read_tag((1,2))
                    if tag == 1: # color base
                        model, palette = map(ord, data)
                    elif tag == 2: # color description
                        self.append_color(colors, model, data)
                # 
                self._print('\n')
        else:
            read = self.file.read
            for i in range(count):
                model, palette = map(ord, read(2))
                self._print('%3d: %4s %-6s ', i, color_models[model],
                            color_palettes[palette])
                data = read(color_bytes[model])
                self.append_color(colors, model, data)
                self._print('\n')
        self._print('\n')
        return colors

    def append_screen(self, screens, data):
        spot, frequency, user, angle, overprint = unpack('<hHiiB', data)
        angle = angle * self.angle_factor
        screens.append((spot, frequency, angle, overprint))
        self._print('%3d spot %d frequency %d user %d angle %g'
                    ' overprint %d\n',
                    len(screens), spot, frequency, user, angle, overprint)

    def read_screens(self, chunk):
        self._print('Screens\n-------\n')
        screens = [None,]
        self.file.seek(chunk.data_start)
        count = self.read_16()
        self._print('%d %s\n', count, 'screens')
        if self.tagged:
            for i in range(count):
                tag = -1
                while tag != 255:
                    tag, data = self.read_tag((1, 2))
                    if tag == 1: # screen basic
                        self.append_screen(screens, data)
                    elif tag == 2: # screen ps function
                        pass
        else:
            read16 = self.read_16
            for i in range(count):
                data = self.file.read(13)
                spot = self.append_screen(screens, data)
                if spot == 3: # user defined
                    ps_function = self.read_string()
                else:
                    ps_function = ''
        self._print('\n')
        return screens

    def append_dashes(self, dashes, data):
        dashes.append(unpack('<' + (len(data) / 2) * 'h', data)) 
        self._print('%3d: %s\n', len(dashes), dashes[-1])

    def read_dot(self, chunk):
        self._print('Dashes\n------\n')
        dashes = [None,]
        self.file.seek(chunk.data_start)
        count = self.read_16()
        self._print('%d %s\n', count, 'dashes')
        if self.tagged:
            for i in range(count):
                tag = -1
                while tag != 255:
                    tag, data = self.read_tag((1,))
                    if tag == 1:
                        self.append_dashes(dashes, data[2:])
        else:
            read16 = self.read_16
            for i in range(count):
                num = read16()
                self.append_dashes(dashes, self.file.read(2 * num))
        self._print('\n')
        return dashes

    def append_pen(self, pens, data):
        if self.coord_size == 4:
            format = '<hiih';
        else:
            format = '<hhih'
        width, aspect, angle, matrix = unpack(format, data[:calcsize(format)])
        if matrix != 1:
            if self.tagged:
                data = data[-48:]
            else:
                data = self.file.read(48)
            matrix = unpack('<dddddd', data)
        else:
            matrix = (1, 0, 0, 1, 0, 0)
        pens.append((width, aspect, angle, matrix))
        self._print('%3d: %d %d %g %s\n', len(pens), width, aspect, angle,
                    matrix)

    def read_pen(self, chunk):
        self._print('Pens\n----\n')
        pens = [None,]
        self.file.seek(chunk.data_start)
        count = self.read_16()
        self._print('%d %s\n', count, 'pens')
        if self.tagged:
            for i in range(count):
                tag = -1
                while tag != 255:
                    tag, data = self.read_tag((1,))
                    if tag == 1:
                        self.append_pen(pens, data)
        else:
            read16 = self.read_16
            for i in range(count):
                data = self.file.read(10)
                self.append_pen(pens, data)
        self._print('\n')
        return pens

    def print_linestyle(self, style):
        spec, cap, join = style
        self._print('%s %s flags: ',
                    ('miter', 'round', 'square')[cap],
                    ('miter', 'round', 'bevel')[join])
        if spec & 0x01:
            self._print('none ')
        if spec & 0x02:
            self._print('solid ')
        if spec & 0x04:
            self._print('dashed ')
        if spec & 0x10:
            self._print('behind_fill ')
        if spec & 0x20:
            self._print('scale_pen')
        self._print('\n')

    def append_linestyle(self, styles, data):
        spec, capjoin = map(ord, data)
        join = (capjoin & 0xf0) >> 4
        cap = capjoin & 0x0f
        styles.append((spec, cap, join))
        self._print('%3d ', len(styles))
        self.print_linestyle((spec, cap, join))

    def read_linestyles(self, chunk):
        self._print('Line Styles\n-----------\n')
        styles = [None,]
        self.file.seek(chunk.data_start)
        count = self.read_16()
        self._print('%d %s\n', count, 'line styles')
        if self.tagged:
            for i in range(count):
                tag = -1
                while tag != 255:
                    tag, data = self.read_tag((1,))
                    if tag == 1:
                        self.append_linestyle(styles, data)
        else:
            read = self.file.read
            for i in range(count):
                self.append_linestyle(styles, self.file.read(2))
        self._print('\n')
        return styles

    def append_arrowhead(self, heads, data):
        head1, head2 = unpack('<hh', data)
        heads.append((head1, head2))
        self._print('%d: %d %d\n', len(heads) - 1, head1, head2)

    def read_arrowheads(self, chunk):
        self._print('Arrow Heads\n-----------\n')
        heads = [None,]
        self.file.seek(chunk.data_start)
        count = self.read_16()
        self._print('%d %s\n', count, 'arrow heads')
        # The arrow heads section does not seem to use tags
        for i in range(count):
            self.append_arrowhead(heads, self.file.read(4))
        self._print('\n')
        return heads

    def print_outline(self, outline):
        self._print('%s %s %s %s %s %s\n',
                    outline.style, outline.screen, outline.color,
                    outline.arrowheads, outline.pen, outline.dashes)

    def append_outline(self, outlines, data):
        style, screen, color, arrowheads, pen, dash = unpack('<hhhhhh', data)
        outlines.append(Outline(self.line_styles[style], screen,
                                self.colors[color], arrowheads,
                                self.pens[pen], self.dashes[dash]))

        self._print('%3d ', len(outlines))
        self.print_outline(outlines[-1])

    def read_outlines(self, chunk):
        self._print('Outlines\n--------\n')
        outlines = [None,]
        self.file.seek(chunk.data_start)
        count = self.read_16()
        self._print('%d %s\n', count, 'outlines')
        if self.tagged:
            for i in range(count):
                tag = -1
                while tag != 255:
                    tag, data = self.read_tag((1,))
                    if tag == 1:
                        self.append_outline(outlines, data)
        else:
            read16 = self.read_16
            for i in range(count):
                self.append_outline(outlines, self.file.read(12))
        self._print('\n')
        return outlines

    def read_procindex(self, chunk):
        self._print('Procedures\n----------\n')
        procindex = [None,]
        self.file.seek(chunk.data_start)
        count = self.read_16()
        self._print('%d %s\n', count, 'procedures')
        if self.version < 2:
            for i in range(count):
                reflist, offset = unpack('<ll', self.file.read(8))
                self._print('reflist %d, offset %d\n', reflist, offset)
                procindex.append((reflist, offset))
                self._print('\n')
        else:
            for i in range(count):
                length, reflist, offset = unpack('<hll', self.file.read(10))
                unknown = self.file.read(length - 8)
                self._print('length %d, reflist %d, offset %d, unknown %s\n',
                            length, reflist, offset, `unknown`)
                procindex.append((reflist, offset))
                self._print('\n')
        return procindex

    def read_indextable(self, chunk):
        self._print('Table\n-----\n')
        self.file.seek(chunk.data_start)
        count, length, idxtype = unpack('<hhh', self.file.read(6))
        self._print('index type %d, entry length %d\n', idxtype, length)
        self._print('%d %s\n', count, 'entries')
        index = None
        if idxtype == 5:
            self._print('Bitmap Index\n')
            index = self.bitmapindex
        elif idxtype == 6:
            self._print('Arrow Index\n')
            index = self.arrowindex
        if index is not None:
            if self.version < 2:
                pass
            else:
                for i in range(count):
                    data = self.file.read(length)
                    offset = unpack('<l', data[:4])[0]
                    self._print('offset %d\n', offset)
                    index.append(offset)
        self._print('\n')

    def read_embedded_index(self, chunk):
        self._print('Embedded Files\n--------------\n')
        self.file.seek(chunk.data_start)
        count = self.read_16()
        self._print('%d %s\n', count, 'entries')
        for i in range(count):
            length = self.read_16()
            data = self.file.read(length)
            offset, image_type = unpack('<lh', data[:6])
            self._print('offset %d, type %d\n', offset, image_type)
            self.embeddedindex.append((offset, image_type))
        self._print('\n')


    def read_index(self, chunk):
        self._print('Index\n-----\n')
        for subchunk in chunk.subchunks:
            if subchunk.chunk_type == 'ixpc':
                self.procindex = self.read_procindex(subchunk)
            elif subchunk.chunk_type == 'ixtl':
                self.read_indextable(subchunk)
            elif subchunk.chunk_type == 'ixef':
                self.read_embedded_index(subchunk)

    def process_chunks(self):
        chunkdict = {}
        for chunk in self.chunks:
            if chunk.sub_type:
                chunkdict[chunk.sub_type] = chunk
            elif chunk.chunk_type == 'page':
                self.pages.append(chunk)
            else:
                chunkdict[chunk.chunk_type] = chunk
        self.read_cont(chunkdict.get('cont'))
        for fcc, name, method in (('rclr', 'colors', 'read_colors'),
                                  ('rscr', 'screens', 'read_screens'),
                                  ('rdot', 'dashes', 'read_dot'),
                                  ('rpen', 'pens', 'read_pen'),
                                  ('rott', 'line_styles', 'read_linestyles'),
                                  ('rota', 'arrow_heads', 'read_arrowheads'),
                                  # rotl last as it references other attrs 
                                  ('rotl', 'outlines', 'read_outlines')):
            chunk = chunkdict.get(fcc)
            if chunk is not None:
                setattr(self, name, getattr(self, method)(chunk))
        self.read_index(chunkdict.get('indx'))

    def Load(self):
        self.read_header()
        self.chunks = self.read_subchunks(self.riff_header)
        self.process_chunks()

    def NumPages(self):
        return len(self.pages)

    def PageData(self, number):
        chunk = self.pages[number]
        self.file.seek(chunk.data_start)
        return self.file.read(chunk.data_length), chunk.data_start

    def CoordFactor(self):
        if self.unit == 35: # mm
            # Is this really in mm?
            factor = self.coord_factor * units.mm_to_pt * 1000
        elif self.unit == 64: # inches
            factor = self.coord_factor * units.in_to_pt
        else:
            self.warn(_("Unknown unit specification %d, assuming inches")
                      % self.unit)
            factor = self.coord_factor * units.in_to_pt
        return factor

    def CoordTrafo(self):
        factor = self.CoordFactor()
        left, top, right, bottom = self.bbox
        return Trafo(factor, 0, 0, factor,
                     -factor * min(left, right), -factor * min(bottom, top))

    def GetBitmap(self, index):
        offset = self.bitmapindex[index]
        if type(offset) is types.InstanceType:
            return offset
        image = self.load_image(offset)
        if image is not None:
            self.bitmapindex[index] = image
        return image

    def GetImage(self, index):
        image = self.embeddedindex[index]
        if type(image) == types.InstanceType:
            return image
        offset, image_type = image
        image = self.load_image(offset)
        if image is not None:
            self.embeddedindex[index] = image
        return image

    def load_image(self, offset):
        self.file.seek(offset)
        header = read_chunk_header(self.file)
        subchunks = self.read_subchunks(header)
        if (header.sub_type != 'imag'
            or len(subchunks) < 2
            or subchunks[0].chunk_type != 'info'
            or subchunks[1].chunk_type != 'data'):
            raise SketchLoadError(_("Invalid image specification"))
        self.file.seek(subchunks[0].data_start)
        if self.tagged:
            tag, length = unpack('<Bh', self.file.read(3))
        data = self.file.read(12)
        image_type, compression, size, real_size = unpack('<hhll', data)
        self._print('Image type %d, compressed %d, size %d, real_size %d\n',
                    image_type, compression, size, real_size)
        if image_type == 0x08:
            image = self.load_bitmap(subchunks[1].data_start,
                                     subchunks[1].data_length)

        elif image_type == 16:
            image = self.load_rimage(subchunks[1].data_start,
                                     subchunks[1].data_length)
        else:
            image = None
        return image

    def load_bitmap(self, data_start, data_length):
        # load a standard windows bitmap file from data_start
        import PIL.Image, StringIO
        self.file.seek(data_start)
        data = self.file.read(data_length)
        image = PIL.Image.open(StringIO.StringIO(data))
        image.load()
        return image

    def load_rimage(self, data_start, data_length):
        import PIL.Image, StringIO
        self.file.seek(data_start + 5) # skip the initial tag

        # read the file header
        format = '<2slHHl'
        data = self.file.read(calcsize(format))
        header = unpack(format, data)

        # read the Rimage header
        start_pos = self.file.tell()
        format = '<ll ll lll ll ll lllll'
        data = self.file.read(calcsize(format))
        header = unpack(format, data)
        image_type = header[0]
        width, height, planes, bits_per_plane, bytes_per_line = header[2:7]
        xres, yres = header[9:11]
        palette_offset, data_offset = header[11:13]
        # resolution is specified in pixel/km (yes, kilometer).
        xres = xres * 2.54 / 100000 
        yres = yres * 2.54 / 100000
        self._print('Rimage:\n')
        self._print('type %d:\n', image_type)
        self._print('size %d %d, planes %d, bits/plane %d, bytes/line %d\n',
                    width, height, planes, bits_per_plane, bytes_per_line)
        self._print('resolution: %gx%g\n', xres, yres)
        self._print('offsets: palette %d, data %d\n',
                    palette_offset, data_offset)
        if planes == 1 and bits_per_plane == 8:
            # 8 bit palette. XXX should the test be image_type == 10?

            # read palette
            if palette_offset > 64:
                self.file.seek(start_pos + palette_offset)
            palette_type, length = unpack('<hh', self.file.read(4))
            self._print('palette type %d, entries %d\n', palette_type, length)
            palette = self.file.read(length * 3)
            if length < 256:
                palette = palette + '\000\000\000' * (256 - length)

            # read data
            self.file.seek(start_pos + data_offset)
            data = self.file.read(height * bytes_per_line)
            image = PIL.Image.fromstring('P', (width, height), data, 'raw',
                                         'P', bytes_per_line, -1)
            image.putpalette(palette, 'BGR')
        elif planes == 1 and bits_per_plane == 24:
            # XXX should the test be image_type == 1?
            self.file.seek(start_pos + data_offset)
            data = self.file.read(height * bytes_per_line)
            image = PIL.Image.fromstring('RGB', (width, height), data, 'raw',
                                         'BGR', bytes_per_line, -1)
        else:
            image = None
        if image is not None:
            image.info['resolution'] = xres, yres
        return image

class CMXInterpreter:

    def __init__(self, loader, cmxfile, layer_prefix = ''):
        self.loader = loader
        self.cmxfile = cmxfile
        self.source_stack = ()
        self.source = None
        self.pos = 0
        self._print = cmxfile._print
        self.trafo = cmxfile.CoordTrafo()
        self.trafo_stack = ()
        self.factor = cmxfile.CoordFactor()
        self.angle_factor = cmxfile.angle_factor
        self.layer_prefix = layer_prefix

    def warn(self, message):
        self.loader.add_message(message)

    def push_source(self, source):
        self.source_stack = self.source, self.source_stack
        self.source = source

    def pop_source(self):
        self.source, self.source_stack = self.source_stack

    def push_trafo(self, trafo):
        self.trafo_stack = self.trafo, self.trafo_stack
        self.trafo = trafo

    def pop_trafo(self):
        self.trafo, self.trafo_stack = self.trafo_stack

    def get_struct(self, format):
        return self.source.read_struct(format)

    def get_int16(self):
        return self.source.read_struct('h')[0]

    def get_uint16(self):
        return self.source.read_struct('H')[0]

    def get_int32(self):
        return self.source.read_struct('l')[0]

    def get_rectangle(self):
        return self.source.read_struct('iiii')

    def get_matrix(self):
        type = self.get_int16()
        if type == 1: # identity
            return (1, 0, 0, 1, 0, 0)
        return self.source.read_struct('dddddd')

    def get_boolean(self):
        return self.source.read_struct('B')[0]

    get_byte = get_boolean

    def get_bytes(self, count):
        return self.source.read(count)

    def get_string(self):
        count = self.get_int16()
        string = self.source.read(count)
        return string

    def get_angle(self):
        return self.get_int32() * self.angle_factor

    def get_tag(self):
        tag = ord(self.source.read(1))
        if tag != 255:
            size = self.get_uint16()
            self._print('<get tag %d, size %d>\n', tag, size)
            self.push_source(self.source.subfile(size - 3))
        else:
            self._print('<get tag %d>\n', tag)
        return tag

    def skip_tags(self):
        # skip tags until end tag
        tag = -1
        while tag != 255:
            tag = ord(self.source.read(1))
            if tag != 255:
                size = self.get_int16()
                self.source.seek(self.source.tell() + size - 3)            

    #

    def Run(self, data, data_start):
        if self.cmxfile.byte_order == 2:
            # little endian
            byte_order = 0
        else:
            byte_order = 1
        self.push_source(BinaryInput(data, byte_order,
                                     self.cmxfile.coord_size))
        get_int16 = self.get_int16
        get_int32 = self.get_int32
        try:
            while self.source.tell() < len(data):
                p = self.pos
                length = get_int16()
                if length < 0:
                    length = get_int32() - 4
                code = abs(get_int16()) # for some reason the codes are
                                        # negative in CMX1
                command = cmx_commands.get(code)
                self._print('%-20s(%3d) %d\n', command, code, length)
                if command is not None and hasattr(self, command):
                    try:
                        try:
                            self.push_source(self.source.subfile(length - 4))
                            jump = getattr(self, command)()
                        finally:
                            self.pop_source()
                        if jump:
                            self.source.seek(jump - data_start)
                    except SketchLoadError:
                        raise
                    except:
                        warn_tb(INTERNAL, "Exception in CMX command")
                else:
                    self.source.seek(self.source.tell() + length - 4)
        finally:
            self.pop_source()

    def execute_procedure(self, reference):
        reflist, offset = self.cmxfile.procindex[reference]
        file = self.cmxfile.file
        file.seek(offset)
        chunk = read_chunk_header(file)
        if chunk.chunk_type in ('pvtr', 'pctn', 'proc'):
            data = file.read(chunk.data_length)
            self.loader.begin_group()
            try:
                self.Run(data, chunk.data_start)
            finally:
                self.loader.end_group()
        elif chunk.chunk_type == 'plns':
            # lens
            data = file.read(14)
            parent, page, parent_ref, start, end = unpack('<hhhll', data)
            self._print('parent %d, page %d, parentref %d, start %d, end %d\n',
                        parent, page, parent_ref, start, end)
            file.seek(start)
            data = file.read(end - start)
            len = ord(data[0]) + 256 * ord(data[1])
            data = data[len:]
            self.loader.begin_group()
            try:
                self.Run(data, start)
            finally:
                self.loader.end_group()

    def EndGroup(self):
        try:
            self.loader.end_group()
        except EmptyCompositeError:
            pass


class CMXInterpreter16(CMXInterpreter):

    def BeginPage(self):
        page_number, flags = self.source.read_struct('hl')
        bbox = self.get_rectangle()
        endpage, group_count, tally = self.source.read_struct('lhl')
        matrix = self.get_matrix()
        mapflag = self.get_boolean()

        if mapflag:
            maprect1 = self.get_rectangle()
            maprect2 = self.get_rectangle()
        fmt = '    % 10s: %s\n'
        self._print(fmt, 'PageNumber', page_number)
        self._print(fmt, 'flags', flags)
        self._print(fmt, 'GroupCount:', group_count)
        self._print(fmt, 'Tally.', tally)
        self._print(fmt, 'Matrix', matrix)
        if mapflag:
            self._print(fmt, 'MapRect1', maprect1)
            self._print(fmt, 'MapRect2', maprect2)
        else:
            self._print('    No map.\n')

    def BeginLayer(self):
        page_number, layer_number, flags, tally \
                   = self.source.read_struct('hhll')
        layer_name = self.get_string()
        matrix = self.get_matrix()
        mapflag = self.get_boolean()
        if mapflag:
            maprect1 = self.get_rectangle()
            maprect2 = self.get_rectangle()
        fmt = '    % 11s: %s\n'
        self._print(fmt, 'LayerName', layer_name)
        self._print(fmt, 'LayerNumber', layer_number)
        self._print(fmt, 'PageNumber', page_number)
        self._print(fmt, 'flags', flags)
        self._print(fmt, 'Tally.', tally)
        self._print(fmt, 'Matrix', matrix)
        if mapflag:
            self._print(fmt, 'MapRect1', maprect1)
            self._print(fmt, 'MapRect2', maprect2)
        else:
            self._print('    No map.\n')

        # start layer
        self.loader.layer(self.layer_prefix + layer_name, 1, 1, 0, 0, (0,0,0))

    def BeginGroup(self):
        bbox = self.get_rectangle()
        group_count, tally, endgroup = self.source.read_struct('hll')
        fmt = '    % 10s: %s\n'
        self._print(fmt, 'GroupCount', group_count)
        self._print(fmt, 'Tally.', tally)
        self._print(fmt, 'Bound.Box', bbox)

        # start group
        self.loader.begin_group()

    def get_rendering_attrs(self):
        self._print('	  Rendering Attributes:\n')
        style = self.loader.style
        mask = self.get_byte()
        if mask & 0x01: # fill attrs
            self._print('	Fill:')
            fill = self.get_int16()
            if fill == 0: # no fill
                self._print('no fill\n')
                style.fill_pattern = EmptyPattern
            elif fill == 1: # uniform
                color, screen = self.source.read_struct('hh')
                self._print('uniform %s %s, screen %s\n', color,
                            self.cmxfile.colors[color], screen)
                style.fill_pattern = SolidPattern(self.cmxfile.colors[color])
            elif fill == 2: # fountain (gradient)
                fountain, screen, padding = self.source.read_struct('hhh')
                angle = self.get_angle()
                xoff, yoff, steps, mode = self.source.read_struct('iihh')
                # The rate doesn't seem to be present in CMX1
                #rate_method, data = get_int16(data)
                #rate_value, data = get_int16(data)
                color_count = self.get_int16()
                colors = []
                for i in range(color_count):
                    color, pos = self.source.read_struct('hh')
                    color = self.cmxfile.colors[color]
                    colors.append((pos / 100.0, color))
                self._print('fountain: %s pad %d angle %f off %d %d\n',
                            ('linear','radial','conical','square')[fountain],
                            padding, angle, xoff, yoff)
                self._print('	      steps %d mode %s\n', steps,
                            ('RGB', 'HSB_CW', 'HSB_CCW', 'Custom')[mode])
                self._print('	      colors %s\n', colors)
                style.fill_pattern = LinearGradient(MultiGradient(colors),
                                                    -Polar(angle),
                                                    border = padding / 50.0)
            elif fill == 7: # monochrome bitmap 1 (according to cmxbrowser)
                bitmap = self.get_int16()
                width, height, xoff, yoff, inter, flags \
                     = self.source.read_struct('hhhhhh')
                foreground, background, screen = self.source.read_struct('hhh')
                self._print('twocolor: bitmap %d\n', bitmap)
                self._print('    size (%d, %d), off (%d, %d), inter %d, '
                            'flags %x\n',
                            width, height, xoff, yoff, inter, flags)
                self._print('    foreground %s, background %s, screen %d\n',
                            self.cmxfile.colors[foreground],
                            self.cmxfile.colors[background], screen)
                self.warn(_("fill type 'monochrome-bitmap' not implemented, "
                            "using solid black"))
                style.fill_pattern = SolidPattern(StandardColors.black)
            elif fill == 11: # Texture
                function = self.get_int16()
                width, height, xoff, yoff, inter, flags\
                     = self.source.read_struct('hhhhhh')
                bbox = self.get_rectangle()
                reserved, res, max_edge = self.source.read_struct('blh')
                lib = self.get_string()
                name = self.get_string()
                stl = self.get_string()
                count = self.get_int16()
                params = []
                for i in range(count):
                    params.append(self.source.read_struct('hhhh'))

                self._print('full-color: function %d\n', function)
                self._print('    size (%d, %d), off (%d, %d), inter %d, '
                            'flags %x\n',
                            width, height, xoff, yoff, inter, 0) #flags)
                self._print('    bbox %s\n', bbox)
                self._print('    resolution %d, max edge %d\n', res, max_edge)
                self._print('    library %s\n', `lib`)
                self._print('    name    %s\n', `name`)
                self._print('    style   %s\n', `stl`)
                self._print('    params:\n')
                for param in params:
                    self._print('        %s\n', param)
                self.warn(_("fill type 'texture' not implemented, "
                            "using solid black"))
                style.fill_pattern = SolidPattern(StandardColors.black)
            else:
                if fill <= 8:
                    name = ('NoFill', 'Uniform', 'Fountain', 'PostScript',
                            'TwoColorPattern', 'Monochrom/Transparent',
                            'ImportedBitmap', 'FullColorPattern',
                            'Texture')[fill]
                else:
                    name = 'Unknown!'
                self._print(name + '\n')
                self.warn(_("fill type %d not implemented, using solid black")
                          % fill)
                style.fill_pattern = SolidPattern(StandardColors.black)
        else:
            style.fill_pattern = EmptyPattern

        if mask & 0x02: # line attrs
            outline = self.get_int16()
            #print outline
            outline = self.cmxfile.outlines[outline]
            spec, cap, join = outline.style
            if spec & 1: # the none bit is set. no outline
                style.line_pattern = EmptyPattern
            else:
                style.line_pattern = SolidPattern(outline.color)
                style.line_width = abs(outline.pen[0] * self.factor)
                style.line_cap = cap + 1
                style.line_join = join
                if spec & 0x04: # dash dot
                    style.line_dashes = outline.dashes
                else:
                    style.line_dashes = ()
                # XXX whats the precise meaning of the dash-dot flag and
                # the solid outline flag?
        else:
            style.line_pattern = EmptyPattern

        if mask & 0x04: # lens attributes
            #self._print("	can't handle lens\n")
            raise TypeError, "can't handle lens"

        if mask & 0x08: # canvas
            #self._print("	can't handle canvas\n")
            raise TypeError, "can't handle canvas"

        if mask & 0x10: # container
            #self._print("	can't handle container\n")
            raise TypeError, "can't handle container"

    def PolyCurve(self):
        get_struct = self.source.read_struct; trafo = self.trafo
        self.get_rendering_attrs()
        count = self.get_int16()
        self._print('	  %d points\n', count)
        points = [];
        coords = get_struct(count * 'ii')
        append = points.append
        for i in range(0, 2 * count, 2):
            append(trafo(coords[i], coords[i + 1]))
        nodes = map(ord, self.get_bytes(count))
        if len(nodes) != len(points):
            self._print('lengths of nodes and points differ\n')
        bbox = self.get_rectangle()

        self._print('	bounding box: %s\n\n', bbox)

        path = None
        paths = []
        close = 0; i = 0
        while i < count:
            node = nodes[i]
            p = points[i]
            type = (node & 0xC0) >> 6
            if type == 0:
                if close and path is not None:
                    path.load_close(1)
                close = node & 0x08
                path = CreatePath()
                paths.append(path)
                path.AppendLine(p, (node & 0x30) >> 4)
            elif type == 1:
                path.AppendLine(p, (node & 0x30) >> 4)
            elif type == 3:
                path.AppendBezier(p, points[i + 1], points[i + 2],
                                  (nodes[i + 2] & 0x30) >> 4)
                i = i + 2
            i = i + 1
        if close:
            path.load_close(1)
        for i in range(len(paths) - 1, -1, -1):
            path = paths[i]
            if path.len == 0:
                #print 'empty path %d deleted' % i
                del paths[i]

        if paths:
            self.loader.bezier(tuple(paths))
        else:
            self.get_prop_stack()

    def JumpAbsolute(self):
        return self.get_int32()


class CMXInterpreter32(CMXInterpreter):

    def BeginPage(self):
        fmt = '    % 10s: %s\n'
        tag = -1
        while tag != 255:
            tag = self.get_tag()
            if tag != 255:
                try:
                    if tag == 1:
                        read_struct = self.source.read_struct
                        page_number, flags = read_struct('hi')
                        bbox = read_struct('iiii')
                        endpage, group_count, tally = read_struct('ihi')
                        self._print(fmt, 'PageNumber', page_number)
                        self._print(fmt, 'flags', flags)
                        self._print(fmt, 'GroupCount:', group_count)
                        self._print(fmt, 'Tally.', tally)
                    elif tag == 2:
                        matrix = self.get_matrix()
                        self._print(fmt, 'Matrix', matrix)
                    elif tag == 3:
                        flag = self.get_boolean()
                        if flag:
                            old = self.get_rectangle()
                            new = self.get_rectangle()
                        if flag:
                            self._print(fmt, 'mapping', '')
                            self._print(fmt, 'old rect', old)
                            self._print(fmt, 'new rect', new)
                        else:
                            self._print('    no mapping\n')
                finally:
                    self.pop_source()

    def BeginLayer(self):
        fmt = '    % 11s: %s\n'
        tag = -1
        while tag != 255:
            tag = self.get_tag()
            if tag != 255:
                try:
                    if tag == 1:
                        page_number, layer_number, flags, tally \
                                   = self.source.read_struct('hhll')
                        layer_name = self.get_string()
                        self._print(fmt, 'LayerName', layer_name)
                        self._print(fmt, 'LayerNumber', layer_number)
                        self._print(fmt, 'PageNumber', page_number)
                        self._print(fmt, 'flags', flags)
                    elif tag == 2:
                        matrix = self.get_matrix()
                        self._print(fmt, 'Matrix', matrix)
                    elif tag == 3:
                        flag = self.get_boolean()
                        if flag:
                            old = self.get_rectangle()
                            new = self.get_rectangle()
                        if flag:
                            self._print(fmt, 'mapping', '')
                            self._print(fmt, 'old rect', old)
                            self._print(fmt, 'new rect', new)
                        else:
                            self._print('    no mapping\n')
                finally:
                    self.pop_source()
        # start layer
        self.loader.layer(self.layer_prefix + layer_name, 1, 1, 0, 0, (0,0,0))

    def BeginGroup(self):
        fmt = '    % 10s: %s\n'
        tag = -1
        while tag != 255:
            tag = self.get_tag()
            if tag == 1:
                try:
                    bbox = self.get_rectangle()
                    group_count, end, tally = self.source.read_struct('hll')
                    self._print(fmt, 'Bound.Box', bbox)
                    self._print(fmt, 'GroupCount', group_count)
                    self._print(fmt, 'Tally', tally)
                finally:
                    self.pop_source()
        # start group
        self.loader.begin_group()

    def get_rendering_attrs(self):
        self._print('	  Rendering Attributes:\n')
        style = self.loader.style
        mask = self.get_byte()

        if mask & 0x01: # fill attrs
            self._print('	Fill:')
            self.get_fill(style)
        else:
            style.fill_pattern = EmptyPattern

        if mask & 0x02: # line attrs
            self.get_outline(style)
        else:
            style.line_pattern = EmptyPattern

        if mask & 0x04: # lens attributes
            self.warn(_("Lens specification ignored"))
            self.skip_tags()
            #self.get_lens()

        if mask & 0x08: # canvas (XXX what is that, actually?)
            self.warn(_("Canvas specification ignored"))
            self.skip_tags() # ?

        if mask & 0x10: # container
            #self.warn("Container specification ignored")
            stack = self.loader.get_prop_stack()
            self.get_container()
            self.loader.set_prop_stack(stack)

    def get_fill(self, style):
        tag = -1
        while tag != 255:
            tag = self.get_tag()
            try:
                if tag == 1:
                    fill = self.get_int16()
                    if fill == 0: # no fill
                        self._print('no fill\n')
                        style.fill_pattern = EmptyPattern
                    elif fill == 1: # uniform
                        self.get_uniform_fill(style)
                    elif fill == 2: # fountain (gradient)
                        self.get_fountain_fill(style)
                    elif fill == 7:
                        # monochrome bitmap 1 (according to cmxbrowser)
                        self.get_monochrome_fill(style)
                    elif fill == 9:
                        # color bitmap
                        self.get_colorbitmap_fill(style)
                    elif fill == 11:
                        # texture
                        self.warn(_("Texture fill not implemented, "
                                    "using solid black"))
                        style.fill_pattern = SolidPattern(StandardColors.black)
                    else:
                        self.warn(_("fill type %d not implemented, "
                                    "using solid black") % fill)
                        style.fill_pattern = SolidPattern(StandardColors.black)
            finally:
                if tag != 255:
                    self.pop_source()


    def get_uniform_fill(self, style):
        tag = -1
        while tag != 255:
            tag = self.get_tag()
            try:
                if tag == 1:
                    color, screen = self.source.read_struct('hh')
                    self._print('uniform %s %s, screen %s\n', color,
                                self.cmxfile.colors[color], screen)
                    color = self.cmxfile.colors[color]
                    style.fill_pattern = SolidPattern(color)
            finally:
                if tag != 255:
                    self.pop_source()

    def get_fountain_fill(self, style):
        tag = -1
        while tag != 255:
            tag = self.get_tag()
            try:
                if tag == 1:
                    fountain, screen, padding = self.source.read_struct('hhh')
                    angle = self.get_angle()
                    xoff, yoff, steps, mode, rate_method, rate_value \
                        = self.source.read_struct('iihhhh')
                    self._print('fountain: %s pad %d angle %f off %d %d\n',
                                ('linear','radial','conical','square')[fountain],
                                padding, angle, xoff, yoff)
                    self._print('	      steps %d mode %s\n', steps,
                                ('RGB', 'HSB_CW', 'HSB_CCW', 'Custom')[mode])
                    self._print('	      rate %d value %d\n',
                                rate_method, rate_value)
                elif tag == 2:
                    color_count = self.get_int16()
                    colors = []
                    for i in range(color_count):
                        color, pos = self.source.read_struct('hh')
                        color = self.cmxfile.colors[color]
                        colors.append((pos / 100.0, color))
                    self._print('	      colors %s\n', colors)
                    if mode == 0 and rate_value != 50 and len(colors) == 2:
                        # len(colors) should always be 2 for mode != 3
                        start = colors[0][1]
                        end = colors[1][1]
                        colors.insert(1, (rate_value / 100.0,
                                          start.Blend(end, 0.5, 0.5)))
                    gradient = MultiGradient(colors)
                    border = padding / 50.0
                    center = Point(xoff / 100.0 + 0.5, yoff / 100.0 + 0.5)
                    if fountain == 0:
                        pattern = LinearGradient(gradient, -Polar(angle),
                                                 border = border)
                    elif fountain == 1:
                        pattern = RadialGradient(gradient, center,
                                                 border = border)
                    elif fountain == 2:
                        pattern = ConicalGradient(gradient, center,
                                                  -Polar(angle))
                    else:
                        # probably a square gradient which sketch doesn't have
                        # use a radial gradient instead
                        self.warn(_("Substituting radial gradient for square "
                                    "gradient"))
                        pattern = RadialGradient(gradient, center,
                                                 border = border)
                    style.fill_pattern = pattern
            finally:
                if tag != 255:
                    self.pop_source()

    def get_monochrome_fill(self, style):
        tag = -1
        while tag != 255:
            tag = self.get_tag()
            try:
                if tag == 1:
                    bitmap = self.get_int16()
                    self._print('twocolor: bitmap %d\n', bitmap)
                    image = self.cmxfile.GetBitmap(bitmap)
                    if image.mode != '1':
                        raise SketchLoadError(_("Image for twocolor fill is "
                                                "not 1 bit deep"))
                    width, height, xoff, yoff, inter, flags = self.get_tiling()
                    background, foreground, screen \
                              = self.source.read_struct('hhh')
                    self._print('    foreground %s, background %s,'
                                ' screen %d\n',
                                self.cmxfile.colors[foreground],
                                self.cmxfile.colors[background], screen)
                    foreground = self.cmxfile.colors[foreground]
                    background = self.cmxfile.colors[background]
                    image = image.convert('L')
                    pal = [0] * 768
                    pal[0] = background.red * 255
                    pal[1] = background.green * 255
                    pal[2] = background.blue * 255
                    pal[765] = foreground.red * 255
                    pal[766] = foreground.green * 255
                    pal[767] = foreground.blue * 255
                    image.putpalette(pal, 'RGB')
                    image = image.convert('RGB')
                    trafo = Trafo(width / image.size[0], 0, 0,
                                  height / image.size[1], 0, 0)
                    style.fill_pattern = ImageTilePattern(ImageData(image),
                                                          trafo = trafo)
                    self.loader.fix_tile = xoff, yoff
            finally:
                if tag != 255:
                    self.pop_source()

    def get_colorbitmap_fill(self, style):
        tag = -1
        while tag != 255:
            tag = self.get_tag()
            try:
                if tag == 1:
                    procedure = self.get_int16()
                    self._print('Color image procedure %d\n', procedure)
                    width, height, xoff, yoff, inter, flags = self.get_tiling()

                    stack = self.loader.get_prop_stack()
                    self.execute_procedure(procedure)
                    self.loader.set_prop_stack(stack)

                    group = self.loader.pop_last()
                    image = group[0]
                    image_data = image.Data()
                    trafo = Trafo(width / image_data.size[0], 0, 0,
                                  height / image_data.size[1], 0, 0)
                    trafo = trafo(image.Trafo())
                    pattern = ImageTilePattern(image_data, trafo = trafo)
                    self.loader.style.fill_pattern = pattern

                elif tag != 255:
                    self._print('Unknown tag %d\n', tag)
                    self._print(`self.source.stream` + '\n')
            finally:
                if tag != 255:
                    self.pop_source()


    def get_tiling(self):
        tiling = None
        tag = -1
        while tag != 255:
            tag = self.get_tag()
            try:
                if tag == 1:
                    width, height = self.source.read_struct('ii')
                    width, height = self.trafo.DTransform(width, height)
                    xoff, yoff, inter, flags = self.source.read_struct('hhhh')
                    self._print('    size (%d, %d), off (%d, %d), inter %d, '
                                'flags %x\n',
                                width, height, xoff, yoff, inter, flags)
                    tiling = width, height, xoff, yoff, inter, flags
                elif tag != 255:
                    self._print('Unknown tag %d\n', tag)
                    self._print(`self.source.stream` + '\n')
            finally:
                if tag != 255:
                    self.pop_source()
        return tiling


    def get_outline(self, style):
        tag = -1
        while tag != 255:
            tag = self.get_tag()
            try:
                if tag == 1:
                    outline = self.get_int16()
                    outline = self.cmxfile.outlines[outline]
                    spec, cap, join = outline.style
                    if spec & 1: # the none bit is set. no outline
                        style.line_pattern = EmptyPattern
                    else:
                        style.line_pattern = SolidPattern(outline.color)
                        style.line_width = abs(outline.pen[0] * self.factor)
                        style.line_cap = cap + 1
                        style.line_join = join
                        self._print('Arrow heads %d\n', outline.arrowheads)
                        heads = self.cmxfile.arrow_heads[outline.arrowheads]
                        style.line_arrow1 = self.get_arrow(heads[0])
                        style.line_arrow2 = self.get_arrow(heads[1])
                    if spec & 0x04: # dash dot
                        style.line_dashes = outline.dashes
                    else:
                        style.line_dashes = ()
                    # XXX whats the precise meaning of the dash-dot flag and
                    # the solid outline flag?
            finally:
                if tag != 255:
                    self.pop_source()

    def get_arrow(self, index):
        if index == 0:
            return None
        offset = self.cmxfile.arrowindex[index]
        if type(offset) == types.InstanceType:
            return offset
        file = self.cmxfile.file
        file.seek(offset)
        data = file.read(3)
        tag, length = unpack('<bh', data)
        data = file.read(length)
        if self.cmxfile.byte_order == 2:
            # little endian
            byte_order = 0
        else:
            byte_order = 1
        self.push_source(BinaryInput(data, byte_order,
                                     self.cmxfile.coord_size))
        self.push_trafo(Scale(self.trafo.m11 / 50))
        paths = self.read_pointlist()
        self.pop_trafo()
        self.pop_source()

        arrow = Arrow(paths[0])
        self.cmxfile.arrowindex[index] = arrow
        return arrow


    def get_container(self):
        verbosity = self.cmxfile.verbosity
        self.cmxfile.verbosity = 0
        self._print('parsing container\n')
        try:
            tag = -1
            while tag != 255:
                tag = self.get_tag()
                try:
                    if tag == 1:
                        procedure, transform = self.source.read_struct('hB')
                        self._print('procedure %d, transform %d\n',
                                    procedure, transform)
                    elif tag != 255:
                        self._print('Unknown tag %d\n', tag)
                        self._print(`self.source.stream` + '\n')
                finally:
                    if tag != 255:
                        self.pop_source()
            self.execute_procedure(procedure)
            self.loader.fix_clip = 1
        finally:
            self.cmxfile.verbosity = verbosity

    def get_lens(self):
        self._print('parsing container\n')
        procedure = None; args = ()
        tag = -1
        while tag != 255:
            tag = self.get_tag()
            try:
                if tag == 1:
                    lens = self.source.read_struct('B')[0]
                    self._print('lens type %d\n', lens)
                    if lens == 2:
                        self._print('magnifying\n')
                        rate, procedure = self.source.read_struct('hh')
                        self._print('rate %d, procedure %d\n', rate, procedure)
                        args = (lens, rate / 10.0)
                if tag == 3:
                    frozen, active, x, y = self.source.read_struct('llii')
                    args = args + (self.trafo(x, y),)
                elif tag != 255:
                    self._print('get_lens: Unknown tag %d\n', tag)
                    self._print(`self.source.stream` + '\n')
            finally:
                if tag != 255:
                    self.pop_source()
        if procedure is not None:
            self.execute_procedure(procedure)
            self.loader.fix_lens = args

    def read_pointlist(self):
        get_struct = self.source.read_struct; trafo = self.trafo
        count = self.get_int16()
        self._print('pointlist: %d points\n', count)
        points = [];
        coords = get_struct(count * 'ii')
        append = points.append
        for i in range(0, 2 * count, 2):
            append(trafo(coords[i], coords[i + 1]))
        nodes = map(ord, self.get_bytes(count))
        if len(nodes) != len(points):
            self._print('lengths of nodes and points differ\n')

        path = None
        paths = []
        close = 0; i = 0
        while i < count:
            node = nodes[i]
            p = points[i]
            type = (node & 0xC0) >> 6
            if type == 0:
                if close and path is not None:
                    path.load_close(1)
                close = node & 0x08
                path = CreatePath()
                paths.append(path)
                path.AppendLine(p, (node & 0x30) >> 4)
            elif type == 1:
                path.AppendLine(p, (node & 0x30) >> 4)
            elif type == 3:
                path.AppendBezier(p, points[i + 1], points[i + 2],
                                  (nodes[i + 2] & 0x30) >> 4)
                i = i + 2
            i = i + 1
        if close:
            path.load_close(1)
        for i in range(len(paths) - 1, -1, -1):
            path = paths[i]
            if path.len == 0:
                #print 'empty path %d deleted' % i
                del paths[i]

        return tuple(paths)


    def PolyCurve(self):
        # We do the tag handling 'manually' here because the file format
        # is broken for very long point lists. The CMX format has
        # provisisions for very long point lists at the command level
        # where there's a way to use 32 bit sizes instead of the normal
        # 16 bit but this doesn't work at the tag level. In the case of
        # the PolyCurve command the only problematic tag is probably the
        # tag with id 2 containing the point list so we have to ignore
        # the tag size for that.
        paths = ()
        while 1:
            tag = ord(self.source.read(1))
            if tag == 255:
                self._print('<get tag %d>\n', tag)
                break
            else:
                size = self.get_uint16()
                self._print('<get tag %d, size %d>\n', tag, size)
                if tag != 2:
                    # don't push for the points as the size may not be
                    # accurate. For very long point lists the size of
                    # the tag is > 2**16 and wraps around
                    self.push_source(self.source.subfile(size - 3))
                    pop = 1
                else:
                    pop = 0
                try:
                    if tag == 1:
                        self.get_rendering_attrs()
                    elif tag == 2:
                        paths = self.read_pointlist()
                    elif tag == 3:
                        # ignore bounding box
                        pass
                    elif tag != 255:
                        self._print('PolyCurve: Unknown tag %d\n', tag)
                        #self._print(`self.source.stream` + '\n')
                finally:
                    if pop:
                        self.pop_source()

        if paths:
            self.loader.bezier(tuple(paths))
        else:
            self.get_prop_stack()

    def DrawImage(self):
        tag = -1
        while tag != 255:
            tag = self.get_tag()
            try:
                if tag == 1:
                    self.get_rendering_attrs()
                elif tag == 2:
                    left, top, right, bottom = self.get_rectangle()
                    crop = self.get_rectangle()
                    matrix = self.get_matrix()
                    image_type, image1, image2 = self.source.read_struct('hhh')
                    real_width = self.factor * abs(right - left)
                    real_height = self.factor * abs(top - bottom)
                    self._print('extent %gx%g, crop %s\n',
                                real_width, real_height, crop)
                    self._print('matrix %s\n', matrix)
                    self._print('type %d, references %d, %d\n', image_type,
                                image1, image2)
                    image = self.cmxfile.GetImage(image1)
                    if image is not None:
                        width, height = image.size
                        trafo = Trafo(real_width / float(width), 0,
                                      0, real_height / float(height), 0, 0)
                        trafo = apply(Trafo, matrix[:4])(trafo)
                        trafo = Translation(self.trafo(matrix[-2:]))(trafo)
                        self.loader.image(image, trafo)
                elif tag != 255:
                    self._print('Unknown tag %d\n', tag)
                    self._print(`self.source.stream` + '\n')
            finally:
                if tag != 255:
                    self.pop_source()


    def JumpAbsolute(self):
        tag = -1
        while tag != 255:
            tag = self.get_tag()
            try:
                if tag == 1:
                    return self.get_int32()
            finally:
                if tag != 255:
                    self.pop_source()

class CMXLoader(GenericLoader):

    def __init__(self, file, filename, match):
        GenericLoader.__init__(self, file, filename, match)
        self.fix_tile = None
        self.fix_clip = 0
        self.fix_lens = ()

    def Load(self):
        try:
            cmx = CMXFile(self, self.file)
            cmx.Load()
            self.document()
            prefix = ''
            num_pages = cmx.NumPages()
            for num in range(num_pages):
                data, start = cmx.PageData(num)
                if data:
                    if num_pages > 1:
                        prefix = 'Page %d: ' % num
                    if cmx.coord_size == 2:
                        interpreter = CMXInterpreter16(self, cmx,
                                                       layer_prefix = prefix)
                    else:
                        interpreter = CMXInterpreter32(self, cmx,
                                                       layer_prefix = prefix)
                    interpreter.Run(data, start)
            self.end_all()
            self.object.load_Completed()
            return self.object
        except RiffEOF:
            raise SketchLoadError(_("Unexpected end of file"))
        except:
            import traceback
            traceback.print_exc()
            raise

    def append_object(self, object):
        if self.fix_tile:
            if object.has_fill:
                pattern = object.Properties().fill_pattern
                if pattern.is_Tiled:
                    width, height = pattern.data.size
                    trafo = pattern.trafo
                    y = height * trafo.m22 * self.fix_tile[1] / 100.0
                    rect = object.coord_rect
                    pattern.Transform(Translation(0, rect.bottom-rect.top + y))
            self.fix_tile = None
        if self.fix_clip:
            group = self.pop_last()
            if group is not None and group.is_Group:
                objects = group.GetObjects()
                objects.insert(0, object)
                object = MaskGroup(objects)
            self.fix_clip = 0
        if self.fix_lens:
            group = self.pop_last()
            if group is not None and group.is_Group:
                lens = self.fix_lens[0]
                if lens == 2:
                    rate, viewpoint = self.fix_lens[1:]
                    center = object.coord_rect.center()
                    trafo = Translation(-viewpoint)
                    trafo = Translation(center)(Scale(rate)(trafo))
                    group.Transform(trafo)
                    objects = group.GetObjects()
                    objects.insert(0, object)
                    object = MaskGroup(objects)
            self.fix_lens = ()
        GenericLoader.append_object(self, object)

