Ejemplo n.º 1
0
 def create_pixel_img(
     self,
     scale,
     text,
     color,
 ):
     '''Creats a new canvas and draws text onto it if overridin'''
     font = Font(self.new_txt_color(color), self.font, self.font_size)
     bg_color = self.new_bg_color(color)
     img = Draw('RGB', scale, bg_color)
     img.text((0, 7), text, font)
     return img
Ejemplo n.º 2
0
 def create_pixel_img(self, scale, text, color):
     '''Creats a new canvas and draws text onto it if overridin'''
     #define the font and the blank canvas
     font = Font(self.new_txt_color(color), self.font, self.font_size)
     #defines the background color of the canvas
     bg_color = self.new_bg_color(color)
     img = Draw('RGB', scale, bg_color)
     #draw text on to the canvas by default only one line of text is drawn
     #this method can be overriden to draw as much text as you want
     #Note that the if the scale is not large enough not all the text will be displayed
     img.text((0, 0), text, font)
     return img
Ejemplo n.º 3
0
def draw_text(frame, x, y, text, size=16, color='white'):

    im = Image.fromarray(frame)
    draw = Draw(im)
    _, fh = draw.size

    font = Font(color, os.path.join(os.path.dirname(__file__), './Reith.ttf'),
                DRAW_SCALE_FACTOR)

    text_w, text_h = draw.textsize(text, font)
    y -= text_h
    draw.text((x + 2, y + 4), text, font)
    draw.flush()

    return np.array(im)
Ejemplo n.º 4
0
 def create_pixel_img(
     self,
     scale,
     text,
     color,
 ):
     '''Creats a new canvas and draws text onto it if overridin'''
     font = Font(self.new_txt_color(color), self.font, self.font_size)
     bg_color = self.new_bg_color(color)
     img = Draw('RGB', scale, bg_color)
     img.text((8, 0), text[:5], font)
     img.text((8, 10), text[5:10], font)
     img.text((8, 20), text[10:15], font)
     img.text((8, 30), text[15:20], font)
     img.text((8, 40), text[20:], font)
     return img
Ejemplo n.º 5
0
  def addCanvasText(self, text, pos, font, color=(0, 0, 0), **kwargs):
    orientation = kwargs.get('orientation', 'E')
    color = convertColor(color)
    aggFont = Font(color, faceMap[font.face], size=font.size * self.fontScale)

    blocks = list(re.finditer(r'\<(.+?)\>(.+?)\</\1\>', text))
    w, h = 0, 0
    supH = 0
    subH = 0
    if not len(blocks):
      w, h = self.draw.textsize(text, aggFont)
      tw, th = w, h
      offset = w * pos[2]
      dPos = pos[0] - w / 2. + offset, pos[1] - h / 2.
      self.draw.text(dPos, text, aggFont)
    else:
      dblocks = []
      idx = 0
      for block in blocks:
        blockStart, blockEnd = block.span(0)
        if blockStart != idx:
          # untagged text:
          tblock = text[idx:blockStart]
          tw, th = self.draw.textsize(tblock, aggFont)
          w += tw
          h = max(h, th)
          dblocks.append((tblock, '', tw, th))
        fmt = block.groups()[0]
        tblock = block.groups()[1]
        if fmt in ('sub', 'sup'):
          lFont = Font(color, faceMap[font.face], size=0.8 * font.size * self.fontScale)
        else:
          lFont = aggFont
        tw, th = self.draw.textsize(tblock, lFont)
        w += tw
        if fmt == 'sub':
          subH = max(subH, th)
        elif fmt == 'sup':
          supH = max(supH, th)
        else:
          h = max(h, th)
        dblocks.append((tblock, fmt, tw, th))
        idx = blockEnd
      if idx != len(text):
        # untagged text:
        tblock = text[idx:]
        tw, th = self.draw.textsize(tblock, aggFont)
        w += tw
        h = max(h, th)
        dblocks.append((tblock, '', tw, th))

      supH *= 0.5
      subH *= 0.5
      h += supH + subH
      offset = w * pos[2]
      if orientation == 'W':
        dPos = [pos[0] - w + offset, pos[1] - h / 2.]
      elif orientation == 'E':
        dPos = [pos[0] + offset, pos[1] - h / 2.]
      else:
        dPos = [pos[0] - w / 2. + offset, pos[1] - h / 2.]

      if supH:
        dPos[1] += supH
      for txt, fmt, tw, th in dblocks:
        tPos = dPos.copy()
        if fmt == 'sub':
          tPos[1] += subH
        elif fmt == 'sup':
          tPos[1] -= supH
        if fmt in ('sub', 'sup'):
          lFont = Font(color, faceMap[font.face], size=0.8 * font.size * self.fontScale)
        else:
          lFont = aggFont
        self.draw.text(tPos, txt, lFont)
        dPos[0] += tw
    return (tw + th * .4, th + th * .4, offset)
Ejemplo n.º 6
0
def main():
    parser = get_parser()
    args = parser.parse_args()

    levels = [logging.ERROR, logging.WARN, logging.INFO, logging.DEBUG]
    logging.basicConfig(level=levels[min(3, args.verbosity)])

    if args.output_filename is None:
        args.output_filename = [x[:-3] + "png" for x in args.input_tiff]
    else:
        assert len(args.output_filename) == len(
            args.input_tiff
        ), "Output filenames must be equal to number of input tiffs"

    if not (args.add_borders or args.add_coastlines or args.add_grid
            or args.add_rivers or args.add_colorbar):
        LOG.error(
            "Please specify one of the '--add-X' options to modify the image")
        return -1

    # we may be dealing with large images that look like decompression bombs
    # let's turn off the check for the image size in PIL/Pillow
    Image.MAX_IMAGE_PIXELS = None

    for input_tiff, output_filename in zip(args.input_tiff,
                                           args.output_filename):
        LOG.info("Creating {} from {}".format(output_filename, input_tiff))
        gtiff = gdal.Open(input_tiff)
        proj4_str = osr.SpatialReference(gtiff.GetProjection()).ExportToProj4()
        ul_x, res_x, _, ul_y, _, res_y = gtiff.GetGeoTransform()
        half_pixel_x = res_x / 2.
        half_pixel_y = res_y / 2.
        area_extent = (
            ul_x - half_pixel_x,  # lower-left X
            ul_y + res_y * gtiff.RasterYSize - half_pixel_y,  # lower-left Y
            ul_x + res_x * gtiff.RasterXSize + half_pixel_x,  # upper-right X
            ul_y + half_pixel_y,  # upper-right Y
        )
        img = Image.open(input_tiff).convert('RGBA' if gtiff.RasterCount in (
            2, 4) else 'RGB')
        area_def = (proj4_str, area_extent)

        cw = ContourWriterAGG(args.shapes_dir)

        if args.add_coastlines:
            outline = args.coastlines_outline[0] if len(
                args.coastlines_outline) == 1 else tuple(
                    int(x) for x in args.coastlines_outline)
            if args.coastlines_fill:
                fill = args.coastlines_fill[0] if len(
                    args.coastlines_fill) == 1 else tuple(
                        int(x) for x in args.coastlines_fill)
            else:
                fill = None
            cw.add_coastlines(img,
                              area_def,
                              resolution=args.coastlines_resolution,
                              level=args.coastlines_level,
                              width=args.coastlines_width,
                              outline=outline,
                              fill=fill)

        if args.add_rivers:
            outline = args.rivers_outline[0] if len(
                args.rivers_outline) == 1 else tuple(
                    int(x) for x in args.rivers_outline)
            cw.add_rivers(img,
                          area_def,
                          resolution=args.rivers_resolution,
                          level=args.rivers_level,
                          width=args.rivers_width,
                          outline=outline)

        if args.add_borders:
            outline = args.borders_outline[0] if len(
                args.borders_outline) == 1 else tuple(
                    int(x) for x in args.borders_outline)
            cw.add_borders(img,
                           area_def,
                           resolution=args.borders_resolution,
                           level=args.borders_level,
                           outline=outline,
                           width=args.borders_width)

        if args.add_grid:
            outline = args.grid_outline[0] if len(
                args.grid_outline) == 1 else tuple(
                    int(x) for x in args.grid_outline)
            minor_outline = args.grid_minor_outline[0] if len(
                args.grid_minor_outline) == 1 else tuple(
                    int(x) for x in args.grid_minor_outline)
            fill = args.grid_fill[0] if len(args.grid_fill) == 1 else tuple(
                int(x) for x in args.grid_fill)
            font_path = find_font(args.grid_font, args.grid_text_size)
            font = Font(outline, font_path, size=args.grid_text_size)
            cw.add_grid(img,
                        area_def,
                        args.grid_D,
                        args.grid_d,
                        font,
                        fill=fill,
                        outline=outline,
                        minor_outline=minor_outline,
                        write_text=args.grid_text,
                        width=args.grid_width,
                        lon_placement=args.grid_lon_placement,
                        lat_placement=args.grid_lat_placement)

        if args.add_colorbar:
            from pydecorate import DecoratorAGG
            font_color = args.colorbar_text_color
            font_color = font_color[0] if len(font_color) == 1 else tuple(
                int(x) for x in font_color)
            font_path = find_font(args.colorbar_font, args.colorbar_text_size)
            # this actually needs an aggdraw font
            font = Font(font_color, font_path, size=args.colorbar_text_size)
            band_count = gtiff.RasterCount
            if band_count not in [1, 2]:
                raise ValueError("Can't add colorbar to RGB/RGBA image")

            # figure out what colormap we are dealing with
            band = gtiff.GetRasterBand(1)
            cmap = get_colormap(band, band_count)

            # figure out our limits
            vmin = args.colorbar_min
            vmax = args.colorbar_max
            metadata = gtiff.GetMetadata_Dict()
            vmin = vmin or metadata.get('min_in')
            vmax = vmax or metadata.get('max_in')
            if isinstance(vmin, str):
                vmin = float(vmin)
            if isinstance(vmax, str):
                vmax = float(vmax)
            if vmin is None or vmax is None:
                data = gtiff.GetRasterBand(1).ReadAsArray()
                vmin = vmin or np.iinfo(data.dtype).min
                vmax = vmax or np.iinfo(data.dtype).max
            cmap.set_range(vmin, vmax)

            dc = DecoratorAGG(img)
            if args.colorbar_align == 'top':
                dc.align_top()
            elif args.colorbar_align == 'bottom':
                dc.align_bottom()
            elif args.colorbar_align == 'left':
                dc.align_left()
            elif args.colorbar_align == 'right':
                dc.align_right()

            if args.colorbar_vertical:
                dc.write_vertically()
            else:
                dc.write_horizontally()

            if args.colorbar_width is None or args.colorbar_height is None:
                LOG.warning("'--colorbar-width' or '--colorbar-height' were "
                            "not specified. Forcing '--colorbar-extend'.")
                args.colorbar_extend = True
            kwargs = {}
            if args.colorbar_width:
                kwargs['width'] = args.colorbar_width
            if args.colorbar_height:
                kwargs['height'] = args.colorbar_height
            dc.add_scale(cmap,
                         extend=args.colorbar_extend,
                         font=font,
                         line=font_color,
                         tick_marks=args.colorbar_tick_marks,
                         title=args.colorbar_title,
                         unit=args.colorbar_units,
                         **kwargs)

        img.save(output_filename)
Ejemplo n.º 7
0
    def add_overlay_from_dict(self, overlays, area_def, cache_epoch=None, background=None):
        """Create and return a transparent image adding all the overlays contained in the `overlays` dict.

        :Parameters:
            overlays : dict
                overlays configuration
            area_def : object
                Area Definition of the creating image
            cache_epoch: seconds since epoch
                The latest time allowed for cache the cache file. If the cache file is older than this (mtime),
                the cache should be regenerated.
            background: pillow image instance
                The image on which to write the overlays on. If it's None (default),
                a new image is created, otherwise the provide background is use
                an change *in place*.


            The keys in `overlays` that will be taken into account are:
            cache, coasts, rivers, borders, cities, grid

            For all of them except `cache`, the items are the same as the corresponding
            functions in pycoast, so refer to the docstrings of these functions
            (add_coastlines, add_rivers, add_borders, add_grid, add_cities).
            For cache, two parameters are configurable: `file` which specifies the directory
            and the prefix of the file to save the caches decoration to
            (for example /var/run/black_coasts_red_borders), and `regenerate` that can be
            True or False (default) to force the overwriting of an already cached file.

        """

        # Cache management
        cache_file = None
        if 'cache' in overlays:
            cache_file = (overlays['cache']['file'] + '_' +
                          area_def.area_id + '.png')

            try:
                config_time = cache_epoch
                cache_time = os.path.getmtime(cache_file)
                # Cache file will be used only if it's newer than config file
                if ((config_time is not None and config_time < cache_time)
                        and not overlays['cache'].get('regenerate', False)):
                    foreground = Image.open(cache_file)
                    logger.info('Using image in cache %s', cache_file)
                    if background is not None:
                        background.paste(foreground, mask=foreground.split()[-1])
                    return foreground
                else:
                    logger.info("Regenerating cache file.")
            except OSError:
                logger.info("No overlay image found, new overlay image will be saved in cache.")

        x_size = area_def.width
        y_size = area_def.height
        if cache_file is None and background is not None:
            foreground = background
        else:
            foreground = Image.new('RGBA', (x_size, y_size), (0, 0, 0, 0))

        default_resolution = get_resolution_from_area(area_def)

        DEFAULT = {'level': 1,
                   'outline': 'white',
                   'width': 1,
                   'fill': None,
                   'fill_opacity': 255,
                   'outline_opacity': 255,
                   'x_offset': 0,
                   'y_offset': 0,
                   'resolution': default_resolution}

        is_agg = self._draw_module == "AGG"

        # Coasts, rivers, borders
        for section, fun in zip(['coasts', 'rivers', 'borders'],
                                [self.add_coastlines,
                                 self.add_rivers,
                                 self.add_borders]):

            if section in overlays:

                params = DEFAULT.copy()
                params.update(overlays[section])

                params['level'] = int(params['level'])
                params['x_offset'] = float(params['x_offset'])
                params['y_offset'] = float(params['y_offset'])
                params['width'] = float(params['width'])
                params['outline_opacity'] = int(params['outline_opacity'])
                params['fill_opacity'] = int(params['fill_opacity'])

                if section != "coasts":
                    params.pop('fill_opacity', None)
                    params.pop('fill', None)

                if not is_agg:
                    for key in ['width', 'outline_opacity', 'fill_opacity']:
                        params.pop(key, None)

                fun(foreground, area_def, **params)
                logger.info("%s added", section.capitalize())

        # Cities management
        if 'cities' in overlays:
            DEFAULT_FONT_SIZE = 12
            DEFAULT_OUTLINE = "yellow"

            citylist = [s.lstrip()
                        for s in overlays['cities']['list'].split(',')]
            font_file = overlays['cities']['font']
            font_size = int(overlays['cities'].get('font_size',
                                                   DEFAULT_FONT_SIZE))
            outline = overlays['cities'].get('outline', DEFAULT_OUTLINE)
            pt_size = int(overlays['cities'].get('pt_size', None))
            box_outline = overlays['cities'].get('box_outline', None)
            box_opacity = int(overlays['cities'].get('box_opacity', 255))

            self.add_cities(foreground, area_def, citylist, font_file,
                            font_size, pt_size, outline, box_outline,
                            box_opacity)

        if 'grid' in overlays:
            lon_major = float(overlays['grid'].get('lon_major', 10.0))
            lat_major = float(overlays['grid'].get('lat_major', 10.0))
            lon_minor = float(overlays['grid'].get('lon_minor', 2.0))
            lat_minor = float(overlays['grid'].get('lat_minor', 2.0))
            font = overlays['grid'].get('font', None)
            font_size = int(overlays['grid'].get('font_size', 10))

            write_text = overlays['grid'].get('write_text', True)
            if isinstance(write_text, str):
                write_text = write_text.lower() in ['true', 'yes', '1', 'on']
            outline = overlays['grid'].get('outline', 'white')
            if isinstance(font, str):
                if is_agg:
                    from aggdraw import Font
                    font = Font(outline, font, size=font_size)
                else:
                    from PIL.ImageFont import truetype
                    font = truetype(font, font_size)
            fill = overlays['grid'].get('fill', None)
            minor_outline = overlays['grid'].get('minor_outline', 'white')
            minor_is_tick = overlays['grid'].get('minor_is_tick', True)
            if isinstance(minor_is_tick, str):
                minor_is_tick = minor_is_tick.lower() in ['true', 'yes', '1']
            lon_placement = overlays['grid'].get('lon_placement', 'tb')
            lat_placement = overlays['grid'].get('lat_placement', 'lr')

            self.add_grid(foreground, area_def, (lon_major, lat_major),
                          (lon_minor, lat_minor),
                          font=font, write_text=write_text, fill=fill,
                          outline=outline, minor_outline=minor_outline,
                          minor_is_tick=minor_is_tick,
                          lon_placement=lon_placement,
                          lat_placement=lat_placement)

        if cache_file is not None:
            try:
                foreground.save(cache_file)
            except IOError as e:
                logger.error("Can't save cache: %s", str(e))
            if background is not None:
                background.paste(foreground, mask=foreground.split()[-1])
        return foreground
Ejemplo n.º 8
0
def main():
    parser = get_parser()
    args = parser.parse_args()

    levels = [logging.ERROR, logging.WARN, logging.INFO, logging.DEBUG]
    logging.basicConfig(level=levels[min(3, args.verbosity)])

    if args.output_filename is None:
        args.output_filename = [x[:-3] + "png" for x in args.input_tiff]
    else:
        assert len(args.output_filename) == len(
            args.input_tiff
        ), "Output filenames must be equal to number of input tiffs"

    if not (args.add_borders or args.add_coastlines or args.add_grid or args.add_rivers or args.add_colorbar):
        LOG.error("Please specify one of the '--add-X' options to modify the image")
        return -1

    if args.cache_dir and not os.path.isdir(args.cache_dir):
        LOG.info(f"Creating cache directory: {args.cache_dir}")
        os.makedirs(args.cache_dir, exist_ok=True)

    # we may be dealing with large images that look like decompression bombs
    # let's turn off the check for the image size in PIL/Pillow
    Image.MAX_IMAGE_PIXELS = None
    # gather all options into a single dictionary that we can pass to pycoast
    pycoast_options = _args_to_pycoast_dict(args)
    for input_tiff, output_filename in zip(args.input_tiff, args.output_filename):
        LOG.info("Creating {} from {}".format(output_filename, input_tiff))
        img = Image.open(input_tiff)
        img_bands = img.getbands()
        num_bands = len(img_bands)
        # P = palette which we assume to be an RGBA colormap
        img = img.convert("RGBA" if num_bands in (2, 4) or "P" in img_bands else "RGB")
        if pycoast_options:
            area_id = os.path.splitext(input_tiff[0])[0]
            area_def = get_area_def_from_raster(input_tiff, area_id=area_id)
            cw = ContourWriterAGG(args.shapes_dir)
            cw.add_overlay_from_dict(pycoast_options, area_def, background=img)

        if args.add_colorbar:
            from pydecorate import DecoratorAGG

            font_color = args.colorbar_text_color
            font_color = font_color[0] if len(font_color) == 1 else tuple(int(x) for x in font_color)
            font_path = find_font(args.colorbar_font, args.colorbar_text_size)
            # this actually needs an aggdraw font
            font = Font(font_color, font_path, size=args.colorbar_text_size)
            if num_bands not in (1, 2):
                raise ValueError("Can't add colorbar to RGB/RGBA image")

            # figure out what colormap we are dealing with
            rio_ds = rasterio.open(input_tiff)
            input_dtype = np.dtype(rio_ds.meta["dtype"])
            rio_ct = _get_rio_colormap(rio_ds, 1)
            cmap = get_colormap(input_dtype, rio_ct, num_bands)

            # figure out our limits
            vmin = args.colorbar_min
            vmax = args.colorbar_max
            metadata = rio_ds.tags()
            vmin = vmin or metadata.get("min_in")
            vmax = vmax or metadata.get("max_in")
            if isinstance(vmin, str):
                vmin = float(vmin)
            if isinstance(vmax, str):
                vmax = float(vmax)
            if vmin is None or vmax is None:
                vmin = vmin or np.iinfo(input_dtype).min
                vmax = vmax or np.iinfo(input_dtype).max
            cmap.set_range(vmin, vmax)

            dc = DecoratorAGG(img)
            if args.colorbar_align == "top":
                dc.align_top()
            elif args.colorbar_align == "bottom":
                dc.align_bottom()
            elif args.colorbar_align == "left":
                dc.align_left()
            elif args.colorbar_align == "right":
                dc.align_right()

            if args.colorbar_vertical:
                dc.write_vertically()
            else:
                dc.write_horizontally()

            if args.colorbar_width is None or args.colorbar_height is None:
                LOG.warning(
                    "'--colorbar-width' or '--colorbar-height' were " "not specified. Forcing '--colorbar-extend'."
                )
                args.colorbar_extend = True
            kwargs = {}
            if args.colorbar_width:
                kwargs["width"] = args.colorbar_width
            if args.colorbar_height:
                kwargs["height"] = args.colorbar_height
            dc.add_scale(
                cmap,
                extend=args.colorbar_extend,
                font=font,
                line=font_color,
                tick_marks=args.colorbar_tick_marks,
                title=args.colorbar_title,
                unit=args.colorbar_units,
                **kwargs,
            )

        img.save(output_filename)
Ejemplo n.º 9
0
def _args_to_pycoast_dict(args):
    opts = {}
    if args.add_coastlines:
        outline = (
            args.coastlines_outline[0]
            if len(args.coastlines_outline) == 1
            else tuple(int(x) for x in args.coastlines_outline)
        )
        if args.coastlines_fill:
            fill = (
                args.coastlines_fill[0]
                if len(args.coastlines_fill) == 1
                else tuple(int(x) for x in args.coastlines_fill)
            )
        else:
            fill = None
        opts["coasts"] = {
            "resolution": args.coastlines_resolution,
            "level": args.coastlines_level,
            "width": args.coastlines_width,
            "outline": outline,
            "fill": fill,
        }

    if args.add_rivers:
        outline = (
            args.rivers_outline[0] if len(args.rivers_outline) == 1 else tuple(int(x) for x in args.rivers_outline)
        )
        opts["rivers"] = {
            "resolution": args.rivers_resolution,
            "level": args.rivers_level,
            "width": args.rivers_width,
            "outline": outline,
        }

    if args.add_borders:
        outline = (
            args.borders_outline[0] if len(args.borders_outline) == 1 else tuple(int(x) for x in args.borders_outline)
        )
        opts["borders"] = {
            "resolution": args.borders_resolution,
            "level": args.borders_level,
            "width": args.borders_width,
            "outline": outline,
        }

    if args.add_grid:
        outline = args.grid_outline[0] if len(args.grid_outline) == 1 else tuple(int(x) for x in args.grid_outline)
        minor_outline = (
            args.grid_minor_outline[0]
            if len(args.grid_minor_outline) == 1
            else tuple(int(x) for x in args.grid_minor_outline)
        )
        fill = args.grid_fill[0] if len(args.grid_fill) == 1 else tuple(int(x) for x in args.grid_fill)
        font_path = find_font(args.grid_font, args.grid_text_size)
        font = Font(outline, font_path, size=args.grid_text_size)
        opts["grid"] = {
            "lon_major": args.grid_D[0],
            "lat_major": args.grid_D[1],
            "lon_minor": args.grid_d[0],
            "lat_minor": args.grid_d[1],
            "font": font,
            "fill": fill,
            "outline": outline,
            "minor_outline": minor_outline,
            "write_text": args.grid_text,
            "width": args.grid_width,
            "lon_placement": args.grid_lon_placement,
            "lat_placement": args.grid_lat_placement,
        }

    if args.cache_dir:
        opts["cache"] = {
            # add "add_coastlines" prefix to cached image name
            "file": os.path.join(args.cache_dir, "add_coastlines"),
            "regenerate": args.cache_regenerate,
        }

    return opts
Ejemplo n.º 10
0
    def addCanvasText(self, text, pos, font, color=(0, 0, 0), **kwargs):
        orientation = kwargs.get('orientation', 'E')
        color = convertColor(color)
        aggFont = Font(color,
                       faceMap[font.face],
                       size=font.size * self.fontScale)

        blocks = list(re.finditer(r'\<(.+?)\>(.+?)\</\1\>', text))
        w, h = 0, 0
        supH = 0
        subH = 0
        if not len(blocks):
            w, h = self.draw.textsize(text, aggFont)
            bw, bh = w * 1.1, h * 1.1
            dPos = pos[0] - bw / 2., pos[1] - bh / 2.
            bgColor = kwargs.get('bgColor', (1, 1, 1))
            bgColor = convertColor(bgColor)
            self.draw.rectangle((dPos[0], dPos[1], dPos[0] + bw, dPos[1] + bh),
                                None, Brush(bgColor))
            dPos = pos[0] - w / 2., pos[1] - h / 2.
            self.draw.text(dPos, text, aggFont)
        else:
            dblocks = []
            idx = 0
            for block in blocks:
                blockStart, blockEnd = block.span(0)
                if blockStart != idx:
                    # untagged text:
                    tblock = text[idx:blockStart]
                    tw, th = self.draw.textsize(tblock, aggFont)
                    w += tw
                    h = max(h, th)
                    dblocks.append((tblock, '', tw, th))
                fmt = block.groups()[0]
                tblock = block.groups()[1]
                if fmt in ('sub', 'sup'):
                    lFont = Font(color,
                                 faceMap[font.face],
                                 size=0.8 * font.size * self.fontScale)
                else:
                    lFont = aggFont
                tw, th = self.draw.textsize(tblock, lFont)
                w += tw
                if fmt == 'sub':
                    subH = max(subH, th)
                elif fmt == 'sup':
                    supH = max(supH, th)
                else:
                    h = max(h, th)
                dblocks.append((tblock, fmt, tw, th))
                idx = blockEnd
            if idx != len(text):
                # untagged text:
                tblock = text[idx:]
                tw, th = self.draw.textsize(tblock, aggFont)
                w += tw
                h = max(h, th)
                dblocks.append((tblock, '', tw, th))

            supH *= 0.25
            subH *= 0.25
            h += supH + subH
            bw, bh = w * 1.1, h
            #dPos = pos[0]-bw/2.,pos[1]-bh/2.
            dPos = [pos[0] - w / 2., pos[1] - h / 2.]
            if orientation == 'W':
                dPos = [pos[0] - w, pos[1] - h / 2.]
            elif orientation == 'E':
                dPos = [pos[0], pos[1] - h / 2.]
            else:
                dPos = [pos[0] - w / 2, pos[1] - h / 2.]

            bgColor = kwargs.get('bgColor', (1, 1, 1))
            bgColor = convertColor(bgColor)
            self.draw.rectangle((dPos[0], dPos[1], dPos[0] + bw, dPos[1] + bh),
                                None, Brush(bgColor))
            if supH: dPos[1] += supH
            for txt, fmt, tw, th in dblocks:
                tPos = dPos[:]
                if fmt == 'sub':
                    tPos[1] += subH
                elif fmt == 'sup':
                    tPos[1] -= supH
                if fmt in ('sub', 'sup'):
                    lFont = Font(color,
                                 faceMap[font.face],
                                 size=0.8 * font.size * self.fontScale)
                else:
                    lFont = aggFont
                self.draw.text(tPos, txt, lFont)
                dPos[0] += tw