|
45 | 45 | import matplotlib as mpl |
46 | 46 | from matplotlib import rcParams |
47 | 47 | from matplotlib._png import read_png |
| 48 | +import matplotlib.dviread as dviread |
| 49 | +import re |
48 | 50 |
|
49 | 51 | DEBUG = False |
50 | 52 |
|
@@ -262,12 +264,83 @@ def make_tex(self, tex, fontsize): |
262 | 264 |
|
263 | 265 | return texfile |
264 | 266 |
|
| 267 | + |
| 268 | + _re_vbox = re.compile(r"MatplotlibBox:\(([\d.]+)pt\+([\d.]+)pt\)x([\d.]+)pt") |
| 269 | + |
| 270 | + def make_tex_preview(self, tex, fontsize): |
| 271 | + """ |
| 272 | + Generate a tex file to render the tex string at a specific |
| 273 | + font size. It uses the preview.sty to determin the dimension |
| 274 | + (width, height, descent) of the output. |
| 275 | +
|
| 276 | + returns the file name |
| 277 | + """ |
| 278 | + basefile = self.get_basefile(tex, fontsize) |
| 279 | + texfile = '%s.tex'%basefile |
| 280 | + fh = file(texfile, 'w') |
| 281 | + custom_preamble = self.get_custom_preamble() |
| 282 | + fontcmd = {'sans-serif' : r'{\sffamily %s}', |
| 283 | + 'monospace' : r'{\ttfamily %s}'}.get(self.font_family, |
| 284 | + r'{\rmfamily %s}') |
| 285 | + tex = fontcmd % tex |
| 286 | + |
| 287 | + if rcParams['text.latex.unicode']: |
| 288 | + unicode_preamble = """\usepackage{ucs} |
| 289 | +\usepackage[utf8x]{inputenc}""" |
| 290 | + else: |
| 291 | + unicode_preamble = '' |
| 292 | + |
| 293 | + |
| 294 | + |
| 295 | + # newbox, setbox, immediate, etc. are used to find the box |
| 296 | + # extent of the rendered text. |
| 297 | + |
| 298 | + |
| 299 | + s = r"""\documentclass{article} |
| 300 | +%s |
| 301 | +%s |
| 302 | +%s |
| 303 | +\usepackage[active,showbox,tightpage]{preview} |
| 304 | +%%\usepackage[papersize={72in,72in}, body={70in,70in}, margin={1in,1in}]{geometry} |
| 305 | +
|
| 306 | +%% we override the default showbox as it is treated as an error and makes |
| 307 | +%% the exit status not zero |
| 308 | +\def\showbox#1{\immediate\write16{MatplotlibBox:(\the\ht#1+\the\dp#1)x\the\wd#1}} |
| 309 | +
|
| 310 | +\begin{document} |
| 311 | +\begin{preview} |
| 312 | +{\fontsize{%f}{%f}%s} |
| 313 | +\end{preview} |
| 314 | +\end{document} |
| 315 | +""" % (self._font_preamble, unicode_preamble, custom_preamble, |
| 316 | + fontsize, fontsize*1.25, tex) |
| 317 | + if rcParams['text.latex.unicode']: |
| 318 | + fh.write(s.encode('utf8')) |
| 319 | + else: |
| 320 | + try: |
| 321 | + fh.write(s) |
| 322 | + except UnicodeEncodeError, err: |
| 323 | + mpl.verbose.report("You are using unicode and latex, but have " |
| 324 | + "not enabled the matplotlib 'text.latex.unicode' " |
| 325 | + "rcParam.", 'helpful') |
| 326 | + raise |
| 327 | + |
| 328 | + fh.close() |
| 329 | + |
| 330 | + return texfile |
| 331 | + |
| 332 | + |
265 | 333 | def make_dvi(self, tex, fontsize): |
266 | 334 | """ |
267 | 335 | generates a dvi file containing latex's layout of tex string |
268 | 336 |
|
269 | 337 | returns the file name |
270 | 338 | """ |
| 339 | + |
| 340 | + |
| 341 | + if rcParams['text.latex.preview']: |
| 342 | + return self.make_dvi_preview(tex, fontsize) |
| 343 | + |
271 | 344 | basefile = self.get_basefile(tex, fontsize) |
272 | 345 | dvifile = '%s.dvi'% basefile |
273 | 346 |
|
@@ -298,6 +371,55 @@ def make_dvi(self, tex, fontsize): |
298 | 371 |
|
299 | 372 | return dvifile |
300 | 373 |
|
| 374 | + |
| 375 | + def make_dvi_preview(self, tex, fontsize): |
| 376 | + """ |
| 377 | + generates a dvi file containing latex's layout of tex |
| 378 | + string. It calls make_tex_preview() method and store the size |
| 379 | + information (width, height, descent) in a separte file. |
| 380 | +
|
| 381 | + returns the file name |
| 382 | + """ |
| 383 | + basefile = self.get_basefile(tex, fontsize) |
| 384 | + dvifile = '%s.dvi'% basefile |
| 385 | + baselinefile = '%s.baseline'% basefile |
| 386 | + |
| 387 | + if DEBUG or not os.path.exists(dvifile) or \ |
| 388 | + not os.path.exists(baselinefile): |
| 389 | + texfile = self.make_tex_preview(tex, fontsize) |
| 390 | + outfile = basefile+'.output' |
| 391 | + command = self._get_shell_cmd('cd "%s"'% self.texcache, |
| 392 | + 'latex -interaction=nonstopmode %s > "%s"'\ |
| 393 | + %(os.path.split(texfile)[-1], outfile)) |
| 394 | + mpl.verbose.report(command, 'debug') |
| 395 | + exit_status = os.system(command) |
| 396 | + try: |
| 397 | + fh = file(outfile) |
| 398 | + report = fh.read() |
| 399 | + fh.close() |
| 400 | + |
| 401 | + except IOError: |
| 402 | + report = 'No latex error report available.' |
| 403 | + if exit_status: |
| 404 | + raise RuntimeError(('LaTeX was not able to process the following \ |
| 405 | +string:\n%s\nHere is the full report generated by LaTeX: \n\n'% repr(tex)) + report) |
| 406 | + else: mpl.verbose.report(report, 'debug') |
| 407 | + |
| 408 | + # find the box extent information in the latex output |
| 409 | + # file and store them in ".baseline" file |
| 410 | + m = TexManager._re_vbox.search(report) |
| 411 | + open(basefile+'.baseline',"w").write(" ".join(m.groups())) |
| 412 | + |
| 413 | + for fname in glob.glob(basefile+'*'): |
| 414 | + if fname.endswith('dvi'): pass |
| 415 | + elif fname.endswith('tex'): pass |
| 416 | + elif fname.endswith('baseline'): pass |
| 417 | + else: |
| 418 | + try: os.remove(fname) |
| 419 | + except OSError: pass |
| 420 | + |
| 421 | + return dvifile |
| 422 | + |
301 | 423 | def make_png(self, tex, fontsize, dpi): |
302 | 424 | """ |
303 | 425 | generates a png file containing latex's rendering of tex string |
@@ -441,3 +563,37 @@ def get_rgba(self, tex, fontsize=None, dpi=None, rgb=(0,0,0)): |
441 | 563 | self.rgba_arrayd[key] = Z |
442 | 564 |
|
443 | 565 | return Z |
| 566 | + |
| 567 | + |
| 568 | + def get_text_width_height_descent(self, tex, fontsize, renderer=None): |
| 569 | + """ |
| 570 | + return width, heigth and descent of the text. |
| 571 | + """ |
| 572 | + |
| 573 | + if renderer: |
| 574 | + dpi_fraction = renderer.points_to_pixels(1.) |
| 575 | + else: |
| 576 | + dpi_fraction = 1. |
| 577 | + |
| 578 | + if rcParams['text.latex.preview']: |
| 579 | + # use preview.sty |
| 580 | + basefile = self.get_basefile(tex, fontsize) |
| 581 | + baselinefile = '%s.baseline'% basefile |
| 582 | + |
| 583 | + |
| 584 | + if DEBUG or not os.path.exists(baselinefile): |
| 585 | + dvifile = self.make_dvi_preview(tex, fontsize) |
| 586 | + |
| 587 | + l = open(baselinefile).read().split() |
| 588 | + height, depth, width = [float(l1)*dpi_fraction for l1 in l] |
| 589 | + return width, height+depth, depth |
| 590 | + |
| 591 | + else: |
| 592 | + # use dviread. It sometimes returns a wrong descent. |
| 593 | + dvifile = self.make_dvi(tex, fontsize) |
| 594 | + dvi = dviread.Dvi(dvifile, 72*dpi_fraction) |
| 595 | + page = iter(dvi).next() |
| 596 | + dvi.close() |
| 597 | + # A total height (including the descent) needs to be returned. |
| 598 | + return page.width, page.height+page.descent, page.descent |
| 599 | + |
0 commit comments