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.
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  := 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  := CHR (ORD (ch) - ORD ('@')); option  := 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
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  := 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' 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' 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  := ASCII.ESC; Append (PrReset, 'E'); (* Printer reset *) PrSetup  := 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.
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