class Map(object): padding = 50 def __init__(self): pass def load(self, filename): self.stations = SortedDict() self.lines = SortedDict() self.extents = [0, 0, 0, 0] self.outbounds = [] draw_last = [] draw_first = [] with open(filename) as fh: for lineno, line in enumerate(fh): line = line.strip() if line and line[0] != "#": # Get the parts parts = line.split() type, parts = parts[0], parts[1:] # What kind of line is it? if type == "line": # Line definition code = parts[0] colors = [( int(part[0:2], 16) / 255.0, int(part[2:4], 16) / 255.0, int(part[4:8], 16) / 255.0, ) for part in parts[1].split(",")] self.lines[code] = Line(code, colors) # Track segment elif type in ("track", "subtrack"): # It's a station-to-station description station_code, platform_number = parts[0].split("-", 1) dest_code, dest_number = parts[1].split("-", 1) station = self.stations[station_code] # Check for reverses leaves_start = False if platform_number[-1] == "!": leaves_start = True platform_number = platform_number[:-1] finishes_end = False if dest_number[-1] == "!": finishes_end = True dest_number = dest_number[:-1] # Add it try: self.add_outbound( station.platforms[platform_number], self.stations[dest_code].platforms[dest_number], self.lines[parts[2]], leaves_start = leaves_start, finishes_end = finishes_end, subtrack = (type == "subtrack"), ) except: print "Error context: %s, %s" % (station, parts) raise # Station/waypoint record elif type in ("station", "waypoint", "depot", "sidings", "disstation"): # It's a station or points definition code = parts[0] index = 1 while "," not in parts[index]: index += 1 name = " ".join(parts[1:index]) # Work out the coordinates coord_parts = parts[index].split(",") coords = Vector(*map(float, coord_parts[-2:])) * 10 if len(coord_parts) == 3: relative_to = self.stations[coord_parts[0]] else: relative_to = None if type == "station": station_class = Station elif type == "depot": station_class = Depot elif type == "sidings": station_class = Sidings elif type == "disstation": station_class = DisusedStation else: station_class = Points last_station = self.stations[code] = station_class( code, name, coords, relative_to = relative_to, ) self.extents[0] = min(coords.x, self.extents[0]) self.extents[1] = max(coords.x, self.extents[1]) self.extents[2] = min(coords.y, self.extents[2]) self.extents[3] = max(coords.y, self.extents[3]) # Platform record elif type == "platform": # Add a platform to the last station direction = getattr(Direction, parts[1]) try: line = self.lines[parts[2]] except (IndexError, KeyError): line = self.lines["error"] try: platform_side_code = parts[3] platform_side = { "L": Segment.PLATFORM_LEFT, "R": Segment.PLATFORM_RIGHT, "B": Segment.PLATFORM_BOTH, "N": Segment.PLATFORM_NONE, }[platform_side_code.upper()] except IndexError: platform_side = Segment.PLATFORM_BOTH self.stations[code].add_platform(parts[0], direction, line, platform_side) # Drawing order modifiers elif type == "draw": if parts[0] == "first": draw_first.append(last_station) elif parts[0] == "last": draw_last.append(last_station) else: raise ValueError("Unknown draw position %r" % parts[0]) # Label placement modifiers elif type == "label": last_station.label_direction = getattr(Direction, parts[0]) elif type == "label_offset": last_station.label_offset = Vector(map(int, parts[0].split(","))) # Unknown else: raise ValueError("Unknown line type %r" % type) # Now reorder those with special draw clauses for station in draw_first: self.stations.insert(0, station.code, station) for station in draw_last: self.stations.insert(len(self.stations), station.code, station) def save_offsets(self, filename): """ Opens up the file, reads it, and writes new offsets if needs be. """ with open(filename) as in_file: with open(filename + ".new", "w") as out_file: for lineno, line in enumerate(in_file): line = line.strip() parts = line.split() type = parts[0] if parts else None # What kind of line is it? if type in ("station", "waypoint", "depot", "sidings", "disstation"): # Get the code code = parts[1] # Get the real station station = self.stations[code] if station.relative_to: coords = "%s,%.1f,%.1f" % ( station.relative_to.code, (station._offset.x // 5) / 2.0, (station._offset.y // 5) / 2.0, ) else: coords = "%.1f,%.1f" % ( (station._offset.x // 5) / 2.0, (station._offset.y // 5) / 2.0, ) out_file.write("%(type)s %(code)s %(name)s %(coords)s\n" % { "type": type, "code": code, "name": station.name, "coords": coords, }) else: out_file.write(line + "\n") os.rename(filename + ".new", filename) def nearest_station(self, coords): """ Finds the nearest station to the coords, and returns it along with the distance to it. """ nearest = (None, 100000000) for station in self.stations.values(): distance = abs(station.offset - coords) if distance < nearest[1]: nearest = (station, distance) return nearest def stations_inside_bounds(self, tl, br): """ Finds the nearest station to the coords, and returns it along with the distance to it. """ for station in self.stations.values(): if tl.x <= station.offset.x <= br.x and \ tl.y <= station.offset.y <= br.y: if not station.relative_to: yield station def add_outbound(self, platform, destination, line, subtrack=False, leaves_start=False, finishes_end=False): self.outbounds.append(( platform, destination, line, subtrack, leaves_start, finishes_end, )) def draw(self, ctx): """ Draws the entire map. """ for station in self.stations.values(): for platform in station.platforms.values(): platform.drawn = False self.draw_outbound(ctx) for station in self.stations.values(): station.draw(ctx) def draw_debug(self, ctx, highlighted=set()): """ Draws debug hints for stations and platforms. """ for station in self.stations.values(): station.draw_debug(ctx, highlighted) def draw_outbound(self, ctx): # Draw outbound segments for platform, destination, line, subtrack, leaves_start, finishes_end in self.outbounds: # Draw the ends if they've not been done yet. if not platform.drawn: platform.draw(ctx) if not destination.drawn: destination.draw(ctx) # Make sure which ends we're using if leaves_start: start_point = platform.start_point start_dir = platform.direction.left.left.left.left else: start_point = platform.end_point start_dir = platform.direction if finishes_end: end_point = destination.end_point end_dir = destination.direction.left.left.left.left else: end_point = destination.start_point end_dir = destination.direction # Draw! Segment( start_point, start_dir, end_point, end_dir, line.colors, subtrack = subtrack, ).draw(ctx) def to_pdf(self, filename): width = (self.extents[1] - self.extents[0]) + self.padding * 2 height = (self.extents[3] - self.extents[2]) + self.padding * 2 surface = cairo.PDFSurface(filename, width, height) ctx = cairo.Context(surface) ctx.translate( self.padding - self.extents[0], self.padding - self.extents[2], ) self.draw(ctx) surface.finish()
class Station(object): """ A place on the map that lines go to and from. """ station_gap = 10 platform_class = Platform label_color = (0, 51 / 255.0, 102 / 255.0) label_size = 12 label_distance = Vector(6, 4) def __init__(self, code, name, offset, relative_to=None): self.code = code self.name = name self._offset = offset self.relative_to = relative_to self.platforms = SortedDict() self.placed = SortedDict() self.label_direction = None self.label_offset = Vector(0, 0) @property def offset(self): if self.relative_to: return self.relative_to.offset + self._offset else: return self._offset def add_platform(self, number, direction, line, platform_side): # Work out its coords norm_direction = direction.normalized self.placed[norm_direction] = self.placed.get(norm_direction, 0) + 1 # Make it self.platforms[number] = self.platform_class( station=self, number=number, direction=direction, offset=Vector(0, 0), line=line, platform_side=platform_side, ) self.platforms[number].offset_number = self.placed[norm_direction] - 1 # Recalculate all platform locations for platform in self.platforms.values(): norm_direction = platform.direction.normalized platform.offset = (norm_direction.right.right.vector * (platform.offset_number + 0.5 - (self.placed[norm_direction] / 2.0)) * self.station_gap) def __repr__(self): return "<Station %s (%s)>" % (self.code, self.name) def decide_label_direction(self): self.label_direction = Direction.W def draw(self, ctx): """ Draws the station and its platforms. """ for platform in self.platforms.values(): if not platform.drawn: platform.draw(ctx) if not self.label_direction: self.decide_label_direction() self.draw_label(ctx) def draw_debug(self, ctx, highlighted): """ Draws a debug symbol for this station. """ # Draw a cross on the station ctx.save() if self in highlighted: ctx.set_source_rgba(100, 0, 0, 0.9) else: ctx.set_source_rgba(255, 0, 255, 0.6) ctx.translate(*self.offset) ctx.move_to(-5, -5) ctx.line_to(5, 5) ctx.move_to(5, -5) ctx.line_to(-5, 5) ctx.set_line_width(2) ctx.stroke() # Draw the name of the station/waypoint ctx.select_font_face( "LondonTwo", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL, ) ctx.set_font_size(3) ctx.move_to(6, -3) ctx.show_text(self.code) ctx.fill() # Draw a dot on each platform for platform in self.platforms.values(): ctx.save() ctx.translate(*platform.offset) ctx.arc(0, 0, 3, 0, 7) ctx.fill() ctx.restore() # Draw a number on each platform for platform in self.platforms.values(): ctx.save() ctx.translate(*platform.offset) ctx.set_source_rgb(0, 0, 100) ctx.select_font_face( "LondonTwo", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL, ) ctx.set_font_size(4) ctx.move_to(-1, 1) ctx.show_text(platform.number) ctx.restore() ctx.restore() def draw_label(self, ctx): if self.name: # Work out the bounding box of the platforms x_range = [0, 0] y_range = [0, 0] platform_directions = set() label_dir = self.label_direction for platform in self.platforms.values(): platform_directions.add(platform.direction) # Diagonal platforms perpendicular to label direction # get put closer if platform.direction == label_dir.right.right or \ platform.direction == label_dir.left.left: ends = [platform.mid_point] if platform.platform_side & Segment.PLATFORM_LEFT: ends.append(platform.mid_point + (platform.direction.left.left.vector * Segment.platform_distance)) if platform.platform_side & Segment.PLATFORM_RIGHT: ends.append(platform.mid_point + (platform.direction.right.right.vector * Segment.platform_distance)) # Use bounding box else: ends = [platform.start_point, platform.end_point] if platform.platform_side & Segment.PLATFORM_LEFT: ends.append(platform.start_point + (platform.direction.left.left.vector * Segment.platform_distance)) ends.append(platform.end_point + (platform.direction.left.left.vector * Segment.platform_distance)) if platform.platform_side & Segment.PLATFORM_RIGHT: ends.append(platform.start_point + (platform.direction.right.right.vector * Segment.platform_distance)) ends.append(platform.end_point + (platform.direction.right.right.vector * Segment.platform_distance)) for end in ends: end = end - self.offset x_range[0] = min(end.x, x_range[0]) y_range[0] = min(end.y, y_range[0]) x_range[1] = max(end.x, x_range[1]) y_range[1] = max(end.y, y_range[1]) ctx.set_source_rgb(*self.label_color) ctx.select_font_face( "LondonTwo", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL, ) ctx.set_font_size(self.label_size) lines = [{"text": x.strip()} for x in self.name.split("\\n")] # Work out the size of the entire label dir_vector = self.label_direction.vector width = 0 height = 0 for line in lines: x_bearing, y_bearing, this_width, this_height = \ ctx.text_extents(line['text'])[:4] width = max(width, this_width) height += this_height line['y'] = height line['height'] = this_height line['width'] = this_width line['x_bearing'] = x_bearing line['y_bearing'] = y_bearing height += 1 height -= 1 # Work out where to place it, using the text midpoint as the origin if dir_vector.x < 0: x_offset = x_range[0] - width / 2.0 x_mult = 1 elif dir_vector.x == 0: x_offset = 0 x_mult = 0.5 else: x_offset = x_range[1] + width / 2.0 x_mult = 0 if dir_vector.y < 0: y_offset = y_range[0] - height / 2.0 y_delta = -ctx.font_extents()[1] * 0.6 elif dir_vector.y == 0: y_offset = 0 y_delta = 0 else: y_offset = y_range[1] + height / 2.0 y_delta = 0 y_offset -= (self.label_size / 8.0) # Draw! for line in lines: line_x = -line['x_bearing'] + ( -width / 2.0) - (line['width'] - width) * x_mult line_y = y_delta + line['y'] - (height / 2.0) position = (Vector(x_offset, y_offset) + Vector(line_x, line_y) + Vector( dir_vector.x * self.label_distance.x, dir_vector.y * self.label_distance.y, ) + self.offset + self.label_offset) ctx.move_to(*position) ctx.show_text(line['text']) if False: # debug ctx.fill() ctx.set_source_rgb(0, 1, 1) ctx.arc(self.offset.x + x_offset, self.offset.y + y_offset, 2, 0, 7) ctx.fill() ctx.set_source_rgb(0, 0.5, 1) ctx.arc(self.offset.x + x_range[1], self.offset.y + y_range[1], 2, 0, 7) ctx.fill() ctx.set_source_rgb(1, 0, 1) ctx.arc(self.offset.x + x_range[0], self.offset.y + y_range[0], 2, 0, 7) ctx.fill()
class Map(object): padding = 50 def __init__(self): pass def load(self, filename): self.stations = SortedDict() self.lines = SortedDict() self.extents = [0, 0, 0, 0] self.outbounds = [] draw_last = [] draw_first = [] with open(filename) as fh: for lineno, line in enumerate(fh): line = line.strip() if line and line[0] != "#": # Get the parts parts = line.split() type, parts = parts[0], parts[1:] # What kind of line is it? if type == "line": # Line definition code = parts[0] colors = [( int(part[0:2], 16) / 255.0, int(part[2:4], 16) / 255.0, int(part[4:8], 16) / 255.0, ) for part in parts[1].split(",")] self.lines[code] = Line(code, colors) # Track segment elif type in ("track", "subtrack"): # It's a station-to-station description station_code, platform_number = parts[0].split("-", 1) dest_code, dest_number = parts[1].split("-", 1) station = self.stations[station_code] # Check for reverses leaves_start = False if platform_number[-1] == "!": leaves_start = True platform_number = platform_number[:-1] finishes_end = False if dest_number[-1] == "!": finishes_end = True dest_number = dest_number[:-1] # Add it try: self.add_outbound( station.platforms[platform_number], self.stations[dest_code]. platforms[dest_number], self.lines[parts[2]], leaves_start=leaves_start, finishes_end=finishes_end, subtrack=(type == "subtrack"), ) except: print "Error context: %s, %s" % (station, parts) raise # Station/waypoint record elif type in ("station", "waypoint", "depot", "sidings", "disstation"): # It's a station or points definition code = parts[0] index = 1 while "," not in parts[index]: index += 1 name = " ".join(parts[1:index]) # Work out the coordinates coord_parts = parts[index].split(",") coords = Vector(*map(float, coord_parts[-2:])) * 10 if len(coord_parts) == 3: relative_to = self.stations[coord_parts[0]] else: relative_to = None if type == "station": station_class = Station elif type == "depot": station_class = Depot elif type == "sidings": station_class = Sidings elif type == "disstation": station_class = DisusedStation else: station_class = Points last_station = self.stations[code] = station_class( code, name, coords, relative_to=relative_to, ) self.extents[0] = min(coords.x, self.extents[0]) self.extents[1] = max(coords.x, self.extents[1]) self.extents[2] = min(coords.y, self.extents[2]) self.extents[3] = max(coords.y, self.extents[3]) # Platform record elif type == "platform": # Add a platform to the last station direction = getattr(Direction, parts[1]) try: line = self.lines[parts[2]] except (IndexError, KeyError): line = self.lines["error"] try: platform_side_code = parts[3] platform_side = { "L": Segment.PLATFORM_LEFT, "R": Segment.PLATFORM_RIGHT, "B": Segment.PLATFORM_BOTH, "N": Segment.PLATFORM_NONE, }[platform_side_code.upper()] except IndexError: platform_side = Segment.PLATFORM_BOTH self.stations[code].add_platform( parts[0], direction, line, platform_side) # Drawing order modifiers elif type == "draw": if parts[0] == "first": draw_first.append(last_station) elif parts[0] == "last": draw_last.append(last_station) else: raise ValueError("Unknown draw position %r" % parts[0]) # Label placement modifiers elif type == "label": last_station.label_direction = getattr( Direction, parts[0]) elif type == "label_offset": last_station.label_offset = Vector( map(int, parts[0].split(","))) # Unknown else: raise ValueError("Unknown line type %r" % type) # Now reorder those with special draw clauses for station in draw_first: self.stations.insert(0, station.code, station) for station in draw_last: self.stations.insert(len(self.stations), station.code, station) def save_offsets(self, filename): """ Opens up the file, reads it, and writes new offsets if needs be. """ with open(filename) as in_file: with open(filename + ".new", "w") as out_file: for lineno, line in enumerate(in_file): line = line.strip() parts = line.split() type = parts[0] if parts else None # What kind of line is it? if type in ("station", "waypoint", "depot", "sidings", "disstation"): # Get the code code = parts[1] # Get the real station station = self.stations[code] if station.relative_to: coords = "%s,%.1f,%.1f" % ( station.relative_to.code, (station._offset.x // 5) / 2.0, (station._offset.y // 5) / 2.0, ) else: coords = "%.1f,%.1f" % ( (station._offset.x // 5) / 2.0, (station._offset.y // 5) / 2.0, ) out_file.write( "%(type)s %(code)s %(name)s %(coords)s\n" % { "type": type, "code": code, "name": station.name, "coords": coords, }) else: out_file.write(line + "\n") os.rename(filename + ".new", filename) def nearest_station(self, coords): """ Finds the nearest station to the coords, and returns it along with the distance to it. """ nearest = (None, 100000000) for station in self.stations.values(): distance = abs(station.offset - coords) if distance < nearest[1]: nearest = (station, distance) return nearest def stations_inside_bounds(self, tl, br): """ Finds the nearest station to the coords, and returns it along with the distance to it. """ for station in self.stations.values(): if tl.x <= station.offset.x <= br.x and \ tl.y <= station.offset.y <= br.y: if not station.relative_to: yield station def add_outbound(self, platform, destination, line, subtrack=False, leaves_start=False, finishes_end=False): self.outbounds.append(( platform, destination, line, subtrack, leaves_start, finishes_end, )) def draw(self, ctx): """ Draws the entire map. """ for station in self.stations.values(): for platform in station.platforms.values(): platform.drawn = False self.draw_outbound(ctx) for station in self.stations.values(): station.draw(ctx) def draw_debug(self, ctx, highlighted=set()): """ Draws debug hints for stations and platforms. """ for station in self.stations.values(): station.draw_debug(ctx, highlighted) def draw_outbound(self, ctx): # Draw outbound segments for platform, destination, line, subtrack, leaves_start, finishes_end in self.outbounds: # Draw the ends if they've not been done yet. if not platform.drawn: platform.draw(ctx) if not destination.drawn: destination.draw(ctx) # Make sure which ends we're using if leaves_start: start_point = platform.start_point start_dir = platform.direction.left.left.left.left else: start_point = platform.end_point start_dir = platform.direction if finishes_end: end_point = destination.end_point end_dir = destination.direction.left.left.left.left else: end_point = destination.start_point end_dir = destination.direction # Draw! Segment( start_point, start_dir, end_point, end_dir, line.colors, subtrack=subtrack, ).draw(ctx) def to_pdf(self, filename): width = (self.extents[1] - self.extents[0]) + self.padding * 2 height = (self.extents[3] - self.extents[2]) + self.padding * 2 surface = cairo.PDFSurface(filename, width, height) ctx = cairo.Context(surface) ctx.translate( self.padding - self.extents[0], self.padding - self.extents[2], ) self.draw(ctx) surface.finish()
class Station(object): """ A place on the map that lines go to and from. """ station_gap = 10 platform_class = Platform label_color = (0, 51/255.0, 102/255.0) label_size = 12 label_distance = Vector(6, 4) def __init__(self, code, name, offset, relative_to=None): self.code = code self.name = name self._offset = offset self.relative_to = relative_to self.platforms = SortedDict() self.placed = SortedDict() self.label_direction = None self.label_offset = Vector(0, 0) @property def offset(self): if self.relative_to: return self.relative_to.offset + self._offset else: return self._offset def add_platform(self, number, direction, line, platform_side): # Work out its coords norm_direction = direction.normalized self.placed[norm_direction] = self.placed.get(norm_direction, 0) + 1 # Make it self.platforms[number] = self.platform_class( station = self, number = number, direction = direction, offset = Vector(0, 0), line = line, platform_side = platform_side, ) self.platforms[number].offset_number = self.placed[norm_direction] - 1 # Recalculate all platform locations for platform in self.platforms.values(): norm_direction = platform.direction.normalized platform.offset = ( norm_direction.right.right.vector * (platform.offset_number + 0.5 - (self.placed[norm_direction] / 2.0)) * self.station_gap ) def __repr__(self): return "<Station %s (%s)>" % (self.code, self.name) def decide_label_direction(self): self.label_direction = Direction.W def draw(self, ctx): """ Draws the station and its platforms. """ for platform in self.platforms.values(): if not platform.drawn: platform.draw(ctx) if not self.label_direction: self.decide_label_direction() self.draw_label(ctx) def draw_debug(self, ctx, highlighted): """ Draws a debug symbol for this station. """ # Draw a cross on the station ctx.save() if self in highlighted: ctx.set_source_rgba(100, 0, 0, 0.9) else: ctx.set_source_rgba(255, 0, 255, 0.6) ctx.translate(*self.offset) ctx.move_to(-5, -5) ctx.line_to(5, 5) ctx.move_to(5, -5) ctx.line_to(-5, 5) ctx.set_line_width(2) ctx.stroke() # Draw a dot on each platform for platform in self.platforms.values(): ctx.save() ctx.translate(*platform.offset) ctx.arc(0, 0, 3, 0, 7) ctx.fill() ctx.restore() # Draw a number on each platform for platform in self.platforms.values(): ctx.save() ctx.translate(*platform.offset) ctx.set_source_rgb(0, 0, 100) ctx.select_font_face( "LondonTwo", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL, ) ctx.set_font_size(4) ctx.move_to(-1, 1) ctx.show_text(platform.number) ctx.restore() ctx.restore() def draw_label(self, ctx): if self.name: # Work out the bounding box of the platforms x_range = [0, 0] y_range = [0, 0] platform_directions = set() label_dir = self.label_direction for platform in self.platforms.values(): platform_directions.add(platform.direction) # Diagonal platforms perpendicular to label direction # get put closer if platform.direction == label_dir.right.right or \ platform.direction == label_dir.left.left: ends = [platform.mid_point] if platform.platform_side & Segment.PLATFORM_LEFT: ends.append( platform.mid_point + (platform.direction.left.left.vector * Segment.platform_distance) ) if platform.platform_side & Segment.PLATFORM_RIGHT: ends.append( platform.mid_point + (platform.direction.right.right.vector * Segment.platform_distance) ) # Use bounding box else: ends = [platform.start_point, platform.end_point] if platform.platform_side & Segment.PLATFORM_LEFT: ends.append( platform.start_point + (platform.direction.left.left.vector * Segment.platform_distance) ) ends.append( platform.end_point + (platform.direction.left.left.vector * Segment.platform_distance) ) if platform.platform_side & Segment.PLATFORM_RIGHT: ends.append( platform.start_point + (platform.direction.right.right.vector * Segment.platform_distance) ) ends.append( platform.end_point + (platform.direction.right.right.vector * Segment.platform_distance) ) for end in ends: end = end - self.offset x_range[0] = min(end.x, x_range[0]) y_range[0] = min(end.y, y_range[0]) x_range[1] = max(end.x, x_range[1]) y_range[1] = max(end.y, y_range[1]) ctx.set_source_rgb(*self.label_color) ctx.select_font_face( "LondonTwo", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL, ) ctx.set_font_size(self.label_size) lines = [{"text": x.strip()} for x in self.name.split("\\n")] # Work out the size of the entire label dir_vector = self.label_direction.vector width = 0 height = 0 for line in lines: x_bearing, y_bearing, this_width, this_height = \ ctx.text_extents(line['text'])[:4] width = max(width, this_width) height += this_height line['y'] = height line['height'] = this_height line['width'] = this_width line['x_bearing'] = x_bearing line['y_bearing'] = y_bearing height += 1 height -= 1 # Work out where to place it, using the text midpoint as the origin if dir_vector.x < 0: x_offset = x_range[0] - width / 2.0 x_mult = 1 elif dir_vector.x == 0: x_offset = 0 x_mult = 0.5 else: x_offset = x_range[1] + width / 2.0 x_mult = 0 if dir_vector.y < 0: y_offset = y_range[0] - height / 2.0 y_delta = -ctx.font_extents()[1] * 0.6 elif dir_vector.y == 0: y_offset = 0 y_delta = 0 else: y_offset = y_range[1] + height / 2.0 y_delta = 0 y_offset -= (self.label_size / 8.0) # Draw! for line in lines: line_x = -line['x_bearing'] + (-width/2.0) - (line['width'] - width) * x_mult line_y = y_delta + line['y'] - (height / 2.0) position = ( Vector(x_offset, y_offset) + Vector(line_x, line_y) + Vector( dir_vector.x * self.label_distance.x, dir_vector.y * self.label_distance.y, ) + self.offset + self.label_offset ) ctx.move_to(*position) ctx.show_text(line['text']) if False: # debug ctx.fill() ctx.set_source_rgb(0,1,1) ctx.arc(self.offset.x + x_offset, self.offset.y + y_offset, 2, 0, 7) ctx.fill() ctx.set_source_rgb(0,0.5,1) ctx.arc(self.offset.x + x_range[1], self.offset.y + y_range[1], 2, 0, 7) ctx.fill() ctx.set_source_rgb(1,0,1) ctx.arc(self.offset.x + x_range[0], self.offset.y + y_range[0], 2, 0, 7) ctx.fill()