1#!/usr/local/bin/python3.8 2# -*- coding: utf_8 -*- 3# 4# This is DVIasm, a DVI utility for editing DVI files directly. 5# 6# Copyright (C) 2007-2008 by Jin-Hwan Cho <chofchof@ktug.or.kr> 7# Copyright (C) 2011-2015 by Khaled Hosny <khaledhosny@eglug.org> 8# 9# This program is free software: you can redistribute it and/or modify 10# it under the terms of the GNU General Public License as published by 11# the Free Software Foundation, either version 3 of the License, or 12# (at your option) any later version. 13# 14# This program is distributed in the hope that it will be useful, 15# but WITHOUT ANY WARRANTY; without even the implied warranty of 16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17# GNU General Public License for more details. 18# 19# You should have received a copy of the GNU General Public License 20# along with this program. If not, see <http://www.gnu.org/licenses/>. 21 22import sys, os.path 23from optparse import OptionParser 24 25# Global variables 26is_ptex = False 27is_subfont = False 28cur_font = None 29cur_dsize = 0 30cur_ssize = 0 31subfont_idx = 0 32subfont_list = ['cyberb', 'outbtm', 'outbtb', 'outgtm', 'outgtb'] 33 34# DVI opcodes 35SET_CHAR_0 = 0; SET_CHAR_127 = 127; 36SET1 = 128; SET2 = 129; SET3 = 130; SET4 = 131; 37SET_RULE = 132; 38PUT1 = 133; PUT2 = 134; PUT3 = 135; PUT4 = 136; 39PUT_RULE = 137; 40NOP = 138; 41BOP = 139; EOP = 140; 42PUSH = 141; POP = 142; 43RIGHT1 = 143; RIGHT2 = 144; RIGHT3 = 145; RIGHT4 = 146; 44W0 = 147; W1 = 148; W2 = 149; W3 = 150; W4 = 151; 45X0 = 152; X1 = 153; X2 = 154; X3 = 155; X4 = 156; 46DOWN1 = 157; DOWN2 = 158; DOWN3 = 159; DOWN4 = 160; 47Y0 = 161; Y1 = 162; Y2 = 163; Y3 = 164; Y4 = 165; 48Z0 = 166; Z1 = 167; Z2 = 168; Z3 = 169; Z4 = 170; 49FNT_NUM_0 = 171; FNT_NUM_63 = 234; 50FNT1 = 235; FNT2 = 236; FNT3 = 237; FNT4 = 238; 51XXX1 = 239; XXX2 = 240; XXX3 = 241; XXX4 = 242; 52FNT_DEF1 = 243; FNT_DEF2 = 244; FNT_DEF3 = 245; FNT_DEF4 = 246; 53PRE = 247; POST = 248; POST_POST = 249; 54# DVIV opcodes 55DIR = 255; 56# XDV opcodes 57BEGIN_REFLECT = 250; END_REFLECT = 251; 58NATIVE_FONT_DEF = 252; 59GLYPHS = 253; 60# XDV flags 61XDV_FLAG_VERTICAL = 0x0100; 62XDV_FLAG_COLORED = 0x0200; 63XDV_FLAG_EXTEND = 0x1000; 64XDV_FLAG_SLANT = 0x2000; 65XDV_FLAG_EMBOLDEN = 0x4000; 66# DVI identifications 67DVI_ID = 2; DVIV_ID = 3; XDV_ID = 6; 68 69def warning(msg): 70 sys.stderr.write('%s\n' % msg) 71 72def BadDVI(msg): 73 raise AttributeError, 'Bad DVI file: %s!' % msg 74 75def GetByte(fp): # { returns the next byte, unsigned } 76 try: return ord(fp.read(1)) 77 except: return -1 78 79def SignedByte(fp): # { returns the next byte, signed } 80 try: b = ord(fp.read(1)) 81 except: return -1 82 if b < 128: return b 83 else: return b - 256 84 85def Get2Bytes(fp): # { returns the next two bytes, unsigned } 86 try: a, b = map(ord, fp.read(2)) 87 except: BadDVI('Failed to Get2Bytes()') 88 return (a << 8) + b 89 90def SignedPair(fp): # {returns the next two bytes, signed } 91 try: a, b = map(ord, fp.read(2)) 92 except: BadDVI('Failed to SignedPair()') 93 if a < 128: return (a << 8) + b 94 else: return ((a - 256) << 8) + b 95 96def Get3Bytes(fp): # { returns the next three bytes, unsigned } 97 try: a, b, c = map(ord, fp.read(3)) 98 except: BadDVI('Failed to Get3Bytes()') 99 return (((a << 8) + b) << 8) + c 100 101def SignedTrio(fp): # { returns the next three bytes, signed } 102 try: a, b, c = map(ord, fp.read(3)) 103 except: BadDVI('Failed to SignedTrio()') 104 if a < 128: return (((a << 8) + b) << 8) + c 105 else: return ((((a - 256) << 8) + b) << 8) + c 106 107def Get4Bytes(fp): # { returns the next four bytes, unsigned } 108 try: a, b, c, d = map(ord, fp.read(4)) 109 except: BadDVI('Failed to Get4Bytes()') 110 return (((((a << 8) + b) << 8) + c) << 8) + d 111 112def SignedQuad(fp): # { returns the next four bytes, signed } 113 try: a, b, c, d = map(ord, fp.read(4)) 114 except: BadDVI('Failed to get SignedQuad()') 115 if a < 128: return (((((a << 8) + b) << 8) + c) << 8) + d 116 else: return ((((((a - 256) << 8) + b) << 8) + c) << 8) + d 117 118def PutByte(q): 119 return chr(q & 0xff) 120 121def Put2Bytes(q): 122 return PutByte(q>>8) + PutByte(q) 123 124def Put3Bytes(q): 125 return PutByte(q>>16) + PutByte(q>>8) + PutByte(q) 126 127def PutSignedQuad(q): 128 if q < 0: q += 0x100000000 129 return PutByte(q>>24) + PutByte(q>>16) + PutByte(q>>8) + PutByte(q) 130 131def PutUnsigned(q): 132 if q >= 0x1000000: return (3, PutSignedQuad(q)) 133 if q >= 0x10000: return (2, Put3Bytes(q)) 134 if q >= 0x100: return (1, Put2Bytes(q)) 135 return (0, PutByte(q)) 136 137def PutSigned(q): 138 if 0 <= q < 0x800000: return PutUnsigned(q) 139 if q < -0x800000 or q >= 0x800000: return (3, PutSignedQuad(q)) 140 if q < -0x8000: q += 0x1000000; return (2, Put3Bytes(q)) 141 if q < -0x80: q += 0x10000; return (1, Put2Bytes(q)) 142 return (0, PutByte(q)) 143 144def PutGlyphs(width, glyphs): 145 s = [] 146 length = len(glyphs) 147 s.append(PutByte(GLYPHS)) 148 s.append(PutSignedQuad(width)) 149 s.append(Put2Bytes(length)) 150 for glyph in glyphs: 151 s.append(PutSignedQuad(glyph["x"])) 152 s.append(PutSignedQuad(glyph["y"])) 153 for glyph in glyphs: 154 s.append(Put2Bytes(glyph["id"])) 155 156 return ''.join(s) 157 158def GetInt(s): 159 try: return int(s) 160 except: return -1 161 162def GetStrASCII(s): # used in Parse() 163 if len(s) > 1 and ((s[0] == "'" and s[-1] == "'") or (s[0] == '"' and s[-1] == '"')): return [ord(c) for c in s[1:-1].decode('unicode_escape')] 164 else: return '' 165 166def UCS2toJIS(c): 167 s = c.encode('iso2022-jp') 168 if len(s) == 1: return ord(s) 169 else: return (ord(s[3]) << 8) + ord(s[4]) 170 171def GetStrUTF8(s): # used in Parse() 172 if len(s) > 1 and ((s[0] == "'" and s[-1] == "'") or (s[0] == '"' and s[-1] == '"')): 173 t = s[1:-1].decode('string_escape').decode('utf8') 174 if is_ptex: return [UCS2toJIS(c) for c in t] 175 else: return [ord(c) for c in t] 176 else: return '' 177 178def PutStrASCII(t): # unsed in Dump() 179 s = '' 180 for o in t: 181 if o == 92: s += '\\\\' 182 elif 32 <= o < 127: s += chr(o) 183 elif o < 256: s += ('\\x%02x' % o) 184 elif o < 65536: s += ('\\u%04x' % o) 185 else: 186 warning('Not support characters > 65535; may skip %d.\n' % o) 187 return "'%s'" % s 188 189def PutStrLatin1(t): # unsed in Dump() 190 s = '' 191 for o in t: 192 if o == 92: s += '\\\\' 193 elif 32 <= o < 127 or 161 <= o < 256: s += chr(o) 194 elif o < 256: s += ('\\x%02x' % o) 195 elif o < 65536: s += ('\\u%04x' % o) 196 else: 197 warning('Not support characters > 65535; may skip %d.\n' % o) 198 return "'%s'" % s 199 200def PutStrUTF8(t): # unsed in Dump() 201 s = '' 202 if is_subfont: 203 for o in t: 204 s += unichr((subfont_idx << 8) + o).encode('utf8') 205 else: # not the case of subfont 206 for o in t: 207 if o == 92: s += '\\\\' 208 elif 32 <= o < 127: s += chr(o) 209 elif o < 128: s += ('\\x%02x' % o) 210 elif is_ptex: 211 s += ''.join(['\x1b$B', chr(o/256), chr(o%256)]).decode('iso2022-jp').encode('utf8') 212 else: s += unichr(o).encode('utf8') 213 return "'%s'" % s 214 215def PutStrSJIS(t): # unsed in Dump() 216 s = '' 217 for o in t: 218 if o == 92: s += '\\\\' 219 elif 32 <= o < 127: s += chr(o) 220 elif o < 128: s += ('\\x%02x' % o) 221 else: 222 s += ''.join(['\x1b$B', chr(o/256), chr(o%256)]).decode('iso2022-jp').encode('sjis') 223 return "'%s'" % s 224 225def IsFontChanged(f, z): 226 global cur_font, cur_ssize, subfont_idx, is_subfont 227 for n in subfont_list: 228 if n == f[:-2]: 229 is_subfont = True 230 subfont_idx = int(f[-2:], 16) 231 if cur_font == n and cur_ssize == z: 232 return False 233 else: 234 cur_font = n; cur_ssize = z 235 return True 236 else: 237 is_subfont = False 238 cur_font = f; cur_ssize = z 239 return True 240 241############################################################ 242# DVI class 243############################################################ 244class DVI(object): 245 def __init__(self, unit='pt'): 246 if unit == 'sp': self.byconv = self.by_sp_conv 247 elif unit == 'bp': self.byconv = self.by_bp_conv 248 elif unit == 'mm': self.byconv = self.by_mm_conv 249 elif unit == 'cm': self.byconv = self.by_cm_conv 250 elif unit == 'in': self.byconv = self.by_in_conv 251 else: self.byconv = self.by_pt_conv 252 self.Initialize() 253 254 ########################################################## 255 # Initialize: Required by __init__(), Load(), and Parse() 256 ########################################################## 257 def Initialize(self): 258 self.id = DVI_ID 259 self.numerator = 25400000 260 self.denominator = 473628672 261 self.mag = 1000 262 self.ComputeConversionFactors() 263 self.comment = '' 264 self.font_def = {} 265 self.max_v = self.max_h = self.max_s = self.total_pages = 0 266 self.pages = [] 267 268 ########################################################## 269 # Load: DVI -> Internal Format 270 ########################################################## 271 def Load(self, fn): 272 fp = file(fn, 'rb') 273 self.LoadFromFile(fp) 274 fp.close() 275 276 def LoadFromFile(self, fp): 277 self.Initialize() 278 fp.seek(0, 2) 279 if fp.tell() < 53: BadDVI('less than 53 bytes long') 280 self.ProcessPreamble(fp) 281 self.ProcessPostamble(fp) 282 loc = self.first_backpointer 283 while loc >= 0: 284 fp.seek(loc) 285 if GetByte(fp) != BOP: BadDVI('byte %d is not bop' % fp.tell()) 286 cnt = [SignedQuad(fp) for i in xrange(10)] 287 loc = SignedQuad(fp) 288 page = self.ProcessPage(fp) 289 self.pages.insert(0, {'count':cnt, 'content':page}) 290 291 def ProcessPreamble(self, fp): 292 fp.seek(0) 293 if GetByte(fp) != PRE: BadDVI("First byte isn't start of preamble") 294 id = GetByte(fp) 295 if id != DVI_ID and id != DVIV_ID and id != XDV_ID: 296 warning("ID byte is %d; use the default %d!" % (id, DVI_ID)) 297 else: 298 self.id = id 299 numerator = SignedQuad(fp) 300 if numerator <= 0: 301 warning('numerator is %d; use the default 25400000!' % numerator) 302 else: 303 self.numerator = numerator 304 denominator = SignedQuad(fp) 305 if denominator <= 0: 306 warning('denominator is %d; use the default 473628672!' % denominator) 307 else: 308 self.denominator = denominator 309 mag = SignedQuad(fp) 310 if mag <= 0: 311 warning('magnification is %d; use the default 1000!' % mag) 312 else: 313 self.mag = mag 314 self.comment = fp.read(GetByte(fp)) 315 self.ComputeConversionFactors() 316 317 def ProcessPostamble(self, fp): 318 fp.seek(-5, 2) # at least four 223's 319 while True: 320 k = GetByte(fp) 321 if k < 0: BadDVI('all 223s; is it a DVI file?') # found EOF 322 elif k != 223: break 323 fp.seek(-2, 1) 324 if k != DVI_ID and k != DVIV_ID and k != XDV_ID: 325 warning('ID byte is %d' % k) 326 fp.seek(-5, 1) 327 q = SignedQuad(fp) 328 m = fp.tell() # id_byte 329 if q < 0 or q > m - 33: BadDVI('post pointer %d at byte %d' % (q, m - 4)) 330 fp.seek(q) # move to post 331 k = GetByte(fp) 332 if k != POST: BadDVI('byte %d is not post' % k) 333 self.post_loc = q 334 self.first_backpointer = SignedQuad(fp) 335 336 if SignedQuad(fp) != self.numerator: 337 warning("numerator doesn't match the preamble!") 338 if SignedQuad(fp) != self.denominator: 339 warning("denominator doesn't match the preamble!") 340 if SignedQuad(fp) != self.mag: 341 warning("magnification doesn't match the preamble!") 342 self.max_v = SignedQuad(fp) 343 self.max_h = SignedQuad(fp) 344 self.max_s = Get2Bytes(fp) 345 self.total_pages = Get2Bytes(fp) 346 while True: 347 k = GetByte(fp) 348 if k == FNT_DEF1: p = GetByte(fp) 349 elif k == FNT_DEF2: p = Get2Bytes(fp) 350 elif k == FNT_DEF3: p = Get3Bytes(fp) 351 elif k == FNT_DEF4: p = SignedQuad(fp) 352 elif k == NATIVE_FONT_DEF: p = SignedQuad(fp) 353 elif k != NOP: break 354 if k == NATIVE_FONT_DEF: self.DefineNativeFont(p, fp) 355 else: self.DefineFont(p, fp) 356 if k != POST_POST: 357 warning('byte %d is not postpost!' % (fp.tell() - 1)) 358 if SignedQuad(fp) != self.post_loc: 359 warning('bad postamble pointer in byte %d!' % (fp.tell() - 4)) 360 m = GetByte(fp) 361 if m != DVI_ID and m != DVIV_ID and m != XDV_ID: 362 warning('identification in byte %d should be %d, %d, or %d!' % (fp.tell() - 1, DVI_ID, DVIV_ID, XDV_ID)) 363 364 def DefineFont(self, e, fp): 365 c = SignedQuad(fp) # font_check_sum 366 q = SignedQuad(fp) # font_scaled_size 367 d = SignedQuad(fp) # font_design_size 368 n = fp.read(GetByte(fp) + GetByte(fp)) 369 try: 370 f = self.font_def[e] 371 except KeyError: 372 self.font_def[e] = {'name':n, 'checksum':c, 'scaled_size':q, 'design_size':d} 373 if q <= 0 or q >= 01000000000: 374 warning("%s---not loaded, bad scale (%d)!" % (n, q)) 375 elif d <= 0 or d >= 01000000000: 376 warning("%s---not loaded, bad design size (%d)!" % (n, d)) 377 else: 378 if f['checksum'] != c: 379 warning("\t---check sum doesn't match previous definition!") 380 if f['scaled_size'] != q: 381 warning("\t---scaled size doesn't match previous definition!") 382 if f['design_size'] != d: 383 warning("\t---design size doesn't match previous definition!") 384 if f['name'] != n: 385 warning("\t---font name doesn't match previous definition!") 386 387 def DefineNativeFont(self, e, fp): 388 size = Get4Bytes(fp) # scaled size 389 flags = Get2Bytes(fp) 390 l = GetByte(fp) # name length 391 fnt_name = fp.read(l) 392 index = Get4Bytes(fp) # face index 393 ext = [] 394 embolden = 0 395 if flags & XDV_FLAG_VERTICAL: ext.append("vertical") 396 if flags & XDV_FLAG_COLORED: ext.append("color=%08X" % Get4Bytes(fp)) 397 if flags & XDV_FLAG_EXTEND: ext.append("extend=%d" % SignedQuad(fp)) 398 if flags & XDV_FLAG_SLANT: ext.append("slant=%d" % SignedQuad(fp)) 399 if flags & XDV_FLAG_EMBOLDEN: ext.append("embolden=%d" % SignedQuad(fp)) 400 try: 401 f = self.font_def[e] 402 except KeyError: 403 if index > 0: 404 fnt_name += "[%d]" % index 405 name = '"%s"' % fnt_name 406 if ext: 407 name = '"%s:%s"' % (fnt_name, ";".join(ext)) 408 409 self.font_def[e] = { 410 'name': name, 411 'checksum': 0, 412 'scaled_size': size, 413 'design_size': 655360, # hardcoded 414 } 415 416 def ProcessPage(self, fp): 417 s = [] 418 while True: 419 o = GetByte(fp) 420 p = self.Get1Arg(o, fp) 421 if o < SET_CHAR_0 + 128 or o in (SET1, SET2, SET3, SET4): 422 q = [p] 423 while True: 424 o = GetByte(fp) 425 p = self.Get1Arg(o, fp) 426 if o < SET_CHAR_0 + 128 or o in (SET1, SET2, SET3, SET4): 427 q.append(p) 428 else: 429 break 430 s.append([SET1, q]) 431 if o == SET_RULE: 432 s.append([SET_RULE, [p, SignedQuad(fp)]]) 433 elif o in (PUT1, PUT2, PUT3, PUT4): 434 s.append([PUT1, p]) 435 elif o == PUT_RULE: 436 s.append([PUT_RULE, [p, SignedQuad(fp)]]) 437 elif o == NOP: 438 continue 439 elif o == BOP: 440 warning('bop occurred before eop!') 441 break 442 elif o == EOP: 443 break 444 elif o == PUSH: 445 s.append([PUSH]) 446 elif o == POP: 447 s.append([POP]) 448 elif o in (RIGHT1, RIGHT2, RIGHT3, RIGHT4): 449 s.append([RIGHT1, p]) 450 elif o == W0: 451 s.append([W0]) 452 elif o in (W1, W2, W3, W4): 453 s.append([W1, p]) 454 elif o == X0: 455 s.append([X0]) 456 elif o in (X1, X2, X3, X4): 457 s.append([X1, p]) 458 elif o in (DOWN1, DOWN2, DOWN3, DOWN4): 459 s.append([DOWN1, p]) 460 elif o == Y0: 461 s.append([Y0]) 462 elif o in (Y1, Y2, Y3, Y4): 463 s.append([Y1, p]) 464 elif o == Z0: 465 s.append([Z0]) 466 elif o in (Z1, Z2, Z3, Z4): 467 s.append([Z1, p]) 468 elif o < FNT_NUM_0 + 64 or o in (FNT1, FNT2, FNT3, FNT4): 469 s.append([FNT1, p]) 470 elif o in (XXX1, XXX2, XXX3, XXX4): 471 q = fp.read(p) 472 s.append([XXX1, q]) 473 elif o in (FNT_DEF1, FNT_DEF2, FNT_DEF3, FNT_DEF4): 474 self.DefineFont(p, fp) 475 elif o == NATIVE_FONT_DEF: 476 self.DefineNativeFont(p, fp) 477 elif o == GLYPHS: 478 s.append([GLYPHS, self.GetGlyphs(fp)]) 479 elif o == DIR: 480 s.append([DIR, p]) 481 elif o == BEGIN_REFLECT: 482 s.append([BEGIN_REFLECT]) 483 elif o == END_REFLECT: 484 s.append([END_REFLECT]) 485 elif o == PRE: 486 warning('preamble command within a page!') 487 break 488 elif o in (POST, POST_POST): 489 warning('postamble command %d!' % o) 490 break 491 else: 492 warning('undefined command %d!' % o) 493 break 494 return s 495 496 def Get1Arg(self, o, fp): 497 if o < SET_CHAR_0 + 128: 498 return o - SET_CHAR_0 499 if o in (SET1, PUT1, FNT1, XXX1, FNT_DEF1, DIR): 500 return GetByte(fp) 501 if o in (SET2, PUT2, FNT2, XXX2, FNT_DEF2): 502 return Get2Bytes(fp) 503 if o in (SET3, PUT3, FNT3, XXX3, FNT_DEF3): 504 return Get3Bytes(fp) 505 if o in (RIGHT1, W1, X1, DOWN1, Y1, Z1): 506 return SignedByte(fp) 507 if o in (RIGHT2, W2, X2, DOWN2, Y2, Z2): 508 return SignedPair(fp) 509 if o in (RIGHT3, W3, X3, DOWN3, Y3, Z3): 510 return SignedTrio(fp) 511 if o in (SET4, SET_RULE, PUT4, PUT_RULE, RIGHT4, W4, X4, DOWN4, Y4, Z4, FNT4, XXX4, FNT_DEF4, NATIVE_FONT_DEF): 512 return SignedQuad(fp) 513 if o in (NOP, BOP, EOP, PUSH, POP, PRE, POST, POST_POST) or o > POST_POST: 514 return 0 515 if o in (W0, X0, Y0, Z0, BEGIN_REFLECT, END_REFLECT): 516 return 0 517 if o < FNT_NUM_0 + 64: 518 return o - FNT_NUM_0 519 520 def GetGlyphs(self, fp): 521 width = SignedQuad(fp) 522 length = Get2Bytes(fp) 523 glyphs = {} 524 for i in range(length): 525 glyphs[i] = {} 526 glyphs[i]["x"] = SignedQuad(fp) 527 glyphs[i]["y"] = SignedQuad(fp) 528 529 for i in range(length): 530 glyphs[i]["id"] = Get2Bytes(fp) 531 532 return (width, glyphs) 533 534 def ReadGlyphs(self, val): 535 import re 536 glyphs = [] 537 w, g = val.split(" ", 1) 538 for m in re.finditer(r"gid(?P<id>\d+?)\((?P<pos>.*?.)\)", g): 539 gid = m.group("id") 540 pos = m.group("pos") 541 542 if "," in pos: 543 x, y = pos.split(",") 544 else: 545 x, y = pos, "0sp" 546 547 glyphs.append({"id": int(gid), 'x': self.ConvLen(x), 'y': self.ConvLen(y)}) 548 549 return (self.ConvLen(w), glyphs) 550 551 ########################################################## 552 # Save: Internal Format -> DVI 553 ########################################################## 554 def Save(self, fn): 555 fp = file(fn, 'wb') 556 self.SaveToFile(fp) 557 fp.close() 558 559 def SaveToFile(self, fp): 560 # WritePreamble 561 fp.write(''.join([chr(PRE), PutByte(self.id), PutSignedQuad(self.numerator), PutSignedQuad(self.denominator), PutSignedQuad(self.mag), PutByte(len(self.comment)), self.comment])) 562 # WriteFontDefinitions 563 self.WriteFontDefinitions(fp) 564 # WritePages 565 stackdepth = 0; loc = -1 566 for page in self.pages: 567 w = x = y = z = 0; stack = [] 568 s = [chr(BOP)] 569 s.extend([PutSignedQuad(c) for c in page['count']]) 570 s.append(PutSignedQuad(loc)) 571 for cmd in page['content']: 572 if cmd[0] == SET1: 573 for o in cmd[1]: 574 if o < 128: s.append(chr(SET_CHAR_0 + o)) 575 else: s.append(self.CmdPair([SET1, o])) 576 elif cmd[0] in (SET_RULE, PUT_RULE): 577 s.append(chr(cmd[0]) + PutSignedQuad(cmd[1][0]) + PutSignedQuad(cmd[1][1])) 578 elif cmd[0] == PUT1: 579 s.append(self.CmdPair([PUT1, cmd[1][0]])) 580 elif cmd[0] in (RIGHT1, DOWN1): 581 s.append(self.CmdPair(cmd)) 582 elif cmd[0] in (W0, X0, Y0, Z0): 583 s.append(chr(cmd[0])) 584 elif cmd[0] == PUSH: 585 s.append(chr(PUSH)) 586 stack.append((w, x, y, z)) 587 if len(stack) > stackdepth: stackdepth = len(stack) 588 elif cmd[0] == POP: 589 s.append(chr(POP)) 590 w, x, y, z = stack.pop() 591 elif cmd[0] == W1: 592 w = cmd[1]; s.append(self.CmdPair(cmd)) 593 elif cmd[0] == X1: 594 x = cmd[1]; s.append(self.CmdPair(cmd)) 595 elif cmd[0] == Y1: 596 y = cmd[1]; s.append(self.CmdPair(cmd)) 597 elif cmd[0] == Z1: 598 z = cmd[1]; s.append(self.CmdPair(cmd)) 599 elif cmd[0] == FNT1: 600 if cmd[1] < 64: s.append(chr(FNT_NUM_0 + cmd[1])) 601 else: s.append(self.CmdPair(cmd)) 602 elif cmd[0] == XXX1: 603 l = len(cmd[1]) 604 if l < 256: s.append(chr(XXX1) + chr(l) + cmd[1]) 605 else: s.append(chr(XXX4) + PutSignedQuad(l) + cmd[1]) 606 elif cmd[0] == DIR: 607 s.append(chr(DIR) + chr(cmd[1])) 608 elif cmd[0] == BEGIN_REFLECT: 609 s.append(chr(BEGIN_REFLECT)) 610 elif cmd[0] == END_REFLECT: 611 s.append(chr(END_REFLECT)) 612 elif cmd[0] == GLYPHS: 613 s.append(PutGlyphs(cmd[1], cmd[2])) 614 else: 615 warning('invalid command %s!' % cmd[0]) 616 s.append(chr(EOP)) 617 loc = fp.tell() 618 fp.write(''.join(s)) 619 # WritePostamble 620 post_loc = fp.tell() 621 fp.write(''.join([chr(POST), PutSignedQuad(loc), PutSignedQuad(self.numerator), PutSignedQuad(self.denominator), PutSignedQuad(self.mag), PutSignedQuad(self.max_v), PutSignedQuad(self.max_h), Put2Bytes(stackdepth+1), Put2Bytes(len(self.pages))])) 622 # WriteFontDefinitions 623 self.WriteFontDefinitions(fp) 624 # WritePostPostamble 625 fp.write(''.join([chr(POST_POST), PutSignedQuad(post_loc), PutByte(self.id), '\xdf\xdf\xdf\xdf'])) 626 loc = fp.tell() 627 while (loc % 4) != 0: 628 fp.write('\xdf'); loc += 1 629 630 def WriteFontDefinitions(self, fp): 631 s = [] 632 for e in sorted(self.font_def.keys()): 633 if self.font_def[e]['native']: 634 flags = self.font_def[e]['flags'] 635 s.append(PutByte(NATIVE_FONT_DEF)) 636 s.append(PutSignedQuad(e)) 637 s.append(PutSignedQuad(self.font_def[e]['scaled_size'])) 638 s.append(Put2Bytes(flags)) 639 s.append(PutByte(len(self.font_def[e]['name']))) 640 s.append(self.font_def[e]['name']) 641 s.append(PutSignedQuad(self.font_def[e]['index'])) 642 print >> sys.stderr, self.font_def[e]['name'], self.font_def[e]['index'] 643 if flags & XDV_FLAG_COLORED: s.append(PutSignedQuad(self.font_def[e]['color'])) 644 if flags & XDV_FLAG_EXTEND: s.append(PutSignedQuad(self.font_def[e]['extend'])) 645 if flags & XDV_FLAG_SLANT: s.append(PutSignedQuad(self.font_def[e]['slant'])) 646 if flags & XDV_FLAG_EMBOLDEN: s.append(PutSignedQuad(self.font_def[e]['embolden'])) 647 else: 648 l, q = PutUnsigned(e) 649 s.append(PutByte(FNT_DEF1 + l)) 650 s.append(q) 651 s.append(PutSignedQuad(self.font_def[e]['checksum'])) 652 s.append(PutSignedQuad(self.font_def[e]['scaled_size'])) 653 s.append(PutSignedQuad(self.font_def[e]['design_size'])) 654 s.append('\x00') 655 s.append(PutByte(len(self.font_def[e]['name']))) 656 s.append(self.font_def[e]['name']) 657 fp.write(''.join(s)) 658 659 def CmdPair(self, cmd): 660 l, q = PutSigned(cmd[1]) 661 return chr(cmd[0] + l) + q 662 663 ########################################################## 664 # Parse: Text -> Internal Format 665 ########################################################## 666 def Parse(self, fn, encoding=''): 667 fp = file(fn, 'r') 668 s = fp.read() 669 fp.close() 670 self.ParseFromString(s, encoding=encoding) 671 672 def ParseFromString(self, s, encoding=''): 673 global GetStr, cur_font, cur_dsize, cur_ssize, subfont_idx 674 if encoding == 'ascii': GetStr = GetStrASCII 675 else: GetStr = GetStrUTF8 676 self.Initialize() 677 self.fnt_num = 0 678 for l in s.split('\n'): 679 l = l.strip() 680 if not l or l[0] == '%': continue 681 try: 682 key, val = l.split(':', 1) 683 key = key.strip(); val = val.strip() 684 except: 685 if l[-1] == ']': v = l[:-1].split(' ') 686 else: v = l.split(' ') 687 if v[0] == "[page": 688 self.cur_page = [] 689 count = [GetInt(c) for c in v[1:]] 690 if len(count) < 10: count += ([0] * (10-len(count))) 691 self.pages.append({'count':count, 'content':self.cur_page}) 692 continue 693 # ParsePreamble 694 if key == "id": 695 self.id = GetInt(val) 696 if self.id != DVI_ID and self.id != DVIV_ID and self.id != XDV_ID: 697 warning("identification byte should be %d, %d, or %d!" % (DVI_ID, DVIV_ID, XDV_ID)) 698 elif key == "numerator": 699 d = GetInt(val) 700 if d <= 0: 701 warning('non-positive numerator %d!' % d) 702 else: 703 self.numerator = d 704 self.ComputeConversionFactors() 705 elif key == "denominator": 706 d = GetInt(val) 707 if d <= 0: 708 warning('non-positive denominator %d!' % d) 709 else: 710 self.denominator = d 711 self.ComputeConversionFactors() 712 elif key == "magnification": 713 d = GetInt(val) 714 if d <= 0: 715 warning('non-positive magnification %d!' % d) 716 else: 717 self.mag = d 718 elif key == "comment": 719 self.comment = val[1:-1] 720 # Parse Postamble 721 elif key == "maxv": 722 self.max_v = self.ConvLen(val) 723 elif key == "maxh": 724 self.max_h = self.ConvLen(val) 725 elif key == "maxs": 726 self.max_s = GetInt(val) 727 elif key == "pages": 728 self.total_pages = GetInt(val) 729 # Parse Font Definitions 730 elif key == "fntdef": 731 self.font_def[self.fnt_num] = self.GetFntDef(val) 732 self.fnt_num += 1 733 # Parse Pages 734 elif key == 'xxx': 735 self.cur_page.append([XXX1, eval(val)]) 736 elif key == 'set': 737 ol = GetStr(val) 738 if is_subfont: 739 subfont_idx = (ol[0] >> 8) 740 self.AppendFNT1() 741 nl = [ol[0] & 0xff] 742 for o in ol[1:]: 743 idx = (o >> 8) 744 if idx != subfont_idx: 745 self.cur_page.append([SET1, nl]) 746 subfont_idx = idx 747 self.AppendFNT1() 748 nl = [o & 0xff] 749 else: 750 nl.append(o & 0xff) 751 self.cur_page.append([SET1, nl]) 752 else: 753 self.cur_page.append([SET1, ol]) 754 elif key == 'put': 755 self.cur_page.append([PUT1, GetStr(val)]) 756 elif key == 'setrule': 757 v = val.split(' ') 758 if len(v) != 2: 759 warning('two values are required for setrule!') 760 continue 761 self.cur_page.append([SET_RULE, [self.ConvLen(c) for c in v]]) 762 elif key == 'putrule': 763 v = val.split(' ') 764 if len(v) != 2: 765 warning('two values are required for putrule!') 766 continue 767 self.cur_page.append([PUT_RULE, [self.ConvLen(c) for c in v]]) 768 elif key == 'fnt': 769 f = self.GetFntDef(val) 770 n = f['name'] 771 d = f['design_size'] 772 q = f['scaled_size'] 773 if n in subfont_list: 774 is_subfont = True 775 cur_font = n; cur_dsize = d; cur_ssize = q 776 else: 777 is_subfont = False 778 try: 779 e = self.font_def.keys()[self.font_def.values().index(f)] 780 except: 781 e = self.fnt_num 782 self.font_def[self.fnt_num] = f 783 self.fnt_num += 1 784 self.cur_page.append([FNT1, e]) 785 elif key == 'right': 786 self.cur_page.append([RIGHT1, self.ConvLen(val)]) 787 elif key == 'down': 788 self.cur_page.append([DOWN1, self.ConvLen(val)]) 789 elif key == 'w': 790 self.cur_page.append([W1, self.ConvLen(val)]) 791 elif key == 'x': 792 self.cur_page.append([X1, self.ConvLen(val)]) 793 elif key == 'y': 794 self.cur_page.append([Y1, self.ConvLen(val)]) 795 elif key == 'z': 796 self.cur_page.append([Z1, self.ConvLen(val)]) 797 elif key == 'push': 798 self.cur_page.append([PUSH]) 799 elif key == 'pop': 800 self.cur_page.append([POP]) 801 elif key == 'w0': 802 self.cur_page.append([W0]) 803 elif key == 'x0': 804 self.cur_page.append([X0]) 805 elif key == 'y0': 806 self.cur_page.append([Y0]) 807 elif key == 'z0': 808 self.cur_page.append([Z0]) 809 elif key == 'dir': 810 self.cur_page.append([DIR, GetInt(val)]) 811 elif key == 'begin_reflect': 812 self.cur_page.append([BEGIN_REFLECT]) 813 elif key == 'end_reflect': 814 self.cur_page.append([END_REFLECT]) 815 elif key == 'setglyphs': 816 w, glyphs = self.ReadGlyphs(val) 817 self.cur_page.append([GLYPHS, w, glyphs]) 818 else: 819 warning('invalid command %s!' % key) 820 821 def AppendFNT1(self): 822 f = {'name':cur_font+"%02x"%subfont_idx, 'design_size':cur_dsize, 'scaled_size':cur_ssize, 'checksum':0} 823 try: 824 e = self.font_def.keys()[self.font_def.values().index(f)] 825 except: 826 e = self.fnt_num 827 self.font_def[e] = f 828 self.fnt_num += 1 829 self.cur_page.append([FNT1, e]) 830 831 ########################################################## 832 # Dump: Internal Format -> Text 833 ########################################################## 834 def Dump(self, fn, tabsize=2, encoding=''): 835 fp = file(fn, 'w') 836 self.DumpToFile(fp, tabsize=tabsize, encoding=encoding) 837 fp.close() 838 839 def DumpToFile(self, fp, tabsize=2, encoding=''): 840 global PutStr 841 if encoding == 'ascii': PutStr = PutStrASCII 842 elif encoding == 'latin1': PutStr = PutStrLatin1 843 elif encoding == 'sjis': PutStr = PutStrSJIS 844 else: PutStr = PutStrUTF8 845 # DumpPreamble 846 fp.write("[preamble]\n") 847 fp.write("id: %d\n" % self.id) 848 fp.write("numerator: %d\n" % self.numerator) 849 fp.write("denominator: %d\n" % self.denominator) 850 fp.write("magnification: %d\n" % self.mag) 851 fp.write("comment: %s\n" % repr(self.comment)) 852 # DumpPostamble 853 fp.write("\n[postamble]\n") 854 fp.write("maxv: %s\n" % self.byconv(self.max_v)) 855 fp.write("maxh: %s\n" % self.byconv(self.max_h)) 856 fp.write("maxs: %d\n" % self.max_s) 857 fp.write("pages: %d\n" % self.total_pages) 858 # DumpFontDefinitions 859 fp.write("\n[font definitions]\n") 860 for e in sorted(self.font_def.keys()): 861 fp.write("fntdef: %s" % self.font_def[e]['name']) 862 if self.font_def[e]['design_size'] != self.font_def[e]['scaled_size']: 863 fp.write(" (%s) " % self.by_pt_conv(self.font_def[e]['design_size'])) 864 fp.write(" at %s\n" % self.by_pt_conv(self.font_def[e]['scaled_size'])) 865 # DumpPages 866 for page in self.pages: 867 fp.write("\n[page" + (" %d"*10 % tuple(page['count'])) + "]\n") 868 indent = 0 869 for cmd in page['content']: 870 if cmd[0] == POP: 871 indent -= tabsize 872 fp.write("%spop:\n" % (' ' * indent)) 873 continue 874 fp.write("%s" % (' ' * indent)) 875 if cmd[0] == PUSH: 876 fp.write("push:\n") 877 indent += tabsize 878 elif cmd[0] == XXX1: 879 fp.write("xxx: %s\n" % repr(cmd[1])) 880 elif cmd[0] == DIR: 881 fp.write("dir: %d\n" % cmd[1]) 882 elif cmd[0] == BEGIN_REFLECT: 883 fp.write("begin_reflect:\n") 884 elif cmd[0] == END_REFLECT: 885 fp.write("end_reflect:\n") 886 elif cmd[0] == SET_RULE: 887 fp.write("setrule: %s %s\n" % (self.byconv(cmd[1][0]), self.byconv(cmd[1][1]))) 888 elif cmd[0] == PUT_RULE: 889 fp.write("putrule: %s %s\n" % (self.byconv(cmd[1][0]), self.byconv(cmd[1][1]))) 890 elif cmd[0] == SET1: 891 fp.write("set: %s\n" % PutStr(cmd[1])) 892 elif cmd[0] == PUT1: 893 fp.write("put: %s\n" % PutStr(cmd[1])) 894 elif cmd[0] == FNT1: 895 f = self.font_def[cmd[1]]['name'] 896 z = self.font_def[cmd[1]]['scaled_size'] 897 if IsFontChanged(f, z): 898 fp.write("fnt: %s " % cur_font) 899 if self.font_def[cmd[1]]['design_size'] != self.font_def[cmd[1]]['scaled_size']: 900 fp.write("(%s) " % self.by_pt_conv(self.font_def[cmd[1]]['design_size'])) 901 fp.write("at %s\n" % self.by_pt_conv(cur_ssize)) 902 elif cmd[0] == GLYPHS: 903 fp.write("setglyphs: %s\n" % self.DumpGlyphs(cmd[1][0], cmd[1][1])) 904 elif cmd[0] == RIGHT1: 905 fp.write("right: %s\n" % self.byconv(cmd[1])) 906 elif cmd[0] == DOWN1: 907 fp.write("down: %s\n" % self.byconv(cmd[1])) 908 elif cmd[0] == W1: 909 fp.write("w: %s\n" % self.byconv(cmd[1])) 910 elif cmd[0] == X1: 911 fp.write("x: %s\n" % self.byconv(cmd[1])) 912 elif cmd[0] == Y1: 913 fp.write("y: %s\n" % self.byconv(cmd[1])) 914 elif cmd[0] == Z1: 915 fp.write("z: %s\n" % self.byconv(cmd[1])) 916 elif cmd[0] == W0: 917 fp.write("w0:\n") 918 elif cmd[0] == X0: 919 fp.write("x0:\n") 920 elif cmd[0] == Y0: 921 fp.write("y0:\n") 922 elif cmd[0] == Z0: 923 fp.write("z0:\n") 924 925 def DumpGlyphs(self, w, g): 926 yPresent = False 927 for i in g: 928 if g[i]["y"] != 0: 929 yPresent = True 930 931 glyphs = [] 932 for i in g: 933 gid = "gid%s" % g[i]["id"] 934 x = self.byconv(g[i]["x"]) 935 y = self.byconv(g[i]["y"]) 936 if yPresent: 937 glyphs.append("%s(%s, %s)" % (gid, x, y)) 938 else: 939 glyphs.append("%s(%s)" % (gid, x)) 940 941 return "%s %s" % (self.byconv(w), " ".join(glyphs)) 942 943 ########################################################## 944 # Misc Functions 945 ########################################################## 946 def ComputeConversionFactors(self): 947 self.sp_conv = (self.numerator / 25400000.) * (473628672. / self.denominator) 948 self.pt_conv = (self.numerator / 25400000.) * (7227. / self.denominator) 949 self.bp_conv = (self.numerator / 254000.) * (72. / self.denominator) 950 self.mm_conv = (self.numerator / 10000.) / self.denominator 951 self.cm_conv = (self.numerator / 100000.) / self.denominator 952 self.in_conv = (self.numerator / 254000.) * (1. / self.denominator) 953 954 def ConvLen(self, s): 955 try: return int(s) 956 except: pass 957 try: f = float(s[:-2]) 958 except: return 0 959 m = s[-2:] 960 if m == "pt": return int(round(f / self.pt_conv)) 961 elif m == "in": return int(round(f / self.in_conv)) 962 elif m == "mm": return int(round(f / self.mm_conv)) 963 elif m == "cm": return int(round(f / self.cm_conv)) 964 elif m == "bp": return int(round(f / self.bp_conv)) 965 elif m == "sp": return int(round(f / self.sp_conv)) 966 else: 967 try: return int(round(f / self.pt_conv)) 968 except: return 0 969 970 def GetFntDef(self, s): 971 f = {} 972 try: 973 n, size = s.split('(', 1) 974 d, q = size.split(')', 1) 975 except: 976 n, q = s.split(' ', 1) 977 n = n.strip(); q = q.strip() 978 if n.startswith('"') and n.endswith('"'): 979 f['native'] = True 980 n = n.strip('"') 981 flags = 0 982 color = 0 983 extend = 0 984 slant = 0 985 embolden = 0 986 try: 987 name, ext = n.split(':') 988 except: 989 name, ext = n, "" 990 991 try: 992 name, index = name.split('[') 993 index = index.split(']')[0] 994 except: 995 index = 0 996 997 if ext: 998 ext = ext.split(';') 999 for opt in ext: 1000 try: 1001 key, value = opt.split('=') 1002 except: 1003 key, value = opt, "" 1004 if key == "color": 1005 flags |= XDV_FLAG_COLORED 1006 color = int(value, 16) 1007 if key == "vertical": 1008 flags |= XDV_FLAG_VERTICAL 1009 if key == "extend": 1010 flags |= XDV_FLAG_EXTEND 1011 extend = int(value) 1012 if key == "slant": 1013 flags |= XDV_FLAG_SLANT 1014 slant = int(value) 1015 if key == "embolden": 1016 flags |= XDV_FLAG_EMBOLDEN 1017 embolden = int(value) 1018 1019 f['name'] = name 1020 f['index'] = int(index) 1021 f['flags'] = flags 1022 f['color'] = color 1023 f['extend'] = extend 1024 f['slant'] = slant 1025 f['embolden'] = embolden 1026 else: 1027 f['native'] = False 1028 f['name'] = n 1029 1030 if q[:2] == "at": q = q[2:] 1031 q = self.ConvLen(q.strip()) 1032 try: d = self.ConvLen(d.strip()) 1033 except: d = q 1034 1035 f['design_size'] = d 1036 f['scaled_size'] = q 1037 f['checksum'] = 0 1038 1039 return f 1040 1041 def by_sp_conv(self, a): 1042 v = self.sp_conv * a 1043 return "%dsp" % int(v) 1044 1045 def by_pt_conv(self, a): 1046 v = self.pt_conv * a 1047 if v == int(v): return "%dpt" % int(v) 1048 else: return "%fpt" % v 1049 1050 def by_bp_conv(self, a): 1051 v = self.bp_conv * a 1052 if v == int(v): return "%dbp" % int(v) 1053 else: return "%fbp" % v 1054 1055 def by_mm_conv(self, a): 1056 v = self.mm_conv * a 1057 if v == int(v): return "%dmm" % int(v) 1058 else: return "%fmm" % v 1059 1060 def by_cm_conv(self, a): 1061 v = self.cm_conv * a 1062 if v == int(v): return "%dcm" % int(v) 1063 else: return "%fcm" % v 1064 1065 def by_in_conv(self, a): 1066 v = self.in_conv * a 1067 if v == int(v): return "%din" % int(v) 1068 else: return "%fin" % v 1069 1070############################################################ 1071# Misc Functions for Main Routine 1072############################################################ 1073def ProcessOptions(): 1074 usage = """%prog [options] dvi_file|dvi_dump_file 1075 1076DVIasm is a Python script to support changing or creating DVI files 1077via disassembling into text, editing, and then reassembling into 1078binary format. It is fully documented at 1079 1080http://tug.org/TUGboat/Articles/tb28-2/tb89cho.pdf 1081http://ajt.ktug.kr/assets/2008/5/1/0201cho.pdf""" 1082 1083 version = """This is %prog-20150412 by Jin-Hwan Cho (Korean TeX Society) 1084 1085Copyright (C) 2007-2008 by Jin-Hwan Cho <chofchof@ktug.or.kr> 1086Copyright (C) 2011-2015 by Khaled Hosny <khaledhosny@eglug.org> 1087 1088This is free software; you can redistribute it and/or modify 1089it under the terms of the GNU General Public License as published by 1090the Free Software Foundation, either version 3 of the License, or 1091(at your option) any later version.""" 1092 1093 parser = OptionParser(usage=usage, version=version) 1094 parser.add_option("-u", "--unit", 1095 action="store", type="string", dest="unit", 1096 metavar="STR", 1097 help="unit (sp, pt, bp, mm, cm, in) [default=%default]") 1098 parser.add_option("-o", "--output", 1099 action="store", type="string", dest="output", 1100 metavar="FILE", 1101 help="filename for output instead of stdout") 1102 parser.add_option("-e", "--encoding", 1103 action="store", type="string", dest="encoding", 1104 metavar="STR", 1105 help="encoding for input/output [default=%default]") 1106 parser.add_option("-t", "--tabsize", 1107 action="store", type="int", dest="tabsize", 1108 metavar="INT", 1109 help="tab size for push/pop [default=%default]") 1110 parser.add_option("-p", "--ptex", 1111 action="store_true", dest="ptex", default=False, 1112 help="extended DVI for Japanese pTeX") 1113 parser.add_option("-s", "--subfont", 1114 action="append", type="string", dest="subfont", 1115 metavar="STR", 1116 help="the list of fonts with UCS2 subfont scheme (comma separated); disable internal subfont list if STR is empty") 1117 parser.set_defaults(unit='pt', encoding='utf8', tabsize=2) 1118 (options, args) = parser.parse_args() 1119 if not options.unit in ['sp', 'pt', 'bp', 'mm', 'cm', 'in']: 1120 parser.error("invalid unit name '%s'!" % options.unit) 1121 if options.tabsize < 0: 1122 parser.error("negative tabsize!") 1123 if not options.encoding in ['ascii', 'latin1', 'utf8', 'sjis']: 1124 parser.error("invalid encoding '%s'!" % options.encoding) 1125 if options.ptex: 1126 global is_ptex 1127 is_ptex = True 1128 if not options.encoding in ['utf8', 'sjis']: 1129 parser.error("invalid encoding '%s' for Japanese pTeX!" % options.encoding) 1130 if options.subfont: 1131 global subfont_list 1132 if not options.subfont[0]: # disable subfont 1133 subfont_list = [] 1134 for l in options.subfont: 1135 subfont_list.extend([f.strip() for f in l.split(',')]) 1136 if len(args) != 1: 1137 parser.error("try with the option --help!") 1138 return (options, args) 1139 1140def IsDVI(fname): 1141 from os.path import splitext 1142 if splitext(fname)[1] not in ('.dvi', '.xdv'): return False 1143 try: 1144 fp = file(fname, 'rb') 1145 fp.seek(0) 1146 if GetByte(fp) != PRE: return False 1147 fp.seek(-4, 2) 1148 if GetByte(fp) != 223: return False 1149 fp.close() 1150 except: 1151 sys.stderr.write('Failed to read %s\n' % fname) 1152 return False 1153 return True 1154 1155############################################################ 1156# Main Routine 1157############################################################ 1158if __name__ == '__main__': 1159 (options, args) = ProcessOptions() 1160 aDVI = DVI(unit=options.unit) 1161 if IsDVI(args[0]): # dvi -> dump 1162 aDVI.Load(args[0]) 1163 if options.output: aDVI.Dump(options.output, tabsize=options.tabsize, encoding=options.encoding) 1164 else: aDVI.DumpToFile(sys.stdout, tabsize=options.tabsize, encoding=options.encoding) 1165 else: # dump -> dvi 1166 aDVI.Parse(args[0], encoding=options.encoding) 1167 if options.output: aDVI.Save(options.output) 1168 else: aDVI.SaveToFile(sys.stdout) 1169