PageRenderTime 61ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/external/linux32/lib/python2.6/site-packages/matplotlib/mathtext.py

https://gitlab.com/github-cloud-corporation/nupic
Python | 1453 lines | 1395 code | 0 blank | 58 comment | 8 complexity | be144bab96fde6c95c78b0dc4d524476 MD5 | raw file
  1. r"""
  2. :mod:`~matplotlib.mathtext` is a module for parsing a subset of the
  3. TeX math syntax and drawing them to a matplotlib backend.
  4. For a tutorial of its usage see :ref:`mathtext-tutorial`. This
  5. document is primarily concerned with implementation details.
  6. The module uses pyparsing_ to parse the TeX expression.
  7. .. _pyparsing: http://pyparsing.wikispaces.com/
  8. The Bakoma distribution of the TeX Computer Modern fonts, and STIX
  9. fonts are supported. There is experimental support for using
  10. arbitrary fonts, but results may vary without proper tweaking and
  11. metrics for those fonts.
  12. If you find TeX expressions that don't parse or render properly,
  13. please email mdroe@stsci.edu, but please check KNOWN ISSUES below first.
  14. """
  15. from __future__ import division
  16. import os
  17. from cStringIO import StringIO
  18. from math import ceil
  19. try:
  20. set
  21. except NameError:
  22. from sets import Set as set
  23. import unicodedata
  24. from warnings import warn
  25. from numpy import inf, isinf
  26. import numpy as np
  27. from matplotlib.pyparsing import Combine, Group, Optional, Forward, \
  28. Literal, OneOrMore, ZeroOrMore, ParseException, Empty, \
  29. ParseResults, Suppress, oneOf, StringEnd, ParseFatalException, \
  30. FollowedBy, Regex, ParserElement
  31. # Enable packrat parsing
  32. ParserElement.enablePackrat()
  33. from matplotlib.afm import AFM
  34. from matplotlib.cbook import Bunch, get_realpath_and_stat, \
  35. is_string_like, maxdict
  36. from matplotlib.ft2font import FT2Font, FT2Image, KERNING_DEFAULT, LOAD_FORCE_AUTOHINT, LOAD_NO_HINTING
  37. from matplotlib.font_manager import findfont, FontProperties
  38. from matplotlib._mathtext_data import latex_to_bakoma, \
  39. latex_to_standard, tex2uni, latex_to_cmex, stix_virtual_fonts
  40. from matplotlib import get_data_path, rcParams
  41. import matplotlib.colors as mcolors
  42. import matplotlib._png as _png
  43. ####################
  44. ##############################################################################
  45. # FONTS
  46. def get_unicode_index(symbol):
  47. """get_unicode_index(symbol) -> integer
  48. Return the integer index (from the Unicode table) of symbol. *symbol*
  49. can be a single unicode character, a TeX command (i.e. r'\pi'), or a
  50. Type1 symbol name (i.e. 'phi').
  51. """
  52. # From UTF #25: U+2212 minus sign is the preferred
  53. # representation of the unary and binary minus sign rather than
  54. # the ASCII-derived U+002D hyphen-minus, because minus sign is
  55. # unambiguous and because it is rendered with a more desirable
  56. # length, usually longer than a hyphen.
  57. if symbol == '-':
  58. return 0x2212
  59. try:# This will succeed if symbol is a single unicode char
  60. return ord(symbol)
  61. except TypeError:
  62. pass
  63. try:# Is symbol a TeX symbol (i.e. \alpha)
  64. return tex2uni[symbol.strip("\\")]
  65. except KeyError:
  66. message = """'%(symbol)s' is not a valid Unicode character or
  67. TeX/Type1 symbol"""%locals()
  68. raise ValueError, message
  69. class MathtextBackend(object):
  70. """
  71. The base class for the mathtext backend-specific code. The
  72. purpose of :class:`MathtextBackend` subclasses is to interface
  73. between mathtext and a specific matplotlib graphics backend.
  74. Subclasses need to override the following:
  75. - :meth:`render_glyph`
  76. - :meth:`render_filled_rect`
  77. - :meth:`get_results`
  78. And optionally, if you need to use a Freetype hinting style:
  79. - :meth:`get_hinting_type`
  80. """
  81. def __init__(self):
  82. self.fonts_object = None
  83. def set_canvas_size(self, w, h, d):
  84. 'Dimension the drawing canvas'
  85. self.width = w
  86. self.height = h
  87. self.depth = d
  88. def render_glyph(self, ox, oy, info):
  89. """
  90. Draw a glyph described by *info* to the reference point (*ox*,
  91. *oy*).
  92. """
  93. raise NotImplementedError()
  94. def render_filled_rect(self, x1, y1, x2, y2):
  95. """
  96. Draw a filled black rectangle from (*x1*, *y1*) to (*x2*, *y2*).
  97. """
  98. raise NotImplementedError()
  99. def get_results(self, box):
  100. """
  101. Return a backend-specific tuple to return to the backend after
  102. all processing is done.
  103. """
  104. raise NotImplementedError()
  105. def get_hinting_type(self):
  106. """
  107. Get the Freetype hinting type to use with this particular
  108. backend.
  109. """
  110. return LOAD_NO_HINTING
  111. class MathtextBackendBbox(MathtextBackend):
  112. """
  113. A backend whose only purpose is to get a precise bounding box.
  114. Only required for the Agg backend.
  115. """
  116. def __init__(self, real_backend):
  117. MathtextBackend.__init__(self)
  118. self.bbox = [0, 0, 0, 0]
  119. self.real_backend = real_backend
  120. def _update_bbox(self, x1, y1, x2, y2):
  121. self.bbox = [min(self.bbox[0], x1),
  122. min(self.bbox[1], y1),
  123. max(self.bbox[2], x2),
  124. max(self.bbox[3], y2)]
  125. def render_glyph(self, ox, oy, info):
  126. self._update_bbox(ox + info.metrics.xmin,
  127. oy - info.metrics.ymax,
  128. ox + info.metrics.xmax,
  129. oy - info.metrics.ymin)
  130. def render_rect_filled(self, x1, y1, x2, y2):
  131. self._update_bbox(x1, y1, x2, y2)
  132. def get_results(self, box):
  133. orig_height = box.height
  134. orig_depth = box.depth
  135. ship(0, 0, box)
  136. bbox = self.bbox
  137. bbox = [bbox[0] - 1, bbox[1] - 1, bbox[2] + 1, bbox[3] + 1]
  138. self._switch_to_real_backend()
  139. self.fonts_object.set_canvas_size(
  140. bbox[2] - bbox[0],
  141. (bbox[3] - bbox[1]) - orig_depth,
  142. (bbox[3] - bbox[1]) - orig_height)
  143. ship(-bbox[0], -bbox[1], box)
  144. return self.fonts_object.get_results(box)
  145. def get_hinting_type(self):
  146. return self.real_backend.get_hinting_type()
  147. def _switch_to_real_backend(self):
  148. self.fonts_object.mathtext_backend = self.real_backend
  149. self.real_backend.fonts_object = self.fonts_object
  150. self.real_backend.ox = self.bbox[0]
  151. self.real_backend.oy = self.bbox[1]
  152. class MathtextBackendAggRender(MathtextBackend):
  153. """
  154. Render glyphs and rectangles to an FTImage buffer, which is later
  155. transferred to the Agg image by the Agg backend.
  156. """
  157. def __init__(self):
  158. self.ox = 0
  159. self.oy = 0
  160. self.image = None
  161. MathtextBackend.__init__(self)
  162. def set_canvas_size(self, w, h, d):
  163. MathtextBackend.set_canvas_size(self, w, h, d)
  164. self.image = FT2Image(ceil(w), ceil(h + d))
  165. def render_glyph(self, ox, oy, info):
  166. info.font.draw_glyph_to_bitmap(
  167. self.image, ox, oy - info.metrics.ymax, info.glyph)
  168. def render_rect_filled(self, x1, y1, x2, y2):
  169. height = max(int(y2 - y1) - 1, 0)
  170. if height == 0:
  171. center = (y2 + y1) / 2.0
  172. y = int(center - (height + 1) / 2.0)
  173. else:
  174. y = int(y1)
  175. self.image.draw_rect_filled(int(x1), y, ceil(x2), y + height)
  176. def get_results(self, box):
  177. return (self.ox,
  178. self.oy,
  179. self.width,
  180. self.height + self.depth,
  181. self.depth,
  182. self.image,
  183. self.fonts_object.get_used_characters())
  184. def get_hinting_type(self):
  185. return LOAD_FORCE_AUTOHINT
  186. def MathtextBackendAgg():
  187. return MathtextBackendBbox(MathtextBackendAggRender())
  188. class MathtextBackendBitmapRender(MathtextBackendAggRender):
  189. def get_results(self, box):
  190. return self.image, self.depth
  191. def MathtextBackendBitmap():
  192. """
  193. A backend to generate standalone mathtext images. No additional
  194. matplotlib backend is required.
  195. """
  196. return MathtextBackendBbox(MathtextBackendBitmapRender())
  197. class MathtextBackendPs(MathtextBackend):
  198. """
  199. Store information to write a mathtext rendering to the PostScript
  200. backend.
  201. """
  202. def __init__(self):
  203. self.pswriter = StringIO()
  204. self.lastfont = None
  205. def render_glyph(self, ox, oy, info):
  206. oy = self.height - oy + info.offset
  207. postscript_name = info.postscript_name
  208. fontsize = info.fontsize
  209. symbol_name = info.symbol_name
  210. if (postscript_name, fontsize) != self.lastfont:
  211. ps = """/%(postscript_name)s findfont
  212. %(fontsize)s scalefont
  213. setfont
  214. """ % locals()
  215. self.lastfont = postscript_name, fontsize
  216. self.pswriter.write(ps)
  217. ps = """%(ox)f %(oy)f moveto
  218. /%(symbol_name)s glyphshow\n
  219. """ % locals()
  220. self.pswriter.write(ps)
  221. def render_rect_filled(self, x1, y1, x2, y2):
  222. ps = "%f %f %f %f rectfill\n" % (x1, self.height - y2, x2 - x1, y2 - y1)
  223. self.pswriter.write(ps)
  224. def get_results(self, box):
  225. ship(0, -self.depth, box)
  226. #print self.depth
  227. return (self.width,
  228. self.height + self.depth,
  229. self.depth,
  230. self.pswriter,
  231. self.fonts_object.get_used_characters())
  232. class MathtextBackendPdf(MathtextBackend):
  233. """
  234. Store information to write a mathtext rendering to the PDF
  235. backend.
  236. """
  237. def __init__(self):
  238. self.glyphs = []
  239. self.rects = []
  240. def render_glyph(self, ox, oy, info):
  241. filename = info.font.fname
  242. oy = self.height - oy + info.offset
  243. self.glyphs.append(
  244. (ox, oy, filename, info.fontsize,
  245. info.num, info.symbol_name))
  246. def render_rect_filled(self, x1, y1, x2, y2):
  247. self.rects.append((x1, self.height - y2, x2 - x1, y2 - y1))
  248. def get_results(self, box):
  249. ship(0, -self.depth, box)
  250. return (self.width,
  251. self.height + self.depth,
  252. self.depth,
  253. self.glyphs,
  254. self.rects,
  255. self.fonts_object.get_used_characters())
  256. class MathtextBackendSvg(MathtextBackend):
  257. """
  258. Store information to write a mathtext rendering to the SVG
  259. backend.
  260. """
  261. def __init__(self):
  262. self.svg_glyphs = []
  263. self.svg_rects = []
  264. def render_glyph(self, ox, oy, info):
  265. oy = self.height - oy + info.offset
  266. thetext = unichr(info.num)
  267. self.svg_glyphs.append(
  268. (info.font, info.fontsize, thetext, ox, oy, info.metrics))
  269. def render_rect_filled(self, x1, y1, x2, y2):
  270. self.svg_rects.append(
  271. (x1, self.height - y1 + 1, x2 - x1, y2 - y1))
  272. def get_results(self, box):
  273. ship(0, -self.depth, box)
  274. svg_elements = Bunch(svg_glyphs = self.svg_glyphs,
  275. svg_rects = self.svg_rects)
  276. return (self.width,
  277. self.height + self.depth,
  278. self.depth,
  279. svg_elements,
  280. self.fonts_object.get_used_characters())
  281. class MathtextBackendCairo(MathtextBackend):
  282. """
  283. Store information to write a mathtext rendering to the Cairo
  284. backend.
  285. """
  286. def __init__(self):
  287. self.glyphs = []
  288. self.rects = []
  289. def render_glyph(self, ox, oy, info):
  290. oy = oy - info.offset - self.height
  291. thetext = unichr(info.num)
  292. self.glyphs.append(
  293. (info.font, info.fontsize, thetext, ox, oy))
  294. def render_rect_filled(self, x1, y1, x2, y2):
  295. self.rects.append(
  296. (x1, y1 - self.height, x2 - x1, y2 - y1))
  297. def get_results(self, box):
  298. ship(0, -self.depth, box)
  299. return (self.width,
  300. self.height + self.depth,
  301. self.depth,
  302. self.glyphs,
  303. self.rects)
  304. class Fonts(object):
  305. """
  306. An abstract base class for a system of fonts to use for mathtext.
  307. The class must be able to take symbol keys and font file names and
  308. return the character metrics. It also delegates to a backend class
  309. to do the actual drawing.
  310. """
  311. def __init__(self, default_font_prop, mathtext_backend):
  312. """
  313. *default_font_prop*: A
  314. :class:`~matplotlib.font_manager.FontProperties` object to use
  315. for the default non-math font, or the base font for Unicode
  316. (generic) font rendering.
  317. *mathtext_backend*: A subclass of :class:`MathTextBackend`
  318. used to delegate the actual rendering.
  319. """
  320. self.default_font_prop = default_font_prop
  321. self.mathtext_backend = mathtext_backend
  322. # Make these classes doubly-linked
  323. self.mathtext_backend.fonts_object = self
  324. self.used_characters = {}
  325. def destroy(self):
  326. """
  327. Fix any cyclical references before the object is about
  328. to be destroyed.
  329. """
  330. self.used_characters = None
  331. def get_kern(self, font1, fontclass1, sym1, fontsize1,
  332. font2, fontclass2, sym2, fontsize2, dpi):
  333. """
  334. Get the kerning distance for font between *sym1* and *sym2*.
  335. *fontX*: one of the TeX font names::
  336. tt, it, rm, cal, sf, bf or default (non-math)
  337. *fontclassX*: TODO
  338. *symX*: a symbol in raw TeX form. e.g. '1', 'x' or '\sigma'
  339. *fontsizeX*: the fontsize in points
  340. *dpi*: the current dots-per-inch
  341. """
  342. return 0.
  343. def get_metrics(self, font, font_class, sym, fontsize, dpi):
  344. """
  345. *font*: one of the TeX font names::
  346. tt, it, rm, cal, sf, bf or default (non-math)
  347. *font_class*: TODO
  348. *sym*: a symbol in raw TeX form. e.g. '1', 'x' or '\sigma'
  349. *fontsize*: font size in points
  350. *dpi*: current dots-per-inch
  351. Returns an object with the following attributes:
  352. - *advance*: The advance distance (in points) of the glyph.
  353. - *height*: The height of the glyph in points.
  354. - *width*: The width of the glyph in points.
  355. - *xmin*, *xmax*, *ymin*, *ymax* - the ink rectangle of the glyph
  356. - *iceberg* - the distance from the baseline to the top of
  357. the glyph. This corresponds to TeX's definition of
  358. "height".
  359. """
  360. info = self._get_info(font, font_class, sym, fontsize, dpi)
  361. return info.metrics
  362. def set_canvas_size(self, w, h, d):
  363. """
  364. Set the size of the buffer used to render the math expression.
  365. Only really necessary for the bitmap backends.
  366. """
  367. self.width, self.height, self.depth = ceil(w), ceil(h), ceil(d)
  368. self.mathtext_backend.set_canvas_size(self.width, self.height, self.depth)
  369. def render_glyph(self, ox, oy, facename, font_class, sym, fontsize, dpi):
  370. """
  371. Draw a glyph at
  372. - *ox*, *oy*: position
  373. - *facename*: One of the TeX face names
  374. - *font_class*:
  375. - *sym*: TeX symbol name or single character
  376. - *fontsize*: fontsize in points
  377. - *dpi*: The dpi to draw at.
  378. """
  379. info = self._get_info(facename, font_class, sym, fontsize, dpi)
  380. realpath, stat_key = get_realpath_and_stat(info.font.fname)
  381. used_characters = self.used_characters.setdefault(
  382. stat_key, (realpath, set()))
  383. used_characters[1].add(info.num)
  384. self.mathtext_backend.render_glyph(ox, oy, info)
  385. def render_rect_filled(self, x1, y1, x2, y2):
  386. """
  387. Draw a filled rectangle from (*x1*, *y1*) to (*x2*, *y2*).
  388. """
  389. self.mathtext_backend.render_rect_filled(x1, y1, x2, y2)
  390. def get_xheight(self, font, fontsize, dpi):
  391. """
  392. Get the xheight for the given *font* and *fontsize*.
  393. """
  394. raise NotImplementedError()
  395. def get_underline_thickness(self, font, fontsize, dpi):
  396. """
  397. Get the line thickness that matches the given font. Used as a
  398. base unit for drawing lines such as in a fraction or radical.
  399. """
  400. raise NotImplementedError()
  401. def get_used_characters(self):
  402. """
  403. Get the set of characters that were used in the math
  404. expression. Used by backends that need to subset fonts so
  405. they know which glyphs to include.
  406. """
  407. return self.used_characters
  408. def get_results(self, box):
  409. """
  410. Get the data needed by the backend to render the math
  411. expression. The return value is backend-specific.
  412. """
  413. return self.mathtext_backend.get_results(box)
  414. def get_sized_alternatives_for_symbol(self, fontname, sym):
  415. """
  416. Override if your font provides multiple sizes of the same
  417. symbol. Should return a list of symbols matching *sym* in
  418. various sizes. The expression renderer will select the most
  419. appropriate size for a given situation from this list.
  420. """
  421. return [(fontname, sym)]
  422. class TruetypeFonts(Fonts):
  423. """
  424. A generic base class for all font setups that use Truetype fonts
  425. (through FT2Font).
  426. """
  427. class CachedFont:
  428. def __init__(self, font):
  429. self.font = font
  430. self.charmap = font.get_charmap()
  431. self.glyphmap = dict(
  432. [(glyphind, ccode) for ccode, glyphind in self.charmap.iteritems()])
  433. def __repr__(self):
  434. return repr(self.font)
  435. def __init__(self, default_font_prop, mathtext_backend):
  436. Fonts.__init__(self, default_font_prop, mathtext_backend)
  437. self.glyphd = {}
  438. self._fonts = {}
  439. filename = findfont(default_font_prop)
  440. default_font = self.CachedFont(FT2Font(str(filename)))
  441. self._fonts['default'] = default_font
  442. def destroy(self):
  443. self.glyphd = None
  444. Fonts.destroy(self)
  445. def _get_font(self, font):
  446. if font in self.fontmap:
  447. basename = self.fontmap[font]
  448. else:
  449. basename = font
  450. cached_font = self._fonts.get(basename)
  451. if cached_font is None:
  452. font = FT2Font(basename)
  453. cached_font = self.CachedFont(font)
  454. self._fonts[basename] = cached_font
  455. self._fonts[font.postscript_name] = cached_font
  456. self._fonts[font.postscript_name.lower()] = cached_font
  457. return cached_font
  458. def _get_offset(self, cached_font, glyph, fontsize, dpi):
  459. if cached_font.font.postscript_name == 'Cmex10':
  460. return glyph.height/64.0/2.0 + 256.0/64.0 * dpi/72.0
  461. return 0.
  462. def _get_info(self, fontname, font_class, sym, fontsize, dpi):
  463. key = fontname, font_class, sym, fontsize, dpi
  464. bunch = self.glyphd.get(key)
  465. if bunch is not None:
  466. return bunch
  467. cached_font, num, symbol_name, fontsize, slanted = \
  468. self._get_glyph(fontname, font_class, sym, fontsize)
  469. font = cached_font.font
  470. font.set_size(fontsize, dpi)
  471. glyph = font.load_char(
  472. num,
  473. flags=self.mathtext_backend.get_hinting_type())
  474. xmin, ymin, xmax, ymax = [val/64.0 for val in glyph.bbox]
  475. offset = self._get_offset(cached_font, glyph, fontsize, dpi)
  476. metrics = Bunch(
  477. advance = glyph.linearHoriAdvance/65536.0,
  478. height = glyph.height/64.0,
  479. width = glyph.width/64.0,
  480. xmin = xmin,
  481. xmax = xmax,
  482. ymin = ymin+offset,
  483. ymax = ymax+offset,
  484. # iceberg is the equivalent of TeX's "height"
  485. iceberg = glyph.horiBearingY/64.0 + offset,
  486. slanted = slanted
  487. )
  488. result = self.glyphd[key] = Bunch(
  489. font = font,
  490. fontsize = fontsize,
  491. postscript_name = font.postscript_name,
  492. metrics = metrics,
  493. symbol_name = symbol_name,
  494. num = num,
  495. glyph = glyph,
  496. offset = offset
  497. )
  498. return result
  499. def get_xheight(self, font, fontsize, dpi):
  500. cached_font = self._get_font(font)
  501. cached_font.font.set_size(fontsize, dpi)
  502. pclt = cached_font.font.get_sfnt_table('pclt')
  503. if pclt is None:
  504. # Some fonts don't store the xHeight, so we do a poor man's xHeight
  505. metrics = self.get_metrics(font, 'it', 'x', fontsize, dpi)
  506. return metrics.iceberg
  507. xHeight = (pclt['xHeight'] / 64.0) * (fontsize / 12.0) * (dpi / 100.0)
  508. return xHeight
  509. def get_underline_thickness(self, font, fontsize, dpi):
  510. # This function used to grab underline thickness from the font
  511. # metrics, but that information is just too un-reliable, so it
  512. # is now hardcoded.
  513. return ((0.75 / 12.0) * fontsize * dpi) / 72.0
  514. def get_kern(self, font1, fontclass1, sym1, fontsize1,
  515. font2, fontclass2, sym2, fontsize2, dpi):
  516. if font1 == font2 and fontsize1 == fontsize2:
  517. info1 = self._get_info(font1, fontclass1, sym1, fontsize1, dpi)
  518. info2 = self._get_info(font2, fontclass2, sym2, fontsize2, dpi)
  519. font = info1.font
  520. return font.get_kerning(info1.num, info2.num, KERNING_DEFAULT) / 64.0
  521. return Fonts.get_kern(self, font1, fontclass1, sym1, fontsize1,
  522. font2, fontclass2, sym2, fontsize2, dpi)
  523. class BakomaFonts(TruetypeFonts):
  524. """
  525. Use the Bakoma TrueType fonts for rendering.
  526. Symbols are strewn about a number of font files, each of which has
  527. its own proprietary 8-bit encoding.
  528. """
  529. _fontmap = { 'cal' : 'cmsy10',
  530. 'rm' : 'cmr10',
  531. 'tt' : 'cmtt10',
  532. 'it' : 'cmmi10',
  533. 'bf' : 'cmb10',
  534. 'sf' : 'cmss10',
  535. 'ex' : 'cmex10'
  536. }
  537. fontmap = {}
  538. def __init__(self, *args, **kwargs):
  539. self._stix_fallback = StixFonts(*args, **kwargs)
  540. TruetypeFonts.__init__(self, *args, **kwargs)
  541. if not len(self.fontmap):
  542. for key, val in self._fontmap.iteritems():
  543. fullpath = findfont(val)
  544. self.fontmap[key] = fullpath
  545. self.fontmap[val] = fullpath
  546. _slanted_symbols = set(r"\int \oint".split())
  547. def _get_glyph(self, fontname, font_class, sym, fontsize):
  548. symbol_name = None
  549. if fontname in self.fontmap and sym in latex_to_bakoma:
  550. basename, num = latex_to_bakoma[sym]
  551. slanted = (basename == "cmmi10") or sym in self._slanted_symbols
  552. try:
  553. cached_font = self._get_font(basename)
  554. except RuntimeError:
  555. pass
  556. else:
  557. symbol_name = cached_font.font.get_glyph_name(num)
  558. num = cached_font.glyphmap[num]
  559. elif len(sym) == 1:
  560. slanted = (fontname == "it")
  561. try:
  562. cached_font = self._get_font(fontname)
  563. except RuntimeError:
  564. pass
  565. else:
  566. num = ord(sym)
  567. gid = cached_font.charmap.get(num)
  568. if gid is not None:
  569. symbol_name = cached_font.font.get_glyph_name(
  570. cached_font.charmap[num])
  571. if symbol_name is None:
  572. return self._stix_fallback._get_glyph(
  573. fontname, font_class, sym, fontsize)
  574. return cached_font, num, symbol_name, fontsize, slanted
  575. # The Bakoma fonts contain many pre-sized alternatives for the
  576. # delimiters. The AutoSizedChar class will use these alternatives
  577. # and select the best (closest sized) glyph.
  578. _size_alternatives = {
  579. '(' : [('rm', '('), ('ex', '\xa1'), ('ex', '\xb3'),
  580. ('ex', '\xb5'), ('ex', '\xc3')],
  581. ')' : [('rm', ')'), ('ex', '\xa2'), ('ex', '\xb4'),
  582. ('ex', '\xb6'), ('ex', '\x21')],
  583. '{' : [('cal', '{'), ('ex', '\xa9'), ('ex', '\x6e'),
  584. ('ex', '\xbd'), ('ex', '\x28')],
  585. '}' : [('cal', '}'), ('ex', '\xaa'), ('ex', '\x6f'),
  586. ('ex', '\xbe'), ('ex', '\x29')],
  587. # The fourth size of '[' is mysteriously missing from the BaKoMa
  588. # font, so I've ommitted it for both '[' and ']'
  589. '[' : [('rm', '['), ('ex', '\xa3'), ('ex', '\x68'),
  590. ('ex', '\x22')],
  591. ']' : [('rm', ']'), ('ex', '\xa4'), ('ex', '\x69'),
  592. ('ex', '\x23')],
  593. r'\lfloor' : [('ex', '\xa5'), ('ex', '\x6a'),
  594. ('ex', '\xb9'), ('ex', '\x24')],
  595. r'\rfloor' : [('ex', '\xa6'), ('ex', '\x6b'),
  596. ('ex', '\xba'), ('ex', '\x25')],
  597. r'\lceil' : [('ex', '\xa7'), ('ex', '\x6c'),
  598. ('ex', '\xbb'), ('ex', '\x26')],
  599. r'\rceil' : [('ex', '\xa8'), ('ex', '\x6d'),
  600. ('ex', '\xbc'), ('ex', '\x27')],
  601. r'\langle' : [('ex', '\xad'), ('ex', '\x44'),
  602. ('ex', '\xbf'), ('ex', '\x2a')],
  603. r'\rangle' : [('ex', '\xae'), ('ex', '\x45'),
  604. ('ex', '\xc0'), ('ex', '\x2b')],
  605. r'\__sqrt__' : [('ex', '\x70'), ('ex', '\x71'),
  606. ('ex', '\x72'), ('ex', '\x73')],
  607. r'\backslash': [('ex', '\xb2'), ('ex', '\x2f'),
  608. ('ex', '\xc2'), ('ex', '\x2d')],
  609. r'/' : [('rm', '/'), ('ex', '\xb1'), ('ex', '\x2e'),
  610. ('ex', '\xcb'), ('ex', '\x2c')],
  611. r'\widehat' : [('rm', '\x5e'), ('ex', '\x62'), ('ex', '\x63'),
  612. ('ex', '\x64')],
  613. r'\widetilde': [('rm', '\x7e'), ('ex', '\x65'), ('ex', '\x66'),
  614. ('ex', '\x67')],
  615. r'<' : [('cal', 'h'), ('ex', 'D')],
  616. r'>' : [('cal', 'i'), ('ex', 'E')]
  617. }
  618. for alias, target in [('\leftparen', '('),
  619. ('\rightparent', ')'),
  620. ('\leftbrace', '{'),
  621. ('\rightbrace', '}'),
  622. ('\leftbracket', '['),
  623. ('\rightbracket', ']')]:
  624. _size_alternatives[alias] = _size_alternatives[target]
  625. def get_sized_alternatives_for_symbol(self, fontname, sym):
  626. return self._size_alternatives.get(sym, [(fontname, sym)])
  627. class UnicodeFonts(TruetypeFonts):
  628. """
  629. An abstract base class for handling Unicode fonts.
  630. While some reasonably complete Unicode fonts (such as DejaVu) may
  631. work in some situations, the only Unicode font I'm aware of with a
  632. complete set of math symbols is STIX.
  633. This class will "fallback" on the Bakoma fonts when a required
  634. symbol can not be found in the font.
  635. """
  636. fontmap = {}
  637. use_cmex = True
  638. def __init__(self, *args, **kwargs):
  639. # This must come first so the backend's owner is set correctly
  640. if rcParams['mathtext.fallback_to_cm']:
  641. self.cm_fallback = BakomaFonts(*args, **kwargs)
  642. else:
  643. self.cm_fallback = None
  644. TruetypeFonts.__init__(self, *args, **kwargs)
  645. if not len(self.fontmap):
  646. for texfont in "cal rm tt it bf sf".split():
  647. prop = rcParams['mathtext.' + texfont]
  648. font = findfont(prop)
  649. self.fontmap[texfont] = font
  650. prop = FontProperties('cmex10')
  651. font = findfont(prop)
  652. self.fontmap['ex'] = font
  653. _slanted_symbols = set(r"\int \oint".split())
  654. def _map_virtual_font(self, fontname, font_class, uniindex):
  655. return fontname, uniindex
  656. def _get_glyph(self, fontname, font_class, sym, fontsize):
  657. found_symbol = False
  658. if self.use_cmex:
  659. uniindex = latex_to_cmex.get(sym)
  660. if uniindex is not None:
  661. fontname = 'ex'
  662. found_symbol = True
  663. if not found_symbol:
  664. try:
  665. uniindex = get_unicode_index(sym)
  666. found_symbol = True
  667. except ValueError:
  668. uniindex = ord('?')
  669. warn("No TeX to unicode mapping for '%s'" %
  670. sym.encode('ascii', 'backslashreplace'),
  671. MathTextWarning)
  672. fontname, uniindex = self._map_virtual_font(
  673. fontname, font_class, uniindex)
  674. # Only characters in the "Letter" class should be italicized in 'it'
  675. # mode. Greek capital letters should be Roman.
  676. if found_symbol:
  677. new_fontname = fontname
  678. if fontname == 'it':
  679. if uniindex < 0x10000:
  680. unistring = unichr(uniindex)
  681. if (not unicodedata.category(unistring)[0] == "L"
  682. or unicodedata.name(unistring).startswith("GREEK CAPITAL")):
  683. new_fontname = 'rm'
  684. slanted = (new_fontname == 'it') or sym in self._slanted_symbols
  685. found_symbol = False
  686. try:
  687. cached_font = self._get_font(new_fontname)
  688. except RuntimeError:
  689. pass
  690. else:
  691. try:
  692. glyphindex = cached_font.charmap[uniindex]
  693. found_symbol = True
  694. except KeyError:
  695. pass
  696. if not found_symbol:
  697. if self.cm_fallback:
  698. warn("Substituting with a symbol from Computer Modern.",
  699. MathTextWarning)
  700. return self.cm_fallback._get_glyph(
  701. fontname, 'it', sym, fontsize)
  702. else:
  703. if fontname == 'it' and isinstance(self, StixFonts):
  704. return self._get_glyph('rm', font_class, sym, fontsize)
  705. warn("Font '%s' does not have a glyph for '%s'" %
  706. (fontname, sym.encode('ascii', 'backslashreplace')),
  707. MathTextWarning)
  708. warn("Substituting with a dummy symbol.", MathTextWarning)
  709. fontname = 'rm'
  710. new_fontname = fontname
  711. cached_font = self._get_font(fontname)
  712. uniindex = 0xA4 # currency character, for lack of anything better
  713. glyphindex = cached_font.charmap[uniindex]
  714. slanted = False
  715. symbol_name = cached_font.font.get_glyph_name(glyphindex)
  716. return cached_font, uniindex, symbol_name, fontsize, slanted
  717. def get_sized_alternatives_for_symbol(self, fontname, sym):
  718. if self.cm_fallback:
  719. return self.cm_fallback.get_sized_alternatives_for_symbol(
  720. fontname, sym)
  721. return [(fontname, sym)]
  722. class StixFonts(UnicodeFonts):
  723. """
  724. A font handling class for the STIX fonts.
  725. In addition to what UnicodeFonts provides, this class:
  726. - supports "virtual fonts" which are complete alpha numeric
  727. character sets with different font styles at special Unicode
  728. code points, such as "Blackboard".
  729. - handles sized alternative characters for the STIXSizeX fonts.
  730. """
  731. _fontmap = { 'rm' : 'STIXGeneral',
  732. 'it' : 'STIXGeneral:italic',
  733. 'bf' : 'STIXGeneral:weight=bold',
  734. 'nonunirm' : 'STIXNonUnicode',
  735. 'nonuniit' : 'STIXNonUnicode:italic',
  736. 'nonunibf' : 'STIXNonUnicode:weight=bold',
  737. 0 : 'STIXGeneral',
  738. 1 : 'STIXSize1',
  739. 2 : 'STIXSize2',
  740. 3 : 'STIXSize3',
  741. 4 : 'STIXSize4',
  742. 5 : 'STIXSize5'
  743. }
  744. fontmap = {}
  745. use_cmex = False
  746. cm_fallback = False
  747. _sans = False
  748. def __init__(self, *args, **kwargs):
  749. TruetypeFonts.__init__(self, *args, **kwargs)
  750. if not len(self.fontmap):
  751. for key, name in self._fontmap.iteritems():
  752. fullpath = findfont(name)
  753. self.fontmap[key] = fullpath
  754. self.fontmap[name] = fullpath
  755. def _map_virtual_font(self, fontname, font_class, uniindex):
  756. # Handle these "fonts" that are actually embedded in
  757. # other fonts.
  758. mapping = stix_virtual_fonts.get(fontname)
  759. if self._sans and mapping is None:
  760. mapping = stix_virtual_fonts['sf']
  761. doing_sans_conversion = True
  762. else:
  763. doing_sans_conversion = False
  764. if mapping is not None:
  765. if isinstance(mapping, dict):
  766. mapping = mapping[font_class]
  767. # Binary search for the source glyph
  768. lo = 0
  769. hi = len(mapping)
  770. while lo < hi:
  771. mid = (lo+hi)//2
  772. range = mapping[mid]
  773. if uniindex < range[0]:
  774. hi = mid
  775. elif uniindex <= range[1]:
  776. break
  777. else:
  778. lo = mid + 1
  779. if uniindex >= range[0] and uniindex <= range[1]:
  780. uniindex = uniindex - range[0] + range[3]
  781. fontname = range[2]
  782. elif not doing_sans_conversion:
  783. # This will generate a dummy character
  784. uniindex = 0x1
  785. fontname = 'it'
  786. # Handle private use area glyphs
  787. if (fontname in ('it', 'rm', 'bf') and
  788. uniindex >= 0xe000 and uniindex <= 0xf8ff):
  789. fontname = 'nonuni' + fontname
  790. return fontname, uniindex
  791. _size_alternatives = {}
  792. def get_sized_alternatives_for_symbol(self, fontname, sym):
  793. alternatives = self._size_alternatives.get(sym)
  794. if alternatives:
  795. return alternatives
  796. alternatives = []
  797. try:
  798. uniindex = get_unicode_index(sym)
  799. except ValueError:
  800. return [(fontname, sym)]
  801. fix_ups = {
  802. ord('<'): 0x27e8,
  803. ord('>'): 0x27e9 }
  804. uniindex = fix_ups.get(uniindex, uniindex)
  805. for i in range(6):
  806. cached_font = self._get_font(i)
  807. glyphindex = cached_font.charmap.get(uniindex)
  808. if glyphindex is not None:
  809. alternatives.append((i, unichr(uniindex)))
  810. self._size_alternatives[sym] = alternatives
  811. return alternatives
  812. class StixSansFonts(StixFonts):
  813. """
  814. A font handling class for the STIX fonts (that uses sans-serif
  815. characters by default).
  816. """
  817. _sans = True
  818. class StandardPsFonts(Fonts):
  819. """
  820. Use the standard postscript fonts for rendering to backend_ps
  821. Unlike the other font classes, BakomaFont and UnicodeFont, this
  822. one requires the Ps backend.
  823. """
  824. basepath = os.path.join( get_data_path(), 'fonts', 'afm' )
  825. fontmap = { 'cal' : 'pzcmi8a', # Zapf Chancery
  826. 'rm' : 'pncr8a', # New Century Schoolbook
  827. 'tt' : 'pcrr8a', # Courier
  828. 'it' : 'pncri8a', # New Century Schoolbook Italic
  829. 'sf' : 'phvr8a', # Helvetica
  830. 'bf' : 'pncb8a', # New Century Schoolbook Bold
  831. None : 'psyr' # Symbol
  832. }
  833. def __init__(self, default_font_prop):
  834. Fonts.__init__(self, default_font_prop, MathtextBackendPs())
  835. self.glyphd = {}
  836. self.fonts = {}
  837. filename = findfont(default_font_prop, fontext='afm')
  838. default_font = AFM(file(filename, 'r'))
  839. default_font.fname = filename
  840. self.fonts['default'] = default_font
  841. self.pswriter = StringIO()
  842. def _get_font(self, font):
  843. if font in self.fontmap:
  844. basename = self.fontmap[font]
  845. else:
  846. basename = font
  847. cached_font = self.fonts.get(basename)
  848. if cached_font is None:
  849. fname = os.path.join(self.basepath, basename + ".afm")
  850. cached_font = AFM(file(fname, 'r'))
  851. cached_font.fname = fname
  852. self.fonts[basename] = cached_font
  853. self.fonts[cached_font.get_fontname()] = cached_font
  854. return cached_font
  855. def _get_info (self, fontname, font_class, sym, fontsize, dpi):
  856. 'load the cmfont, metrics and glyph with caching'
  857. key = fontname, sym, fontsize, dpi
  858. tup = self.glyphd.get(key)
  859. if tup is not None:
  860. return tup
  861. # Only characters in the "Letter" class should really be italicized.
  862. # This class includes greek letters, so we're ok
  863. if (fontname == 'it' and
  864. (len(sym) > 1 or
  865. not unicodedata.category(unicode(sym)).startswith("L"))):
  866. fontname = 'rm'
  867. found_symbol = False
  868. if sym in latex_to_standard:
  869. fontname, num = latex_to_standard[sym]
  870. glyph = chr(num)
  871. found_symbol = True
  872. elif len(sym) == 1:
  873. glyph = sym
  874. num = ord(glyph)
  875. found_symbol = True
  876. else:
  877. warn("No TeX to built-in Postscript mapping for '%s'" % sym,
  878. MathTextWarning)
  879. slanted = (fontname == 'it')
  880. font = self._get_font(fontname)
  881. if found_symbol:
  882. try:
  883. symbol_name = font.get_name_char(glyph)
  884. except KeyError:
  885. warn("No glyph in standard Postscript font '%s' for '%s'" %
  886. (font.postscript_name, sym),
  887. MathTextWarning)
  888. found_symbol = False
  889. if not found_symbol:
  890. glyph = sym = '?'
  891. num = ord(glyph)
  892. symbol_name = font.get_name_char(glyph)
  893. offset = 0
  894. scale = 0.001 * fontsize
  895. xmin, ymin, xmax, ymax = [val * scale
  896. for val in font.get_bbox_char(glyph)]
  897. metrics = Bunch(
  898. advance = font.get_width_char(glyph) * scale,
  899. width = font.get_width_char(glyph) * scale,
  900. height = font.get_height_char(glyph) * scale,
  901. xmin = xmin,
  902. xmax = xmax,
  903. ymin = ymin+offset,
  904. ymax = ymax+offset,
  905. # iceberg is the equivalent of TeX's "height"
  906. iceberg = ymax + offset,
  907. slanted = slanted
  908. )
  909. self.glyphd[key] = Bunch(
  910. font = font,
  911. fontsize = fontsize,
  912. postscript_name = font.get_fontname(),
  913. metrics = metrics,
  914. symbol_name = symbol_name,
  915. num = num,
  916. glyph = glyph,
  917. offset = offset
  918. )
  919. return self.glyphd[key]
  920. def get_kern(self, font1, fontclass1, sym1, fontsize1,
  921. font2, fontclass2, sym2, fontsize2, dpi):
  922. if font1 == font2 and fontsize1 == fontsize2:
  923. info1 = self._get_info(font1, fontclass1, sym1, fontsize1, dpi)
  924. info2 = self._get_info(font2, fontclass2, sym2, fontsize2, dpi)
  925. font = info1.font
  926. return (font.get_kern_dist(info1.glyph, info2.glyph)
  927. * 0.001 * fontsize1)
  928. return Fonts.get_kern(self, font1, fontclass1, sym1, fontsize1,
  929. font2, fontclass2, sym2, fontsize2, dpi)
  930. def get_xheight(self, font, fontsize, dpi):
  931. cached_font = self._get_font(font)
  932. return cached_font.get_xheight() * 0.001 * fontsize
  933. def get_underline_thickness(self, font, fontsize, dpi):
  934. cached_font = self._get_font(font)
  935. return cached_font.get_underline_thickness() * 0.001 * fontsize
  936. ##############################################################################
  937. # TeX-LIKE BOX MODEL
  938. # The following is based directly on the document 'woven' from the
  939. # TeX82 source code. This information is also available in printed
  940. # form:
  941. #
  942. # Knuth, Donald E.. 1986. Computers and Typesetting, Volume B:
  943. # TeX: The Program. Addison-Wesley Professional.
  944. #
  945. # The most relevant "chapters" are:
  946. # Data structures for boxes and their friends
  947. # Shipping pages out (Ship class)
  948. # Packaging (hpack and vpack)
  949. # Data structures for math mode
  950. # Subroutines for math mode
  951. # Typesetting math formulas
  952. #
  953. # Many of the docstrings below refer to a numbered "node" in that
  954. # book, e.g. node123
  955. #
  956. # Note that (as TeX) y increases downward, unlike many other parts of
  957. # matplotlib.
  958. # How much text shrinks when going to the next-smallest level. GROW_FACTOR
  959. # must be the inverse of SHRINK_FACTOR.
  960. SHRINK_FACTOR = 0.7
  961. GROW_FACTOR = 1.0 / SHRINK_FACTOR
  962. # The number of different sizes of chars to use, beyond which they will not
  963. # get any smaller
  964. NUM_SIZE_LEVELS = 4
  965. # Percentage of x-height of additional horiz. space after sub/superscripts
  966. SCRIPT_SPACE = 0.2
  967. # Percentage of x-height that sub/superscripts drop below the baseline
  968. SUBDROP = 0.3
  969. # Percentage of x-height that superscripts drop below the baseline
  970. SUP1 = 0.5
  971. # Percentage of x-height that subscripts drop below the baseline
  972. SUB1 = 0.0
  973. # Percentage of x-height that superscripts are offset relative to the subscript
  974. DELTA = 0.18
  975. class MathTextWarning(Warning):
  976. pass
  977. class Node(object):
  978. """
  979. A node in the TeX box model
  980. """
  981. def __init__(self):
  982. self.size = 0
  983. def __repr__(self):
  984. return self.__internal_repr__()
  985. def __internal_repr__(self):
  986. return self.__class__.__name__
  987. def get_kerning(self, next):
  988. return 0.0
  989. def shrink(self):
  990. """
  991. Shrinks one level smaller. There are only three levels of
  992. sizes, after which things will no longer get smaller.
  993. """
  994. self.size += 1
  995. def grow(self):
  996. """
  997. Grows one level larger. There is no limit to how big
  998. something can get.
  999. """
  1000. self.size -= 1
  1001. def render(self, x, y):
  1002. pass
  1003. class Box(Node):
  1004. """
  1005. Represents any node with a physical location.
  1006. """
  1007. def __init__(self, width, height, depth):
  1008. Node.__init__(self)
  1009. self.width = width
  1010. self.height = height
  1011. self.depth = depth
  1012. def shrink(self):
  1013. Node.shrink(self)
  1014. if self.size < NUM_SIZE_LEVELS:
  1015. self.width *= SHRINK_FACTOR
  1016. self.height *= SHRINK_FACTOR
  1017. self.depth *= SHRINK_FACTOR
  1018. def grow(self):
  1019. Node.grow(self)
  1020. self.width *= GROW_FACTOR
  1021. self.height *= GROW_FACTOR
  1022. self.depth *= GROW_FACTOR
  1023. def render(self, x1, y1, x2, y2):
  1024. pass
  1025. class Vbox(Box):
  1026. """
  1027. A box with only height (zero width).
  1028. """
  1029. def __init__(self, height, depth):
  1030. Box.__init__(self, 0., height, depth)
  1031. class Hbox(Box):
  1032. """
  1033. A box with only width (zero height and depth).
  1034. """
  1035. def __init__(self, width):
  1036. Box.__init__(self, width, 0., 0.)
  1037. class Char(Node):
  1038. """
  1039. Represents a single character. Unlike TeX, the font information
  1040. and metrics are stored with each :class:`Char` to make it easier
  1041. to lookup the font metrics when needed. Note that TeX boxes have
  1042. a width, height, and depth, unlike Type1 and Truetype which use a
  1043. full bounding box and an advance in the x-direction. The metrics
  1044. must be converted to the TeX way, and the advance (if different
  1045. from width) must be converted into a :class:`Kern` node when the
  1046. :class:`Char` is added to its parent :class:`Hlist`.
  1047. """
  1048. def __init__(self, c, state):
  1049. Node.__init__(self)
  1050. self.c = c
  1051. self.font_output = state.font_output
  1052. assert isinstance(state.font, (str, unicode, int))
  1053. self.font = state.font
  1054. self.font_class = state.font_class
  1055. self.fontsize = state.fontsize
  1056. self.dpi = state.dpi
  1057. # The real width, height and depth will be set during the
  1058. # pack phase, after we know the real fontsize
  1059. self._update_metrics()
  1060. def __internal_repr__(self):
  1061. return '`%s`' % self.c
  1062. def _update_metrics(self):
  1063. metrics = self._metrics = self.font_output.get_metrics(
  1064. self.font, self.font_class, self.c, self.fontsize, self.dpi)
  1065. if self.c == ' ':
  1066. self.width = metrics.advance
  1067. else:
  1068. self.width = metrics.width
  1069. self.height = metrics.iceberg
  1070. self.depth = -(metrics.iceberg - metrics.height)
  1071. def is_slanted(self):
  1072. return self._metrics.slanted
  1073. def get_kerning(self, next):
  1074. """
  1075. Return the amount of kerning between this and the given
  1076. character. Called when characters are strung together into
  1077. :class:`Hlist` to create :class:`Kern` nodes.
  1078. """
  1079. advance = self._metrics.advance - self.width
  1080. kern = 0.
  1081. if isinstance(next, Char):
  1082. kern = self.font_output.get_kern(
  1083. self.font, self.font_class, self.c, self.fontsize,
  1084. next.font, next.font_class, next.c, next.fontsize,
  1085. self.dpi)
  1086. return advance + kern
  1087. def render(self, x, y):
  1088. """
  1089. Render the character to the canvas
  1090. """
  1091. self.font_output.render_glyph(
  1092. x, y,
  1093. self.font, self.font_class, self.c, self.fontsize, self.dpi)
  1094. def shrink(self):
  1095. Node.shrink(self)
  1096. if self.size < NUM_SIZE_LEVELS:
  1097. self.fontsize *= SHRINK_FACTOR
  1098. self.width *= SHRINK_FACTOR
  1099. self.height *= SHRINK_FACTOR
  1100. self.depth *= SHRINK_FACTOR
  1101. def grow(self):
  1102. Node.grow(self)
  1103. self.fontsize *= GROW_FACTOR
  1104. self.width *= GROW_FACTOR
  1105. self.height *= GROW_FACTOR
  1106. self.depth *= GROW_FACTOR
  1107. class Accent(Char):
  1108. """
  1109. The font metrics need to be dealt with differently for accents,
  1110. since they are already offset correctly from the baseline in
  1111. TrueType fonts.
  1112. """
  1113. def _update_metrics(self):
  1114. metrics = self._metrics = self.font_output.get_metrics(
  1115. self.font, self.font_class, self.c, self.fontsize, self.dpi)
  1116. self.width = metrics.xmax - metrics.xmin
  1117. self.height = metrics.ymax - metrics.ymin
  1118. self.depth = 0
  1119. def shrink(self):
  1120. Char.shrink(self)
  1121. self._update_metrics()
  1122. def grow(self):
  1123. Char.grow(self)
  1124. self._update_metrics()
  1125. def render(self, x, y):
  1126. """
  1127. Render the character to the canvas.
  1128. """
  1129. self.font_output.render_glyph(
  1130. x - self._metrics.xmin, y + self._metrics.ymin,
  1131. self.font, self.font_class, self.c, self.fontsize, self.dpi)
  1132. class List(Box):
  1133. """
  1134. A list of nodes (either horizontal or vertical).
  1135. """
  1136. def __init__(self, elements):
  1137. Box.__init__(self, 0., 0., 0.)
  1138. self.shift_amount = 0. # An arbitrary offset
  1139. self.children = elements # The child nodes of this list
  1140. # The following parameters are set in the vpack and hpack functions
  1141. self.glue_set = 0. # The glue setting of this list
  1142. self.glue_sign = 0 # 0: normal, -1: shrinking, 1: stretching
  1143. self.glue_order = 0 # The order of infinity (0 - 3) for the glue
  1144. def __repr__(self):
  1145. return '[%s <%.02f %.02f %.02f %.02f> %s]' % (
  1146. self.__internal_repr__(),
  1147. self.width, self.height,
  1148. self.depth, self.shift_amount,
  1149. ' '.join([repr(x) for x in self.children]))
  1150. def _determine_order(self, totals):
  1151. """
  1152. A helper function to determine the highest order of glue
  1153. used by the members of this list. Used by vpack and hpack.
  1154. """
  1155. o = 0
  1156. for i in range(len(totals) - 1, 0, -1):
  1157. if totals[i] != 0.0:
  1158. o = i
  1159. break
  1160. return o
  1161. def _set_glue(self, x, sign, totals, error_type):
  1162. o = self._determine_order(totals)
  1163. self.glue_order = o
  1164. self.glue_sign = sign
  1165. if totals[o] != 0.:
  1166. self.glue_set = x / totals[o]
  1167. else:
  1168. self.glue_sign = 0
  1169. self.glue_ratio = 0.
  1170. if o == 0:
  1171. if len(self.children):
  1172. warn("%s %s: %r" % (error_type, self.__class__.__name__, self),
  1173. MathTextWarning)
  1174. def shrink(self):
  1175. for child in self.children:
  1176. child.shrink()
  1177. Box.shrink(self)
  1178. if self.size < NUM_SIZE_LEVELS:
  1179. self.shift_amount *= SHRINK_FACTOR
  1180. self.glue_set *= SHRINK_FACTOR
  1181. def grow(self):
  1182. for child in self.children:
  1183. child.grow()
  1184. Box.grow(self)
  1185. self.shift_amount *= GROW_FACTOR
  1186. self.glue_set *= GROW_FACTOR
  1187. class Hlist(List):
  1188. """
  1189. A horizontal list of boxes.
  1190. """
  1191. def __init__(self, elements, w=0., m='additional', do_kern=True):
  1192. List.__init__(self, elements)
  1193. if do_kern:
  1194. self.kern()
  1195. self.hpack()
  1196. def kern(self):
  1197. """
  1198. Insert :class:`Kern` nodes between :class:`Char` nodes to set
  1199. kerning. The :class:`Char` nodes themselves determine the
  1200. amount of kerning they need (in :meth:`~Char.get_kerning`),
  1201. and this function just creates the linked list in the correct
  1202. way.
  1203. """
  1204. new_children = []
  1205. num_children = len(self.children)
  1206. if num_children:
  1207. for i in range(num_children):
  1208. elem = self.children[i]
  1209. if i < num_children - 1:
  1210. next = self.children[i + 1]
  1211. else:
  1212. next = None
  1213. new_children.append(elem)
  1214. kerning_distance = elem.get_kerning(next)
  1215. if kerning_distance != 0.:
  1216. kern = Kern(kerning_distance)
  1217. new_children.append(kern)
  1218. self.children = new_children
  1219. # This is a failed experiment to fake cross-font kerning.
  1220. # def get_kerning(self, next):
  1221. # if len(self.children) >= 2 and isinstance(self.children[-2], Char):
  1222. # if isinstance(next, Char):
  1223. # print "CASE A"
  1224. # return self.children[-2].get_kerning(next)
  1225. # elif isinstance(next, Hlist) and len(next.children) and isinstance(next.children[0], Char):
  1226. # print "CASE B"
  1227. #