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