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