1 #include "tfilter.h"
2 
3 #include "textout.h"
4 #include <string.h>
5 
6 /*
7  * This is the general output engine for text.  It allows the various commands
8  * to be stored as strings, with substitions from the arguments handled
9  * as necessary.  In addition, it allows two models of font handling:
10  * grouped (e.g., {\tt ...} or <TT> ... </TT>) and ungrouped (e.g., \n.I ...).
11  * It also handles newline interpretation, ensuring that the "correct"
12  * number of newlines is generated.
13  *
14  * A command consists of a string with some special characters.  These are:
15  * %% just a %
16  * %1 Insert string s1
17  * %2 Insert string s2 etc
18  * %i Insert integer i1 (but see note on definitions)
19  * %n Insert newline ONLY if not AT a newline.
20  * %N=".." Replace newlines with this string (%N revert to original behavior)
21  *         Max of 32 characters.
22  * %p Insert end_par ONLY if not already present.  Requires end_par to
23  *    be defined.
24  * %f Insert end-font-string, if any (not an error if none defined)
25  * %e="..." Define end-font-string.  Max of 32 characters
26  * %u1, %u2, etc.  Just like %1, but upper case the string.
27  * %r1, %r2, etc.  The values of registers defined with %a or SetOutRegister
28  * %rq1, etc. is like %r1, but the output is quoted (so that filters further
29  * downstream do not try to process the data)
30  * %a1="..." define register 1.  Ditto for a2, etc.
31  * %m="..." Define mode.  This is used for text systems that have special
32  * processing modes, such as verbatim in LaTeX, where character processing
33  * is changed.
34  *
35  * Others as needed
36  * (We might want general registers to be filled or emptied, or some
37  * way to indicated "paragraph only if you need to").  We need to be careful;
38  * we could re-invent PostScript/Nroff/TeX/HTML/Fortran :).
39  *
40  * Strings out written with self if possible (see OutString)
41  */
42 
43 /* This the structure that holds the information about a command -
44    Not used for now */
45 typedef struct {
46     char *cmdstring;
47     } CmdInfo;
48 
49 /* Names are matched as
50    cmdname-i1 (if i1 != TEXT_I_IGNORE)
51       then
52    cmdname
53  */
MatchCommand(const char * cmdname,int i1,SrEntry ** entry)54 int TextOut::MatchCommand( const char *cmdname, int i1, SrEntry **entry )
55 {
56     if (i1 != TEXT_I_IGNORE) {
57 	char cmdbuf[256];
58 	sprintf( cmdbuf, "%s-%d", cmdname, i1 );
59 	if (!userops->Lookup( cmdbuf, entry )) return 0;
60 	return userops->Lookup( cmdname, entry );
61 	}
62     else {
63 	return userops->Lookup( cmdname, entry );
64 	}
65 }
66 
HasOp(const char * cmdname)67 int TextOut::HasOp( const char *cmdname )
68 {
69     SrEntry *entry;
70 
71     PutChar( 0 );
72     if (!userops && next)
73         return next->HasOp( cmdname );
74     return MatchCommand( cmdname, TEXT_I_IGNORE, &entry );
75 }
76 
77 //
78 // All of the PutOp commands use PutChar( 0 ) to flush any local buffers
79 //
PutOp(const char * cmdname,char * s1,char * s2,int i1)80 int TextOut::PutOp( const char *cmdname, char *s1, char *s2, int i1 )
81 {
82     SrEntry *entry;
83 
84     PutChar( 0 );
85     if (!userops && next)
86         return next->PutOp( cmdname, s1, s2, i1 );
87     if (MatchCommand( cmdname, i1, &entry )) {
88 	/* Did not find, so do nothing */
89 	return PutOpError( cmdname, (char *)0 );
90 	}
91     else {
92 	PutCommand( (char *)entry->extra_data, s1, s2, (char *)0, (char *)0,
93 		    i1 );
94 	}
95     return 0;
96 }
97 
PutOp(const char * cmdname,char * s1,char * s2,char * s3,char * s4)98 int TextOut::PutOp( const char *cmdname,
99 		    char *s1, char *s2, char *s3, char *s4 )
100 {
101     SrEntry *entry;
102 
103     PutChar( 0 );
104     if (!userops && next)
105         return next->PutOp( cmdname, s1, s2, s3, s4 );
106     if (userops->Lookup( cmdname, &entry )) {
107 	/* Did not find, so do nothing */
108 	return PutOpError( cmdname, (char *)0 );
109 	}
110     else {
111 	PutCommand( (char *)entry->extra_data, s1, s2, s3, s4, 0 );
112 	}
113     return 0;
114 }
115 
PutOp(const char * cmdname,char * s1)116 int TextOut::PutOp( const char *cmdname, char *s1 )
117 {
118     SrEntry *entry;
119 
120     PutChar( 0 );
121     if (!userops && next)
122         return next->PutOp( cmdname, s1 );
123     if (userops->Lookup( cmdname, &entry )) {
124 	/* Did not find, so do nothing */
125 	return PutOpError( cmdname, (char *)0 );
126 	}
127     else {
128 	PutCommand( (char *)entry->extra_data, s1, (char *)0, (char *)0,
129 		    (char *)0, 0 );
130 	}
131     return 0;
132 }
133 
PutOp(const char * cmdname)134 int TextOut::PutOp( const char *cmdname )
135 {
136     SrEntry *entry;
137 
138     PutChar( 0 );
139     if (!userops && next)
140         return next->PutOp( cmdname );
141     if (userops->Lookup( cmdname, &entry )) {
142 	/* Did not find, so do nothing */
143 	return PutOpError( cmdname, (char *)0 );
144 	}
145     else {
146 	PutCommand( (char *)entry->extra_data, (char *)0, (char *)0,
147 		    (char *)0, (char *)0, 0 );
148 	}
149     return 0;
150 }
151 
PutOp(const char * cmdname,char * s1,int i1)152 int TextOut::PutOp( const char *cmdname, char *s1, int i1 )
153 {
154     SrEntry *entry;
155 
156     PutChar( 0 );
157     if (!userops && next)
158         return next->PutOp( cmdname, s1, i1 );
159     if (MatchCommand( cmdname, i1, &entry )) {
160 	/* Did not find, so do nothing */
161 	return PutOpError( cmdname, (char *)0 );
162 	}
163     else {
164 	PutCommand( (char *)entry->extra_data, s1, (char *)0, (char *)0,
165 		    (char *)0, i1 );
166 	}
167     return 0;
168 }
169 
170 /* Default error behavior for PutOp command not found.
171  */
PutOpError(const char * cmdname,const char * msg)172 int TextOut::PutOpError( const char *cmdname, const char *msg )
173 {
174 	if (msg) fprintf( stderr, "%s\n", msg);
175 	else
176 		fprintf( stderr, "Did not find definition for command %s\n", cmdname );
177 	return 1;
178 }
179 
SetDebug(int flag)180 int TextOut::SetDebug( int flag )
181 {
182     debug_flag = flag;
183     return 0;
184 }
185 
SetOutstream(OutStream * in_outs)186 int TextOut::SetOutstream( OutStream *in_outs )
187 {
188     out = in_outs;
189     if (debug_flag) out->Debug(debug_flag);
190     if (next) next->SetOutstream( in_outs );
191     return 0;
192 }
193 
Flush()194 int TextOut::Flush( )
195 {
196     return 0;
197 }
~TextOut()198 TextOut::~TextOut()
199 {
200 }
201 
202 #include <ctype.h>
203 
GetDQtext(char * cmdstring,char * out_str,int maxlen)204 char * TextOut::GetDQtext( char *cmdstring, char *out_str, int maxlen )
205 {
206     /* Skip the =, if any */
207     *out_str = 0;
208     if (*cmdstring == '=') cmdstring++;
209     if (*cmdstring++ != '"') return 0;
210     while (maxlen && *cmdstring != '"') {
211 	*out_str++ = *cmdstring++;
212 	maxlen--;
213 	}
214     *out_str = 0;
215     if (*cmdstring == '"') cmdstring++;
216     return cmdstring;
217 }
218 
219 /*
220  * Since we want to allow optional processing of new lines, we have a special
221  * output routine
222  */
223 
OutString(const char * string)224 int TextOut::OutString( const char *string )
225 {
226     char ch;
227     int  rc = 0;
228 
229     if (!string) return 0;
230     if (next) {
231       if (debug_flag) printf( "TextOut::Outstring to next TextOut\n" );
232       return next->OutString( string );
233     }
234 
235     if (debug_flag)
236       printf( "TextOut::Outstring to related Outstream %s\n", out->MyName() );
237 #ifdef FOO
238     if (!nl)
239 	rc = out->PutToken( 0, string );
240     else {
241 	while (!rc && (ch = *string++)) {
242 	    if (ch == '\n')
243 		rc = out->PutQuoted( 0, nl );
244 	    else
245 		rc = out->PutChar( ch );
246 	    }
247 	}
248 #else
249     if (!nl)
250 	rc = PutToken( 0, string );
251     else {
252 	while (!rc && (ch = *string++)) {
253 	    if (ch == '\n')
254 		rc = PutQuoted( 0, nl );
255 	    else
256 		rc = PutChar( ch );
257 	    }
258 	}
259 #endif
260 	if (rc) return err->ErrMsg( rc, "Error writing character" );
261 	else   return 0;
262 }
263 
264 /* Upper-case version of OutString */
OutStringUC(const char * string)265 int TextOut::OutStringUC( const char *string )
266 {
267     char ch;
268     int  rc = 0;
269 
270     if (next)
271     	return next->OutStringUC( string );
272     if (!nl) {
273       while (*string) {
274 	rc = out->PutChar( toupper( *string ) );
275 	string++;
276       }
277     }
278     else {
279 	while (!rc && (ch = *string++)) {
280 	    if (ch == '\n')
281 		rc = out->PutQuoted( 0, nl );
282 	    else
283 		rc = out->PutChar( toupper( ch ) );
284 	    }
285 	}
286 	if (rc) return err->ErrMsg( rc, "Error writing character" );
287 	else   return 0;
288 }
289 
290 /*
291  * This COULD use varargs or arg matching
292  */
PutCommand(char * cmdstring,char * s1,char * s2,char * s3,char * s4,int i1)293 int TextOut::PutCommand( char *cmdstring,
294 			 char *s1, char *s2, char *s3, char *s4, int i1 )
295 {
296     char ch, ch2;
297     int  l_was_par;
298     int  l_was_nl;
299 
300     if (!cmdstring) return 1;
301 
302     if (debug_flag)
303 	printf( "Processing command string %s\n", cmdstring );
304     /* Interpret and replace string */
305     while ((ch = *cmdstring++)) {
306 	if (ch == '%') {
307 	    ch2 = *cmdstring++;
308 	    if (debug_flag) printf( "Processing command %%%c\n", ch2 );
309 	    l_was_par	 = last_was_par;
310 	    last_was_par = 0;
311 	    l_was_nl     = last_was_nl;
312 	    last_was_nl  = 0;
313 	    switch (ch2) {
314 	        int regnum, r_quoted;
315 		case '%': out->PutChar( ch ); break;
316 	        case '1': OutString( s1 ); break;
317                 case '2': OutString( s2 ); break;
318                 case '3': OutString( s3 ); break;
319                 case '4': OutString( s4 ); break;
320   	        case 'a':
321 		  /* Get the register number and string value */
322 		  ch2 = *cmdstring++;
323 		  regnum = ch2 - '0';
324 		  if (regnum < 0 || regnum >= MAX_TEXT_REGNUM) return 1;
325 		  cmdstring = GetDQtext( cmdstring, cmdreg[regnum],
326 					 MAX_TEXT_REGISTER );
327 		  //printf( "stng=%s\n", cmdreg[regnum] );
328 		  /* if cmdstring == 0, an error was found */
329 		  if (!cmdstring) return 1;
330 
331 		  break;
332 	        case 'r':
333 	          /* Get the register number and output the value in that
334 		     register */
335 		  ch2 = *cmdstring++;
336 		  r_quoted = (ch2 == 'q');
337 		  if (r_quoted) ch2 = *cmdstring++;
338 		  //if (debug_flag & r_quoted)
339 		  //  printf( "quoted output\n" );
340 		  regnum = ch2 - '0';
341 		  if (regnum < 0 || regnum >= MAX_TEXT_REGNUM) return 1;
342 		  //printf( "stng_out%d=%s\n", regnum, cmdreg[regnum] );
343 		  if (r_quoted)
344 		    out->PutQuoted( 0, cmdreg[regnum] );
345 		  else
346 		    out->PutToken( 0, cmdreg[regnum] );
347 		  break;
348 
349    	        case 'e':
350 		  /* Get replacement string; (="...") set lfont */
351 		  cmdstring = GetDQtext( cmdstring, font_str, 32 );
352 		  /* if cmdstring == 0, an error was found */
353 		  if (!cmdstring) return 1;
354 		  lfont = font_str;
355 		  break;
356 		case 'f': if (lfont) { out->PutToken( 0, lfont ); }
357 			  lfont = 0;
358 			  break;
359 	        case 'i': out->PutInt( i1 ); break;
360    	        case 'm':
361 		  /* Get mode string; (="...") set mode */
362 		  cmdstring = GetDQtext( cmdstring, mode_str, 32 );
363 		  /* if cmdstring == 0, an error was found */
364 		  if (!cmdstring) return 1;
365 		  // An empty mode_str give a null mode (simplifies binary
366 		  // modes)
367 		  if (*mode_str)
368 		    mode = mode_str;
369 		  else
370 		    mode = 0;
371 		  break;
372 	        case 'n': if (!l_was_nl) {
373 		             out->PutToken( 0, newline_onoutput );
374 			     UpdateNL( 1 );
375 		             }
376 		    break;
377  	        case 'N':
378 		    if (*cmdstring == '=') {
379 			cmdstring = GetDQtext( cmdstring, newline_str, 32 );
380 			if (!cmdstring) return 1;
381 			nl = newline_str;
382 			}
383 		    else
384 			nl = 0;
385 		    break;
386 
387 		case 'p':
388 			  if (l_was_par == 0)
389 			      PutOp( "end_par" );
390 			  last_was_par = 1;
391 			  break;
392 	        case 'u':
393 	                  ch2 = *cmdstring++;
394 			  switch (ch2) {
395 			  case '1': OutStringUC( s1 ); break;
396 			  case '2': OutStringUC( s2 ); break;
397 			  case '3': OutStringUC( s3 ); break;
398 			  case '4': OutStringUC( s4 ); break;
399 			  }
400 		          break;
401 		default:  out->PutChar( '%' );
402 			  out->PutChar( ch2 );
403 			  break;
404 		}
405 	    }
406 	else {
407 	    if (nl && ch == '\n') {
408 		out->PutToken( 0, nl );
409 		}
410 	    else {
411 		out->PutChar( ch );
412 		}
413 	    if (ch)
414 	      UpdateNL( ch == '\n' );
415 	    last_was_par = 0;
416 	    }
417 	}
418     return 0;
419 }
420 
421 /* These are the default versions.  */
PutChar(const char ch)422 int TextOut::PutChar( const char ch )
423 {
424   if (debug_flag && ch) printf( "Generic TextOut::PutChar of %c\n", ch );
425     /* This really needs to remember newlines in last_was_nl */
426     if (ch)
427       UpdateNL( ch == '\n' );
428     if (next) return next->PutChar( ch );
429     else      return out->PutChar( ch );
430 }
431 
PutToken(int nsp,const char * token)432 int TextOut::PutToken( int nsp, const char *token )
433 {
434     /* This really needs to remember newlines in last_was_nl */
435     if (token && token[0])
436       UpdateNL( token[strlen(token)-1] == '\n' );
437     if (next) return next->PutToken( nsp, token );
438     else      return out->PutToken( nsp, token );
439 }
440 
PutTokenRaw(int nsp,const char * token)441 int TextOut::PutTokenRaw( int nsp, const char *token )
442 {
443     /* This really needs to remember newlines in last_was_nl */
444     return PutToken(nsp, token);
445 }
446 
PutQuoted(int nsp,const char * token)447 int TextOut::PutQuoted( int nsp, const char *token )
448 {
449   if (debug_flag && token && *token)
450     printf( "Generic puttoken for %s\n", token );
451   if (token && *token) UpdateNL( 0 );
452   if (next) return next->PutQuoted( nsp, token );
453   else      return out->PutQuoted( nsp, token );
454 }
455 
PutNewline()456 int TextOut::PutNewline( )
457 {
458   UpdateNL( 1 );
459   if (next) return next->PutNewline();
460   else      {
461     char *p = newline_onoutput;
462     int rc;
463     while (*p) if ((rc = out->PutChar( *p++ ))) break;
464     return rc;
465   }
466 }
SetNewlineString(const char * str)467 void TextOut::SetNewlineString( const char *str )
468 {
469   strncpy( newline_onoutput, str, 3 );
470   newline_onoutput[2] = 0;
471 }
472 
SetMode(const char * str)473 const char *TextOut::SetMode( const char * str )
474 {
475   const char * savemode = mode;
476   mode = str;
477   // printf( "Setting mode to %s\n", str ? str : "NULL" );
478   return (const char *)savemode;
479 }
480 
SetRegisterValue(int regnum,const char * val)481 int TextOut::SetRegisterValue( int regnum, const char * val )
482 {
483   //printf( "reg val %d is %s\n", regnum, val );
484   if (strlen(val) >= MAX_TEXT_REGISTER) return 1;
485   strncpy( cmdreg[regnum], val, MAX_TEXT_REGISTER-1 );
486   return 0;
487 }
488 
489 //
490 // This is used to propagate newline changes down the list
491 // Do we need up as well?
492 //
UpdateNL(int in_last_was_nl)493 int TextOut::UpdateNL( int in_last_was_nl )
494 {
495     if (next) next->UpdateNL( in_last_was_nl );
496     last_was_nl = in_last_was_nl;
497     return 0;
498 }
499 
500 /*
501    The format of the command file is
502    command-name command-string
503    # is used for comments, and \ is used (at the end of the line ONLY) to
504    continue to the next line.
505 
506    Note that the user can define ANY commands; they are added to the table.
507 
508    Note that command-names can have the form
509    foo-3
510    This will match a command name of foo and an integer value of 3.  This
511    is a simple way to specify different behavior for different values
512    of an input integer (e.g., heading level or itemize depth).
513  */
514 #define MAX_NAME 128
515 #define MAX_COMMAND 1024
ReadCommands(InStream * ins)516 int TextOut::ReadCommands( InStream *ins ){
517     char    ch;
518     char    nametoken[MAX_NAME], *namep;
519     char    *p, command[MAX_COMMAND], *pold;
520     int     ln, nsp, maxlen;
521     SrEntry *entry;
522     int     prepend, postpend;   // indicate whether the command is added
523     				 // or replaces existing command.
524 
525     ins->SetBreakChar( '-', BREAK_ALPHA );
526     ins->SetBreakChar( '_', BREAK_ALPHA );
527     ins->SetBreakChar( '+', BREAK_ALPHA );
528     while (!ins->GetToken( MAX_NAME, nametoken, &nsp )) {
529 	/* Skip comments */
530 	if (nametoken[0] == '#') {
531 	    ins->SkipLine();
532 	    continue;
533 	    }
534 	// Check for prepend/postpend markers
535 	prepend  = 0;
536 	postpend = 0;
537 	namep    = nametoken;
538 	if (namep[0] == '+') {
539 	    prepend = 1;
540 	    namep++;
541 	    }
542 	ln = strlen( nametoken );
543 	if (nametoken[ln-1] == '+') {
544 	    postpend = 1;
545 	    nametoken[ln-1] = 0;
546 	    }
547 	if (prepend && postpend) {
548 	    // Should issue error message
549 	    postpend = 0;
550 	    }
551 
552 	/* Skip the leading space (upto newline) */
553 	while (!ins->GetChar( &ch ) && isspace( ch ) && ch != '\n')
554 	  ;
555         ins->UngetChar( ch );
556 
557 	// Get command string for name
558 	p      = command;
559 	maxlen = MAX_COMMAND - 1;
560 	// Allow \<space> and \<newline> as special characters.
561 	while (!ins->GetChar( &ch ) && maxlen > 0) {
562 	    if (ch == '\n') break;
563 	    if (ch == '\\') {
564 		ins->GetChar( &ch );
565 		if (ch == '\n')
566 		    continue;
567 		else if (ch != ' ') {
568 		    *p++ = '\\';
569 		    maxlen--;
570 		    }
571 		}
572 	    *p++ = ch;
573 	    maxlen--;
574 	    }
575 	if (ch != '\n' && maxlen == 0) {
576 	  // command too long
577 	  return 1;
578 	}
579 	*p = 0;
580 	// Add to definitions.
581 	// namep is nametoken, with optional + stripped off.
582 	if (!userops->Insert( namep, &entry )) {
583 	    /* Install the definition of this command in the table */
584 	    /* Handle the case of prepending or postpending a definition */
585 	    if (entry->extra_data) {
586 	    	pold = (char *)entry->extra_data;
587 	        if (prepend) {
588 	            p = new char[strlen(command) + strlen(pold) + 1];
589 	            if (!p) return 1;
590 	            strcpy( p, command );
591 	            strcat( p, pold );
592 	            }
593 	        else if (postpend) {
594 	            p = new char[strlen(command) + strlen(pold) + 1];
595 	            if (!p) return 1;
596 	            strcpy( p, pold );
597 	            strcat( p, command );
598 	            }
599 	        else {
600 	            p = new char[strlen(command)+1];
601 	            if (!p) return 1;
602 	            strcpy( p, command );
603 	            }
604 	        delete (char*)entry->extra_data;
605 	        }
606 	    else {
607 	            p = new char[strlen(command)+1];
608 	            if (!p) return 1;
609 	            strcpy( p, command );
610 	        }
611 	    entry->extra_data = (void *)p;
612 	    }
613         }
614     return 0;
615 }
616 
Debug(int flag)617 int TextOut::Debug( int flag )
618 {
619   int old_flag = debug_flag;
620   debug_flag = flag;
621   return old_flag;
622 }
623 
624 /*
625    It is sometimes useful to define an output stream that is really a textout
626    object.
627  */
TextOutStrm(OutStream * outs)628 TextOutStrm::TextOutStrm( OutStream * outs )
629 {
630     out		 = outs;
631     next	 = 0;
632 
633     lfont	 = 0;
634     nl		 = 0;
635     last_was_nl	 = 0;
636     last_was_par = 0;
637     debug_flag	 = 0;
638     userops	 = 0;
639     strcpy( newline_onoutput, "\n" );
640 }
~TextOutStrm()641 TextOutStrm::~TextOutStrm()
642 {
643     if (out) delete out;
644     out = 0;
645 }
646