コード例 #1
0
ファイル: draw.py プロジェクト: ahojnnes/mapython
    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()
コード例 #2
0
ファイル: draw.py プロジェクト: ahojnnes/mapython
    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()