# 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)
            
