Tell a printer she's a plotter!
It is possible to use HPGL commands and have a laserprinter produce plotter artwork. Please read the other
file for more details. This file is an example of how to use a high level language for producing high volumes
of production administration sheets in the shortest time.
I like using Modula-2 because it is self explanatory: the comments are embedded in the procedure names.
Mostly.
As each Modula-2 program, this one too starts with the declarations:
Module Sheets06 (yes, I made 5 buggy versions before..)
MODULE Sheets06; (* Version 0.1 : first attempt to get it working  OK : 06-06-2000 Version 0.2 : Add some Zing to it all OK : 07-06-2000 Version 0.3 : Make it output the production result sheets OK : 08-06-2000 Version 1.0 : Same as 0.3, debugged and printing all 5 OK : 08-06-2000 Version 1.1 : Add QA visual inspection sheet OK : 08-06-2000 Version 1.2 : Add monthly production reports OK : 09-06-2000 Version 1.3 : Add titles for the monthlyproduction reports OK : 09-06-2000 *)If you look at the version history, it will be clear that I program in small steps. Get it working, add some make-up, make it print a few sheets, get the obvious errors out, add some more functionality, add some more make-up. As a QA engineer, my motto is simple:
The IMPORT, TYPE and VAR sections.
FROM ASCII IMPORT EOL, ESC, FF;
FROM FileSystem IMPORT Close, File, Lookup, ReadChar, Response;
FROM InOut IMPORT CloseInput, CloseOutput, Done, RedirectInput,
RedirectOutput, Read,ReadString, Write, WriteCard,
WriteLn, WriteLine, WriteString;
FROM NumberConversion IMPORT StringToCard;
FROM Strings IMPORT CompareStr;
FROM System IMPORT GetArg, Terminate;
FROM SYSTEM IMPORT ASSEMBLER;
FROM TimeDate IMPORT GetTime, Time;
FROM Xchar IMPORT UpperString;
TYPE PrinterStatus = (Ready, Offline);
OneWord = ARRAY [0..31] OF CHAR;
VAR WorkDay : ARRAY [1..31] OF BOOLEAN;
Word : OneWord;
Month, Year, MaxDay,
MaxLine, ActiveDays : CARDINAL;
Magic : BOOLEAN;
Here ends the definition and declarations part. From now, only code is generated. We start out with the easy
part: the contact with the user. The user is always doing it's best to try to fool the software. So, the
software must be foolproof, which is a difficult task, since the development of the fools has not stopped
either. Nowadays, better fools are being made than ever before in history. I dare say that the quality of
fools is at a peak. Lucky for us, the majority of fools are using Windows, so most of the target audience is
knowledgable. But you never know: fools easily loose their way and end up testing a fine piece of code.
Talk to the user via StdOut.
PROCEDURE UserMessage (Number : CARDINAL);
BEGIN
WriteLn;
CASE Number OF
0 : WriteLn;
WriteLine ("Thank you for using this software. This is GNU GPL style FREE SOFTWARE.");
WriteLine ("This software should not be used outside the factory of Ushio Tilburg.");
WriteLn;
WriteLine ("CopyLeft 2000, Ushio Europe (Tilburg) BV, 5046 AT 14, The Netherlands");
|
1 : WriteLine ("One of the '.DAT' files is not present. Program aborted.");
UserMessage (0);
|
2 : WriteLine ("The printer does not respond. Please check.");
UserMessage (0);
|
3 : WriteLine ("Sorry, I cannot process dates before May 1, 2000.");
UserMessage (0);
|
4 : WriteLine ("Usage instructions: SHEETS <Month> <Year> [EXCLUDE <list of dates>]");
WriteLn;
WriteLine ("After 'EXCLUDE' you can mention the days without scheduled production.");
WriteLn;
WriteLine ("Example : Sheets september 2000 exclude 12 13 25");
WriteLn;
WriteLine ("Please consult the manual for more details. If that fails, consult jv.");
UserMessage (0);
|
5 : WriteLine ("The month you specified is not part of the Julian calendar.");
WriteLine ("Please refrain from using oriental month designators.");
UserMessage (4);
|
6 : WriteLine ("Error while entering numbers to EXCLUDE. Please adjust and retry.");
UserMessage (4);
|
7 : WriteLine ("Could not open file 'Monthly.Lst'. Program aborted.");
UserMessage (0)
END;
WriteLn
END UserMessage;
Telling time.
The software needs to know which month we are going to process. To make life easier on the user, only the first three letters have significance. Why three and not four? Because of May.
PROCEDURE FindMonth (Month : ARRAY OF CHAR) : CARDINAL;
VAR month : ARRAY [0..2] OF CHAR;
Number, n : CARDINAL;
BEGIN
FOR n := 0 TO 2 DO
month [n] := CAP (Month[n]); (* Just use first 3 letters of month *)
END;
IF CompareStr (month, 'JAN') = 0 THEN
Number := 1
ELSIF CompareStr (month, 'FEB') = 0 THEN
Number := 2
ELSIF CompareStr (month, 'MAR') = 0 THEN
Number := 3
ELSIF CompareStr (month, 'APR') = 0 THEN
Number := 4
ELSIF CompareStr (month, 'MAY') = 0 THEN
Number := 5
ELSIF CompareStr (month, 'JUN') = 0 THEN
Number := 6
ELSIF CompareStr (month, 'JUL') = 0 THEN
Number := 7
ELSIF CompareStr (month, 'AUG') = 0 THEN
Number := 8
ELSIF CompareStr (month, 'SEP') = 0 THEN
Number := 9
ELSIF CompareStr (month, 'OCT') = 0 THEN
Number := 10
ELSIF CompareStr (month, 'NOV') = 0 THEN
Number := 11
ELSIF CompareStr (month, 'DEC') = 0 THEN
Number := 12
ELSE
UserMessage (5);
Terminate (5)
END;
RETURN Number
END FindMonth;
Check the printer
We are going to rely on a working laserprinter. It's going to output around 50 sheets of paper, unattendedly. So we want to make sure it is connected and responding. That's why I used some InLine assembly to interrogate the machine via INT 17h.
PROCEDURE CheckPrinter () : PrinterStatus;
VAR Status : CARDINAL;
BEGIN
ASM
MOV AH, 1
MOV DX, 0
INT 17H
MOV AL, AH
MOV AH, 0
MOV [Status], AX
END;
IF Status = 90H THEN
RETURN Ready
ELSE
RETURN Offline
END
END CheckPrinter;
DOW: Day of week
We need to know what kind of day the first of this month is, in order to isolate the working days from the saturday and sundays. For this, we need to know how many days have passed since May 1, 2000 (which was a Monday).
PROCEDURE DayOfWeek (Month, Year : CARDINAL) : CARDINAL;
VAR month, year, days : CARDINAL;
BEGIN
month := 5;
year := 2000;
days := 0;
LOOP
IF (month = Month) AND (year = Year) THEN EXIT END;
CASE month OF
1,3,5,7,8,10,12 : INC (days, 31); |
4,6,9,11 : INC (days, 30); |
2 : IF (year MOD 4) = 0 THEN
INC (days, 29)
ELSE
INC (days, 28)
END
END;
INC (month);
IF month = 13 THEN
month := 1;
INC (year)
END
END;
RETURN (days MOD 7) + 1; (* Monday = 1, Sunday = 7 *)
END DayOfWeek;
Drawing lines in sheets
We must be able to draw a sheet of lines and other artwork without using I/O redirection. When we are in the
midst of a subroutine, we will have redirected output to the printer and redirected input to a customisation
file. So we need a way to pump out data to the printer without having to close and re-open files through
piping.
The DrawSheet procedure does just that: it opens a file in binary mode and transfers it character by character
to the printer. This procedure may only be used when the standard output (stdout) has been redirected to the
printer.
PROCEDURE DrawSheet (SheetName : ARRAY OF CHAR);
VAR ch : CHAR;
InFile : File;
BEGIN
Lookup (InFile, SheetName, FALSE);
IF InFile.res = notdone THEN
CloseOutput; (* If file not found, tell the moron *)
UserMessage (1);
Terminate (1)
END;
ReadChar (InFile, ch);
WHILE InFile.eof = FALSE DO
Write (ch); (* Write to (redirected) StdOut *)
ReadChar (InFile, ch) (* Fetch new token from file *)
END;
Close (InFile) (* If done, close file *)
END DrawSheet;
Write the month to paper
WriteMonth should be clear. If it isn't, send a mail to a friend.
PROCEDURE WriteMonth;
BEGIN
CASE Month OF
1 : WriteString ("January"); |
2 : WriteString ("February"); |
3 : WriteString ("March"); |
4 : WriteString ("April"); |
5 : WriteString ("May"); |
6 : WriteString ("June"); |
7 : WriteString ("July"); |
8 : WriteString ("August"); |
9 : WriteString ("September"); |
10 : WriteString ("October"); |
11 : WriteString ("November"); |
12 : WriteString ("December")
END;
WriteCard (Year, 5)
END WriteMonth;
Reading a name from (redirected) StdIn
ReadName is a handy procedure. It reads quoted text. It looks for a quote and considers all other characters as whitespace. After it has found the first quote (") it starts accepting everything it finds as being non-whitespace. All characters are stored in an array, until the second quote is found and control is transferred back to the caller.
PROCEDURE ReadName (VAR buffer : ARRAY OF CHAR);
VAR ch : CHAR;
n : CARDINAL;
BEGIN
n := 0;
REPEAT
Read (ch)
UNTIL ch = '"'; (* get rid of spaces *)
LOOP
Read (ch);
IF ch = '"' THEN EXIT END;
buffer [n] := ch;
INC (n)
END;
IF n <= HIGH (buffer) THEN
buffer [n] := 0C
END
END ReadName;
Making white paper appear 7% gray
From here, the actual sheets are being produced. The output of the program must comply to the rules of the HPGL: ASCII text only. So we must produce tekst like
PROCEDURE MakePRsheets; (* Make the Production Results sheets *)
VAR i, n, Xpos : CARDINAL;
BEGIN
IF CheckPrinter () = Offline THEN
UserMessage (2);
Terminate (2)
ELSE
RedirectOutput ("PRN")
END;
FOR n := 1 TO MaxLine DO
Write (ESC); WriteString ("%1B"); (* Make printer think it's a plotter *)
ActiveDays := 0;
DrawSheet ("ProdRep.Dat");
The general purpose part of sheet is printed
At this point, we have succesfully written the raw outline sheet to the printer. Now we will have to customize the sheet for this production facility and for this month. We start out by lifting the pen and moving it across the paper to a new position. There we lower the pen and write some text with the LB command. Don't forget to terminate it, since it will halt execution of the software here and now.
WriteString ("pu; pa 400, 10550; pd; lbT-"); WriteCard (n, 1); Write ("#");
WriteString ("pu; pa 5400, 10550; pd; lb"); WriteMonth; Write ("#");
WriteString ("si 0.12, 0.25;"); (* Define character height *)
Xpos := 1920;
FOR i := 1 TO MaxDay DO
IF WorkDay [i] = TRUE THEN
WriteString ("pu; pa"); WriteCard (Xpos, 5); WriteString (", 220; pd; lb");
WriteCard (i, 2); Write ("#");
WriteString ("pu; pa"); WriteCard (Xpos, 5); WriteString (", 10080; pd; lb");
WriteCard (i, 2); Write ("#");
INC (ActiveDays);
INC (Xpos, 250)
END
END;
WriteString ("si 0.12, 0.25;");
WriteString ("pu; pa 6400, 50; pd; lbWorkingdays : ");
WriteCard (ActiveDays, 2); Write ("#");
Write (ESC); WriteString ("%1A"); (* Tell printer she's a printer *)
Write (FF) (* Eject ready page *)
END;
CloseOutput
END MakePRsheets;
Two more sheets
By now we have output MaxLine copies of this sheet to the printer. The following two procedures are comparable to this one. They only differ in the shape of the sheet being produced and the numbers. See if you can decipher what the code does. If in doubt, send me a mail.
PROCEDURE MakeVIsheet; (* Produce QA Visual Inspection sheet *)
VAR i, n, Xpos : CARDINAL;
BEGIN
IF CheckPrinter () = Offline THEN
UserMessage (2);
Terminate (2)
ELSE
RedirectOutput ("PRN")
END;
FOR n := 1 TO MaxLine DO
Write (ESC); WriteString ("%1B"); (* Make printer think it's a plotter *)
DrawSheet ("QAsheet.Dat");
WriteString ("pu; pa 400, 10550; pd; lbT-"); WriteCard (n, 1); Write ("#");
WriteString ("pu; pa 5400, 10550; pd; lb"); WriteMonth; Write ("#");
WriteString ("si 0.12, 0.25;"); (* Define character height *)
WriteString ("pu; pa 6400, 50; pd; lbWorkingdays : ");
WriteCard (ActiveDays, 2); Write ("#");
Xpos := 1920;
FOR i := 1 TO MaxDay DO
IF WorkDay [i] = TRUE THEN
WriteString ("pu; pa"); WriteCard (Xpos, 5); WriteString (", 1420; pd; lb");
WriteCard (i, 2); Write ("#");
WriteString ("pu; pa"); WriteCard (Xpos, 5); WriteString (", 8740; pd; lb");
WriteCard (i, 2); Write ("#");
INC (Xpos, 250)
END
END;
Write (ESC); WriteString ("%1A"); (* Tell printer she's a printer *)
Write (FF) (* Eject ready page *)
END;
CloseOutput
END MakeVIsheet;
PROCEDURE MakePOsheets; (* Produce Process Output sheet *)
VAR i, n, Xpos : CARDINAL;
Pline : ARRAY [0..3] OF CHAR;
Pprocess : ARRAY [0..63] OF CHAR;
BEGIN
IF CheckPrinter () = Offline THEN
UserMessage (2);
Terminate (2)
ELSE
RedirectOutput ("PRN")
END;
RedirectInput ("Monthly.Lst");
IF NOT Done THEN (* Tell the user the file is absent *)
CloseOutput;
UserMessage (7);
Terminate (7)
END;
REPEAT
ReadString (Word)
UNTIL CompareStr (Word, "BEGIN") = 0;
LOOP
ReadString (Pline);
IF CompareStr (Pline, "END") = 0 THEN EXIT END;
ReadName (Pprocess);
Write (ESC); WriteString ("%1B"); (* Make printer think it's a plotter *)
DrawSheet ("Monthly.Dat");
WriteString ("pu; pa 400, 10550; pd; lb"); WriteString (Pline); Write ("#");
WriteString ("pu; pa 5400, 10550; pd; lb"); WriteMonth; Write ("#");
WriteString ("pu; pa 3000, 10200; pd; lb"); WriteString (Pprocess); Write ("#");
WriteString ("si 0.12, 0.25;"); (* Define character height *)
WriteString ("pu; pa 6400, 50; pd; lbWorkingdays : ");
WriteCard (ActiveDays, 2); Write ("#");
Xpos := 1920;
FOR i := 1 TO MaxDay DO
IF WorkDay [i] = TRUE THEN
WriteString ("pu; pa"); WriteCard (Xpos, 5); WriteString (", 1540; pd; lb");
WriteCard (i, 2); Write ("#");
INC (Xpos, 250)
END
END;
Write (ESC); WriteString ("%1A"); (* Tell printer she's a printer *)
Write (FF); (* Eject ready page *)
IF Magic THEN EXIT END
END;
CloseInput;
CloseOutput
END MakePOsheets;
Initialisations: process the command line arguments
Next comes the initialisation part of the program. We are going to read the commandline and see how good a fool the user is. We are going to decrypt the month, year. The magic and the exceptions (like national and religious holidays).
PROCEDURE Initialize;
VAR Option : ARRAY [0..15] OF CHAR;
count, i, d : CARDINAL;
ok : BOOLEAN;
BEGIN
Magic := FALSE;
MaxLine := 5;
GetArg (Option, count); (* Try to fetch MONTH *)
IF count = 0 THEN
UserMessage (4); (* If none, inform user *)
Terminate (4)
END;
Month := FindMonth (Option); (* Else determine month *)
GetArg (Option, count); (* Try to retrieve YEAR *)
IF count = 0 THEN
UserMessage (4);
Terminate (4)
END;
StringToCard (Option, Year, ok); (* Convert ASCII to number *)
IF (NOT ok) OR (Year < 2000) THEN
UserMessage (3);
Terminate (3)
END;
IF (Year = 2000) AND (Month < 5) THEN
UserMessage (3);
Terminate (3)
END;
(* Here both Month and Year are known *)
CASE Month OF
2 : IF (Year MOD 4) = 0 THEN
MaxDay := 29
ELSE
MaxDay := 28
END; |
4,6,9,11 : MaxDay := 30
ELSE
MaxDay := 31
END;
d := DayOfWeek (Month, Year);
FOR i := 1 TO MaxDay DO (* Fill up array with workingdays *)
IF d < 6 THEN
WorkDay [i] := TRUE
ELSE
WorkDay [i] := FALSE
END;
INC (d);
IF d > 7 THEN d := 1 END
END;
GetArg (Option, count); (* is there an EXCLUDE option? *)
IF count = 0 THEN RETURN END;
IF CompareStr (Option, 'MaGiC') = 0 THEN
Magic := TRUE;
MaxLine := 1;
GetArg (Option, count);
IF count = 0 THEN RETURN END
END;
UpperString (Option);
IF CompareStr (Option, "EXCLUDE") # 0 THEN (* Check for proper syntax *)
UserMessage (4);
Terminate (4)
END;
LOOP
GetArg (Option, count);
IF count = 0 THEN EXIT END;
StringToCard (Option, i, ok);
IF (NOT ok) OR (i > MaxDay) THEN
UserMessage (6);
Terminate (6)
ELSE
WorkDay [i] := FALSE
END
END
END Initialize;
Some debug-routines (of course I never needed them..)
So far the actual code which is compiled all the times. What follows is a debugging routine. It has been commented out since it will clobber up the screen.
(*
PROCEDURE ShowDebugData;
VAR i : CARDINAL;
BEGIN
WriteString ("Month : "); WriteCard (Month, 4); Write (11C);
WriteString ("Year : "); WriteCard (Year, 8); WriteLn;
WriteString ("MaxDay : "); WriteCard (MaxDay, 4); WriteLn;
WriteLine ("The current days are scheduled:"); WriteLn;
FOR i := 1 TO MaxDay DO
IF WorkDay [i] THEN WriteCard (i, 5) END;
IF (i MOD 7) = 0 THEN WriteLn END
END
END ShowDebugData; *)
The MAIN routine, which has no name in Modula-2
What remains is the actual main routine: four lines of code. This is what I like about Modula-2 (and Forth, for that matter). They force you to define sub-modules of a program and have these executed.
BEGIN
Initialize; (* Interpret commandline and such *)
(* ShowDebugData; (* Show some data on screen *) *)
MakePRsheets; (* Produce Production Report sheets *)
MakeVIsheet; (* Produce QA Visual Inspection sheets *)
MakePOsheets; (* Produce Process Output sheets *)
END Sheets06.
Rounding down
So far this program. If you want to access the source code, just download the entire package. It is only 7 Kb.
Here is also a sample of what the sheets look like. The software completes the page by filling in all the production dates and month/year information. Plus all other production related labels.
Feet back
If you have something to tell me, just send me a note using the link in the navigator frame on the right.
Page created June 2002,
Page equipped with FroogleBuster technology