# -*- coding: utf-8 -*-

# Copyright (C) 2007 by Igor Novikov
# Copyright (C) 1999, 2000 by Bernhard Herzog (CMX parsing)
#
# This library is covered by GNU General Public License v2.0.
# For more info see COPYRIGHTS file in sK1 root directory.

###Sketch Config
#type = Import
#class_name = 'CCXLoader'
#rx_magic = '(?s)RIFF....CDRX'
#tk_file_type = ('Corel Compressed Presentation Exchange', '.ccx')
#format_name = 'CDRX'
#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.
#



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

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

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 CCXFile:

	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
		from sk1libs.imaging import Image
		import StringIO
		self.file.seek(data_start)
		data = self.file.read(data_length)
		image = Image.open(StringIO.StringIO(data))
		image.load()
		return image

	def load_rimage(self, data_start, data_length):
		from sk1libs.imaging import Image
		import 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 = 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 = 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 CCXLoader(GenericLoader):

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

	def Load(self):
		try:
			self.file=CDRXfile(self.filename)
			cmx = CCXFile(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)

class CDRXfile:
	
	file_content=''
	point=0
	
	def __init__(self, filename):
		f = open(filename, 'rb')
		buf = f.read()
		f.close()
		cdrx = RiffChunk()
		cdrx.load(buf)
		self.construct_file(cdrx)

	def construct_file(self, cdrx):
		for chunk in cdrx.contents:
			if chunk.fourcc=='pack':
				pass
			else:				
				[rawsize] = struct.unpack('<I', chunk.chunksize)
				if rawsize & 1:
					rawsize += 1
					self.file_content=self.file_content+chunk.fourcc+struct.pack('<I',rawsize)+chunk.data+'\x00'
				else:
					self.file_content=self.file_content+chunk.fourcc+struct.pack('<I',rawsize)+chunk.data
		self.file_content='RIFF'+struct.pack('<I',4+len(self.file_content))+'CDRX'+self.file_content
		
	def seek(self, point):
		self.point=point
	
	def read(self, len):
		self.point+=len
		return self.file_content[self.point-len:self.point]
	
	def tell(self):
		return self.point


class RiffChunk:
	fourcc = '????'
	hdroffset = 0
	rawsize = 0
	data = ''
	contents = []
	fullname= ''
	chunkname= ''
	chunksize= ''
	compression = False
	number=0
	listtype=''
	parent_chunk=None
	
	def __init__(self, chunk=None):
		self.parent_chunk=chunk
	
	def load_pack(self):
		self.compression=True
		decomp = zlib.decompressobj()
		self.uncompresseddata = decomp.decompress(self.data[12:])
		offset = 0
		self.contents = []
		while offset < len(self.uncompresseddata):
			chunk = RiffChunk()
			chunk.parent = self.parent
			chunk.load(self.uncompresseddata, offset)
			self.parent.contents.append(chunk)
			offset += 8 + chunk.rawsize
	

	def load(self, buf, offset=0, blocksizes=()):
		self.hdroffset = offset
		self.fourcc = buf[offset:offset+4]
		self.chunksize = buf[offset+4:offset+8]
		[self.rawsize] = struct.unpack('<I', buf[offset+4:offset+8])
		if len(blocksizes):
			self.rawsize = blocksizes[self.rawsize]
		self.data = buf[offset+8:offset+8+self.rawsize]
		if self.rawsize & 1:
			self.rawsize += 1

		self.contents = []       
		if self.fourcc == 'pack':	
			self.load_pack()
		if self.fourcc == 'RIFF' or self.fourcc == 'LIST':
			self.listtype = buf[offset+8:offset+12]

			offset += 12
			while offset < self.hdroffset + 8 + self.rawsize:
				chunk = RiffChunk(self)
				chunk.parent = self
				chunk.load(buf, offset, blocksizes)
				self.contents.append(chunk)
				offset += 8 + chunk.rawsize
