def draw_text_on_line( self, coords, text, color=(0, 0, 0), font_size=10, font_family='Tahoma', font_style=cairo.FONT_SLANT_NORMAL, font_weight=cairo.FONT_WEIGHT_NORMAL, text_halo_width=1, text_halo_color=(1, 1, 1), text_halo_line_cap=cairo.LINE_CAP_ROUND, text_halo_line_join=cairo.LINE_JOIN_ROUND, text_halo_line_dash=None, text_transform=None, ): ''' Draws text on a line. Tries to find a position with the least change in gradient and which is closest to the middle of the line. :param coords: iterable containing all coordinates as ``(lon, lat)`` :param text: text to be drawn :param color: ``(r, g, b[, a])`` :param font_size: font-size in unit (pixel/point) :param font_family: font name :param font_style: ``cairo.FONT_SLANT_NORMAL``, ``cairo.FONT_SLANT_ITALIC`` or ``cairo.FONT_SLANT_OBLIQUE`` :param font_weight: ``cairo.FONT_WEIGHT_NORMAL`` or ``cairo.FONT_WEIGHT_BOLD`` :param text_halo_width: border-width in unit (pixel/point) :param text_halo_color: ``(r, g, b[, a])`` :param text_halo_line_cap: one of :const:`cairo.LINE_CAP_*` :param text_halo_line_join: one of :const:`cairo.LINE_JOIN_*` :param text_halo_line_dash: list/tuple used by :meth:`cairo.Context.set_dash` :param text_transform: one of ``'lowercase'``, ``'uppercase'`` or ``'capitalize'`` ''' text = text.strip() if not text: return coords = map(lambda c: self.transform_coords(*c), coords) self.context.select_font_face(font_family, font_style, font_weight) self.context.set_font_size(font_size) text = utils.text_transform(text, text_transform) width, height = self.context.text_extents(text)[2:4] font_ascent, font_descent = self.context.font_extents()[0:2] self.context.new_path() #: make sure line does not intersect other conflict objects line = LineString(coords) line = self.map_area.intersection(line) line = line.difference(self.map_area.exterior.buffer(height)) line = line.difference(self.conflict_area) #: check whether line is empty or is split into several different parts if line.geom_type == 'GeometryCollection': return elif line.geom_type == 'MultiLineString': longest = None min_len = width * 1.2 for seg in line.geoms: seg_len = seg.length if seg_len > min_len: longest = seg min_len = seg_len if longest is None: return line = longest coords = tuple(line.coords) seg = utils.linestring_text_optimal_segment(coords, width) # line has either to much change in gradients or is too short if seg is None: return #: crop optimal segment of linestring start, end = seg coords = coords[start:end+1] #: make sure text is rendered from left to right if coords[-1][0] < coords[0][0]: coords = tuple(reversed(coords)) # translate linestring so text is rendered vertically in the middle line = LineString(tuple(coords)) offset = font_ascent / 2. - font_descent line = line.parallel_offset(offset, 'left', resolution=3) # make sure text is rendered centered on line start_len = (line.length - width) / 2. char_coords = None chars = utils.generate_char_geoms(self.context, text) #: draw all character paths for char in utils.iter_chars_on_line(chars, line, start_len): for geom in char.geoms: char_coords = iter(geom.exterior.coords) self.context.move_to(*char_coords.next()) for lon, lat in char_coords: self.context.line_to(lon, lat) self.context.close_path() #: only add line to reserved area if text was drawn if char_coords is not None: covered = line.buffer(height) self.conflict_union(covered) #: draw border around characters self.context.set_line_cap(cairo.LINE_CAP_ROUND) self.context.set_source_rgba(*text_halo_color) self.context.set_line_width(2 * text_halo_width) self.context.set_line_cap(text_halo_line_cap) self.context.set_line_join(text_halo_line_join) self.context.set_dash(text_halo_line_dash or tuple()) self.context.stroke_preserve() #: fill actual text self.context.set_source_rgba(*color) self.context.fill()
def draw_text( self, coord, text, color=(0, 0, 0), font_size=11, font_family='Tahoma', font_style=cairo.FONT_SLANT_NORMAL, font_weight=cairo.FONT_WEIGHT_NORMAL, text_halo_width=3, text_halo_color=(1, 1, 1), text_halo_line_cap=cairo.LINE_CAP_ROUND, text_halo_line_join=cairo.LINE_JOIN_ROUND, text_halo_line_dash=None, text_transform=None, image=None, image_margin=4 ): ''' Draws text either centered on coordinate or the image centered on coordinate and text on the right of the image. :param coord: ``(lon, lat)`` :param text: text to be drawn :param color: ``(r, g, b[, a])`` :param font_size: font-size in unit (pixel/point) :param font_family: font name :param font_style: ``cairo.FONT_SLANT_NORMAL``, ``cairo.FONT_SLANT_ITALIC`` or ``cairo.FONT_SLANT_OBLIQUE`` :param font_weight: ``cairo.FONT_WEIGHT_NORMAL`` or ``cairo.FONT_WEIGHT_BOLD`` :param text_halo_width: border-width in unit (pixel/point) :param text_halo_color: ``(r, g, b[, a])`` :param text_halo_line_cap: one of :const:`cairo.LINE_CAP_*` :param text_halo_line_join: one of :const:`cairo.LINE_JOIN_*` :param text_halo_line_dash: list/tuple used by :meth:`cairo.Context.set_dash` :param text_transform: one of ``'lowercase'``, ``'uppercase'`` or ``'capitalize'`` :param image: file object or path to image file :param image_margin: space between text and image in int or float ''' x, y = self.transform_coords(*coord) # abort if there are already too many text_paths in this area if self.conflict_density(x, y) > 0.07: self.context.new_path() return text = utils.text_transform(text, text_transform) #: draw spot name self.context.select_font_face(font_family, font_style, font_weight) self.context.set_font_size(font_size) width, height = self.context.text_extents(text)[2:4] if image is not None: image = cairo.ImageSurface.create_from_png(image) image_width, image_height = image.get_width(), image.get_height() text_area = box( x - image_width / 2.0, y - max(height, image_height) / 2.0, x + image_width + width + image_margin, y + max(height, image_height) / 2.0 ) else: # place text directly on coord x -= width / 2.0 text_area = box(x, y - height / 2., x + width, y + height / 2.) try: newx, newy = self.find_free_position(text_area) except TypeError: # no free position found self.context.new_path() return if image is not None: y = newy + (max(height, image_height) - image_height) / 2.0 self.context.set_source_surface(image, newx, y) self.context.paint() image_area = box(x, y, x + image_width, y + image_height) newx += image_width + image_margin newy += (image_height + height) / 2.0 else: # find_free_position uses minx and miny as position but # cairo uses bottom left corner newx, newy = newx, newy + height # abort if new position is too far away from original position if Point(newx, newy).distance(Point(x, y)) > 0.1 * self.max_size: self.context.new_path() return # round positions for clear text rendering self.context.move_to(int(newx), int(newy)) self.context.text_path(text) #: draw text halo self.context.set_line_cap(cairo.LINE_CAP_ROUND) self.context.set_source_rgba(*text_halo_color) self.context.set_line_width(2 * text_halo_width) self.context.set_line_cap(text_halo_line_cap) self.context.set_line_join(text_halo_line_join) self.context.set_dash(text_halo_line_dash or tuple()) self.context.stroke_preserve() #: determine covered area by text area = box(*self.context.path_extents()) if image is not None: area = area.union(image_area) self.conflict_union(area) #: fill characters with color self.context.set_source_rgba(*color) self.context.fill()