From: Paul B. <ba...@st...> - 2004-03-12 16:52:29
|
Hi John, I have some proposed changes to the way matplotlib handles fonts. The suggested changes are along the lines of the W3C Cascading Style Sheet, Level 1 (CSS1; http://www.w3.org/TR/1999/REC-CSS1-19990111) document. Note that there is also a Level 2 and 2.1 document. For fonts, HTML/XML documents can specify 5 font properties for font matching purposes. They are: font-family, font-style, font-variant, font-weight, and font-size. The font_family property is a list of prioritized font names. The nicer fonts are listed first, e.g. ['Lucida Grande', Verdana, Geneva, Lucida, Arial, Helvetica, sans-serif]. The first font name is apparently a very elegent sans-serif font that is common on Mac OS X. Verdana is common to Windows and Helvetica is common on UNIX/Linux. font-style can be normal, italic, or oblique; font-variant is normal or small-caps; font-weight has many options, e.g. normal, semi-bold, bold, etc.; and font-size also has many options, e.g. small, medium, large, smaller, larger, 10pt, 12pt, etc. The CSS1 document also gives a font matching algorithm in case your system doesn't have the exact font available. The Text object in matplotlib has several similar attributes to the CSS1 font properties, so it would seem that you were aware of this approach. They are fontname, fontangle, fontweight, and fontsize. Given the similarities, my suggestion is to replace fontname with fontfamily and allow it to be a list of prioritized fonts. I also suggest replacing fontangle with fontstyle, adding fontvariant, and using the CSS1 options for these attributes. The fontfamily approach makes it more likely that the more elegant fonts will be used by matplotlib and that if none of the recommended fonts are available, at least some font closely resembling the requested one will be used, i.e. some type of sans-serif font will be used if requested, instead of a serif font. The CSS1 options for font weights are: [normal=400, bold=700, 100, 200, 300, 400, 500, 600, 700, 800, 900, bolder, and lighter]. The latter two are relative values. It appears that newer fonts will use these numerical values to indicate their font weight, instead of using a descriptive names which are not uniform across font foundries. Like the CSS1 options for font weight, the size options are: [xx-small, x-small, small, medium, large, x-large, xx-large, smaller, larger], plus point sizes. One of the benefits of using a name option for size is that the sizes are all relative to the medium size, which can be 10pt, 12pt, 14pt, etc. This would make it easy to change all the fonts in a plot just by changing the definition of the medium font from 12pt to 14pt. As a final suggestion, we could have the Text object initialize the fontfamily attribute to a sans-serif font list, e.g. the one given above, since that seems to be a popular font family for many users. Please let me know what you think about this suggestion and if you have any changes to the design. -- Paul -- Paul Barrett, PhD Space Telescope Science Institute Phone: 410-338-4475 ESS/Science Software Branch FAX: 410-338-4767 Baltimore, MD 21218 |
From: John H. <jdh...@ac...> - 2004-03-12 18:59:37
|
>>>>> "Paul" == Paul Barrett <ba...@st...> writes: Paul> As a final suggestion, we could have the Text object Paul> initialize the fontfamily attribute to a sans-serif font Paul> list, e.g. the one given above, since that seems to be a Paul> popular font family for many users. I think all of the suggestions are good ones. I wasn't aware of the CSS standards, but GTK (which provided the matplotlib font model) is very close to it, including the use of xxlarge, etc. Paul> Please let me know what you think about this suggestion and Paul> if you have any changes to the design. I think font handling should be factored out of the Text class into a dedicated class. class Font: pass class Text(dpi, bbox, font, etc...): pass AFAIK, noone is directly instantiating Text instances in their code so I don't think we'll have many API complaints. Then the respective font finders (ttf and afm) can use the font instance in combination with the CSS algorithm to find the fonts. This won't be too hard on the backends since most of them are already using the font finders to get the ttf or afm filenames, so most of these changes will be insulated except for changing the arguments of a few functional calls. The major overhaul will be in matplotlib.text and in the classes that instantiate text (Axis, Axes, Figure). I like the idea of using relative sizes in all these classes. For user API compatibility, the critical thing is to preserve the getters and setters of Text, since the following is legal code t = title('hi mom', fontname='Courier', fontsize=12) which calls text.set_fontname and text.fontsize under the hood. But the setters and getters can just forward the call to the new font instance as necessary. Vis-a-vis backends: GTKAgg, Agg, TkAgg, GD, Paint: no problems here as they all use ttf_font_finder PS: As far as the finder algorithm is concerned, it would be nice if it was sufficiently generic that backend_ps could use it too. If you define an API that the algorithm needs vis-a-vis font properties, we modify the AFM and FT2Font classes to provide these. GTK: I think with minor changes we can make GTK play nicely with any ttf font on the system. I have to do a little more research. If not, the GTK font setup is so close to the CSS one that I can do the mapping pretty easily. WX: There is a standard set of WX fonts. I'm not sure how to map the generic ideas you put forth to the concrete fonts wx knows about, other than extending the fontnames dictionary to handle as many of the typical font names as possible and/or having users set the wx fontnames in matplotlibrc. It would also help to provide some helper functions which map numeric font weights to one of normal|bold|heavy|etc and the string font sizes to points (eg 'medium'->12pt) for the backends that don't know about these newfangled options. That way in backend_wx we could do s = font.get_weight_as_string() weight = self.fontweights[s] # this dict already exists in WX and GTK size = font.get_size_in_points() ...etc... In addition, Jeremy has indicated an interest in implementing WXAgg backend, which would get the new font handling for free. Division of labor: if you want to make the required changes to text.Text, ttf_font_finder and (optionally) matplotlibrc I'm all for it. Once you have a prototype, I can help with all the text instantiators if you like, or you can do this too. Whichever way, I can definitely help with the backend ports once the above issues are resolved. I've already been planning to refactor the backend text API, that would have required a font instance anyway. For all the other attributes (lines, rectangles, etc), the backends don't know about the matplotlib objects. eg, we say draw_line(x1,y1, x2,y2), not draw_line(lineObject). But I am currently passing a text instance in to draw_text. I realized this was a problem when I wanted to fix newline handling across backends. The best way to do this is for the Text class to split strings on newlines, do the layout in the front end, and then call draw_text(x,y,s,font) for all the newline split strings in the original string. Likewise, most of the text layout stuff like alignment that is currently done in the backend should be moved to the Text class. So I'll work on these changes in parallel. JDH |
From: Paul B. <ba...@st...> - 2004-03-12 20:17:35
|
John Hunter wrote: > > I think font handling should be factored out of the Text class into a > dedicated class. > > class Font: > > pass > > class Text(dpi, bbox, font, etc...): > pass Just to make sure that I have this straight, the Text class should look like this: def __init__(self, ..., font, ...): ... self._color = color self._text = text self._verticalalignment = verticalalignment self._horizontalalignment = horizontalalignment self._rotation = rotation self._font = font instead of this: def __init__(self, ...): ... self._color = color self._text = text self._verticalalignment = verticalalignment self._horizontalalignment = horizontalalignment self._rotation = rotation self._fontname = fontname self._fontsize = fontsize self._fontweight = fontweight self._fontangle = fontangle If so, do you planning on changing get_fontxxxx() for get_font()? > > For user API compatibility, the critical thing is to preserve the > getters and setters of Text, since the following is legal code > > t = title('hi mom', fontname='Courier', fontsize=12) > > which calls text.set_fontname and text.fontsize under the hood. > > But the setters and getters can just forward the call to the new font > instance as necessary. OK. > Vis-a-vis backends: > > PS: As far as the finder algorithm is concerned, it would be nice if > it was sufficiently generic that backend_ps could use it too. If > you define an API that the algorithm needs vis-a-vis font > properties, we modify the AFM and FT2Font classes to provide > these. Yes, this is my intention. > Division of labor: if you want to make the required changes to > text.Text, ttf_font_finder and (optionally) matplotlibrc I'm all for > it. Once you have a prototype, I can help with all the text > instantiators if you like, or you can do this too. Whichever way, I > can definitely help with the backend ports once the above issues are > resolved. I should be able to do most of it. -- Paul -- Paul Barrett, PhD Space Telescope Science Institute Phone: 410-338-4475 ESS/Science Software Branch FAX: 410-338-4767 Baltimore, MD 21218 |
From: John H. <jdh...@ac...> - 2004-03-12 22:13:46
|
>>>>> "Paul" == Paul Barrett <ba...@st...> writes: Paul> Just to make sure that I have this straight, the Text class Paul> should look like this: Paul> def __init__(self, ..., font, ...): ... Yep Paul> If so, do you planning on changing get_fontxxxx() for Paul> get_font()? No. you should have a get_font, an also get_fontxxxx that forwards the call to the font object for backwards compatability. Paul> I should be able to do most of it. Awesome! JDH |