23
23
from six .moves import xrange
24
24
25
25
from collections import namedtuple
26
- from contextlib import closing
27
26
from functools import partial , wraps
28
27
import logging
29
28
import numpy as np
@@ -185,8 +184,7 @@ def wrapper(self, byte):
185
184
class Dvi (object ):
186
185
"""
187
186
A reader for a dvi ("device-independent") file, as produced by TeX.
188
- The current implementation can only iterate through pages in order,
189
- and does not even attempt to verify the postamble.
187
+ The current implementation can only iterate through pages in order.
190
188
191
189
This class can be used as a context manager to close the underlying
192
190
file upon exit. Pages can be read via iteration. Here is an overly
@@ -195,33 +193,55 @@ class Dvi(object):
195
193
>>> with matplotlib.dviread.Dvi('input.dvi', 72) as dvi:
196
194
>>> for page in dvi:
197
195
>>> print ''.join(unichr(t.glyph) for t in page.text)
196
+
197
+ Parameters
198
+ ----------
199
+
200
+ filename : str
201
+ dvi file to read
202
+ dpi : number or None
203
+ Dots per inch, can be floating-point; this affects the
204
+ coordinates returned. Use None to get TeX's internal units
205
+ which are likely only useful for debugging.
206
+ cache : _tex_support_cache instance, optional
207
+ Support file cache instance, defaults to the _tex_support_cache
208
+ singleton.
198
209
"""
199
210
# dispatch table
200
211
_dtable = [None for _ in xrange (256 )]
201
212
dispatch = partial (_dispatch , _dtable )
202
213
203
- def __init__ (self , filename , dpi ):
214
+ def __init__ (self , filename , dpi , cache = None ):
204
215
"""
205
216
Read the data from the file named *filename* and convert
206
217
TeX's internal units to units of *dpi* per inch.
207
218
*dpi* only sets the units and does not limit the resolution.
208
219
Use None to return TeX's internal units.
209
220
"""
210
221
_log .debug ('Dvi: %s' , filename )
222
+ if cache is None :
223
+ cache = _tex_support_cache .get_cache ()
224
+ self .cache = cache
211
225
self .file = open (filename , 'rb' )
212
226
self .dpi = dpi
213
227
self .fonts = {}
214
228
self .state = _dvistate .pre
215
229
self .baseline = self ._get_baseline (filename )
230
+ self .fontnames = sorted (set (self ._read_fonts ()))
231
+ # populate kpsewhich cache with font pathnames
232
+ find_tex_files ([x + suffix for x in self .fontnames
233
+ for suffix in ('.tfm' , '.vf' , '.pfb' )],
234
+ cache )
235
+ cache .optimize ()
216
236
217
237
def _get_baseline (self , filename ):
218
238
if rcParams ['text.latex.preview' ]:
219
239
base , ext = os .path .splitext (filename )
220
240
baseline_filename = base + ".baseline"
221
241
if os .path .exists (baseline_filename ):
222
242
with open (baseline_filename , 'rb' ) as fd :
223
- l = fd .read ().split ()
224
- height , depth , width = l
243
+ line = fd .read ().split ()
244
+ height , depth , width = line
225
245
return float (depth )
226
246
return None
227
247
@@ -308,6 +328,62 @@ def _output(self):
308
328
return Page (text = text , boxes = boxes , width = (maxx - minx )* d ,
309
329
height = (maxy_pure - miny )* d , descent = descent )
310
330
331
+ def _read_fonts (self ):
332
+ """Read the postamble of the file and return a list of fonts used."""
333
+
334
+ file = self .file
335
+ offset = - 1
336
+ while offset > - 100 :
337
+ file .seek (offset , 2 )
338
+ byte = ord (file .read (1 )[0 ])
339
+ if byte != 223 :
340
+ break
341
+ offset -= 1
342
+ if offset >= - 4 :
343
+ raise ValueError (
344
+ "malformed dvi file %s: too few 223 bytes" % file .name )
345
+ if byte != 2 :
346
+ raise ValueError (
347
+ ("malformed dvi file %s: post-postamble "
348
+ "identification byte not 2" ) % file .name )
349
+ file .seek (offset - 4 , 2 )
350
+ offset = struct .unpack ('!I' , file .read (4 ))[0 ]
351
+ file .seek (offset , 0 )
352
+ try :
353
+ byte = ord (file .read (1 )[0 ])
354
+ except TypeError :
355
+ # "ord() expected a character, but string of length 0 found"
356
+ raise ValueError (
357
+ "malformed dvi file %s: postamble offset %d out of range"
358
+ % (file .name , offset ))
359
+ if byte != 248 :
360
+ raise ValueError (
361
+ "malformed dvi file %s: postamble not found at offset %d"
362
+ % (file .name , offset ))
363
+
364
+ fonts = []
365
+ file .seek (28 , 1 )
366
+ while True :
367
+ byte = ord (file .read (1 )[0 ])
368
+ if 243 <= byte <= 246 :
369
+ _ , _ , _ , _ , a , length = (
370
+ _arg_olen1 (self , byte - 243 ),
371
+ _arg (4 , False , self , None ),
372
+ _arg (4 , False , self , None ),
373
+ _arg (4 , False , self , None ),
374
+ _arg (1 , False , self , None ),
375
+ _arg (1 , False , self , None ))
376
+ fontname = file .read (a + length )[- length :].decode ('ascii' )
377
+ fonts .append (fontname )
378
+ elif byte == 249 :
379
+ break
380
+ else :
381
+ raise ValueError (
382
+ "malformed dvi file %s: opcode %d in postamble"
383
+ % (file .name , byte ))
384
+ file .seek (0 , 0 )
385
+ return fonts
386
+
311
387
def _read (self ):
312
388
"""
313
389
Read one page from the file. Return True if successful,
@@ -616,6 +692,10 @@ class Vf(Dvi):
616
692
----------
617
693
618
694
filename : string or bytestring
695
+ vf file to read
696
+ cache : _tex_support_cache instance, optional
697
+ Support file cache instance, defaults to the _tex_support_cache
698
+ singleton.
619
699
620
700
Notes
621
701
-----
@@ -626,8 +706,8 @@ class Vf(Dvi):
626
706
but replaces the `_read` loop and dispatch mechanism.
627
707
"""
628
708
629
- def __init__ (self , filename ):
630
- Dvi .__init__ (self , filename , 0 )
709
+ def __init__ (self , filename , cache = None ):
710
+ Dvi .__init__ (self , filename , dpi = 0 , cache = cache )
631
711
try :
632
712
self ._first_font = None
633
713
self ._chars = {}
@@ -638,6 +718,27 @@ def __init__(self, filename):
638
718
def __getitem__ (self , code ):
639
719
return self ._chars [code ]
640
720
721
+ def _read_fonts (self ):
722
+ """Read through the font-definition section of the vf file
723
+ and return the list of font names."""
724
+ fonts = []
725
+ self .file .seek (0 , 0 )
726
+ while True :
727
+ byte = ord (self .file .read (1 )[0 ])
728
+ if byte <= 242 or byte >= 248 :
729
+ break
730
+ elif 243 <= byte <= 246 :
731
+ _ = self ._arg (byte - 242 )
732
+ _ , _ , _ , a , length = [self ._arg (x ) for x in (4 , 4 , 4 , 1 , 1 )]
733
+ fontname = self .file .read (a + length )[- length :].decode ('ascii' )
734
+ fonts .append (fontname )
735
+ elif byte == 247 :
736
+ _ , k = self ._arg (1 ), self ._arg (1 )
737
+ _ = self .file .read (k )
738
+ _ , _ = self ._arg (4 ), self ._arg (4 )
739
+ self .file .seek (0 , 0 )
740
+ return fonts
741
+
641
742
def _read (self ):
642
743
"""
643
744
Read one page from the file. Return True if successful,
@@ -674,8 +775,8 @@ def _read(self):
674
775
self ._init_packet (packet_len )
675
776
elif 243 <= byte <= 246 :
676
777
k = self ._arg (byte - 242 , byte == 246 )
677
- c , s , d , a , l = [self ._arg (x ) for x in (4 , 4 , 4 , 1 , 1 )]
678
- self ._fnt_def_real (k , c , s , d , a , l )
778
+ c , s , d , a , length = [self ._arg (x ) for x in (4 , 4 , 4 , 1 , 1 )]
779
+ self ._fnt_def_real (k , c , s , d , a , length )
679
780
if self ._first_font is None :
680
781
self ._first_font = k
681
782
elif byte == 247 : # preamble
0 commit comments