From: <jo...@us...> - 2007-09-02 18:49:30
|
Revision: 3767 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3767&view=rev Author: jouni Date: 2007-09-02 11:49:20 -0700 (Sun, 02 Sep 2007) Log Message: ----------- Some refactoring of dviread, plus a reader for psfonts.map format files Modified Paths: -------------- trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py trunk/matplotlib/lib/matplotlib/dviread.py Modified: trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py =================================================================== --- trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py 2007-08-31 19:35:09 UTC (rev 3766) +++ trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py 2007-09-02 18:49:20 UTC (rev 3767) @@ -1288,9 +1288,8 @@ texmanager = self.get_texmanager() fontsize = prop.get_size_in_points() dvifile = texmanager.make_dvi(s, fontsize) - dvi = Dvi(dvifile) - dvi.read() - text, boxes = dvi.output(72) + dvi = Dvi(dvifile, 72) + text, boxes = iter(dvi).next() fontdir = os.path.join(get_data_path(), 'fonts', 'ttf') if angle == 0: # avoid rounding errors in common case Modified: trunk/matplotlib/lib/matplotlib/dviread.py =================================================================== --- trunk/matplotlib/lib/matplotlib/dviread.py 2007-08-31 19:35:09 UTC (rev 3766) +++ trunk/matplotlib/lib/matplotlib/dviread.py 2007-09-02 18:49:20 UTC (rev 3767) @@ -1,40 +1,78 @@ """ -An experimental module for reading single-page dvi files output by -TeX. Several limitations make this not (currently) useful as a -general-purpose dvi preprocessor. The idea is that the file has a -single page with only a single formula or other piece of text. +An experimental module for reading dvi files output by TeX. Several +limitations make this not (currently) useful as a general-purpose dvi +preprocessor. Interface: - dvi = Dvi(filename) - dvi.read() - text, boxes = dvi.output(72) - for x,y,font,glyph in text: - fontname, pointsize = dvi.fontinfo(font) - ... - for x,y,height,width in boxes: - ... + dvi = Dvi(filename, 72) + for text, boxes in dvi: # iterate over pages + text, boxes = dvi.output(72) + for x,y,font,glyph in text: + fontname, pointsize = dvi.fontinfo(font) + ... + for x,y,height,width in boxes: + ... """ -from matplotlib.cbook import Bunch +import matplotlib +import matplotlib.cbook as mpl_cbook import os import struct -dvistate = Bunch(pre=0, outer=1, inpage=2, post_post=3, finale=4) +_dvistate = mpl_cbook.Bunch(pre=0, outer=1, inpage=2, post_post=3, finale=4) class Dvi(object): + """ + A dvi ("device-independent") file, as produced by TeX. + The current implementation only reads the first page and does not + even attempt to verify the postamble. + """ - def __init__(self, filename): - self.filename = filename - self.text = [] # list of (x,y,fontnum,glyphnum) - self.boxes = [] # list of (x,y,width,height) + def __init__(self, filename, dpi): + """ + Initialize the object. This takes the filename as input and + opens the file; actually reading the file happens when + iterating through the pages of the file. + """ + self.file = open(filename, 'rb') + self.dpi = dpi self.fonts = {} + self.state = _dvistate.pre - def output(self, dpi): - """Return lists of text and box objects transformed into a standard - Cartesian coordinate system at the given dpi value. The coordinates - are floating point numbers, but otherwise precision is not lost and - coordinate values are not clipped to integers.""" + def __iter__(self): + """ + Iterate through the pages of the file. + + Returns (text, pages) pairs, where: + text is a list of (x, y, fontnum, glyphnum) tuples + boxes is a list of (x, y, height, width) tuples + + The coordinates are transformed into a standard Cartesian + coordinate system at the dpi value given when initializing. + The coordinates are floating point numbers, but otherwise + precision is not lost and coordinate values are not clipped to + integers. + """ + while True: + have_page = self._read() + if have_page: + yield self.text, self.boxes + else: + break + + def close(self): + """ + Close the underlying file if it is open. + """ + if not self.file.closed: + self.file.close() + + def _output(self): + """ + Output the text and boxes belonging to the most recent page. + text, boxes = dvi._output() + """ t0 = self.text[0] minx, miny, maxx, maxy = t0[0], t0[1], t0[0], t0[1] for x,y,_,_ in self.text + self.boxes: @@ -42,31 +80,43 @@ if y < miny: miny = y if x > maxx: maxx = x if y > maxy: maxy = y - d = dpi / (72.27 * 2**16) # from TeX's "scaled points" to dpi units + d = self.dpi / (72.27 * 2**16) # from TeX's "scaled points" to dpi units text = [ ((x-minx)*d, (maxy-y)*d, f, g) for (x,y,f,g) in self.text ] boxes = [ ((x-minx)*d, (maxy-y)*d, h*d, w*d) for (x,y,h,w) in self.boxes ] return text, boxes def fontinfo(self, f): - """Name and size in (Adobe) points.""" + """ + texname, pointsize = dvi.fontinfo(fontnum) + + Name and size in points (Adobe points, not TeX points). + """ return self.fonts[f].name, self.fonts[f].scale * (72.0 / (72.27 * 2**16)) - def read(self, debug=False): - self.file = open(self.filename, 'rb') - try: - self.state = dvistate.pre - while True: - byte = ord(self.file.read(1)) - if byte == '': - break # eof - self.dispatch(byte) - if debug and self.state == dvistate.inpage: - print self.h, self.v - if byte == 140: break # end of page; we only read a single page for now - finally: - self.file.close() + def _read(self): + """ + Read one page from the file. Return True if successful, + False if there were no more pages. + """ + while True: + byte = ord(self.file.read(1)) + self._dispatch(byte) + if self.state == _dvistate.inpage: + matplotlib.verbose.report( + 'Dvi._read: after %d at %f,%f' % + (byte, self.h, self.v), + 'debug-annoying') + if byte == 140: # end of page + return True + if self.state == _dvistate.post_post: # end of file + self.close() + return False - def arg(self, nbytes, signed=False): + def _arg(self, nbytes, signed=False): + """ + Read and return an integer argument "nbytes" long. + Signedness is determined by the "signed" keyword. + """ str = self.file.read(nbytes) value = ord(str[0]) if signed and value >= 0x80: @@ -75,76 +125,81 @@ value = 0x100*value + ord(str[i]) return value - def dispatch(self, byte): - if 0 <= byte <= 127: self.set_char(byte) - elif byte == 128: self.set_char(self.arg(1)) - elif byte == 129: self.set_char(self.arg(2)) - elif byte == 130: self.set_char(self.arg(3)) - elif byte == 131: self.set_char(self.arg(4, True)) - elif byte == 132: self.set_rule(self.arg(4, True), self.arg(4, True)) - elif byte == 133: self.put_char(self.arg(1)) - elif byte == 134: self.put_char(self.arg(2)) - elif byte == 135: self.put_char(self.arg(3)) - elif byte == 136: self.put_char(self.arg(4, True)) - elif byte == 137: self.put_rule(self.arg(4, True), self.arg(4, True)) - elif byte == 138: self.nop() - elif byte == 139: self.bop(*[self.arg(4, True) for i in range(11)]) - elif byte == 140: self.eop() - elif byte == 141: self.push() - elif byte == 142: self.pop() - elif byte == 143: self.right(self.arg(1, True)) - elif byte == 144: self.right(self.arg(2, True)) - elif byte == 145: self.right(self.arg(3, True)) - elif byte == 146: self.right(self.arg(4, True)) - elif byte == 147: self.right_w(None) - elif byte == 148: self.right_w(self.arg(1, True)) - elif byte == 149: self.right_w(self.arg(2, True)) - elif byte == 150: self.right_w(self.arg(3, True)) - elif byte == 151: self.right_w(self.arg(4, True)) - elif byte == 152: self.right_x(None) - elif byte == 153: self.right_x(self.arg(1, True)) - elif byte == 154: self.right_x(self.arg(2, True)) - elif byte == 155: self.right_x(self.arg(3, True)) - elif byte == 156: self.right_x(self.arg(4, True)) - elif byte == 157: self.down(self.arg(1, True)) - elif byte == 158: self.down(self.arg(2, True)) - elif byte == 159: self.down(self.arg(3, True)) - elif byte == 160: self.down(self.arg(4, True)) - elif byte == 161: self.down_y(None) - elif byte == 162: self.down_y(self.arg(1, True)) - elif byte == 163: self.down_y(self.arg(2, True)) - elif byte == 164: self.down_y(self.arg(3, True)) - elif byte == 165: self.down_y(self.arg(4, True)) - elif byte == 166: self.down_z(None) - elif byte == 167: self.down_z(self.arg(1, True)) - elif byte == 168: self.down_z(self.arg(2, True)) - elif byte == 169: self.down_z(self.arg(3, True)) - elif byte == 170: self.down_z(self.arg(4, True)) - elif 171 <= byte <= 234: self.fnt_num(byte-171) - elif byte == 235: self.fnt_num(self.arg(1)) - elif byte == 236: self.fnt_num(self.arg(2)) - elif byte == 237: self.fnt_num(self.arg(3)) - elif byte == 238: self.fnt_num(self.arg(4, True)) + def _dispatch(self, byte): + """ + Based on the opcode "byte", read the correct kinds of + arguments from the dvi file and call the method implementing + that opcode with those arguments. + """ + if 0 <= byte <= 127: self._set_char(byte) + elif byte == 128: self._set_char(self._arg(1)) + elif byte == 129: self._set_char(self._arg(2)) + elif byte == 130: self._set_char(self._arg(3)) + elif byte == 131: self._set_char(self._arg(4, True)) + elif byte == 132: self._set_rule(self._arg(4, True), self._arg(4, True)) + elif byte == 133: self._put_char(self._arg(1)) + elif byte == 134: self._put_char(self._arg(2)) + elif byte == 135: self._put_char(self._arg(3)) + elif byte == 136: self._put_char(self._arg(4, True)) + elif byte == 137: self._put_rule(self._arg(4, True), self._arg(4, True)) + elif byte == 138: self._nop() + elif byte == 139: self._bop(*[self._arg(4, True) for i in range(11)]) + elif byte == 140: self._eop() + elif byte == 141: self._push() + elif byte == 142: self._pop() + elif byte == 143: self._right(self._arg(1, True)) + elif byte == 144: self._right(self._arg(2, True)) + elif byte == 145: self._right(self._arg(3, True)) + elif byte == 146: self._right(self._arg(4, True)) + elif byte == 147: self._right_w(None) + elif byte == 148: self._right_w(self._arg(1, True)) + elif byte == 149: self._right_w(self._arg(2, True)) + elif byte == 150: self._right_w(self._arg(3, True)) + elif byte == 151: self._right_w(self._arg(4, True)) + elif byte == 152: self._right_x(None) + elif byte == 153: self._right_x(self._arg(1, True)) + elif byte == 154: self._right_x(self._arg(2, True)) + elif byte == 155: self._right_x(self._arg(3, True)) + elif byte == 156: self._right_x(self._arg(4, True)) + elif byte == 157: self._down(self._arg(1, True)) + elif byte == 158: self._down(self._arg(2, True)) + elif byte == 159: self._down(self._arg(3, True)) + elif byte == 160: self._down(self._arg(4, True)) + elif byte == 161: self._down_y(None) + elif byte == 162: self._down_y(self._arg(1, True)) + elif byte == 163: self._down_y(self._arg(2, True)) + elif byte == 164: self._down_y(self._arg(3, True)) + elif byte == 165: self._down_y(self._arg(4, True)) + elif byte == 166: self._down_z(None) + elif byte == 167: self._down_z(self._arg(1, True)) + elif byte == 168: self._down_z(self._arg(2, True)) + elif byte == 169: self._down_z(self._arg(3, True)) + elif byte == 170: self._down_z(self._arg(4, True)) + elif 171 <= byte <= 234: self._fnt_num(byte-171) + elif byte == 235: self._fnt_num(self._arg(1)) + elif byte == 236: self._fnt_num(self._arg(2)) + elif byte == 237: self._fnt_num(self._arg(3)) + elif byte == 238: self._fnt_num(self._arg(4, True)) elif 239 <= byte <= 242: - len = self.arg(byte-238) + len = self._arg(byte-238) special = self.file.read(len) - self.xxx(special) + self._xxx(special) elif 243 <= byte <= 246: - k = self.arg(byte-242, byte==246) - c, s, d, a, l = [ self.arg(x) for x in (4, 4, 4, 1, 1) ] + k = self._arg(byte-242, byte==246) + c, s, d, a, l = [ self._arg(x) for x in (4, 4, 4, 1, 1) ] n = self.file.read(a+l) - self.fnt_def(k, c, s, d, a, l, n) + self._fnt_def(k, c, s, d, a, l, n) elif byte == 247: - i, num, den, mag, k = [ self.arg(x) for x in (1, 4, 4, 4, 1) ] + i, num, den, mag, k = [ self._arg(x) for x in (1, 4, 4, 4, 1) ] x = self.file.read(k) - self.pre(i, num, den, mag, x) - elif byte == 248: self.post() - elif byte == 249: self.post_post() + self._pre(i, num, den, mag, x) + elif byte == 248: self._post() + elif byte == 249: self._post_post() else: raise ValueError, "unknown command: byte %d"%byte - def pre(self, i, num, den, mag, comment): - if self.state != dvistate.pre: + def _pre(self, i, num, den, mag, comment): + if self.state != _dvistate.pre: raise ValueError, "pre command in middle of dvi file" if i != 2: raise ValueError, "Unknown dvi format %d"%i @@ -159,111 +214,116 @@ raise ValueError, "nonstandard magnification in dvi file" # meaning: LaTeX seems to frown on setting \mag, so # I think we can assume this is constant - self.state = dvistate.outer + self.state = _dvistate.outer - def set_char(self, char): - if self.state != dvistate.inpage: + def _set_char(self, char): + if self.state != _dvistate.inpage: raise ValueError, "misplaced set_char in dvi file" - self.put_char(char) + self._put_char(char) font = self.fonts[self.f] width = font.tfm.width[char] width = (width * font.scale) >> 20 self.h += width - def set_rule(self, a, b): - if self.state != dvistate.inpage: + def _set_rule(self, a, b): + if self.state != _dvistate.inpage: raise ValueError, "misplaced set_rule in dvi file" - self.put_rule(a, b) + self._put_rule(a, b) self.h += b - def put_char(self, char): - if self.state != dvistate.inpage: + def _put_char(self, char): + if self.state != _dvistate.inpage: raise ValueError, "misplaced put_char in dvi file" self.text.append((self.h, self.v, self.f, char)) - def put_rule(self, a, b): - if self.state != dvistate.inpage: + def _put_rule(self, a, b): + if self.state != _dvistate.inpage: raise ValueError, "misplaced put_rule in dvi file" if a > 0 and b > 0: self.boxes.append((self.h, self.v, a, b)) - def nop(self): + def _nop(self): pass - def bop(self, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, p): - if self.state != dvistate.outer: + def _bop(self, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, p): + if self.state != _dvistate.outer: + print '+++', self.state raise ValueError, "misplaced bop in dvi file" - self.state = dvistate.inpage + self.state = _dvistate.inpage self.h, self.v, self.w, self.x, self.y, self.z = 0, 0, 0, 0, 0, 0 self.stack = [] + self.text = [] # list of (x,y,fontnum,glyphnum) + self.boxes = [] # list of (x,y,width,height) - def eop(self): - if self.state != dvistate.inpage: + def _eop(self): + if self.state != _dvistate.inpage: raise ValueError, "misplaced eop in dvi file" - self.state = dvistate.outer + self.state = _dvistate.outer del self.h, self.v, self.w, self.x, self.y, self.z, self.stack - def push(self): - if self.state != dvistate.inpage: + def _push(self): + if self.state != _dvistate.inpage: raise ValueError, "misplaced push in dvi file" self.stack.append((self.h, self.v, self.w, self.x, self.y, self.z)) - def pop(self): - if self.state != dvistate.inpage: + def _pop(self): + if self.state != _dvistate.inpage: raise ValueError, "misplaced pop in dvi file" self.h, self.v, self.w, self.x, self.y, self.z = self.stack.pop() - def right(self, b): - if self.state != dvistate.inpage: + def _right(self, b): + if self.state != _dvistate.inpage: raise ValueError, "misplaced right in dvi file" self.h += b - def right_w(self, new_w): - if self.state != dvistate.inpage: + def _right_w(self, new_w): + if self.state != _dvistate.inpage: raise ValueError, "misplaced w in dvi file" if new_w is not None: self.w = new_w self.h += self.w - def right_x(self, new_x): - if self.state != dvistate.inpage: + def _right_x(self, new_x): + if self.state != _dvistate.inpage: raise ValueError, "misplaced x in dvi file" if new_x is not None: self.x = new_x self.h += self.x - def down(self, a): - if self.state != dvistate.inpage: + def _down(self, a): + if self.state != _dvistate.inpage: raise ValueError, "misplaced down in dvi file" self.v += a - def down_y(self, new_y): - if self.state != dvistate.inpage: + def _down_y(self, new_y): + if self.state != _dvistate.inpage: raise ValueError, "misplaced y in dvi file" if new_y is not None: self.y = new_y self.v += self.y - def down_z(self, new_z): - if self.state != dvistate.inpage: + def _down_z(self, new_z): + if self.state != _dvistate.inpage: raise ValueError, "misplaced z in dvi file" if new_z is not None: self.z = new_z self.v += self.z - def fnt_num(self, k): - if self.state != dvistate.inpage: + def _fnt_num(self, k): + if self.state != _dvistate.inpage: raise ValueError, "misplaced fnt_num in dvi file" self.f = k - def xxx(self, special): - pass + def _xxx(self, special): + matplotlib.verbose.report( + 'Dvi._xxx: encountered special: %s' + % ''.join((32 <= ord(ch) < 127) and ch + or '<%02x>' % ord(ch) + for ch in special), + 'debug') - def fnt_def(self, k, c, s, d, a, l, n): - filename = n[-l:] + '.tfm' - pipe = os.popen('kpsewhich ' + filename, 'r') - filename = pipe.readline().rstrip() - pipe.close() + def _fnt_def(self, k, c, s, d, a, l, n): + filename = find_tex_file(n[-l:] + '.tfm') tfm = Tfm(filename) if c != 0 and tfm.checksum != 0 and c != tfm.checksum: raise ValueError, 'tfm checksum mismatch: %s'%n @@ -271,41 +331,186 @@ #if d != tfm.design_size: # raise ValueError, 'tfm design size mismatch: %d in dvi, %d in %s'%\ # (d, tfm.design_size, n) - self.fonts[k] = Bunch(scale=s, tfm=tfm, name=n) + self.fonts[k] = mpl_cbook.Bunch(scale=s, tfm=tfm, name=n) - def post(self): - raise NotImplementedError + def _post(self): + if self.state != _dvistate.outer: + raise ValueError, "misplaced post in dvi file" + self.state = _dvistate.post_post + # TODO: actually read the postamble and finale? + # currently post_post just triggers closing the file - def post_post(self): + def _post_post(self): raise NotImplementedError class Tfm(object): + """ + A TeX Font Metric file. This implementation covers only the bare + minimum needed by the Dvi class. + Attributes: + checksum: for verifying against dvi file + design_size: design size of the font (in what units?) + width[i]: width of character #i, needs to be scaled + by the factor specified in the dvi file + (this is a dict because indexing may not start from 0) + """ + def __init__(self, filename): file = open(filename, 'rb') - header1 = file.read(24) - lh, bc, ec, nw = \ - struct.unpack('!4H', header1[2:10]) - header2 = file.read(4*lh) - self.checksum, self.design_size = \ - struct.unpack('!2I', header2[:8]) - # plus encoding information etc. + try: + header1 = file.read(24) + lh, bc, ec, nw = \ + struct.unpack('!4H', header1[2:10]) + header2 = file.read(4*lh) + self.checksum, self.design_size = \ + struct.unpack('!2I', header2[:8]) + # there is also encoding information etc. + char_info = file.read(4*(ec-bc+1)) + widths = file.read(4*nw) + finally: + file.close() - char_info = file.read(4*(ec-bc+1)) - widths = file.read(4*nw) - - file.close() - widths = struct.unpack('!%dI' % nw, widths) self.width = {} for i in range(ec-bc): self.width[bc+i] = widths[ord(char_info[4*i])] +class PsfontsMap(object): + """ + A psfonts.map formatted file, mapping TeX fonts to PS fonts. + Usage: map = PsfontsMap('.../psfonts.map'); map['cmr10'] + + For historical reasons, TeX knows many Type-1 fonts by different + names than the outside world. (For one thing, the names have to + fit in eight characters.) Also, TeX's native fonts are not Type-1 + but Metafont, which is nontrivial to convert to PostScript except + as a bitmap. While high-quality conversions to Type-1 format exist + and are shipped with modern TeX distributions, we need to know + which Type-1 fonts are the counterparts of which native fonts. For + these reasons a mapping is needed from internal font names to font + file names. + + A texmf tree typically includes mapping files called e.g. + psfonts.map, pdftex.map, dvipdfm.map. psfonts.map is used by + dvips, pdftex.map by pdfTeX, and dvipdfm.map by dvipdfm. + psfonts.map might avoid embedding the 35 PostScript fonts, while + the pdf-related files perhaps only avoid the "Base 14" pdf fonts. + But the user may have configured these files differently. + """ + + def __init__(self, filename): + self._font = {} + file = open(filename, 'rt') + try: + self._parse(file) + finally: + file.close() + + def __getitem__(self, texname): + result = self._font[texname] + if result.filename is not None \ + and not result.filename.startswith('/'): + result.filename = find_tex_file(result.filename) + if result.encoding is not None \ + and not result.encoding.startswith('/'): + result.encoding = find_tex_file(result.encoding) + return result + + def _parse(self, file): + """Parse each line into words.""" + for line in file: + line = line.strip() + if line == '' or line.startswith('%'): + continue + words, pos = [], 0 + while pos < len(line): + if line[pos] == '"': # double quoted word + pos += 1 + end = line.index('"', pos) + words.append(line[pos:end]) + pos = end + 1 + else: # ordinary word + end = line.find(' ', pos+1) + if end == -1: end = len(line) + words.append(line[pos:end]) + pos = end + while pos < len(line) and line[pos] == ' ': + pos += 1 + self._register(words) + + def _register(self, words): + """Register a font described by "words". + + The format is, AFAIK: texname fontname [effects and filenames] + Effects are PostScript snippets like ".177 SlantFont", + filenames begin with one or two less-than signs. A filename + ending in enc is an encoding file, other filenames are font + files. This can be overridden with a left bracket: <[foobar + indicates an encoding file named foobar. + + There is some difference between <foo.pfb and <<bar.pfb in + subsetting, but I have no example of << in my TeX installation. + """ + texname, psname = words[:2] + effects, encoding, filename = [], None, None + for word in words[2:]: + if not word.startswith('<'): + effects.append(word) + else: + word = word.lstrip('<') + if word.startswith('['): + assert encoding is None + encoding = word[1:] + elif word.endswith('.enc'): + assert encoding is None + encoding = word + else: + assert filename is None + filename = word + self._font[texname] = mpl_cbook.Bunch( + texname=texname, psname=psname, effects=effects, + encoding=encoding, filename=filename) + +def find_tex_file(filename, format=None): + """ + Call kpsewhich to find a file in the texmf tree. + If format is not None, it is used as the value for the --format option. + See the kpathsea documentation for more information. + + Apparently most existing TeX distributions on Unix-like systems + use kpathsea. I hear MikTeX (a popular distribution on Windows) + doesn't use kpathsea, so what do we do? (TODO) + """ + + cmd = 'kpsewhich ' + if format is not None: + assert "'" not in format + cmd += "--format='" + format + "' " + assert "'" not in filename + cmd += "'" + filename + "'" + + pipe = os.popen(cmd, 'r') + result = pipe.readline().rstrip() + pipe.close() + + return result + if __name__ == '__main__': - dvi = Dvi('foo.dvi') - dvi.read(debug=True) - for x,y,f,c in dvi.text: - print x,y,c,chr(c),dvi.fonts[f].__dict__ - print dvi.output(72) + matplotlib.verbose.set_level('debug') + dvi = Dvi('foo.dvi', 72) + fontmap = PsfontsMap(find_tex_file('pdftex.map')) + for text,boxes in dvi: + print '=== new page ===' + fPrev = None + for x,y,f,c in text: + texname = dvi.fonts[f].name + print x,y,c,chr(c),texname + if f != fPrev: + print 'font', texname, '=', fontmap[texname].__dict__ + fPrev = f + for x,y,w,h in boxes: + print x,y,'BOX',w,h + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |