From: <lee...@us...> - 2009-08-30 20:26:16
|
Revision: 7606 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=7606&view=rev Author: leejjoon Date: 2009-08-30 20:26:05 +0000 (Sun, 30 Aug 2009) Log Message: ----------- add TextPath class in text.py. Update demo_text_path.py Modified Paths: -------------- trunk/matplotlib/examples/pylab_examples/demo_text_path.py trunk/matplotlib/lib/matplotlib/text.py Modified: trunk/matplotlib/examples/pylab_examples/demo_text_path.py =================================================================== --- trunk/matplotlib/examples/pylab_examples/demo_text_path.py 2009-08-30 18:30:29 UTC (rev 7605) +++ trunk/matplotlib/examples/pylab_examples/demo_text_path.py 2009-08-30 20:26:05 UTC (rev 7606) @@ -4,147 +4,54 @@ import matplotlib.pyplot as plt from matplotlib.image import BboxImage import numpy as np -from matplotlib.transforms import Affine2D, IdentityTransform +from matplotlib.transforms import IdentityTransform -import matplotlib.font_manager as font_manager -from matplotlib.ft2font import FT2Font, KERNING_DEFAULT, LOAD_NO_HINTING -from matplotlib.font_manager import FontProperties -from matplotlib.path import Path import matplotlib.patches as mpatches from matplotlib.offsetbox import AnnotationBbox,\ AnchoredOffsetbox, AuxTransformBox -#from matplotlib.offsetbox import - from matplotlib.cbook import get_sample_data +from matplotlib.text import TextPath -class TextPatch(mpatches.PathPatch): - FONT_SCALE = 100. +class PathClippedImagePatch(mpatches.PathPatch): + """ + The given image is used to draw the face of the patch. Internally, + it uses BboxImage whose clippath set to the path of the patch. - def __init__(self, xy, s, size=None, prop=None, bbox_image=None, - *kl, **kwargs): - if prop is None: - prop = FontProperties() - - if size is None: - size = prop.get_size_in_points() - - self._xy = xy - self.set_size(size) - - self.text_path = self.text_get_path(prop, s) - - mpatches.PathPatch.__init__(self, self.text_path, *kl, **kwargs) - + FIXME : The result is currently dpi dependent. + """ + def __init__(self, path, bbox_image, **kwargs): + mpatches.PathPatch.__init__(self, path, **kwargs) + self._facecolor = "none" self._init_bbox_image(bbox_image) - + def set_facecolor(self, color): + pass + def _init_bbox_image(self, im): - if im is None: - self.bbox_image = None - else: - bbox_image = BboxImage(self.get_window_extent, - norm = None, - origin=None, - ) - bbox_image.set_transform(IdentityTransform()) + bbox_image = BboxImage(self.get_window_extent, + norm = None, + origin=None, + ) + bbox_image.set_transform(IdentityTransform()) - bbox_image.set_data(im) - self.bbox_image = bbox_image + bbox_image.set_data(im) + self.bbox_image = bbox_image def draw(self, renderer=None): - if self.bbox_image is not None: - # the clip path must be updated every draw. any solution? -JJ - self.bbox_image.set_clip_path(self.text_path, self.get_transform()) - self.bbox_image.draw(renderer) + # the clip path must be updated every draw. any solution? -JJ + self.bbox_image.set_clip_path(self._path, self.get_transform()) + self.bbox_image.draw(renderer) mpatches.PathPatch.draw(self, renderer) - def set_size(self, size): - self._size = size - - def get_size(self): - return self._size - - def get_patch_transform(self): - tr = Affine2D().scale(self._size/self.FONT_SCALE, self._size/self.FONT_SCALE) - return tr.translate(*self._xy) - - def glyph_char_path(self, glyph, currx=0.): - - verts, codes = [], [] - for step in glyph.path: - if step[0] == 0: # MOVE_TO - verts.append((step[1], step[2])) - codes.append(Path.MOVETO) - elif step[0] == 1: # LINE_TO - verts.append((step[1], step[2])) - codes.append(Path.LINETO) - elif step[0] == 2: # CURVE3 - verts.extend([(step[1], step[2]), - (step[3], step[4])]) - codes.extend([Path.CURVE3, Path.CURVE3]) - elif step[0] == 3: # CURVE4 - verts.extend([(step[1], step[2]), - (step[3], step[4]), - (step[5], step[6])]) - codes.extend([Path.CURVE4, Path.CURVE4, Path.CURVE4]) - elif step[0] == 4: # ENDPOLY - verts.append((0, 0,)) - codes.append(Path.CLOSEPOLY) - - verts = [(x+currx, y) for (x,y) in verts] - - return verts, codes - - - def text_get_path(self, prop, s): - - fname = font_manager.findfont(prop) - font = FT2Font(str(fname)) - - font.set_size(self.FONT_SCALE, 72) - - cmap = font.get_charmap() - lastgind = None - - currx = 0 - - verts, codes = [], [] - - for c in s: - - ccode = ord(c) - gind = cmap.get(ccode) - if gind is None: - ccode = ord('?') - gind = 0 - glyph = font.load_char(ccode, flags=LOAD_NO_HINTING) - - - if lastgind is not None: - kern = font.get_kerning(lastgind, gind, KERNING_DEFAULT) - else: - kern = 0 - currx += (kern / 64.0) #/ (self.FONT_SCALE) - - verts1, codes1 = self.glyph_char_path(glyph, currx) - verts.extend(verts1) - codes.extend(codes1) - - - currx += (glyph.linearHoriAdvance / 65536.0) #/ (self.FONT_SCALE) - lastgind = gind - - return Path(verts, codes) - if 1: fig = plt.figure(1) @@ -156,11 +63,13 @@ from matplotlib._png import read_png fn = get_sample_data("lena.png", asfileobj=False) arr = read_png(fn) - p = TextPatch((0, 0), "!?", size=150, fc="none", ec="k", - bbox_image=arr, - transform=IdentityTransform()) - p.set_clip_on(False) + text_path = TextPath((0, 0), "!?", size=150) + p = PathClippedImagePatch(text_path, arr, ec="k", + transform=IdentityTransform()) + + #p.set_clip_on(False) + # make offset box offsetbox = AuxTransformBox(IdentityTransform()) offsetbox.add_artist(p) @@ -176,21 +85,21 @@ ax = plt.subplot(212) - shadow1 = TextPatch((3, -2), "TextPath", size=70, fc="none", ec="0.6", lw=3, - transform=IdentityTransform()) - shadow2 = TextPatch((3, -2), "TextPath", size=70, fc="0.3", ec="none", - transform=IdentityTransform()) - arr = np.arange(256).reshape(1,256)/256. - text_path = TextPatch((0, 0), "TextPath", size=70, fc="none", ec="none", lw=1, - bbox_image=arr, - transform=IdentityTransform()) + text_path = TextPath((0, 0), "TextPath", size=70) + text_patch = PathClippedImagePatch(text_path, arr, ec="none", + transform=IdentityTransform()) + + shadow1 = mpatches.Shadow(text_patch, 3, -2, props=dict(fc="none", ec="0.6", lw=3)) + shadow2 = mpatches.Shadow(text_patch, 3, -2, props=dict(fc="0.3", ec="none")) + + # make offset box offsetbox = AuxTransformBox(IdentityTransform()) offsetbox.add_artist(shadow1) offsetbox.add_artist(shadow2) - offsetbox.add_artist(text_path) + offsetbox.add_artist(text_patch) # place the anchored offset box using AnnotationBbox ab = AnnotationBbox(offsetbox, (0.5, 0.5), @@ -198,16 +107,13 @@ boxcoords="offset points", box_alignment=(0.5,0.5), ) + #text_path.set_size(10) - ax.add_artist(ab) ax.set_xlim(0, 1) ax.set_ylim(0, 1) - - - plt.draw() plt.show() Modified: trunk/matplotlib/lib/matplotlib/text.py =================================================================== --- trunk/matplotlib/lib/matplotlib/text.py 2009-08-30 18:30:29 UTC (rev 7605) +++ trunk/matplotlib/lib/matplotlib/text.py 2009-08-30 20:26:05 UTC (rev 7606) @@ -23,6 +23,11 @@ import matplotlib.nxutils as nxutils +from matplotlib.path import Path +import matplotlib.font_manager as font_manager +from matplotlib.ft2font import FT2Font, KERNING_DEFAULT, LOAD_NO_HINTING + + def _process_text_args(override, fontdict=None, **kwargs): "Return an override dict. See :func:`~pyplot.text' docstring for info" @@ -1764,3 +1769,174 @@ docstring.interpd.update(Annotation=Annotation.__init__.__doc__) + +class TextPath(Path): + """ + Create a path from the text. + """ + + # TODO : math text is currently not supported, but it would not be easy. + + FONT_SCALE = 100. + + def __init__(self, xy, s, size=None, prop=None, + _interpolation_steps=1, + *kl, **kwargs): + """ + Create a path from the text. No support for TeX yet. Note that + it simply is a path, not an artist. You need to use the + PathPatch (or other artists) to draw this path onto the + canvas. + + xy : position of the text. + s : text + size : font size + prop : font property + """ + + + if prop is None: + prop = FontProperties() + + if size is None: + size = prop.get_size_in_points() + + + self._xy = xy + self.set_size(size) + + self._cached_vertices = None + + self._vertices, self._codes = self.text_get_vertices_codes(prop, s) + + self.should_simplify = False + self.simplify_threshold = rcParams['path.simplify_threshold'] + self.has_nonfinite = False + self._interpolation_steps = _interpolation_steps + + + def set_size(self, size): + """ + set the size of the text + """ + self._size = size + self._invalid = True + + def get_size(self): + """ + get the size of the text + """ + return self._size + + def _get_vertices(self): + """ + Return the cached path after updating it if necessary. + """ + self._revalidate_path() + return self._cached_vertices + + def _get_codes(self): + """ + Return the codes + """ + return self._codes + + vertices = property(_get_vertices) + codes = property(_get_codes) + + def _revalidate_path(self): + """ + update the path if necessary. + + The path for the text is initially create with the font size + of FONT_SCALE, and this path is rescaled to other size when + necessary. + + """ + if self._invalid or \ + (self._cached_vertices is None): + tr = Affine2D().scale(self._size/self.FONT_SCALE, + self._size/self.FONT_SCALE).translate(*self._xy) + self._cached_vertices = tr.transform(self._vertices) + self._invalid = False + + + def glyph_char_path(self, glyph, currx=0.): + """ + convert the glyph to vertices and codes. Mostly copied from + backend_svg.py. + """ + verts, codes = [], [] + for step in glyph.path: + if step[0] == 0: # MOVE_TO + verts.append((step[1], step[2])) + codes.append(Path.MOVETO) + elif step[0] == 1: # LINE_TO + verts.append((step[1], step[2])) + codes.append(Path.LINETO) + elif step[0] == 2: # CURVE3 + verts.extend([(step[1], step[2]), + (step[3], step[4])]) + codes.extend([Path.CURVE3, Path.CURVE3]) + elif step[0] == 3: # CURVE4 + verts.extend([(step[1], step[2]), + (step[3], step[4]), + (step[5], step[6])]) + codes.extend([Path.CURVE4, Path.CURVE4, Path.CURVE4]) + elif step[0] == 4: # ENDPOLY + verts.append((0, 0,)) + codes.append(Path.CLOSEPOLY) + + verts = [(x+currx, y) for (x,y) in verts] + + return verts, codes + + + def text_get_vertices_codes(self, prop, s): + """ + convert the string *s* to vertices and codes using the + provided font property *prop*. Mostly copied from + backend_svg.py. + """ + + fname = font_manager.findfont(prop) + font = FT2Font(str(fname)) + + font.set_size(self.FONT_SCALE, 72) + + cmap = font.get_charmap() + lastgind = None + + currx = 0 + + verts, codes = [], [] + + + # I'm not sure if I get kernings right. Needs to be verified. -JJL + + for c in s: + + ccode = ord(c) + gind = cmap.get(ccode) + if gind is None: + ccode = ord('?') + gind = 0 + glyph = font.load_char(ccode, flags=LOAD_NO_HINTING) + + + if lastgind is not None: + kern = font.get_kerning(lastgind, gind, KERNING_DEFAULT) + else: + kern = 0 + currx += (kern / 64.0) #/ (self.FONT_SCALE) + + verts1, codes1 = self.glyph_char_path(glyph, currx) + verts.extend(verts1) + codes.extend(codes1) + + + currx += (glyph.linearHoriAdvance / 65536.0) #/ (self.FONT_SCALE) + lastgind = gind + + return verts, codes + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |