# 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 Presentation Exchange 6/7', '.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 app import CreatePath, ContSmooth, ContAngle, ContSymmetrical, \
		SolidPattern, EmptyPattern, LinearGradient, RadialGradient, \
		ConicalGradient, MultiGradient,\
		CreateRGBColor, CreateCMYKColor, Trafo, Point, Polar, Translation, \
		Scale, StandardColors, ImageTilePattern, ImageData, MaskGroup, \
		Arrow

from app.events.warn import INTERNAL, warn_tb
from app.io.load import GenericLoader, SketchLoadError, EmptyCompositeError
from app.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, ('RGB',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, ('RGB',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)
			
