Example #1
0
	def read_polyline(self, line):
		readline = self.readline; tokenize = skread.tokenize_line
		args = tokenize(line)
		if len(args) != 15:
			raise SketchLoadError('Invalid PolyLine specification')
		sub_type, line_style, thickness, pen_color, fill_color, depth, \
					pen_style, area_fill, style, join, cap, \
					radius, forward_arrow, backward_arrow, npoints = args
		self.fill(fill_color, area_fill)
		self.line(pen_color, thickness, join, cap, line_style, style)

		if forward_arrow: readline() # XXX: implement this
		if backward_arrow:readline() # XXX: implement this
		if sub_type == 5: readline() # imported picture

		path = CreatePath()
		ncoords = npoints * 2
		pts = self.read_tokens(ncoords)
		if not pts:
			raise SketchLoadError('Missing points for polyline')
		if len(pts) > ncoords:
			del pts[ncoords:]
		map(path.AppendLine, coords_to_points(pts, self.trafo))
		if sub_type in (2, 3, 4):
			path.load_close(1)
		self.bezier(paths = path)
		self.set_depth(depth)
	def Polygon(self):
		points = self.read_points(self.get_int16())
		if points:
			path = CreatePath()
			map(path.AppendLine, points)
			if path.Node(-1) != path.Node(0):
				#print 'correct polygon'
				path.AppendLine(path.Node(0))
			path.load_close()
			self.prop_stack.AddStyle(self.curstyle.Duplicate())
			self.bezier((path,))
Example #3
0
    def read_polyline(self, line):
        readline = self.readline
        tokenize = skread.tokenize_line
        args = tokenize(line)
        if len(args) != 15:
            raise SketchLoadError('Invalid PolyLine specification')
        sub_type, line_style, thickness, pen_color, fill_color, depth, \
           pen_style, area_fill, style, join, cap, \
           radius, forward_arrow, backward_arrow, npoints = args
        self.fill(fill_color, area_fill)
        self.line(pen_color, thickness, join, cap, line_style, style)

        if forward_arrow: readline()  # XXX: implement this
        if backward_arrow: readline()  # XXX: implement this
        if sub_type == 5: readline()  # imported picture

        ncoords = npoints * 2
        pts = self.read_tokens(ncoords)
        if not pts:
            raise SketchLoadError('Missing points for polyline')
        if len(pts) > ncoords:
            del pts[ncoords:]

        trafo = self.trafo

        if sub_type in (1, 3, 5):
            path = CreatePath()
            map(path.AppendLine, coords_to_points(pts, trafo))
            if sub_type == 3:
                path.load_close(1)
            self.bezier(paths=path)
            self.set_depth(depth)

        elif sub_type in (2, 4):
            wx, wy = trafo(pts[2], pts[3]) - trafo(pts[0], pts[1])
            hx, hy = trafo(pts[4], pts[5]) - trafo(pts[2], pts[3])
            x, y = trafo(pts[0], pts[1])
            if sub_type == 4 and radius > 0:
                radius1 = (radius * 72.0 / 80.0) / max(abs(wx), abs(wy))
                radius2 = (radius * 72.0 / 80.0) / max(abs(hx), abs(hy))
            else:
                radius1 = radius2 = 0
            self.rectangle(wx,
                           wy,
                           hx,
                           hy,
                           x,
                           y,
                           radius1=radius1,
                           radius2=radius2)
            self.set_depth(depth)
Example #4
0
class Arrow:

	def __init__(self, path, closed = 0):
		self.path = CreatePath()
		if type(path) in (ListType, TupleType):
			for segment in path:
				if len(segment) == 2:
					apply(self.path.AppendLine, segment)
				else:
					apply(self.path.AppendBezier, segment)
		else:
			self.path = path
		if closed:
			self.path.load_close()

	def BoundingRect(self, pos, dir, width):
		angle = atan2(dir.y, dir.x)
		if width < 1.0:
			width = 1.0
		s = width * sin(angle)
		c = width * cos(angle)
		trafo = Trafo(c, s, -s, c, pos.x, pos.y)
		return self.path.accurate_rect(trafo)

	def Draw(self, device, rect = None):
		if self.path.closed:
			device.FillBezierPath(self.path, rect)
		else:
			device.DrawBezierPath(self.path, rect)

	def Paths(self):
		return (self.path,)

	def IsFilled(self):
		return self.path.closed

	def SaveRepr(self):
		path = map(lambda t: t[:-1], self.path.get_save())
		return (path, self.path.closed)

	def __hash__(self):
		return hash(id(self.path))

	def __cmp__(self, other):
		if __debug__:
			pdebug(None, 'Arrow.__cmp__, %s', other)
		if isinstance(other, self.__class__):
			return cmp(self.path, other.path)
		return cmp(id(self), id(other))
Example #5
0
class Arrow:
    def __init__(self, path, closed=0):
        self.path = CreatePath()
        if type(path) in (ListType, TupleType):
            for segment in path:
                if len(segment) == 2:
                    apply(self.path.AppendLine, segment)
                else:
                    apply(self.path.AppendBezier, segment)
        else:
            self.path = path
        if closed:
            self.path.load_close()

    def BoundingRect(self, pos, dir, width):
        angle = atan2(dir.y, dir.x)
        if width < 1.0:
            width = 1.0
        s = width * sin(angle)
        c = width * cos(angle)
        trafo = Trafo(c, s, -s, c, pos.x, pos.y)
        return self.path.accurate_rect(trafo)

    def Draw(self, device, rect=None):
        if self.path.closed:
            device.FillBezierPath(self.path, rect)
        else:
            device.DrawBezierPath(self.path, rect)

    def Paths(self):
        return (self.path, )

    def IsFilled(self):
        return self.path.closed

    def SaveRepr(self):
        path = map(lambda t: t[:-1], self.path.get_save())
        return (path, self.path.closed)

    def __hash__(self):
        return hash(id(self.path))

    def __cmp__(self, other):
        if __debug__:
            pdebug(None, 'Arrow.__cmp__, %s', other)
        if isinstance(other, self.__class__):
            return cmp(self.path, other.path)
        return cmp(id(self), id(other))
	def PolyPolygon(self):
		nr_of_polygons = self.get_int16()
		nr_of_points = []
		for i in range(nr_of_polygons):
			nr_of_points.append(self.get_int16())
		path = ()
		for i in nr_of_points:
			points = self.read_points(i)
			if points:
				subpath = CreatePath()
				map(subpath.AppendLine, points)
				if subpath.Node(-1) != subpath.Node(0):
					subpath.AppendLine(subpath.Node(0))
				subpath.load_close()
				path = path + (subpath,)
		if path:
			self.prop_stack.AddStyle(self.curstyle.Duplicate())
			self.bezier(path)
Example #7
0
def convert_outline(outline):
	paths = []
	trafo = Scale(0.001)
	for closed, sub in outline:
		if closed:
			sub.append(sub[0])
		path = CreatePath()
		paths.append(path)
		for item in sub:
			if len(item) == 2:
				apply(path.AppendLine, item)
			else:
				apply(path.AppendBezier, item)
		if closed:
			path.load_close()
	for path in paths:
		path.Transform(trafo)
	return tuple(paths)
	def read_polyline(self, line):
		readline = self.readline; tokenize = skread.tokenize_line
		args = tokenize(line)
		if len(args) != 15:
			raise SketchLoadError('Invalid PolyLine specification')
		sub_type, line_style, thickness, pen_color, fill_color, depth, \
					pen_style, area_fill, style, join, cap, \
					radius, forward_arrow, backward_arrow, npoints = args
		self.fill(fill_color, area_fill)
		self.line(pen_color, thickness, join, cap, line_style, style)

		if forward_arrow: readline() # XXX: implement this
		if backward_arrow:readline() # XXX: implement this
		if sub_type == 5: readline() # imported picture

		ncoords = npoints * 2
		pts = self.read_tokens(ncoords)
		if not pts:
			raise SketchLoadError('Missing points for polyline')
		if len(pts) > ncoords:
			del pts[ncoords:]
		
		trafo = self.trafo
		
		if sub_type in (1, 3, 5):
			path = CreatePath()
			map(path.AppendLine, coords_to_points(pts, trafo))
			if sub_type == 3:
				path.load_close(1)
			self.bezier(paths = path)
			self.set_depth(depth)
			
		elif sub_type in (2, 4):
			wx, wy = trafo(pts[2], pts[3]) - trafo(pts[0], pts[1])
			hx, hy = trafo(pts[4], pts[5]) - trafo(pts[2], pts[3])
			x, y =  trafo(pts[0], pts[1])
			if sub_type == 4 and radius > 0:
				radius1 = (radius * 72.0/80.0) / max(abs(wx),abs(wy))
				radius2 = (radius * 72.0/80.0) / max(abs(hx),abs(hy))
			else:
				radius1 = radius2 = 0
			self.rectangle(wx, wy, hx, hy, x, y, radius1 = radius1, radius2 = radius2)
			self.set_depth(depth)
	def POLYGONSET(self, size):
		path = ()
		subpath = CreatePath()
		for i in range(size / (2 * reff.vdc.size + 2)):
			P = self.Pnt()
			F = self.Enum()
			subpath.AppendLine(self.trafo(P))
			if F in (2, 3):
				if subpath.Node(-1) != subpath.Node(0):
					subpath.AppendLine(subpath.Node(0))
				subpath.load_close()
				path = path + (subpath,)
				subpath = CreatePath()
		if subpath.len != 0:
			if subpath.Node(-1) != subpath.Node(0):
				subpath.AppendLine(subpath.Node(0))
			subpath.load_close()
			path = path + (subpath,)
		self.setfillstyle()
		self.bezier(path)
	def read_spline(self, line):
		readline = self.readline; tokenize = skread.tokenize_line
		args = tokenize(line)
		if len(args) != 13:
			raise SketchLoadError('Invalid Spline specification')
		sub_type, line_style, thickness, pen_color, fill_color, depth, \
					pen_style, area_fill, style, cap, \
					forward_arrow, backward_arrow, npoints = args
		closed = sub_type & 1
		if forward_arrow: readline()
		if backward_arrow:readline()

		# in 3.2 all splines are stored as x-splines...
		if self.format_version == 3.2:
			if sub_type in (0, 2):
				sub_type = 4
			else:
				sub_type = 5
		
		self.fill(fill_color, area_fill)
		self.line(pen_color, thickness, 0, cap, line_style, style)
		
		ncoords = npoints * 2
		pts = self.read_tokens(ncoords)
		if not pts:
			raise SketchLoadError('Missing points for spline')
		if len(pts) > ncoords:
			del pts[ncoords:]
		pts = coords_to_points(pts, self.trafo)
		
		path = CreatePath()
		if sub_type in (2, 3):
			# interpolated spline, read 2 control points for each node
			ncontrols = 4 * npoints
			controls = self.read_tokens(ncontrols)
			if not controls:
				raise SketchLoadError('Missing control points for spline')
			if len(controls) > ncontrols:
				del controls[ncontrols:]
			controls = coords_to_points(controls[2:-2], self.trafo)
			path.AppendLine(pts[0])
			ncontrols = 2 * (npoints - 1)
			controls = [controls] * (npoints - 1)
			map(path.AppendBezier,
				map(getitem, controls, range(0, ncontrols, 2)),
				map(getitem, controls, range(1, ncontrols, 2)),
				pts[1:])
		elif sub_type in (0, 1):
			# approximated spline
			f13 = 1.0 / 3.0; f23 = 2.0 / 3.0
			curve = path.AppendBezier
			straight = path.AppendLine
			last = pts[0]
			cur = pts[1]
			start = node = (last + cur) / 2
			if closed:
				straight(node)
			else:
				straight(last)
				straight(node)
			last = cur
			for cur in pts[2:]:
				c1 = f13 * node + f23 * last
				node = (last + cur) / 2
				c2 = f13 * node + f23 * last
				curve(c1, c2, node)
				last = cur
			if closed:
				curve(f13 * node + f23 * last, f13 * start + f23 * last, start)
			else:
				straight(last)
		elif sub_type in (4, 5):
			# An X-spline. Treat it like a polyline for now.
			# read and discard the control info
			self.read_tokens(npoints)
			self.add_message(_("X-Spline treated as PolyLine"))
			map(path.AppendLine, pts)
			if closed:
				path.AppendLine(path.Node(0))
		if closed:
			path.load_close(1)
		self.bezier(paths = path)
		self.set_depth(depth)
Example #11
0
class AILoader(GenericLoader):

	format_name = format_name

	functions = {"C": 'curveto',
					"c": 'curveto_smooth',
					"V": 'curveto_v',
					"v": 'curveto_v_smooth',
					"Y": 'curveto_y',
					"y": 'curveto_y_smooth',
					"m": 'moveto',
					"l": 'lineto',
					"L": 'lineto',
					"w": 'set_line_width',
					"j": 'set_line_join',
					"J": 'set_line_cap',
					"d": 'set_line_dash',
					"G": 'set_line_gray',
					"K": 'set_line_cmyk',
					"XA": 'set_line_rgb',
					"X": 'set_line_cmyk_custom',
					"XX": 'set_line_generic_custom',
					"P": 'set_line_pattern',
					"g": 'set_fill_gray',
					"k": 'set_fill_cmyk',
					"cmyk": 'set_fill_cmyk',
					"Xa": 'set_fill_rgb',
					"rgb": 'set_fill_rgb',
					"x": 'set_fill_cmyk_custom',
					"Xx": 'set_fill_generic_custom',
					"p": 'set_fill_pattern',
					"F": 'fill',
					"f": 'fill_close',
					"S": 'stroke',
					"s": 'stroke_close',
					"B": 'fill_stroke',
					"b": 'fill_stroke_close',
					"closepath": 'fill_stroke_close',
					"N": 'invisible',        # an invisible open path
					"n": 'invisible_close',  # a invisible closed path
					"u": 'begin_group',
					"U": 'end_group',
					"*u": 'begin_compound_path',
					"newpath": 'begin_compound_path',
					"*U": 'end_compound_path',
					"gsave": 'end_compound_path',
					"*": 'guide',
					"[": 'mark',
					"]": 'make_array',
					"@": 'ignore_operator',
					"&": 'ignore_operator',
					"Bd": 'begin_gradient',
					"Bs": 'gradient_stop',
					"BS": 'dummy_gradient_stop',
					"Br": 'gradient_ramps',
					"BD": 'end_gradient',
					"Bb": 'begin_gradient_instance',
					"Bg": 'gradient_geometry',
					"BB": 'end_gradient_instance',
					"Lb": 'begin_ai_layer',
					"Ln": 'name_layer',
					"LB": 'end_ai_layer',
					"Pb": 'begin_palette',
					"PB": 'end_palette',
					"TE": 'set_standard_encoding',
					"TZ": 'reencode_font',
					"To": 'begin_text',
					"TO": 'end_text',
					"Tr": 'set_text_render',
					"Tf": 'set_text_font',
					"Ta": 'set_text_align',
					"Tp": 'begin_text_path',
					"TP": 'end_text_path',
					"Tx": 'render_text',
					"TX": 'render_text_inv',
					"XI": 'raster_image',
					}

	def __init__(self, file, filename, match,
					treat_toplevel_groups_as_layers = 1,
					flatten_groups = 1):
		GenericLoader.__init__(self, file, filename, match)
		self.line_color = StandardColors.black
		self.fill_color = StandardColors.black
		self.line_width = 0.0
		self.line_join  = const.JoinMiter
		self.line_cap  = const.CapButt
		self.line_dashes = ()
		self.cur_x = self.cur_y = 0.0
		self.treat_toplevel_groups_as_layers = treat_toplevel_groups_as_layers
		self.flatten_groups = flatten_groups
		self.guess_continuity = 1
		self.path = CreatePath()
		self.compound_path = None # If compound_path is None, we're
									# outside of a compound path,
									# otherwise it's a possibly empty list
									# of paths
		self.compound_render = ''
		self.stack = []
		self.gradients = {}
		self.in_gradient_instance = 0
		self.gradient_geo = None # set to a true value after Bg, and set
									# to false by make_gradient_pattern
		self.gradient_rect = None
		self.in_palette = 0
		self.in_text = 0
		self.ignore_fill = 0
		self.text_type = 0      # 0: point text, 1: area text, 2 = path text
		self.text_render = 0    # filled
		self.text_font = None
		self.text_size = 12

		# Test alignment. Possible values: 0: left, 1: center, 2:right,
		# 3: justified, 4: justified including last line
		self.text_align = 0

		self.text_string = []
		self.standard_encoding = encoding.adobe_standard
		self.font_map = {}
		self.guides = []
		self.format_version = 0.0

	def __del__(self):
		pass

	def warn(self, level, *args, **kw):
		message = apply(warn, (level,) + args, kw)
		self.add_message(message)

	def get_compiled(self):
		funclist = {}
		for char, name in self.functions.items():
			method = getattr(self, name)
			argc = method.im_func.func_code.co_argcount - 1
			funclist[char] = (method, argc)
		return funclist

	def pop(self):
		value = self.stack[-1]
		del self.stack[-1]
		return value

	def pop_multi(self, num):
		value = self.stack[-num:]
		del self.stack[-num:]
		return value

	def pop_to_mark(self):
		s = self.stack[:]
		s.reverse()
		try:
			idx = s.index(None)
			if idx:
				array = self.stack[-idx:]
				del self.stack[-idx - 1:]
			else:
				array = []
				del self.stack[-1]
			return array
		except:
			raise RuntimeError, 'No mark on stack'

	def ignore_operator(self):
		pass

	def mark(self):
		self.stack.append(None)

	def make_array(self):
		array = self.pop_to_mark()
		self.stack.append(array)

	def convert_color(self, color_spec):
		c = apply(CreateRGBColor, color_spec)
		return c

	def set_line_join(self, join):
		self.line_join = _ai_join[join]

	def set_line_cap(self, cap):
		self.line_cap = _ai_cap[cap]

	def set_line_width(self, w):
		self.line_width = w

	def set_line_dash(self, array, phase):
		self.line_dashes = tuple(array)

	def set_line_gray(self, k):
		self.line_color = CreateRGBColor(k, k, k)

	def set_line_cmyk(self, c, m, y, k):
		self.line_color = CreateCMYKColor(c, m, y, k)
		
	def set_line_rgb(self, r, g, b):
		self.line_color = CreateRGBColor(r, g, b)

	def set_line_cmyk_custom(self, c, m, y, k, name, tint):
		self.line_color = cmyk_custom_color(c, m, y, k, tint)
		
	def set_line_generic_custom(self, name, tint, type):
		if type == 0:
			# cmyk
			c, m, y, k = self.pop_multi(4)
			self.line_color = cmyk_custom_color(c, m, y, k, tint)
		else:
			# rgb
			r, g, b = self.pop_multi(3)
			self.line_color = rgb_custom_color(r, g, b, tint)

	def set_line_pattern(self, name, px, py, sx, sy, angle, rf, r, k, ka,
							matrix):
		if not self.in_palette:
			self.add_message(_("Vector patterns not supported. Using black"))
		self.line_color = StandardColors.black
			
	def set_fill_gray(self, k):
		self.fill_color = CreateRGBColor(k, k, k)

	def set_fill_cmyk(self, c, m, y, k):
		self.fill_color = CreateCMYKColor(c, m, y, k)
		
	def set_fill_rgb(self, r, g, b):
		self.fill_color = CreateRGBColor(r, g, b)

	def set_fill_cmyk_custom(self, c, m, y, k, name, tint):
		self.fill_color = cmyk_custom_color(c, m, y, k, tint)
		
	def set_fill_generic_custom(self, name, tint, type):
		if type == 0:
			# cmyk
			c, m, y, k = self.pop_multi(4)
			self.fill_color = cmyk_custom_color(c, m, y, k, tint)
		else:
			# rgb
			r, g, b = self.pop_multi(3)
			self.fill_color = rgb_custom_color(r, g, b, tint)

	def set_fill_pattern(self, name, px, py, sx, sy, angle, rf, r, k, ka,
							matrix):
		if not self.in_palette:
			self.add_message(_("Vector patterns not supported. Using black"))
		self.fill_color = StandardColors.black

	def ls(self):
		style = self.style
		style.line_pattern = SolidPattern(self.line_color)
		style.line_width = self.line_width
		style.line_join = self.line_join
		style.line_cap = self.line_cap
		style.line_dashes = self.line_dashes

	def lsnone(self):
		self.style.line_pattern = EmptyPattern

	def fs(self):
		if self.gradient_geo:
			pattern = self.make_gradient_pattern()
		else:
			pattern = SolidPattern(self.fill_color)
		self.style.fill_pattern = pattern

	def fsnone(self):
		self.style.fill_pattern = EmptyPattern

	def stroke(self):
		if self.compound_path is not None:
			self.compound_render = 'stroke'
		else:
			self.ls()
			self.fsnone()
		self.bezier()

	def stroke_close(self):
		self.bezier_close()
		self.stroke()

	def fill(self):
		if self.ignore_fill:
			return
		if self.compound_path is not None:
			self.compound_render = 'fill'
		else:
			self.lsnone()
			self.fs()
		self.bezier()

	def fill_close(self):
		self.bezier_close()
		self.fill()

	def fill_stroke(self):
		if self.compound_path is not None:
			self.compound_render = 'fill_stroke'
		else:
			self.ls()
			self.fs()
		self.bezier()

	def fill_stroke_close(self):
		self.bezier_close()
		self.fill_stroke()

	def invisible(self):
		if self.compound_path is not None:
			self.compound_render = 'invisible'
		else:
			self.lsnone()
			self.fsnone()
		self.bezier()

	def invisible_close(self):
		self.bezier_close()
		self.invisible()

	# Gradient functions
	def begin_gradient(self, name, type, ncolors):
		self.gradient_info = name, type, ncolors

	def gradient_stop(self, color_style, mid_point, ramp_point):
		if color_style == 0:
			# gray scale
			k = self.pop()
			color = CreateRGBColor(k, k, k)
		elif color_style == 1:
			# CMYK
			color = apply(CreateCMYKColor, tuple(self.pop_multi(4)))
		elif color_style == 2:
			# RGB Color
			args = tuple(self.pop_multi(7))
			# The cmyk and rgb values usually differ slightly because AI
			# does some color correction. Which values should we choose
			# here?
			color = apply(CreateRGBColor, args[-3:])
			color = apply(CreateCMYKColor, args[:4])
		elif color_style == 3:
			# CMYK Custom Color
			args = self.pop_multi(6)
			color = apply(CreateCMYKColor, tuple(args[:4]))
		else:
			self.add_message(_("Gradient ColorStyle %d not yet supported."
								"substituted black")
								% color_style)
			if color_style == 4:
				n = 10
			else:
				self.add_message(_("Unknown ColorStyle %d")
									% color_style)
			self.pop_multi(n)
			color = StandardColors.black # XXX
		#color = apply(CreateRGBColor, color)
		self.stack.append((ramp_point / 100.0, color))

	def dummy_gradient_stop(self, color_style, mid_point, ramp_point):
		# same as gradient_stop but ignore all arguments. Illustrator 8
		# seems to introduce this one for printing (i.e. Illustrator 8
		# files with printing info contain the gradient stops *twice* in
		# exactly the same format but once with the Bs operator and once
		# with BS. I guess this has something to do with support for
		# PostScript Level 3 and backwards compatibility with older
		# Illustrator versions.
		if color_style == 0:
			# gray scale
			k = self.pop()
		elif color_style == 1:
			# CMYK
			self.pop_multi(4)
		elif color_style == 2:
			# RGB Color
			self.pop_multi(7)
		elif color_style == 3:
			# CMYK Custom Color
			self.pop_multi(6)
		elif color_style == 4:
			self.pop_multi(10)
		else:
			self.add_message(_("Unknown ColorStyle %d") % color_style)

	def gradient_ramps(self, ramp_type):
		# defines the ramp colors with a bunch of strings for printing.
		# Here we just pop all the strings off the stack
		num = (1, 4, 5, 6, 7, 8, 9)[ramp_type]
		self.pop_multi(num)

	def end_gradient(self):
		self.make_array()
		array = self.pop()
		if len(array) < 2:
			self.add_message(_("less than two color stops in gradient"))
		else:
			# sometimes the ramp_point values are increasing, sometimes
			# decreasing... what's going on here? The docs say they are
			# increasing.
			if array[0][0] > array[-1][0]:
				array.reverse()
			name, type, ncolors = self.gradient_info
			self.gradients[name] = (type, array)
		del self.stack[:]
		#self.pop_to_mark()

	def begin_gradient_instance(self):
		self.in_gradient_instance = 1
		self.ignore_fill = 1

	def gradient_geometry(self, flag, name, xorig, yorig, angle, length,
							a, b, c, d, tx, ty):
		trafo = Trafo(a, b, c, d, tx, ty)
		trafo = artboard_trafo_inv(trafo(artboard_trafo))
		start = Point(xorig, yorig)
		end = start + Polar(length, (pi * angle) / 180.0)
		self.gradient_geo = (name, trafo, start, end)

	def make_gradient_pattern(self):
		name, trafo, start, end = self.gradient_geo
		self.gradient_geo = None
		type, array = self.gradients[name]
		array = array[:]
		if type == 0:
			# linear (axial) gradient
			origdir = end - start
			start = trafo(start)
			end = trafo(end)
			dir = end - start
			try:
				# adjust endpoint to accomodate trafo
				v = trafo.DTransform(origdir.y, -origdir.x).normalized()
				v = Point(v.y, -v.x) # rotate 90 degrees
				end = start + (v * dir) * v
				dir = end - start
			except ZeroDivisionError:
				pass

			trafo2 = Trafo(dir.x, dir.y, dir.y, -dir.x, start.x, start.y)
			trafo2 = trafo2.inverse()
			left, bottom, right, top = trafo2(self.current_bounding_rect())
			if right > left:
				factor = 1 / (right - left)
				offset = -left * factor
			else:
				factor = 1
				offset = 0
			array = fix_gradient(array, factor, offset)
			pattern = LinearGradient(MultiGradient(array),
										(start - end).normalized())
		elif type == 1:
			# radial gradient
			start = trafo(start)
			end = trafo(end)
			left, bottom, right, top = self.current_bounding_rect()
			if left == right or top == bottom:
				# an empty coord_rect????
				center = Point(0, 0)
			else:
				center = Point((start.x - left) / (right - left),
								(start.y - bottom) / (top - bottom))
			radius = max(hypot(left - start.x,  top - start.y),
							hypot(right - start.x, top - start.y),
							hypot(right - start.x, bottom - start.y),
							hypot(left - start.x,  bottom - start.y))
			if radius:
				factor = -abs(start - end) / radius
				array = fix_gradient(array, factor, 1)
			pattern = RadialGradient(MultiGradient(array), center)
		else:
			self.add_message(_("Unknown gradient type %d"), type)
			pattern = EmptyPattern
		return pattern

	def current_bounding_rect(self):
		if self.gradient_rect is not None:
			rect = self.gradient_rect
		else:
			rect = self.path.accurate_rect()
		if not self.style.line_pattern.is_Empty:
			rect = fix_bounding_rect(rect, self.style)
		return rect

	def end_gradient_instance(self, flag):
		self.ignore_fill = 0
		if flag == 2:
			self.fill_stroke_close()
		elif flag == 1:
			self.fill_stroke()
		else:
			self.fill()
		self.in_gradient_instance = 0
			

	# Path construction
	def moveto(self, x, y):
		self.cur_x = x
		self.cur_y = y
		self.path.AppendLine(x, y)

	def lineto(self, x, y):
		self.cur_x = x
		self.cur_y = y
		self.path.AppendLine(x, y)

	def curveto(self, x1, y1, x2, y2, x3, y3):
		self.path.AppendBezier(x1, y1, x2, y2, x3, y3)
		self.cur_x = x3
		self.cur_y = y3

	def curveto_smooth(self, x1, y1, x2, y2, x3, y3):
		self.path.AppendBezier(x1, y1, x2, y2, x3, y3, ContSmooth)
		self.cur_x = x3
		self.cur_y = y3

	def curveto_v(self, x2, y2, x3, y3):
		# current point and first control point are identical
		self.path.AppendBezier(self.cur_x, self.cur_y, x2, y2, x3, y3)
		self.cur_x = x3
		self.cur_y = y3

	def curveto_v_smooth(self, x2, y2, x3, y3):
		# current point and first control point are identical
		self.path.AppendBezier(self.cur_x, self.cur_y, x2, y2, x3, y3,
								ContSmooth)
		self.cur_x = x3
		self.cur_y = y3

	def curveto_y(self, x1, y1, x3, y3):
		# endpoint and last controlpoint are identical
		self.path.AppendBezier(x1, y1, x3, y3, x3, y3)
		self.cur_x = x3
		self.cur_y = y3

	def curveto_y_smooth(self, x1, y1, x3, y3):
		# endpoint and last controlpoint are identical
		self.path.AppendBezier(x1, y1, x3, y3, x3, y3, ContSmooth)
		self.cur_x = x3
		self.cur_y = y3

	def bezier_close(self):
		if self.path.len > 1:
			self.path.AppendLine(self.path.Node(0))
			self.path.load_close(1)

	def bezier(self):
		if self.guess_continuity:
			self.path.guess_continuity()
		if self.path.len > 0:
			if self.compound_path is not None:
				self.compound_path.append(self.path)
			else:
				GenericLoader.bezier(self, paths = (self.path,))
		self.path = CreatePath()

	# compound paths

	def begin_compound_path(self):
		self.compound_path = []

	def end_compound_path(self):
		paths = tuple(self.compound_path)
		self.compound_path = None
		if paths:
			# XXX ugly
			if self.gradient_geo:
				rect = paths[0].accurate_rect()
				for path in paths[1:]:
					rect = UnionRects(rect, path.accurate_rect())
				self.gradient_rect = rect
			else:
				self.gradient_rect = None
			getattr(self, self.compound_render)()
			GenericLoader.bezier(self, paths = paths)
		
	# Groups
	
	def begin_group(self):
		if self.compound_path is None:
			# a normal group
			if self.treat_toplevel_groups_as_layers:
				if self.composite_class == Document:
					self.begin_layer()
					return
			GenericLoader.begin_group(self)
		else:
			# a `compound group'. Ignored since Sketch doesn't have this.
			pass

	def end_group(self):
		if self.compound_path is None:
			# a normal group
			if self.composite_class == Layer:
				self.end_composite()
			else:
				try:
					GenericLoader.end_group(self)
					if self.flatten_groups:
						if self.object.NumObjects() == 1:
							obj = self.object.GetObjects()[0]
							del self.composite_items[-1]
							self.append_object(obj)
				except EmptyCompositeError:
					pass
		else:
			# a `compound group'. Ignored since Sketch doesn't have this.
			pass

	# Layers

	def begin_layer(self):
		self.layer(_("Layer %d") % (len(self.composite_items) + 1))

	def begin_ai_layer(self):
		if self.format_version >= 4.0:
			visible, preview, enabled, printing, dimmed, unused, has_mlm,\
					color, red, green, blue, unused, unused = self.pop_multi(13)
		else:
			visible, preview, enabled, printing, dimmed, has_mlm, \
						color, red, green, blue = self.pop_multi(10)
		color = CreateRGBColor(red / 255.0, green / 255.0, blue / 255.0)
		self.layer_kw_args = {'printable': printing,
								'visible': visible,
								'locked': not enabled,
								'outline_color': color}

	def end_ai_layer(self):
		self.end_layer()

	def name_layer(self, name):
		apply(self.layer, (name,), self.layer_kw_args)

	# Guides

	def guide(self, op):
		#print 'guide', op
		method = getattr(self, self.functions[op])
		method()
		guide = self.pop_last()
		self.guides.append(guide)

	# Palette

	def begin_palette(self):
		self.in_palette = 1

	def end_palette(self):
		self.in_palette = 0

	# Text

	def set_standard_encoding(self):
		encoding = list(self.standard_encoding)
		pos = 0
		defs = self.pop_to_mark()
		for item in defs:
			if type(item) == IntType:
				pos = item
			elif type(item) == StringType:
				encoding[pos] = item
				pos = pos + 1
			else:
				self.add_message('unknown item %s in encoding' % `item`)
		self.standard_encoding = tuple(encoding)

	def define_font(self, psname, newname, encoding = None):
		if encoding is None:
			encoding = self.standard_encoding[:]
		self.font_map[newname] = FontInfo(psname, newname, encoding)

	def reencode_font(self):
		args = self.pop_to_mark()
		if type(args[-1]) == ListType:
			self.add_message(_("Multiple Master fonts not supported. "
								"Using Times Roman"))
			newname = args[-6]
			self.define_font('Times Roman', newname)
		else:
			newname, psname, direction, script, usedefault = args[-5:]
			if len(args) > 5:
				self.add_message(_("Additional encoding ignored"))
			self.define_font(psname, newname)


	def begin_text(self, text_type):
		self.in_text = 1
		self.text_type = text_type
		self.text_string = []
		if text_type == 1:
			self.add_message(_("Area text not supported"))
		if text_type == 2:
			GenericLoader.begin_group(self)

	def end_text(self):
		# we don't support area text (text_type 1) at all. Return
		# immediately in that case.
		if self.text_type == 1:
			return

		# first, turn the text accumulated in the list text_string into
		# a single string and unify line endings to newline characters.
		text = string.join(self.text_string, '')
		text = string.replace(text, '\r\n', '\n')
		text = string.replace(text, '\r', '\n')

		# remove a trailing newline. Many Illustrator files contain a
		# trailing newline as 'overflow' text, there's probably a better
		# way to deal with this...
		if text[-1:] == "\n":
			text = text[:-1]

		# Re-encode to Latin1
		text = self.text_font.Reencode(text)

		if not string.strip(text):
			if self.text_type == 2:
				self.end_composite()
				del self.composite_items[-1]
				if len(self.composite_items) > 0:
					self.object = self.composite_items[-1]
			return

		# first create a simple text object
		self.fs()
		self.style.font = GetFont(self.text_font.psname)
		self.style.font_size = self.text_size
		self.simple_text(text, self.text_trafo,
							halign = _ai_text_align[self.text_align])

		# if we're actually supposed to create a path-text object, turn
		# the text object just created into a path-text object
		if self.text_type == 2:
			GenericLoader.end_group(self)
			group = self.pop_last()
			objects = group.GetObjects()
			if len(objects) == 2:
				path, text = objects
				self.append_object(PathText(text, path,
											start_pos = self.text_start_pos))
				#self.composite_items[-1] = self.object

		# we've finished the text object
		self.in_text = 0

	def set_text_render(self, render):
		self.text_render = render

	def set_text_align(self, align):
		self.text_align = align

	def set_text_font(self):
		# In Illustrator < 7, the operator has two arguments, new
		# fontname and size. In Illustrator >= 7, there are two
		# additional arguments, ascent and descent.
		args = self.pop_multi(2)
		if type(args[0]) != StringType:
			newname, size = self.pop_multi(2)
		else:
			newname, size = args
		if self.font_map.has_key(newname):
			self.text_font = self.font_map[newname]
		elif newname[0] == '_':
			# special case for ai files generated by ps2ai. They don't
			# use the TZ operator to reencode the fonts and define the _
			# names.
			self.define_font(newname[1:], newname)
			self.text_font = self.font_map[newname]
		else:
			self.add_message(_("No font %s.") % newname)
		self.text_size = size


	def begin_text_path(self, a, b, c, d, tx, ty, start_pos):
		self.text_trafo = Trafo(a, b, c, d, tx, ty)
		self.text_start_pos = start_pos

	def end_text_path(self):
		pass

	def render_text(self, text):
		if self.text_type != 2:
			# in a path text only the invisible render operators count
			self.text_string.append(text)

	def render_text_inv(self, text):
		self.text_string.append(text)


	# Raster Image

	def raster_image(self, trafo, llx, lly, urx, ury, width, height,
						bits, mode, alpha, reserved, encoding, mask):
		if bits != 8 or mode not in (1, 3):
			self.add_message(_("Only images with 1 or 3 components "
								"and 8 bits/component supported"))
			self.skip_to_dsc("AI5_EndRaster")
			return
		decode = streamfilter.SubFileDecode(self.tokenizer.source,
											'%AI5_EndRaster')
		if encoding == 0:
			decode = streamfilter.HexDecode(decode)
		data_length = mode * width * height
		data = decode.read(data_length)
		#f = open("/tmp/dump.ppm", "w")
		#if mode == 1:
		#    f.write("P5\n%d %d\n255\n" % (width, height))
		#else:
		#    f.write("P6\n%d %d\n255\n" % (width, height))
		#f.write(data)
		#f.close()
		if mode == 1:
			mode = 'L'
		elif mode == 3:
			mode = 'RGB'
		elif mode == 4:
			mode == 'CMYK'
		image = Image.fromstring(mode, (width, height), data, 'raw', mode)
		self.image(image, apply(Trafo, tuple(trafo)))
		
	#

	def append_object(self, object):
		if self.composite_class == Document \
			and object.__class__ != Layer:
			self.begin_layer()
		self.composite_items.append(object)
		self.object = object

	#
	#

	def skip_to_dsc(self, *endcomments):
		next_dsc = self.tokenizer.next_dsc; split = string.split
		while 1:
			value = next_dsc()
			if not value:
				return
			if ':' in value:
				keyword, value = split(value, ':', 1)
			else:
				keyword = value
			if keyword in endcomments:
				return

	def read_prolog(self):
		next = self.tokenizer.next
		DSC = DSC_COMMENT; split = string.split
		while 1:
			token, value = next()
			if token == DSC:
				if ':' in value:
					keyword, value = split(value, ':', 1)
				else:
					keyword = value
				if keyword in ('EndProlog', 'BeginSetup'):
					return keyword
				if keyword[:14] == "AI5_FileFormat":
					self.format_version = string.atof(keyword[14:])
				elif keyword == 'BeginProcSet':
					# some ai files exported by corel draw don't have an
					# EndProcSet comment after a BeginProcSet...
					self.skip_to_dsc('EndProcSet', 'EndProlog')
				elif keyword == 'BeginResource':
					self.skip_to_dsc('EndResource', 'EndProlog')
				#elif keyword == 'Creator':
					## try to determine whether the file really is an
					## illustrator file as opposed to some other EPS
					## file. It seems that Illustrator itself only
					## accepts EPS files as illustrator files if they
					## contain "Adobe Illustrator" in their Create
					## DSC-comment
					#if string.find(value, "Adobe Illustrator") == -1:
						#self.add_message("This is probably not an"
											#" Illustrator file."
											#" Try embedding it as EPS")
			if token == END:
				return

	def Load(self):
		funclist = self.get_compiled()
		# binding frequently used functions to local variables speeds up
		# the process considerably...
		a = apply; t = tuple
		DSC = DSC_COMMENT; MAX = MAX_DATA_TOKEN; split = string.split
		stack = self.stack; push = self.stack.append
		unknown_operator = (None, None)

		decoder = streamfilter.StringDecode(self.match.string, self.file)
		self.tokenizer = PSTokenizer(decoder)
		self.tokenizer.ai_pseudo_comments = 1
		self.tokenizer.ai_dsc = 1
		next = self.tokenizer.next
		
		self.document()

		value = self.read_prolog()

		while 1:
			token, value = next()
			if token <= MAX:
				push(value)
			elif token == DSC:
				if ':' in value:
					keyword, value = split(value, ':', 1)
				else:
					keyword = value
				if keyword in ('PageTrailer', 'Trailer'):
					break
				elif keyword == 'AI5_BeginPalette':
					self.skip_to_dsc('AI5_EndPalette', 'EndSetup')
				elif keyword == "AI8_BeginBrushPattern":
					self.skip_to_dsc('AI8_EndBrushPattern', 'EndSetup')

			elif token == END:
				break
			elif token == OPERATOR:
				method, argc = funclist.get(value, unknown_operator)
				#if method is not None:
				#    name = method.__name__
				#else:
				#    name = `method`
				if method is None:
					del stack[:]
				else:
					try:
						if argc:
							args = t(stack[-argc:])
							del stack[-argc:]
							a(method, args)
						else:
							method()
					except:
						warn_tb(INTERNAL, 'AILoader: error')

		self.end_all()
		self.object.load_Completed()
		for obj in self.guides:
			self.object.guide_layer.Insert(obj, None)

		return self.object
Example #12
0
class AILoader(GenericLoader):

    format_name = format_name

    functions = {
        "C": 'curveto',
        "c": 'curveto_smooth',
        "V": 'curveto_v',
        "v": 'curveto_v_smooth',
        "Y": 'curveto_y',
        "y": 'curveto_y_smooth',
        "m": 'moveto',
        "l": 'lineto',
        "L": 'lineto',
        "w": 'set_line_width',
        "j": 'set_line_join',
        "J": 'set_line_cap',
        "d": 'set_line_dash',
        "G": 'set_line_gray',
        "K": 'set_line_cmyk',
        "XA": 'set_line_rgb',
        "X": 'set_line_cmyk_custom',
        "XX": 'set_line_generic_custom',
        "P": 'set_line_pattern',
        "g": 'set_fill_gray',
        "k": 'set_fill_cmyk',
        "cmyk": 'set_fill_cmyk',
        "Xa": 'set_fill_rgb',
        "rgb": 'set_fill_rgb',
        "x": 'set_fill_cmyk_custom',
        "Xx": 'set_fill_generic_custom',
        "p": 'set_fill_pattern',
        "F": 'fill',
        "f": 'fill_close',
        "S": 'stroke',
        "s": 'stroke_close',
        "B": 'fill_stroke',
        "b": 'fill_stroke_close',
        "closepath": 'fill_stroke_close',
        "N": 'invisible',  # an invisible open path
        "n": 'invisible_close',  # a invisible closed path
        "u": 'begin_group',
        "U": 'end_group',
        "*u": 'begin_compound_path',
        "newpath": 'begin_compound_path',
        "*U": 'end_compound_path',
        "gsave": 'end_compound_path',
        "*": 'guide',
        "[": 'mark',
        "]": 'make_array',
        "@": 'ignore_operator',
        "&": 'ignore_operator',
        "Bd": 'begin_gradient',
        "Bs": 'gradient_stop',
        "BS": 'dummy_gradient_stop',
        "Br": 'gradient_ramps',
        "BD": 'end_gradient',
        "Bb": 'begin_gradient_instance',
        "Bg": 'gradient_geometry',
        "BB": 'end_gradient_instance',
        "Lb": 'begin_ai_layer',
        "Ln": 'name_layer',
        "LB": 'end_ai_layer',
        "Pb": 'begin_palette',
        "PB": 'end_palette',
        "TE": 'set_standard_encoding',
        "TZ": 'reencode_font',
        "To": 'begin_text',
        "TO": 'end_text',
        "Tr": 'set_text_render',
        "Tf": 'set_text_font',
        "Ta": 'set_text_align',
        "Tp": 'begin_text_path',
        "TP": 'end_text_path',
        "Tx": 'render_text',
        "TX": 'render_text_inv',
        "XI": 'raster_image',
    }

    def __init__(self,
                 file,
                 filename,
                 match,
                 treat_toplevel_groups_as_layers=1,
                 flatten_groups=1):
        GenericLoader.__init__(self, file, filename, match)
        self.line_color = StandardColors.black
        self.fill_color = StandardColors.black
        self.line_width = 0.0
        self.line_join = const.JoinMiter
        self.line_cap = const.CapButt
        self.line_dashes = ()
        self.cur_x = self.cur_y = 0.0
        self.treat_toplevel_groups_as_layers = treat_toplevel_groups_as_layers
        self.flatten_groups = flatten_groups
        self.guess_continuity = 1
        self.path = CreatePath()
        self.compound_path = None  # If compound_path is None, we're
        # outside of a compound path,
        # otherwise it's a possibly empty list
        # of paths
        self.compound_render = ''
        self.stack = []
        self.gradients = {}
        self.in_gradient_instance = 0
        self.gradient_geo = None  # set to a true value after Bg, and set
        # to false by make_gradient_pattern
        self.gradient_rect = None
        self.in_palette = 0
        self.in_text = 0
        self.ignore_fill = 0
        self.text_type = 0  # 0: point text, 1: area text, 2 = path text
        self.text_render = 0  # filled
        self.text_font = None
        self.text_size = 12

        # Test alignment. Possible values: 0: left, 1: center, 2:right,
        # 3: justified, 4: justified including last line
        self.text_align = 0

        self.text_string = []
        self.standard_encoding = encoding.adobe_standard
        self.font_map = {}
        self.guides = []
        self.format_version = 0.0

    def __del__(self):
        pass

    def warn(self, level, *args, **kw):
        message = apply(warn, (level, ) + args, kw)
        self.add_message(message)

    def get_compiled(self):
        funclist = {}
        for char, name in self.functions.items():
            method = getattr(self, name)
            argc = method.im_func.func_code.co_argcount - 1
            funclist[char] = (method, argc)
        return funclist

    def pop(self):
        value = self.stack[-1]
        del self.stack[-1]
        return value

    def pop_multi(self, num):
        value = self.stack[-num:]
        del self.stack[-num:]
        return value

    def pop_to_mark(self):
        s = self.stack[:]
        s.reverse()
        try:
            idx = s.index(None)
            if idx:
                array = self.stack[-idx:]
                del self.stack[-idx - 1:]
            else:
                array = []
                del self.stack[-1]
            return array
        except:
            raise RuntimeError, 'No mark on stack'

    def ignore_operator(self):
        pass

    def mark(self):
        self.stack.append(None)

    def make_array(self):
        array = self.pop_to_mark()
        self.stack.append(array)

    def convert_color(self, color_spec):
        c = apply(CreateRGBColor, color_spec)
        return c

    def set_line_join(self, join):
        self.line_join = _ai_join[join]

    def set_line_cap(self, cap):
        self.line_cap = _ai_cap[cap]

    def set_line_width(self, w):
        self.line_width = w

    def set_line_dash(self, array, phase):
        self.line_dashes = tuple(array)

    def set_line_gray(self, k):
        self.line_color = CreateRGBColor(k, k, k)

    def set_line_cmyk(self, c, m, y, k):
        self.line_color = CreateCMYKColor(c, m, y, k)

    def set_line_rgb(self, r, g, b):
        self.line_color = CreateRGBColor(r, g, b)

    def set_line_cmyk_custom(self, c, m, y, k, name, tint):
        self.line_color = cmyk_custom_color(c, m, y, k, tint)

    def set_line_generic_custom(self, name, tint, type):
        if type == 0:
            # cmyk
            c, m, y, k = self.pop_multi(4)
            self.line_color = cmyk_custom_color(c, m, y, k, tint)
        else:
            # rgb
            r, g, b = self.pop_multi(3)
            self.line_color = rgb_custom_color(r, g, b, tint)

    def set_line_pattern(self, name, px, py, sx, sy, angle, rf, r, k, ka,
                         matrix):
        if not self.in_palette:
            self.add_message(_("Vector patterns not supported. Using black"))
        self.line_color = StandardColors.black

    def set_fill_gray(self, k):
        self.fill_color = CreateRGBColor(k, k, k)

    def set_fill_cmyk(self, c, m, y, k):
        self.fill_color = CreateCMYKColor(c, m, y, k)

    def set_fill_rgb(self, r, g, b):
        self.fill_color = CreateRGBColor(r, g, b)

    def set_fill_cmyk_custom(self, c, m, y, k, name, tint):
        self.fill_color = cmyk_custom_color(c, m, y, k, tint)

    def set_fill_generic_custom(self, name, tint, type):
        if type == 0:
            # cmyk
            c, m, y, k = self.pop_multi(4)
            self.fill_color = cmyk_custom_color(c, m, y, k, tint)
        else:
            # rgb
            r, g, b = self.pop_multi(3)
            self.fill_color = rgb_custom_color(r, g, b, tint)

    def set_fill_pattern(self, name, px, py, sx, sy, angle, rf, r, k, ka,
                         matrix):
        if not self.in_palette:
            self.add_message(_("Vector patterns not supported. Using black"))
        self.fill_color = StandardColors.black

    def ls(self):
        style = self.style
        style.line_pattern = SolidPattern(self.line_color)
        style.line_width = self.line_width
        style.line_join = self.line_join
        style.line_cap = self.line_cap
        style.line_dashes = self.line_dashes

    def lsnone(self):
        self.style.line_pattern = EmptyPattern

    def fs(self):
        if self.gradient_geo:
            pattern = self.make_gradient_pattern()
        else:
            pattern = SolidPattern(self.fill_color)
        self.style.fill_pattern = pattern

    def fsnone(self):
        self.style.fill_pattern = EmptyPattern

    def stroke(self):
        if self.compound_path is not None:
            self.compound_render = 'stroke'
        else:
            self.ls()
            self.fsnone()
        self.bezier()

    def stroke_close(self):
        self.bezier_close()
        self.stroke()

    def fill(self):
        if self.ignore_fill:
            return
        if self.compound_path is not None:
            self.compound_render = 'fill'
        else:
            self.lsnone()
            self.fs()
        self.bezier()

    def fill_close(self):
        self.bezier_close()
        self.fill()

    def fill_stroke(self):
        if self.compound_path is not None:
            self.compound_render = 'fill_stroke'
        else:
            self.ls()
            self.fs()
        self.bezier()

    def fill_stroke_close(self):
        self.bezier_close()
        self.fill_stroke()

    def invisible(self):
        if self.compound_path is not None:
            self.compound_render = 'invisible'
        else:
            self.lsnone()
            self.fsnone()
        self.bezier()

    def invisible_close(self):
        self.bezier_close()
        self.invisible()

    # Gradient functions
    def begin_gradient(self, name, type, ncolors):
        self.gradient_info = name, type, ncolors

    def gradient_stop(self, color_style, mid_point, ramp_point):
        if color_style == 0:
            # gray scale
            k = self.pop()
            color = CreateRGBColor(k, k, k)
        elif color_style == 1:
            # CMYK
            color = apply(CreateCMYKColor, tuple(self.pop_multi(4)))
        elif color_style == 2:
            # RGB Color
            args = tuple(self.pop_multi(7))
            # The cmyk and rgb values usually differ slightly because AI
            # does some color correction. Which values should we choose
            # here?
            color = apply(CreateRGBColor, args[-3:])
            color = apply(CreateCMYKColor, args[:4])
        elif color_style == 3:
            # CMYK Custom Color
            args = self.pop_multi(6)
            color = apply(CreateCMYKColor, tuple(args[:4]))
        else:
            self.add_message(
                _("Gradient ColorStyle %d not yet supported."
                  "substituted black") % color_style)
            if color_style == 4:
                n = 10
            else:
                self.add_message(_("Unknown ColorStyle %d") % color_style)
            self.pop_multi(n)
            color = StandardColors.black  # XXX
        #color = apply(CreateRGBColor, color)
        self.stack.append((ramp_point / 100.0, color))

    def dummy_gradient_stop(self, color_style, mid_point, ramp_point):
        # same as gradient_stop but ignore all arguments. Illustrator 8
        # seems to introduce this one for printing (i.e. Illustrator 8
        # files with printing info contain the gradient stops *twice* in
        # exactly the same format but once with the Bs operator and once
        # with BS. I guess this has something to do with support for
        # PostScript Level 3 and backwards compatibility with older
        # Illustrator versions.
        if color_style == 0:
            # gray scale
            k = self.pop()
        elif color_style == 1:
            # CMYK
            self.pop_multi(4)
        elif color_style == 2:
            # RGB Color
            self.pop_multi(7)
        elif color_style == 3:
            # CMYK Custom Color
            self.pop_multi(6)
        elif color_style == 4:
            self.pop_multi(10)
        else:
            self.add_message(_("Unknown ColorStyle %d") % color_style)

    def gradient_ramps(self, ramp_type):
        # defines the ramp colors with a bunch of strings for printing.
        # Here we just pop all the strings off the stack
        num = (1, 4, 5, 6, 7, 8, 9)[ramp_type]
        self.pop_multi(num)

    def end_gradient(self):
        self.make_array()
        array = self.pop()
        if len(array) < 2:
            self.add_message(_("less than two color stops in gradient"))
        else:
            # sometimes the ramp_point values are increasing, sometimes
            # decreasing... what's going on here? The docs say they are
            # increasing.
            if array[0][0] > array[-1][0]:
                array.reverse()
            name, type, ncolors = self.gradient_info
            self.gradients[name] = (type, array)
        del self.stack[:]
        #self.pop_to_mark()

    def begin_gradient_instance(self):
        self.in_gradient_instance = 1
        self.ignore_fill = 1

    def gradient_geometry(self, flag, name, xorig, yorig, angle, length, a, b,
                          c, d, tx, ty):
        trafo = Trafo(a, b, c, d, tx, ty)
        trafo = artboard_trafo_inv(trafo(artboard_trafo))
        start = Point(xorig, yorig)
        end = start + Polar(length, (pi * angle) / 180.0)
        self.gradient_geo = (name, trafo, start, end)

    def make_gradient_pattern(self):
        name, trafo, start, end = self.gradient_geo
        self.gradient_geo = None
        type, array = self.gradients[name]
        array = array[:]
        if type == 0:
            # linear (axial) gradient
            origdir = end - start
            start = trafo(start)
            end = trafo(end)
            dir = end - start
            try:
                # adjust endpoint to accomodate trafo
                v = trafo.DTransform(origdir.y, -origdir.x).normalized()
                v = Point(v.y, -v.x)  # rotate 90 degrees
                end = start + (v * dir) * v
                dir = end - start
            except ZeroDivisionError:
                pass

            trafo2 = Trafo(dir.x, dir.y, dir.y, -dir.x, start.x, start.y)
            trafo2 = trafo2.inverse()
            left, bottom, right, top = trafo2(self.current_bounding_rect())
            if right > left:
                factor = 1 / (right - left)
                offset = -left * factor
            else:
                factor = 1
                offset = 0
            array = fix_gradient(array, factor, offset)
            pattern = LinearGradient(MultiGradient(array),
                                     (start - end).normalized())
        elif type == 1:
            # radial gradient
            start = trafo(start)
            end = trafo(end)
            left, bottom, right, top = self.current_bounding_rect()
            if left == right or top == bottom:
                # an empty coord_rect????
                center = Point(0, 0)
            else:
                center = Point((start.x - left) / (right - left),
                               (start.y - bottom) / (top - bottom))
            radius = max(hypot(left - start.x, top - start.y),
                         hypot(right - start.x, top - start.y),
                         hypot(right - start.x, bottom - start.y),
                         hypot(left - start.x, bottom - start.y))
            if radius:
                factor = -abs(start - end) / radius
                array = fix_gradient(array, factor, 1)
            pattern = RadialGradient(MultiGradient(array), center)
        else:
            self.add_message(_("Unknown gradient type %d"), type)
            pattern = EmptyPattern
        return pattern

    def current_bounding_rect(self):
        if self.gradient_rect is not None:
            rect = self.gradient_rect
        else:
            rect = self.path.accurate_rect()
        if not self.style.line_pattern.is_Empty:
            rect = fix_bounding_rect(rect, self.style)
        return rect

    def end_gradient_instance(self, flag):
        self.ignore_fill = 0
        if flag == 2:
            self.fill_stroke_close()
        elif flag == 1:
            self.fill_stroke()
        else:
            self.fill()
        self.in_gradient_instance = 0

    # Path construction
    def moveto(self, x, y):
        self.cur_x = x
        self.cur_y = y
        self.path.AppendLine(x, y)

    def lineto(self, x, y):
        self.cur_x = x
        self.cur_y = y
        self.path.AppendLine(x, y)

    def curveto(self, x1, y1, x2, y2, x3, y3):
        self.path.AppendBezier(x1, y1, x2, y2, x3, y3)
        self.cur_x = x3
        self.cur_y = y3

    def curveto_smooth(self, x1, y1, x2, y2, x3, y3):
        self.path.AppendBezier(x1, y1, x2, y2, x3, y3, ContSmooth)
        self.cur_x = x3
        self.cur_y = y3

    def curveto_v(self, x2, y2, x3, y3):
        # current point and first control point are identical
        self.path.AppendBezier(self.cur_x, self.cur_y, x2, y2, x3, y3)
        self.cur_x = x3
        self.cur_y = y3

    def curveto_v_smooth(self, x2, y2, x3, y3):
        # current point and first control point are identical
        self.path.AppendBezier(self.cur_x, self.cur_y, x2, y2, x3, y3,
                               ContSmooth)
        self.cur_x = x3
        self.cur_y = y3

    def curveto_y(self, x1, y1, x3, y3):
        # endpoint and last controlpoint are identical
        self.path.AppendBezier(x1, y1, x3, y3, x3, y3)
        self.cur_x = x3
        self.cur_y = y3

    def curveto_y_smooth(self, x1, y1, x3, y3):
        # endpoint and last controlpoint are identical
        self.path.AppendBezier(x1, y1, x3, y3, x3, y3, ContSmooth)
        self.cur_x = x3
        self.cur_y = y3

    def bezier_close(self):
        if self.path.len > 1:
            self.path.AppendLine(self.path.Node(0))
            self.path.load_close(1)

    def bezier(self):
        if self.guess_continuity:
            self.path.guess_continuity()
        if self.path.len > 0:
            if self.compound_path is not None:
                self.compound_path.append(self.path)
            else:
                GenericLoader.bezier(self, paths=(self.path, ))
        self.path = CreatePath()

    # compound paths

    def begin_compound_path(self):
        self.compound_path = []

    def end_compound_path(self):
        paths = tuple(self.compound_path)
        self.compound_path = None
        if paths:
            # XXX ugly
            if self.gradient_geo:
                rect = paths[0].accurate_rect()
                for path in paths[1:]:
                    rect = UnionRects(rect, path.accurate_rect())
                self.gradient_rect = rect
            else:
                self.gradient_rect = None
            getattr(self, self.compound_render)()
            GenericLoader.bezier(self, paths=paths)

    # Groups

    def begin_group(self):
        if self.compound_path is None:
            # a normal group
            if self.treat_toplevel_groups_as_layers:
                if self.composite_class == Document:
                    self.begin_layer()
                    return
            GenericLoader.begin_group(self)
        else:
            # a `compound group'. Ignored since Sketch doesn't have this.
            pass

    def end_group(self):
        if self.compound_path is None:
            # a normal group
            if self.composite_class == Layer:
                self.end_composite()
            else:
                try:
                    GenericLoader.end_group(self)
                    if self.flatten_groups:
                        if self.object.NumObjects() == 1:
                            obj = self.object.GetObjects()[0]
                            del self.composite_items[-1]
                            self.append_object(obj)
                except EmptyCompositeError:
                    pass
        else:
            # a `compound group'. Ignored since Sketch doesn't have this.
            pass

    # Layers

    def begin_layer(self):
        self.layer(_("Layer %d") % (len(self.composite_items) + 1))

    def begin_ai_layer(self):
        if self.format_version >= 4.0:
            visible, preview, enabled, printing, dimmed, unused, has_mlm,\
              color, red, green, blue, unused, unused = self.pop_multi(13)
        else:
            visible, preview, enabled, printing, dimmed, has_mlm, \
               color, red, green, blue = self.pop_multi(10)
        color = CreateRGBColor(red / 255.0, green / 255.0, blue / 255.0)
        self.layer_kw_args = {
            'printable': printing,
            'visible': visible,
            'locked': not enabled,
            'outline_color': color
        }

    def end_ai_layer(self):
        self.end_layer()

    def name_layer(self, name):
        apply(self.layer, (name, ), self.layer_kw_args)

    # Guides

    def guide(self, op):
        #print 'guide', op
        method = getattr(self, self.functions[op])
        method()
        guide = self.pop_last()
        self.guides.append(guide)

    # Palette

    def begin_palette(self):
        self.in_palette = 1

    def end_palette(self):
        self.in_palette = 0

    # Text

    def set_standard_encoding(self):
        encoding = list(self.standard_encoding)
        pos = 0
        defs = self.pop_to_mark()
        for item in defs:
            if type(item) == IntType:
                pos = item
            elif type(item) == StringType:
                encoding[pos] = item
                pos = pos + 1
            else:
                self.add_message('unknown item %s in encoding' % ` item `)
        self.standard_encoding = tuple(encoding)

    def define_font(self, psname, newname, encoding=None):
        if encoding is None:
            encoding = self.standard_encoding[:]
        self.font_map[newname] = FontInfo(psname, newname, encoding)

    def reencode_font(self):
        args = self.pop_to_mark()
        if type(args[-1]) == ListType:
            self.add_message(
                _("Multiple Master fonts not supported. "
                  "Using Times Roman"))
            newname = args[-6]
            self.define_font('Times Roman', newname)
        else:
            newname, psname, direction, script, usedefault = args[-5:]
            if len(args) > 5:
                self.add_message(_("Additional encoding ignored"))
            self.define_font(psname, newname)

    def begin_text(self, text_type):
        self.in_text = 1
        self.text_type = text_type
        self.text_string = []
        if text_type == 1:
            self.add_message(_("Area text not supported"))
        if text_type == 2:
            GenericLoader.begin_group(self)

    def end_text(self):
        # we don't support area text (text_type 1) at all. Return
        # immediately in that case.
        if self.text_type == 1:
            return

        # first, turn the text accumulated in the list text_string into
        # a single string and unify line endings to newline characters.
        text = string.join(self.text_string, '')
        text = string.replace(text, '\r\n', '\n')
        text = string.replace(text, '\r', '\n')

        # remove a trailing newline. Many Illustrator files contain a
        # trailing newline as 'overflow' text, there's probably a better
        # way to deal with this...
        if text[-1:] == "\n":
            text = text[:-1]

        # Re-encode to Latin1
        text = self.text_font.Reencode(text)

        if not string.strip(text):
            if self.text_type == 2:
                self.end_composite()
                del self.composite_items[-1]
                if len(self.composite_items) > 0:
                    self.object = self.composite_items[-1]
            return

        # first create a simple text object
        self.fs()
        self.style.font = GetFont(self.text_font.psname)
        self.style.font_size = self.text_size
        self.simple_text(text,
                         self.text_trafo,
                         halign=_ai_text_align[self.text_align])

        # if we're actually supposed to create a path-text object, turn
        # the text object just created into a path-text object
        if self.text_type == 2:
            GenericLoader.end_group(self)
            group = self.pop_last()
            objects = group.GetObjects()
            if len(objects) == 2:
                path, text = objects
                self.append_object(
                    PathText(text, path, start_pos=self.text_start_pos))
                #self.composite_items[-1] = self.object

        # we've finished the text object
        self.in_text = 0

    def set_text_render(self, render):
        self.text_render = render

    def set_text_align(self, align):
        self.text_align = align

    def set_text_font(self):
        # In Illustrator < 7, the operator has two arguments, new
        # fontname and size. In Illustrator >= 7, there are two
        # additional arguments, ascent and descent.
        args = self.pop_multi(2)
        if type(args[0]) != StringType:
            newname, size = self.pop_multi(2)
        else:
            newname, size = args
        if self.font_map.has_key(newname):
            self.text_font = self.font_map[newname]
        elif newname[0] == '_':
            # special case for ai files generated by ps2ai. They don't
            # use the TZ operator to reencode the fonts and define the _
            # names.
            self.define_font(newname[1:], newname)
            self.text_font = self.font_map[newname]
        else:
            self.add_message(_("No font %s.") % newname)
        self.text_size = size

    def begin_text_path(self, a, b, c, d, tx, ty, start_pos):
        self.text_trafo = Trafo(a, b, c, d, tx, ty)
        self.text_start_pos = start_pos

    def end_text_path(self):
        pass

    def render_text(self, text):
        if self.text_type != 2:
            # in a path text only the invisible render operators count
            self.text_string.append(text)

    def render_text_inv(self, text):
        self.text_string.append(text)

    # Raster Image

    def raster_image(self, trafo, llx, lly, urx, ury, width, height, bits,
                     mode, alpha, reserved, encoding, mask):
        if bits != 8 or mode not in (1, 3):
            self.add_message(
                _("Only images with 1 or 3 components "
                  "and 8 bits/component supported"))
            self.skip_to_dsc("AI5_EndRaster")
            return
        decode = streamfilter.SubFileDecode(self.tokenizer.source,
                                            '%AI5_EndRaster')
        if encoding == 0:
            decode = streamfilter.HexDecode(decode)
        data_length = mode * width * height
        data = decode.read(data_length)
        #f = open("/tmp/dump.ppm", "w")
        #if mode == 1:
        #    f.write("P5\n%d %d\n255\n" % (width, height))
        #else:
        #    f.write("P6\n%d %d\n255\n" % (width, height))
        #f.write(data)
        #f.close()
        if mode == 1:
            mode = 'L'
        elif mode == 3:
            mode = 'RGB'
        elif mode == 4:
            mode == 'CMYK'
        image = Image.fromstring(mode, (width, height), data, 'raw', mode)
        self.image(image, apply(Trafo, tuple(trafo)))

    #

    def append_object(self, object):
        if self.composite_class == Document \
         and object.__class__ != Layer:
            self.begin_layer()
        self.composite_items.append(object)
        self.object = object

    #
    #

    def skip_to_dsc(self, *endcomments):
        next_dsc = self.tokenizer.next_dsc
        split = string.split
        while 1:
            value = next_dsc()
            if not value:
                return
            if ':' in value:
                keyword, value = split(value, ':', 1)
            else:
                keyword = value
            if keyword in endcomments:
                return

    def read_prolog(self):
        next = self.tokenizer.next
        DSC = DSC_COMMENT
        split = string.split
        while 1:
            token, value = next()
            if token == DSC:
                if ':' in value:
                    keyword, value = split(value, ':', 1)
                else:
                    keyword = value
                if keyword in ('EndProlog', 'BeginSetup'):
                    return keyword
                if keyword[:14] == "AI5_FileFormat":
                    self.format_version = string.atof(keyword[14:])
                elif keyword == 'BeginProcSet':
                    # some ai files exported by corel draw don't have an
                    # EndProcSet comment after a BeginProcSet...
                    self.skip_to_dsc('EndProcSet', 'EndProlog')
                elif keyword == 'BeginResource':
                    self.skip_to_dsc('EndResource', 'EndProlog')
                #elif keyword == 'Creator':
                ## try to determine whether the file really is an
                ## illustrator file as opposed to some other EPS
                ## file. It seems that Illustrator itself only
                ## accepts EPS files as illustrator files if they
                ## contain "Adobe Illustrator" in their Create
                ## DSC-comment
                #if string.find(value, "Adobe Illustrator") == -1:
                #self.add_message("This is probably not an"
                #" Illustrator file."
                #" Try embedding it as EPS")
            if token == END:
                return

    def Load(self):
        # Begin read EPS Binary File Header
        header = self.match.string[0:32]
        if header[0] == chr(0xC5):
            if len(header) < 32:
                header += self.file.read(32 - len(header))
            filetype, startPS, sizePS, startWMF, sizeWMF, \
            startTIFF, sizeTIFF, Checksum = unpack(struct_eps_header, header)
            self.file.seek(startPS)
        # End read EPS Binary File Header

        funclist = self.get_compiled()
        # binding frequently used functions to local variables speeds up
        # the process considerably...
        a = apply
        t = tuple
        DSC = DSC_COMMENT
        MAX = MAX_DATA_TOKEN
        split = string.split
        stack = self.stack
        push = self.stack.append
        unknown_operator = (None, None)

        decoder = streamfilter.StringDecode(self.match.string, self.file)
        self.tokenizer = PSTokenizer(decoder)
        self.tokenizer.ai_pseudo_comments = 1
        self.tokenizer.ai_dsc = 1
        next = self.tokenizer.next

        self.document()

        value = self.read_prolog()

        while 1:
            token, value = next()
            if token <= MAX:
                push(value)
            elif token == DSC:
                if ':' in value:
                    keyword, value = split(value, ':', 1)
                else:
                    keyword = value
                if keyword in ('PageTrailer', 'Trailer'):
                    break
                elif keyword == 'AI5_BeginPalette':
                    self.skip_to_dsc('AI5_EndPalette', 'EndSetup')
                elif keyword == "AI8_BeginBrushPattern":
                    self.skip_to_dsc('AI8_EndBrushPattern', 'EndSetup')

            elif token == END:
                break
            elif token == OPERATOR:
                method, argc = funclist.get(value, unknown_operator)
                #if method is not None:
                #    name = method.__name__
                #else:
                #    name = `method`
                if method is None:
                    del stack[:]
                else:
                    try:
                        if argc:
                            args = t(stack[-argc:])
                            del stack[-argc:]
                            a(method, args)
                        else:
                            method()
                    except:
                        warn_tb(INTERNAL, 'AILoader: error')

        self.end_all()
        self.object.load_Completed()
        for obj in self.guides:
            self.object.guide_layer.Insert(obj, None)

        return self.object