SOUP: the SOUrce Printer

This is a port from the soup project which I made under Linux with the Mocka compiler. Since this is a neat program that saves a lot of time when preparing hardcopy of sources, I decided to port it to DOS.

That was easier said than done. End-of-File handling under DOS is rather different from Unix. For one: it's less reliable, so I needed to practically completely rewrite the source to get something that barely comes close to the original, running under Linux.

Anyway, I succeeded and I now have a satisfactory port of soup to DOS.

The source of soup.

MODULE soup07;

(*     This software is released under the rules of the GNU GPL.
       Please feel free to improve this software, if needed. You may even port this source to a
       lesser language like C. This software comes without any warranty of any kind.            *)

(* 02 : Get a working program, no frills.                                   Apr 20, 2003 *)
(* 03 : Added MakeDate, CardToString                                        Apr 25, 2003 *)
(*      We have an odd bug, causing one extra formfeed after the last page.
        Solved: In 'WrapUp', the linefeeds were one too far, which
        triggered an extra call to 'Skip'                                   Apr 29, 2003 *)
(* 04 : Add /usr/local/soup/soup.rc configuration file,
        Add ErrorMessage function                                           May  9, 2003 *)
(* 05 : Add possibility to force formfeeds in sourcefile, change setup
        file to '/usr/local/prut/soup.rc', make Soup determine if input
        is from file or stdin                                               May 26, 2003 *)
(* 06 : Make soup emit an ASCII.CR between last ASCII.FF and Printer
        Reset string, If FileIO=TRUE then the projectname is derived from
        the name of the inputfile.                                          Jun  2, 2003 *)
(* 07 : Port SOUP to DOS with FST Modula-2 compiler
        Wow, this was tough.. I had to rewrite just about everything.       Oct  9, 2003 *)


IMPORT ASCII;

FROM    InOut               IMPORT  CloseInput, Done, Read, ReadCard, ReadLine, ReadString, RedirectInput,
                                    termCH, Write, WriteString, WriteLn, WriteCard;
FROM    NumberConversion    IMPORT  CardToString;
FROM    Strings             IMPORT  Append, Assign, CompareStr, Length, Pos;
FROM    SYSTEM              IMPORT  ASSEMBLER;
FROM    System              IMPORT  GetArg, Terminate;
FROM    TimeDate            IMPORT  GetTime, Time;


TYPE    ControlString = ARRAY [0..63] OF CHAR;

CONST   maxLINE    =  82;    maxPOS     = 120;
        topMARGIN  =   1;    leftMARGIN =   8;
        StdErr     =   2;

VAR     line, page, Xpos, LeftMargin, MaxLine,
        MaxPos, TopMargin, FillUp               : CARDINAL;
        SomethingPrinted, FirstLineDone         : BOOLEAN;
        FirstLine                               : ARRAY [0..39] OF CHAR;
        DateString                              : ARRAY [0..19] OF CHAR;
        buffer                                  : ARRAY [0..255] OF CHAR;
        string                                  : ARRAY [0..15] OF CHAR;
        PrReset, PrSetup, option                : ControlString;
   
This is all rather straightforward. It's just Modula-2 like with other compilers.
Under Mocka the first function was ErrorMessage, but I had to eliminate this since all data would invariably end up on the printed paper... Instead of ErrorMessage now comes Eject.

After ErrorMessage, the configuration file readers are defined. GetCtrlStr is used to read the printer setup string from the C:\etc\soup.rc file.
Since the weirdest control sequences exist for many printers, I could not use simple ways to construct ESCape sequences. Therefore I had to invent the tricks with Escape, Space and Control. See below and in the file soup.rc.

PROCEDURE Eject;        (*  Have the printer eject one sheet of paper   *)

BEGIN
    ASM
        MOV   DL, 0CH
        MOV   AH, 2
        INT   021H
    END
END Eject;


PROCEDURE GetCtrlStr (VAR str     : ARRAY OF CHAR);

VAR   ch             : CHAR;

BEGIN
    str [0] := 0C;      (* Clear string     *)
    LOOP
        ReadString (option);
        IF    CompareStr (option, 'END')    = 0  THEN  EXIT  END;
        IF    CompareStr (option, '<ESC>')  = 0  THEN  Append (str, 33C)
        ELSIF CompareStr (option, '<_>')    = 0  THEN  Append (str, ' ')
        ELSIF CompareStr (option, '<Ctrl>') = 0  THEN
            REPEAT
                Read (ch)
            UNTIL ch > ' ';
            option [0] := CHR (ORD (ch) - ORD ('@'));
            option [1] := 0C;
            Append (str, option)
        ELSE
            Append (str, option)
        END
    END
END GetCtrlStr;


PROCEDURE ConfigureSoup () : BOOLEAN;

BEGIN
    RedirectInput ('c:\etc\soup.rc');
    IF Done THEN
        REPEAT
            ReadString (option)
        UNTIL CompareStr (option, 'BEGIN') = 0;
        LOOP
            ReadString (option);
            IF    CompareStr (option, 'END')        = 0  THEN  EXIT
            ELSIF CompareStr (option, 'PrReset')    = 0  THEN  GetCtrlStr (PrReset)
            ELSIF CompareStr (option, 'PrSetup')    = 0  THEN  GetCtrlStr (PrSetup)
            ELSIF CompareStr (option, 'LeftMargin') = 0  THEN  ReadCard (LeftMargin)
            ELSIF CompareStr (option, 'TopMargin')  = 0  THEN  ReadCard (TopMargin)
            ELSIF CompareStr (option, 'MaxPos')     = 0  THEN  ReadCard (MaxPos)
            ELSIF CompareStr (option, 'MaxLine')    = 0  THEN  ReadCard (MaxLine)
            ELSE
                WriteString ('Invalid parameter in "c:\etc\soup.rc".');
                WriteLn;
                WriteString ('Reading aborted, assuming HP Laserjet IIP.');
                WriteLn;
                RETURN FALSE
            END
        END;
        CloseInput
    ELSE
        WriteString ("No file 'c:\etc\soup.rc'. Assuming HP Laserjet IIP.");
        WriteLn;
        RETURN FALSE
    END;
    RETURN TRUE
END ConfigureSoup;
   
As you see, the file references have been adjusted for DOS style and the errormessages are now inside the functions.

ConfigureSoup tries to open the rc file and if that fails, it sends an errormessage and falls back to HP LJ mode.
If the soup.rc file exists, it is opened and read. The active part of soup.rc is enclosed between a 'BEGIN' and an 'END'. Everything else (and there's a lot) is comment and explanation.

PROCEDURE Spaces (n  : CARDINAL);        (* Print n spaces  *)

VAR    i         : CARDINAL;

BEGIN
    FOR i := 1 TO n DO
        Write (' ');
        INC (Xpos)
    END
END Spaces;


PROCEDURE Skip;

BEGIN
    WriteLn;                    Xpos := 0;
    Spaces (LeftMargin);        WriteString (FirstLine);
    Spaces (FillUp);            WriteString ('page :');         WriteCard (page, 5);
    Spaces (FillUp);            WriteString (DateString);       Eject;

    SomethingPrinted := FALSE;  INC (page);     line := 0;
    LineFeed (TopMargin)
END Skip;


PROCEDURE LineFeed (n  : CARDINAL);

BEGIN
    Xpos := 0;
    WHILE n > 0 DO
        WriteLn;
        INC (line);
        IF line > MaxLine THEN  Skip  END;
        DEC (n)
    END
END LineFeed;


PROCEDURE WriteLine (str : ARRAY OF CHAR);

VAR   i             : CARDINAL;
      ch            : CHAR;

BEGIN
    Spaces (LeftMargin);
    i := 0;
    LOOP
        ch := str [i];
        IF ch = 0C          THEN  EXIT        END;
        IF ch # ASCII.EOF   THEN  Write (ch)  END;
        INC (Xpos);
        IF Xpos > MaxPos THEN
            LineFeed (1);
            Spaces (LeftMargin)
        END;
        INC (i);
        IF i > HIGH (str) THEN  EXIT  END
    END;
    SomethingPrinted := TRUE;
    LineFeed (1)
END WriteLine;
   
I could omit the CardToString function, since that is part of the FST compiler.

The 'WriteLine' function is a nearly complete rewrite. The lines look the same, or at least mixed up, but the heart of this function needed to be changed to get things working under DOS.

What follows is a stripped version of 'Soup for Linux' MakeDate. DOS MakeDate will produce a simple date string in one format only.

PROCEDURE MakeDate (VAR str  : ARRAY OF CHAR);

VAR     t                               : Time;
        jaar, maand, dag, uur, minuut   : CARDINAL;
        substr                          : ARRAY [0..1] OF CHAR;

BEGIN
    str [0] := 0C;
    GetTime (t);
    uur := t.minute DIV 60;                 minuut := t.minute MOD 60;
    dag := t.day MOD 32;                    maand := (t.day DIV 32) MOD 16;
    jaar := (t.day DIV 512) + 1900 - 2000;
    CASE maand OF
       1:   Append (str, 'Jan');        |
       2:   Append (str, 'Feb');        |
       3:   Append (str, 'Mar');        |
       4:   Append (str, 'Apr');        |
       5:   Append (str, 'May');        |
       6:   Append (str, 'Jun');        |
       7:   Append (str, 'Jul');        |
       8:   Append (str, 'Aug');        |
       9:   Append (str, 'Sep');        |
      10:   Append (str, 'Oct');        |
      11:   Append (str, 'Nov');        |
      12:   Append (str, 'Dec');
    END;
    Append (str, ' ');
    CardToString (dag, substr, 2);
        Append (str, substr);                           Append (str, ', 20');
    CardToString (jaar, substr, 2);
        IF jaar < 10 THEN  substr [0] := '0'  END;
        Append (str, substr);                           Append (str, ' ');
    CardToString (uur, substr, 2);                      Append (str, substr);       Append (str, ':');
    CardToString (minuut, substr, 2);
        IF minuut < 10 THEN  substr [0] := '0'  END;    Append (str, substr);
END MakeDate;


PROCEDURE Init;

VAR     count       : CARDINAL;
        Option      : ARRAY [0..31] OF CHAR;

BEGIN
    FirstLineDone := FALSE;
    IF ConfigureSoup () = FALSE  THEN
        MaxLine := maxLINE;             TopMargin  := topMARGIN;
        MaxPos  := maxPOS;              LeftMargin := leftMARGIN;
        PrReset [0] := ASCII.ESC;       Append (PrReset, 'E');          (*  Printer reset  *)
        PrSetup [0] := ASCII.ESC;       Append (PrSetup, '(10U');
        Append (PrSetup, ASCII.ESC);    Append (PrSetup, '(s0p16.67h8.5v0s0b0T');
        Append (PrSetup, ASCII.ESC);    Append (PrSetup, '&l8D')
    END;
    GetArg (Option, count);
    IF count > 0 THEN
        RedirectInput (Option);
        IF Done THEN
            IF Pos ('mod', Option) > HIGH (Option) THEN
                Assign (Option, FirstLine);
                FirstLineDone := TRUE
            END
        ELSE
            WriteString ('Input file does not exist. Aborting.');
            WriteLn;
            Terminate (1)
        END
    ELSE
        WriteString ('No input file specified.');
        WriteLn;
        Terminate (2)
    END;
    WriteString (PrReset);              WriteString (PrSetup);
    line := 0;
    page := 1;
    Xpos := 0;
    LineFeed (TopMargin);
    SomethingPrinted := FALSE;
    MakeDate (DateString)
END Init;


PROCEDURE WrapUp;

BEGIN
    IF SomethingPrinted = TRUE  THEN
        LineFeed (MaxLine - line);
        Skip
    END;
    Write (ASCII.ESC);          Write ('E');    (*  Printer reset             *)
    Write (ASCII.EOF)
END WrapUp;
   
In the DOS port, the two PROCEDURES which were commented out have been left out. So we start with the main loops.
In the top window is the Linux/Mocka source for the latest version of soup for Linux. In the bottom window is the source for the DOS version of soup. Now you can better compare the versions.
BEGIN                    (*  Mocka/Linux version  *)
   Init;
   IF NOT FileIO THEN
      ReadLine (FirstLine);	WriteLine (FirstLine)
   END;
   FillUp := (MaxPos - 12 - Length (FirstLine) - Length (DateString)) DIV 2;
   REPEAT
      ReadLine (buffer);
      WriteLine (buffer);
      IF pos ('<FormFeed>', buffer) < HIGH (buffer) THEN
         LineFeed (MaxLine - line);
	 Skip
      END
   UNTIL exhausted;
   WrapUp;
END soup06.

-----------------------------------------------------------------------------

BEGIN                    (*  FST/DOS version      *)
    Init;
    IF FirstLineDone = FALSE THEN  ReadLine (FirstLine)  END;
    WriteLine (FirstLine);
    FillUp := (MaxPos - 12 - Length (FirstLine) - Length (DateString)) DIV 2;
    REPEAT
        ReadLine (buffer);
        WriteLine (buffer);
        IF Pos ('<FormFeed>', buffer) < HIGH (buffer) THEN
            LineFeed (MaxLine - line);
            Skip
        END
    UNTIL termCH = ASCII.EOF;
    WrapUp;
    CloseInput
END soup07.
   

The soup.rc configuration file

Below you see the file soup.rc listed. It is full with comments, so I won't explain anything. It should be obvious after some thoughts.

Soup.rc

This is the configuration file for the program 'soup'. 

All parameters in this file are entered between the 'BEGIN' and 'END' 
statements below. All text before 'BEGIN' and after 'END' will be ignored.

Please retain the syntax of the parameter settings. The strings can be of
arbitrary length for different printers. Therefore the parameters PrReset
and PrSetup are considered as 'compound statements'. The keyword starts the
interpretation of the string and it is terminated by the first encountered
'END' statement.

Do not place comments in the section between BEGIN/END. If you need to
supply comments, clarifications or (even worse): names, please do so 
outside the 'payload' of this configuration file.

BEGIN

TopMargin	  1
LeftMargin	  8
MaxLine		 82
MaxPos		120

PrReset		<ESC> E
		END

PrSetup		<ESC> (10U
		<ESC> (s0p16.67h8.5v0s0b0T
		<ESC> &l8D
		END

END
END

TopMargin	nr of lines to skip at top of page

LeftMargin	nr of spaces to insert at start of each line

MaxLine		when this line is reached, soup initiates a SKIP procedure

MaxPos		if a very long line is encountered, and this horizontal
		position is reached, then a line wrap is forced, but WITH
		the specified indentation

PrReset		Freeform string to initiate a printer RESET by software
		This string must be ended by an END statement.

PrSetup		Free form string to setup the printer for the desired font
		and lettertype.
		This string can be any length and hence must be ended by 
		an END statement.

If you need special tokens, make a choice from the following list:

   <ESC> = ASCII Escape character (27)
   <_>   = ASCII space            (32)

   <Ctrl> X = Control character where X = '@..Z' and '[\]^_'
   	      The <Ctrl> must be entered as such in this file. 
	      The 'X' is just an example.

<Ctrl> @ =  0	  <Ctrl> H =  8      <Ctrl> P = 16     <Ctrl> X = 24
<Ctrl> A =  1	  <Ctrl> I =  9      <Ctrl> Q = 17     <Ctrl> Y = 25
<Ctrl> B =  2	  <Ctrl> J = 10      <Ctrl> R = 18     <Ctrl> Z = 26
<Ctrl> C =  3	  <Ctrl> K = 11      <Ctrl> S = 19     <Ctrl> [ = 27
<Ctrl> D =  4	  <Ctrl> L = 12      <Ctrl> T = 20     <Ctrl> \ = 28
<Ctrl> E =  5	  <Ctrl> M = 13      <Ctrl> U = 21     <Ctrl> ] = 29
<Ctrl> F =  6	  <Ctrl> N = 14      <Ctrl> V = 22     <Ctrl> ^ = 30
<Ctrl> G =  7	  <Ctrl> O = 15      <Ctrl> W = 23     <Ctrl> _ = 31

DO make sure there is a SPACE between the '<Ctrl>' prefix and the control
code. The space is ESSENTIAL. Also around the <ESC> and <_> special tokens.

I know this is a lot of fuzz, but I have to take into account that the makers
of printer control codes used every conceivable character for some silly
printer command. So I have to revert to this kind of measure.

If you enter a string like:

   <ESC> E

the printer command string will consist of just TWO characters: ESC and E.

If you enter a string like

   <ESC> ( <Ctrl> P <_> <Ctrl> @ <Ctrl> @

the setup string will contain 6 tokens: 27, '(', 16, ' ', 0, 0


This is the end of the configuration file for soup, which should be located as

     /usr/local/prut/soup.rc

In case of problems, contact the maintainer of this executable.
   

Page created March 2004 and