summaryrefslogtreecommitdiff
path: root/dviware/ivd2dvi/ivd2dvi.c
blob: d2f68397b081647e74191b5a99b49ce81476b19d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
/*
 *  ivd2dvi.
 *  Copyright 1988 by Larry Denenberg.  May be freely distributed as long
 *  as this notice is retained.
 *  
 *  This is a general discussion of ivd2dvi, assuming familiarity with
 *  the format of DVI files and with the semantics of the reflection
 *  commands (see Knuth and Mackay, "Mixing right-to-left text with
 *  left-to-right text" in TUGboat volume 8 number 1).  Notation: let
 *  "BR" stand for the BEGIN_REFLECT command, let "ER" stand for
 *  END_REFLECT, let "a" stand for SET_097 (i.e.  "typeset character 97,"
 *  usually lowercase a), and similarly for the other lowercase letters.
 *  
 *  The simplest idea would be to rearrange reflected text somehow so
 *  that the right thing happens.  Is it enough to reverse the commands?
 *  Certainly if we see "BR a b c ER" we can replace it with "c b a".
 *  But this works only so long as the reflected segment sticks to a
 *  small subset of DVI commands; "BR a FONT_00 b ER" can't in general be
 *  changed to "b FONT_00 a".  We also get in trouble with commands that
 *  change the DVI registers (not to mention PUSH and POP!).
 *  
 *  Some of these difficulties could be overcome with more clever
 *  rearrangement.  Unfortunately, there are situations that can't be
 *  handled by any such scheme:  for example, you can't typeset the
 *  sequence "BR PUSH i POP w ER" without knowing about the widths of
 *  "i" and "w".  The PUTn commands also lead to unsolvable problems.
 *  So we abandon this approach and resign ourselves to reading width
 *  data from TFM files.
 *  
 *  Here's the method we use:  we read through the input DVI file,
 *  copying DVI commands to the output file.  When we see a BR we stop
 *  copying and simply read the input looking for the matching ER.
 *  During this scan we keep track of the total width of the commands
 *  between the BR and ER.  (Each character contributes its width as
 *  revealed by the TFM file, horizontal motions contribute their
 *  width, vertical motions are ignored, etc.)  This procedure is
 *  called *simulating*; at its end we've just read an ER and we know
 *  the width of the segment between the BR and the ER.  Call this
 *  width T.  Note that nothing at all is output during simulation.
 *  
 *  Now we move right (in the output file) by T; of course we do so by
 *  shipping out a RIGHTn command.  Next, we make a second pass over the
 *  commands between the BR and the ER; this time, we typeset them
 *  "backwards."  For example, to SET an "a" we first move left by the
 *  width of an "a", and lay down the "a" without moving---this is the
 *  inverse of laying down the "a" and then moving right.  If the command
 *  is any sort of horizontal motion we negate the distance before moving.
 *  Reflection applies only to horizontal motion, however; vertical
 *  motion commands, font changes, font definitions, and so forth, are
 *  copied unchanged.  (For the precise way in which we reverse, see the
 *  documentation for the individual DVI commands below, especially
 *  /SetChar/ and /SetString/.)
 *
 *  When we see the ER for the second time, the horizontal location in
 *  the output file should be the same as it was when we first saw the BR
 *  (since we've processed all the same commands, only backwards).  So we
 *  once again output a command to move right by T, thus moving to the
 *  place where we typeset the first command after the BR.  We're now
 *  done translating this reflected segment and we continue normally.
 *  
 *  What happens when reflections are nested?  Well, it's easy.  If we
 *  encounter a BR during a simulation, we basically ignore it!  We're
 *  trying to find the total width of the reflected segment; the fact
 *  that part of the segment is further reflected doesn't matter.  (We do
 *  have to be certain to skip over the inner ER so that we match only
 *  the ER we're looking for.)  Later, when we're no longer simulating
 *  but "typesetting backwards," we'll encounter the BR of the inner
 *  reflection.  At that point it gets its own simulation set up, and we
 *  do the whole two-pass business again on the inner segment; of course,
 *  the second pass is now a "forwards" pass, at the end of which we
 *  continue the "backwards" pass over the outer segment.
 *  
 *  All of this can be nested to any depth.  We keep track of what we're
 *  doing with the variable /State/ which takes on the following values:
 *      LTYPESETTING  Typesetting normally, left-to-right
 *      RTYPESETTING  Typesetting "backwards," right-to-left
 *      SIMULATING    First pass over text to be reflected
 *     >SIMULATING    Inside nested begin_reflect/end_reflect pairs
 *  Values of /State/ greater than SIMULATING are used to keep track of
 *  the nesting depth of BR/ER pairs.  While we're simulating, we simply
 *  increment /State/ when we see BR and decrement it when we see ER.
 *  We switch from simulating to typesetting only when we see ER while
 *  /State/ is exactly equal to SIMULATING.  Note carefully that we're
 *  never simulating on behalf of more than one BR/ER pair; there's never
 *  more than one segment whose width we're measuring.
 *  
 *  So the procedure upon hitting a BR in the general case is as follows:
 *  if /State/ equals or exceeds SIMULATING, just increment /State/.
 *  Otherwise set /State/ to SIMULATING and start the first pass.  The
 *  actions necessary at ER are a bit more complex: if /State/ is
 *  *greater* than SIMULATING, just decrement it.  If /State/ is equal to
 *  SIMULATING, we've just finished a simulation: output the motion
 *  command as above, switch to the direction opposite to the one we were
 *  in when we hit the BR, and reset the input file to the point just
 *  after the BR, thus beginning the second pass.  If /State/ is
 *  LTYPESETTING or RTYPESETTING, we've just finished the second pass
 *  over a reflection:  invert /State/, and output the second long motion
 *  command as described above.  Of course, there's lots of things to
 *  save and restore here; details are in the code below.
 *  
 *  Even when we're not simulating we have to keep track of certain
 *  values.  The current font is an obvious example: we have to remember
 *  what it is, because if we start a simulation we'll have to measure
 *  its characters.  We also keep track of the horizontal motion
 *  parameters: if a W0 appears in reflected text we must know the
 *  current value of W, which may have been set before the simulation
 *  began.  Therefore we keep variables in which we store the values, and
 *  a stack to model all pushes and pops in the input file.  The same
 *  stack is used to store values over a simulation; no conflict can
 *  occur.  On the other hand, the values of the vertical motion
 *  parameters never concern us.
 *  
 *  One final twist.  Suppose we encounter the following sequence: "BR
 *  <set W to d> a W0 b W0 c W0 ER" Now if we weren't clever, each W0
 *  would have to be replaced by a longer RIGHTn command, because when
 *  we're typesetting backwards a W0 translates to a rightward motion by
 *  -d, not d.  It's better to set W to -d to begin with; then we can use
 *  W0 inside the reflection and save considerable space.  So we negate
 *  the parameter of Wn commands encountered while RTYPESETTING.  But as
 *  a consequence, the input file may disagree with the output file over
 *  the current value of W.  So we must keep two separate variables,
 *  /WInput/ and /WOutput/, that record the two values.  These two
 *  variables always are equal in absolute value, but we can't compute
 *  from the state whether they're equal or not (because reflections may
 *  start and end independently of changes to W).  More details are given
 *  at /SetWandMoveForward/.  All of this applies to X as well.  With
 *  more work we could do further optimization, catching (e.g.)  cases
 *  like "<set W to d> BR a W0 b W0 ER" in which W0 will not be used as
 *  our scheme stands.
 *  
 *  Jacques Goldberg first suggested the possibility of a dvi-ivd to dvi
 *  processor.  Please send comments, suggestions, and bug reports to
 *  larry@bbn.com or larry@harvard.edu.
 *  
 */


#include <stdio.h>
#include "global.h"
#include "commands.h"

/*  Global variables and procedures defined in auxiliary.c.  */
extern font *CurFont, *FindFont();
extern void Initializations(), BadDVIAbort(), MaxPushLevelOutput();
extern void PushWrite(), PopWrite();
extern void PushWX(), PopXW(), PushDeltaH(), PopDeltaH();
extern void FontDefine(), CopyFontDefinition();

/*  Global variables and procedures defined in io.c.  */
extern unsigned BufSize, SignedBytes();
extern long BytesOutput, CopyWord();
extern long ReadSigned(), ReadUnsigned(), CharWidth(), CopyUnsigned();
extern void WriteByte(), WriteString(), WriteNumber(), WriteWord();
extern void CopyNBytes(), SkipNBytes();
extern void RereadLastByte(), ResetFilePosition();
extern unsigned_byte CopyByte(), ReadByte(), ReadCommand();
extern unsigned_byte *ReadFilePosition();


/* Global variables defined here */
char   *ProgramName;		/* argv[0], used in error messages	*/
boolean	VerboseOutput = FALSE;	/* v flag: report page progress		*/
boolean	ExactOutput = FALSE;	/* X flag: try not to change input file	*/
long	PrevPagePointer = -1;	/* output file location of previous BOP	*/
int	State;			/* current direction or simulation depth*/
long	WInput, WOutput;	/* value of W in input and output files	*/
long	XInput, XOutput;	/* value of X in input and output files	*/
long	DeltaH;			/* total size of reflected segment	*/
int	SavedState;		/* state at start of current simulation	*/
font   *SavedFont;		/* font at start of current simulation	*/
unsigned_byte *SavedPosition;	/* file position at start of simulation	*/
unsigned_byte  CurCommand;	/* DVI command under consideration	*/

#define REVERSE(STATE)	(LTYPESETTING + RTYPESETTING - STATE)


/* Procedures defined in this file, in order of definition */
void main(),Arguments(),FileArgument(),Preliminaries(),MainLoop();
void SetChar(),SetRule(),SetString(),SetFont();
void BeginPage(),EndPage(),BegReflect(),EndReflect();
void MoveForward(),SetWandMoveForward(),SetXandMoveForward();
void Postliminaries(),CopyParametrizedCommand();




/*
 *  Main procedure, whose function is self-documenting.  The only way
 *  ivd2dvi can terminate normally is through the /exit/ here.
 */
void
main(ignore, argv)
int ignore;
char *argv[];
{
  Arguments(argv);
  Initializations();
  Preliminaries();
  MainLoop();
  Postliminaries();
  exit(0);
}



/*
 *  Standard argument processing.  Don't worry if a flag is given more
 *  than once, but allow at most one filename.  We also allow forms like
 *  "-Xv", or even "-Xbv 1024" since the argument following any -b is
 *  taken as the new buffer size.
 */
void
Arguments(argv)
char *argv[];
{
  char *arg;
  boolean seenfile = FALSE;
  int newbufsize;

  ProgramName = argv[0];
  for (arg = *++argv; arg; arg = *++argv)
    if (*arg == '-')
      while (*++arg)
	switch(*arg) {
	  case 'X':
	    ExactOutput = TRUE;
	    break;
	  case 'v':
	    VerboseOutput = TRUE;
	    break;
	  case 'b':
	    if (!*++argv) {
              fprintf(stderr, "%s: missing buffer size, -b ignored\n",
			      ProgramName);
	      return;
	    } else {
	      newbufsize = atoi(*argv);
	      if (newbufsize == 0)
		fprintf(stderr, "%s: illegal buffer size %s ignored\n",
				ProgramName, *argv);
	      else
		BufSize = newbufsize;
	    }
	    break;
	  default:
	    fprintf(stderr, "%s: illegal flag %c ignored\n",
			    ProgramName, *arg);
	}
    else if (seenfile)
      fprintf(stderr, "%s: superflous filename %s ignored\n",
		      ProgramName, arg);
    else {
      seenfile = TRUE;
      FileArgument(arg);
    }
}



/*
 *  Process a file argument.  Try to open the file.  If we can't, and if
 *  the name has no period after its rightmost slash, append ".dvi" and
 *  try again.  In either case we're reopening standard input, so that
 *  we can read the input DVI file from /stdin/ whether or not there's a
 *  filename on the command line.
 */
void
FileArgument(filename)
char *filename;
{
  char buf[MAXFILENAMESIZE], *p;

  if (freopen(filename, "r", stdin) != NULL) return;
  p = rindex(filename, '/');
  if (*p == '\0') p = filename;
  p = index(p, '.');
  if (*p != '\0') {
    fprintf(stderr, "%s: Can't open %s\n", ProgramName, filename);
    exit(1);
  } else {
    (void) sprintf(buf, "%s.dvi", filename);
    if (freopen(buf, "r", stdin) != NULL) return;
    fprintf(stderr, "%s: Can't open %s nor %s\n",
		    ProgramName, filename, buf);
    exit(1);
  }
}



/*
 *  Process the preamble.  We mostly just copy it through untouched,
 *  except that we check the first two bytes for correctness and update
 *  the comment string to say ivd2dvi was here.  (Don't update the
 *  comment string if the -X flag was used nor when the new comment
 *  wouldn't fit.)
 */
void
Preliminaries()
{
  static char *comment = "; postprocessed by ivd2dvi";
  unsigned comlength;

  if (CopyByte() != PRE) BadDVIAbort("no preamble");
  if (CopyByte() != DVIVERSION)
    BadDVIAbort("wrong DVI version in preamble");
  CopyNBytes(12L);
  comlength = ReadByte();
  if (!ExactOutput && (comlength < 256 - strlen(comment))) {
    WriteByte(comlength + strlen(comment));
    CopyNBytes((long) comlength);
    WriteString(comment);
  } else {
    WriteByte(comlength);
    CopyNBytes((long) comlength);
  }
}




/*
 *  The main loop.  Read a command, switch on it, and continue doing so
 *  forever.  The only way out of this routine is when /CurCommand/ is
 *  POST.  Most cases of the main switch are simply procedure calls; the
 *  rest are documented case-by-case.  We first handle the most common
 *  case, for top speed:  if /CurCommand/ is SET_000 through SET_127 and
 *  we're typesetting normally, we need do nothing but write it into the
 *  output.  If we're simulating or typesetting backwards, /SetString/
 *  will handle it.  Other commands are handled inside the switch.  Note:
 *  the first test relies on the assumption that SET_000 is 0, which we
 *  really shouldn't do, but then again it *is* the test for the most
 *  common case!   Expressions like "CurCommand - W1 + 1" return the
 *  number of bytes in the first parameter of commands with four forms.
 */

#define SETTING TRUE

void
MainLoop()
{
  while (TRUE) {

    CurCommand = ReadCommand();

    if (CurCommand <= SETC_127) {
      if (State == LTYPESETTING) WriteByte(CurCommand); else SetString();
      continue;
    }

    switch (CurCommand) {

      case SET1: case SET2: case SET3: case SET4:
	SetChar(CurCommand - SET1 + 1,  SETTING);
	break;

      case PUT1: case PUT2: case PUT3: case PUT4:
	SetChar(CurCommand - PUT1 + 1, !SETTING);
	break;

      case SET_RULE: case PUT_RULE:
	SetRule();
	break;

      case NOP:
	if (ExactOutput) WriteByte(NOP);
	break;

      case BOP:
	BeginPage();
	break;

      case EOP:
	EndPage();
	break;


/*
 *  When we see a PUSH/POP we save/restore the horizontal parameters.
 *  If we're simulating, we also have to save/restore /DeltaH/, since
 *  stuff that happens between the PUSH and the POP has no effect on
 *  the size of the reflected segment.  (If we're not simulating, we
 *  don't care at all about /DeltaH/ and there's no sense saving it.)
 *  Note that the stack discipline works out as long as we restore
 *  in the opposite order that we save, since PUSH/POP must nest with
 *  respect to reflection---so we can't have a PUSH while simulating
 *  that matches a POP while not simulating.  Oh yeah, I almost forgot:
 *  if we're *not* simulating, we must write the command to the output!
 */
      case PUSH:
	PushWX();
	if (State < SIMULATING) PushWrite(); else PushDeltaH();
	break;

      case POP:
	if (State < SIMULATING) PopWrite(); else PopDeltaH();
	PopXW();
	break;

      case RIGHT1: case RIGHT2: case RIGHT3: case RIGHT4:
	MoveForward(ReadSigned(CurCommand - RIGHT1 + 1));
	break;


/*
 *  Move forward by W.  It's up to /MoveForward/ whether "forward" means
 *  left or right (or neither, if we're just simulating).  The amount to
 *  move is of course the *input* file's idea of the current W.
 */
      case W0:
	MoveForward(WInput);
	break;

      case W1: case W2: case W3: case W4:
	SetWandMoveForward(ReadSigned(CurCommand - W1 + 1));
	break;


/*
 *  Move forward by X.  Please see the comments for W0, above.
 */
      case X0:
	MoveForward(XInput);
	break;

      case X1: case X2: case X3: case X4:
	SetXandMoveForward(ReadSigned(CurCommand - X1 + 1));
	break;


/*
 *  We don't especially care about vertical motion, so if we're only
 *  simulating there's nothing to do.  Otherwise, copy the command and
 *  its parameters to the output file.
 */
      case DOWN1: case DOWN2: case DOWN3: case DOWN4:
	if (State < SIMULATING)
	  CopyParametrizedCommand(CurCommand - DOWN1 + 1);
	break;

      case Y0: case Y1: case Y2: case Y3: case Y4:
	if (State < SIMULATING) CopyParametrizedCommand(CurCommand - Y0);
	break;

      case Z0: case Z1: case Z2: case Z3: case Z4:
	if (State < SIMULATING) CopyParametrizedCommand(CurCommand - Z0);
	break;


/*
 *  We must keep track of all font changes, simulating or not.  So call
 *  /SetFont/ on the selected font number, and if we're not simulating
 *  copy the whole command to the output file.
 */
      case FNT1: case FNT2: case FNT3: case FNT4:
        if (State >= SIMULATING)
	  SetFont(ReadUnsigned(CurCommand - FNT1 + 1));
	else {
	  WriteByte(CurCommand);
	  SetFont(CopyUnsigned(CurCommand - FNT1 + 1));
	}
	break;


/*
 *  Skip past if simulating, otherwise copy out the whole command.
 */
      case XXX1: case XXX2: case XXX3: case XXX4:
	if (State >= SIMULATING)
	  SkipNBytes(ReadUnsigned(CurCommand - XXX1 + 1));
	else {
	  WriteByte(CurCommand);
	  CopyNBytes(CopyUnsigned(CurCommand - XXX1 + 1));
	}
	break;

      case FNT_DEF1: case FNT_DEF2: case FNT_DEF3: case FNT_DEF4:
	FontDefine(CurCommand - FNT_DEF1 + 1);
	break;

      case PRE:
	BadDVIAbort("unexpected PRE");
	break;


/*
 *  Shouldn't see the postamble while simulating or reversing; otherwise
 *  it's time to quit.  This is the only normal way out of /MainLoop/.
 */
      case POST:
	if (State != LTYPESETTING) BadDVIAbort("unexpected POST");
	return;

      case POST_POST:
	BadDVIAbort("unexpected POST_POST");
	break;

      case BEG_REFLECT:
	BegReflect();
	break;

      case END_REFLECT:
	EndReflect();
	break;


/*
 *  The only commands left, besides illegal ones, are the one-byte font
 *  selection commands.  Copy them to the output file unless simulating.
 *  Then call /SetFont/ to note the change of font.
 */
      default:
	if (CurCommand >= FONT_00 && CurCommand <= FONT_63) {
	  if (State < SIMULATING) WriteByte(CurCommand);
	  SetFont((long) CurCommand - FONT_00);
	} else
	  BadDVIAbort("unrecognized command");

      }		/* end of "switch (CurCommand) { ... }"	*/
   }	   /* end of "while (TRUE) { ... }"	*/
}	/* end of procedure MainLoop */




/*
 *  Set a single character as specified by a SETn or PUTn command.
 *  (SET_nnn commands don't come here; they go to /SetString/.)
 *  /setting/ tells whether it was a SET or a PUT, and /bytes/ gives the
 *  parameter length.  If we're typesetting normally we just copy out the
 *  command and parameter, and if we're simulating it's enough to add the
 *  width of the desired character to the running total.  If we're
 *  typesetting in reverse there's more to do: first move "forward"
 *  (left, in this case) by the width of the character.  Only then do we
 *  typeset the character.  Moreover, if the original command was a SET,
 *  we PUT the character since we've already moved.  And if the original
 *  command was a PUT, we SET the character---which has the effect of
 *  moving back to the right, for a net motion of zero.
 */
void
SetChar(bytes, setting)
unsigned bytes;
boolean setting;
{
  long charnumber;

  switch (State) {
    case LTYPESETTING:
      CopyParametrizedCommand(bytes);
      break;
    case RTYPESETTING:
      charnumber = ReadUnsigned(bytes);
      MoveForward(CharWidth(charnumber));
      if (setting)
	WriteByte(PUT1 + bytes - 1);
      else
	WriteByte(SET1 + bytes - 1);
      WriteNumber(charnumber, bytes);
      break;
    default:	/* State >= SIMULATING */
      DeltaH += CharWidth(ReadUnsigned(bytes));
  }
}



/*
 *  Same idea as /SetChar/, just above.  If LTYPESETTING just copy, if
 *  simulating just accumulate the width.  If RTYPESETTING first move
 *  left and then interchange SET and PUT.
 */
void
SetRule()
{
  long height, width;

  switch (State) {
    case LTYPESETTING:
      CopyParametrizedCommand(8);
      break;
    case RTYPESETTING:
      height = ReadSigned(4);
      width = ReadSigned(4);
      MoveForward(width);
      if (CurCommand == SET_RULE) WriteByte(PUT_RULE);
	else WriteByte(SET_RULE);
      WriteWord(height);
      WriteWord(width);
      break;
    default:	/* State >= SIMULATING */
      SkipNBytes(4L);
      width = ReadSigned(4);
      if (CurCommand == SET_RULE) DeltaH += width;
  }
}



/*
 *  Handle a SET_nnn command.  This routine is called only while
 *  simulating or typesetting in reverse (the normal case is handled
 *  directly in /MainLoop/ for speed).
 *
 *  If we're just simulating there's nothing to do except add the width
 *  of the character to the running total.  The RTYPESETTING case is the
 *  interesting one.  Here's the story:  We could typeset backwards
 *  as we do in /SetChar/ above:  move left, then PUT (since all these
 *  commands are variants of SET).  But that would mean that every one
 *  of these common one-byte commands would translate into around five
 *  bytes.  So instead of handling a single command, this routine handles
 *  /CurCommand/ and all subsequent SET_nnn commands at once.  We first
 *  continue to read the input until we hit a command that is *not* a
 *  SET_nnn.  As we do so, we accumulate the total width (call it T) of
 *  all the SET_nnn commands that we've read.  Finally, we write the
 *  following instructions into the output file:  (a) move forward---that
 *  is, left---by T,  (b) PUSH, to save the new value of H,  (c) typeset
 *  all the characters we've seen *backwards*,  (d) POP to get back to
 *  the place where we started emitting characters.  This does the trick.
 *
 *  A few notes:  We must back up the input after we're done since that
 *  first non-SET_nnn command must be reconsidered by /MainLoop/.  We
 *  rely on the fact that /ReadFilePosition/ gives us a pointer into a
 *  buffer from which we can rattle off /count/ characters;  this is true
 *  since we must be rescanning input after a simulation.  Finally, if
 *  there's only a single SET_nnn character to typeset we save a byte by
 *  replacing steps (b), (c) and (d) with a simple PUT1.
 *
 *  This routine relies on the fact that SET_nnn in fact has value nnn,
 *  which, stylistically, is absolutely indefensible.
 */
void
SetString()
{
  unsigned_byte *p;
  unsigned_byte nextcommand;
  long totalwidth;
  int count;

  if (State >= SIMULATING)
    DeltaH += CharWidth((long) CurCommand);
  else {		/* State == RTYPESETTING */
    totalwidth = count = 0;
    nextcommand = CurCommand;
    do {
      count++;
      totalwidth += CharWidth((long) nextcommand);
      nextcommand = ReadByte();
    } while (nextcommand <= SETC_127);
    RereadLastByte();
    MoveForward(totalwidth);
    if (count == 1) {
      WriteByte(PUT1);
      WriteByte(CurCommand);
    } else {
      PushWrite();
      p = ReadFilePosition();
      while (count--) WriteByte(*--p);
      PopWrite();
    }
  }
}



/*
 *  Record the current font number, which must be done no matter what
 *  state we're in.  The work is done by /FindFont/;  we just put the
 *  result into /CurFont/ and complain if the font is unknown.
 */
void
SetFont(fontnumber)
long fontnumber;
{
  CurFont = FindFont(fontnumber);
  if (CurFont == NULL) BadDVIAbort("font used but not defined");
}



/*
 *  Pages should end only in normal left-to-right mode.  We write the
 *  BOP, copy the ten \count registers printing the first if in verbose
 *  mode, and write out the pointer to the previous page.  (We can't copy
 *  the old one since page lengths may change.)  We also update this
 *  pointer; it must point to the newly written BOP.  Finally, we start
 *  out the font, W, and X registers correctly.
 */
void
BeginPage()
{
  long pagenumber;

  if (State != LTYPESETTING) BadDVIAbort("unexpected BOP");
  WriteByte(BOP);
  pagenumber = CopyWord();
  if (VerboseOutput) fprintf(stderr, "[%ld", pagenumber);
  CopyNBytes(36L);
  SkipNBytes(4L);
  WriteWord(PrevPagePointer);
  PrevPagePointer = BytesOutput - 45;
  WInput = WOutput = XInput = XOutput = 0;
  CurFont = NULL;
}



/*
 *  End a page.  Must happen in "normal" LTYPESETTING mode, but otherwise
 *  there's nothing to do but write the EOP and chatter if requested.
 */
void
EndPage()
{
  static int pages = 0;

  if (State != LTYPESETTING) BadDVIAbort("unexpected EOP");
  WriteByte(EOP);
  if (VerboseOutput) fprintf(stderr, "]%c", (++pages % 10) ? ' ' : '\n');
}



/*
 *  Start of a reflected segment, which may happen in any state.  If
 *  we're already simulating just increment /State/ to indicate nested
 *  reflections (see the overview above).  Otherwise start simulating:
 *  save the horizontal motion parameters, the current file location, the
 *  current font, and the current state.  Start the running total of the
 *  size of this segment at zero.  Change state to SIMULATING to start
 *  the first pass over the segment; the rest of the work is done at
 *  /EndReflect/.  One subtlety: it's OK to save the font, the state, and
 *  the file position in variables since there's never more than one
 *  simulation at a time.  We could do the same with the horizontal
 *  motion parameters, but we might as well use the extant stack.
 */
void
BegReflect()
{
  if (State >= SIMULATING) ++State;
  else {
    PushWX();
    SavedPosition = ReadFilePosition();
    SavedFont = CurFont;
    SavedState = State;
    State = SIMULATING;
    DeltaH = 0;
  }
}




/*
 *  Come here at the end of a reflected segment.  We first comment the
 *  easy case, which happens to be coded last:  If /State/ is greater
 *  than SIMULATING we're inside nested reflections that don't concern
 *  us yet, so just decrement /State/ to show that we're up a level.
 *
 *  If /State/ says we're at the outermost simulation, we must wrap up
 *  the simulation and start the second pass.  The direction will be the
 *  inverse of the direction just before the simulation started.  We
 *  also reset the font and the horizontal parameters to their saved
 *  values, and set things up so we're rescanning the input just *after*
 *  the BEG_REFLECT that started the simulation.  Finally, we move
 *  "forward" by the total width of the reflected segment;  we'll now
 *  typeset everything "backward" from that point.  (Note that we move
 *  "forward" by the *negative* of /DeltaH/;  this is because we've
 *  already turned the state around.  Exercise:  why must the /PopXW/
 *  precede the /MoveForward/?)  But we're not quite done with /DeltaH/;
 *  we need it at the *end* of the second pass to move forward over
 *  the reflected segment again.  Since the variable /DeltaH/ is used
 *  by any nested simulations, we must save its value on the stack.
 *
 *  If /State/ says we're typesetting (either direction) then we've
 *  just finished the second pass over a reflected segment:  grab the
 *  saved value of /DeltaH/, use it to move back over the reflected
 *  segment, then revert to the previous direction of typesetting
 *  (which is the opposite of the direction just finished).  Be sure
 *  to see the overview if you don't understand this move by /DeltaH/.
 *
 *  Harder exercise:  When we've finished typesetting a simulation as
 *  just described, we repeat the move that we started with to get back
 *  to the right place to continue.  Why don't we use PUSH and POP *in
 *  the output file* to remember the place?  That is, why not add a call
 *  to /PushWrite/ just after /MoveForward(-DeltaH)/ in the SIMULATING
 *  case, and *replace* the /MoveForward/ in the typesetting cases with
 *  a call to /PopWrite/?  We'd save a couple of bytes in the output,
 *  and wouldn't have to worry about the value of /DeltaH/ during the
 *  second pass (thus we could skip pushing and popping /DeltaH/).
 *  What's the disadvantage that vitiates this clever idea?
 */
void
EndReflect()
{
  switch(State) {
    case SIMULATING:
      CurFont = SavedFont;
      ResetFilePosition(SavedPosition);
      State = REVERSE(SavedState);	/* must precede MoveForward */
      PopXW();				/* must precede MoveForward */
      MoveForward(-DeltaH);
      PushDeltaH();
      break;
    case LTYPESETTING: case RTYPESETTING:
      PopDeltaH();
      MoveForward(-DeltaH);
      State = REVERSE(State);
      break;
    default:	/* State >SIMULATING */
      State--;
  }
}



/*
 *  Move "forward" by /distance/.  If we're simulating it's enough to
 *  accumulate /distance/ into the running total, otherwise we need a
 *  motion command in the output file.  Now "forward" means "right" if
 *  typesetting normally, but "left" if typesetting right-to-left, so
 *  first we negate distance if we're in the latter mode.  If we can
 *  accomplish this move with a simple W0 or X0 we do so.  Otherwise,
 *  we use a RIGHTn with the smallest possible n.  Notice that we
 *  test /distance/ against the *output* file's idea of W and X when
 *  trying for the short versions.  These may not agree with W and X
 *  in the input file;  see the two routines immediately following.
 */
void
MoveForward(distance)
long distance;
{
  unsigned bytes;

  if (State >= SIMULATING) { DeltaH += distance; return; }
  if (State == RTYPESETTING) distance = -distance;
  if (distance == WOutput) WriteByte(W0);
    else if (distance == XOutput) WriteByte(X0);
    else {
      bytes = SignedBytes(distance);
      WriteByte(RIGHT1 - 1 + bytes);
      WriteNumber(distance, bytes);
  }
}



/*
 *  Process a command in the range W1..W4.  The parameter has been
 *  already been read.  We always need to set this distance into WInput,
 *  the input file's idea of W.  If we're just simulating, just
 *  accumulate /distance/ into the current total and quit (the value of
 *  WOutput is irrelevant during simulation).  If typesetting left-to-
 *  right, output a Wn command to move and set W in the output file.  If
 *  typesetting right-to-left, the same, except we first negate
 *  /distance/.  This means two things: first of all, we'll move the
 *  opposite direction in the output file as desired.  Secondly, the
 *  value of W in the output file (recorded in /WOutput/) is the negative
 *  of its value in the input file.  That way, upcoming W0 commands will
 *  be directly usable as long as we're typesetting backwards.
 */
void
SetWandMoveForward(distance)
long distance;
{
  unsigned bytes;

  WInput = WOutput = distance;
  if (State >= SIMULATING) { DeltaH += distance; return; }
  if (State == RTYPESETTING) WOutput = distance = -distance;
  bytes = SignedBytes(distance);
  WriteByte(W1 - 1 + bytes);
  WriteNumber(distance, bytes);
}



/*
 *  Don't even think of finding comments for this routine.  Just look up
 *  at /SetWandMoveForward/, OK?
 */
void
SetXandMoveForward(distance)
long distance;
{
  unsigned bytes;

  XInput = XOutput = distance;
  if (State >= SIMULATING) { DeltaH += distance; return; }
  if (State == RTYPESETTING) XOutput = distance = -distance;
  bytes = SignedBytes(distance);
  WriteByte(X1 - 1 + bytes);
  WriteNumber(distance, bytes);
}



/*
 * Process the postamble, having just read a POST command from the input
 *  file.  Mostly we just copy the postamble into the output file, with a
 *  few exceptions: The previous page pointers and maximum stack depth may
 *  be different, and NOP commands are suppressed unless the -X flag was
 *  used.  When we hit POST_POST, we finish off the file with four 223's
 *  and enough more so that the total file length is divisible by four.
 *  Note that ivd2dvi stops reading its input file after seeing the dvi
 *  version character here.  As a consequence, it's always an error for
 *  the input file to come to EOF.
 */
void
Postliminaries()
{
  long i;

  if (VerboseOutput) fprintf(stderr, "[post]\n");
  WriteByte(POST);
  SkipNBytes(4L);
  WriteWord(PrevPagePointer);
  PrevPagePointer = BytesOutput - 5;
  CopyNBytes(20L);
  MaxPushLevelOutput();
  CopyNBytes(2L);
  while (CurCommand != POST_POST) {
    CurCommand = ReadByte();
    switch (CurCommand) {
      case FNT_DEF1: case FNT_DEF2: case FNT_DEF3: case FNT_DEF4:
	CopyFontDefinition(ReadSigned(CurCommand - FNT_DEF1 + 1),
			   CurCommand - FNT_DEF1 + 1);
	break;
      case NOP:
	if (ExactOutput) WriteByte(CurCommand);
	break;
      case POST_POST:
	break;
      default:
        BadDVIAbort("unrecognized command in postamble");
    }
  }
  WriteByte(POST_POST);
  SkipNBytes(4L);
  WriteWord(PrevPagePointer);
  if (CopyByte() != DVIVERSION)
    BadDVIAbort("wrong DVI version in postamble");
  for (i = 7 - ((BytesOutput-1) % 4); i > 0; i--) WriteByte(DVIPADCHAR);
}



/*
 *  Output the current command, then copy /bytes/ more bytes from the
 *  input file to the output file.  Surprisingly useful.
 */
void
CopyParametrizedCommand(bytes)
unsigned bytes;
{
  WriteByte(CurCommand);
  CopyNBytes((long) bytes);
}