Papiersparen ist ‘In’

Getreu nach dem Motto “Papier-Recycling ist gut, Papiersparen ist besser!” wurde das Programm ’Smallprint’ entwickelt. Obwohl durch das Austesten des Programmes vielleicht ein paar Bäume zusätzlich dran glauben mussten, sollte dessen Gebrauch den Papierverschleiss erheblich mindern! Mit Smallprint passen ca. 50% mehr effiziente Algorithmen aufs Papier als ohne.

Die Idee für dieses Programm entsprang der Arbeit an einer SUN-Workstation (unter SUN-Tools/ UNIX). Dort hat man die Möglichkeit, seine Listings sehr platz- und papiersparend auszudrucken. Was UNIX recht ist, ist TOS billig, und da man ja im Besitz eines guten Druckers ist (P6/P2200), war der Rest kaum noch problembehaftet. Nun aber zum Programm: Es druckt, wie schon gesagt, Listings und andere ASCII-Texte in Kleinschrift und reduziertem Zeilenabstand auf Endlospapier oder Einzelblätter, fügt wahlweise einen Header mit Uhrzeit, Datum, Seitennummer und Name des Files hinzu und kann zudem von einer Shell aus (bzw. als TTP-Programm) oder als ‘normales’ Programm mit Fileselektorbox etc. gestartet werden. Sehr flexibel also.

Zuerst zur Benutzung aus einer Shell (oder als TTP-Programm): Dem Programm können bis zu 10 Filenamen als Argumente übergeben werden, die dann (per Default) mit Header und auf Endlospapier gedruckt werden. Werden mehr als 10 Filenamen übergeben, so beachtet das Programm (eigentlich das Megamax-Modula) diese einfach nicht.

Beispiel:

smallpri testfile.txt myshell.m read.me

Ist der Header oder das Endlospapier nicht genehm, kann im ersten Argument mit n/N (= No Header) der Header unterdrückt werden, mit e/E (= Einzelblatt) wird auf Einzelblätter statt auf Endlospapier gedruckt (10 Zeilen weniger) und mit z/ Z wird der Text mit Zeilennummern ausgegeben.

Beispiel:

smallpri -n testfile.m Testfile wird ohne Header auf Endlospapier gedruckt.
smallpri -ez testfile.m Testfile wird mit Header und Zeilennummern auf Einzelblätter gedruckt. 
smallpri -en testfile.m Testfile wird ohne Header auf Einzelblätter gedruckt.

Das '-' kennt man von diversen Shells ja schon: Es zeigt einfach an, daß jetzt irgendwelche Optionen selektiert werden. Ist hier natürlich unnötig, aber der Mensch ist ja ein Gewohnheitstier! Die Reihenfolge von ‘e’ ‘n’ und ‘z’ ist übrigens egal.

Hat man vergessen, den Drucker einzuschalten, oder befindet sich dieser wieder einmal offline, dann geschieht einfach nichts. Das Programm kehrt wieder dahin zurück, woher es gekommen ist...

Jetzt zur Benutzung von Smallprint als ‘normales’ GEM-Programm (mit Endung PRG oder als MOD-File eingebunden in die Megamax-Modula-Shell): Hier hat man die Möglichkeit, den ausgeschalteten Drucker nach Starten des Programmes noch zu aktivieren, solange meldet einem eine Alertbox, daß etwas noch nicht so ganz stimmt. Dann wird ein File aus einer Fileselektorbox ausgewählt. In der jetzigen, abgedruckten Version wird als Default-Suchpfad der aktuelle angenommen. Eine Änderung dieses Pfades wird gespeichert und bei erneuter Auswahl einer Datei verwendet. Nach der Wahl des Files fragt das Programm noch, ob ein Header gedruckt werden soll, und ob die Ausgabe auf Endlospapier erfolgt. Die Default-Einstellungen sind die gleichen wie bei der Benutzung des Programms von einer Shell aus.

Achtung: Bei der Benutzung als GEM-Programm muß die Endung unbedingt .PRG lauten. Beim Start von SMALLPRI.TTP die Argumentzeile nicht leer lassen, da man in der Fileselektorbox keine Datei aus wählen kann!!

Soviel zur reinen Benutzung.

Jetzt geht’s ans Eingemachte. Vielleicht erst mal etwas für diejenigen, die das Listing schon abgetippt haben und es compilieren und linken wollen: Hoffentlich ist die Compilerversion die Version 3.6a oder noch neuer. Ansonsten gibt es keine Garantie, daß alle Ein-/Ausgabefunktionen ordentlich laufen. Das Programm benutzt zwar kein Redirect-Output (dort gab es in früheren Versionen Probleme) für die Druckerausgabe, aber man weiß ja nie. Sollte irgend jemand also noch eine ältere Compilerversion haben und sich nach mehrmaligem Testen nichts am (hoffentlich eingeschalteten) Drucker tun, dann ab nach Application Systems damit (das MODULA-System natürlich!). Sehr guter Service da, also keine Scheu!

Stand der Dinge: Programm erfolgreich abgetippt und compiliert, keine Fehler mehr. Jetzt wird gelinkt: Zuerst bei den Linkeroptionen alle Module außer M2INIT deselektieren, vollständige Optimierung einschalten (falls diese Möglichkeit vorhanden ist) und dann den Linker starten (nur ca. 28 Kbyte bei Optimierung!). Das war’s. Man braucht das Programm nur noch in TTP umzubenennen (falls gewünscht), und das Drucken kann losgehen.

Wie merkt das Programm nun, ob es sich als GEM-Programm verhalten (d.h. anmelden) oder auf der niederen Stufe als TOS-Anwendung verharren soll? Es merkt gar nichts! Es prüft einfach, ob die Argumentzeile leer ist und schließt daraus auf GEM-Anwendung. Ansonsten wird die Argumentzeile ausgewertet. So einfach ist das (im Megamax-Modula).

Anderer Drucker?

Fast kein Problem. Die Routinen, die den Drucker initialisieren (InitPrinter, PrintHead-Line) bzw. zurücksetzen (ResetPrinter) sind gut kommentiert, so daß eine Anpassung nicht sehr schwer sein dürfte. Eins muß aber (auch für P6 und kompatible) gesagt werden: Der Ausdruck sollte immer im NLQ-Modus erfolgen, auch wenn das Programm es nicht vorschreibt. Nur damit ist die Kleinschrift (zumindest beim P6 etc.) anständig lesbar. Sollte ein Drucker Super- und Subscript im NLQ-Modus nicht beherrschen, wäre eine Versuch mit Double-Strike vielleicht noch erfolgreich. Zweifel sind hier allerdings angebracht. Die meisten modernen Drucker beherrschen diesen Modus aber, es sollte in dieser Richtung also keine Probleme geben.

Zur Anzahl Zeilen pro Seite: Die Konstanten MinZeilen, Offset (falls kein Header) und EndlosOffset (zusätzliche Zeilen bei Endlospapier) wurden mit einem P6 und einem P2200 ermittelt. Es kann aber sein, daß andere Drucker weniger (oder sogar mehr) Zeilen pro Seite aufs Papier bringen. Sollte der Drucker also anfangen, die Walze zu bedrucken, ist Probieren angesagt.

Zuletzt noch eine kurze, aber wichtige Bemerkung zum Drucker: Er sollte im IBM-Modus laufen, da es sonst Probleme mit den deutschen Umlauten gibt. Hat der Drucker keinen IBM-Modus, sollte man vor Benutzung von Smallprint einen entsprechenden Druckertreiber starten.

Aber auch für Drucker im IBM-Modus mußten wir zumindest eine Codewandlung vornehmen: Das ‘scharfe s’ wird als Beta ausgegeben, damit ein Listing nicht mit dem Pesetas-Zeichen gespickt wird. Wer Lust und Laune hat, kann diese Codewandlung (in der Prozedur ReadUntilCR) natürlich erweitern und sich so einen zusätzlichen Druckertreiber sparen.

So, wir hoffen nun, daß möglichst viele dieses Programm benutzen können. Für die, die keinen Megamax-Modula-Compiler haben: Das Programm dürfte sich zum Beispiel recht einfach in C übertragen lassen, bei anderen Programmiersprachen muß man eventuell die Argumentzeilenauswertung weglassen.

(************************************************)
 * TITEL   : SmallPrint                         *
 * ZWECK   : Druckt ASCII-Datei in Kleinschrift *
 * AUTOREN : (c) 1988 WuSeL-Soft                *
 *           Martin Wunderli & Patrick Seemann  *
 * DATUM   : 01.09.1988 (Version 1.0)           *
 * SPRACHE : MODULA-2 (MEGAMAX MODULA V1.0)     *
 *           Compilerversion 3.6a               *
 * 88-09-03  1.1a                               *
 *           Ausdruck mit Zeilennummern möglich *
 * 88-09-09  1.1b                               *
 *           Programm funktioniert trotz        *
 *           Seemann-Hack                       *
 ************************************************)

MODULE SmallPrint;

FROM AESForms IMPORT
    (* PROCS *) FormAlert;

FROM AESMisc IMPORT
    (* PROCS *) SelectFile;

FROM ArgCV IMPORT
    (* TYPES *) ArgStr, PtrArgStr,
    (* PROCS *) InitArgCV;

FROM Calls IMPORT
    (* PROCS *) CallSystem;

FROM Clock IMPORT
    (* TYPES *) Date, Time;

FROM Directory IMPORT
    (* PROCS *) DefaultDrive, GetCurrentDir, SplitPath;

FROM Files IMPORT
    (* TYPES *) File, Access,
    (* PROCS *) Open, Close, EOF, GetDateTime, State;

FROM GEMEnv IMPORT
    (* CONST *) RC,
    (* TYPES *) GemHandle, DeviceHandle,
    (* PROCS *) InitGem, ExitGem, CurrGemHandle;

FROM NumberIO IMPORT
    (* PROCS *) WriteCard;

FROM StrConv IMPORT
    (* PROCS *) CardToStr;

FROM Strings IMPORT
    (* TYPES *) String,
    (* PROCS *) Length, Assign, Insert, Delete,
                Concat, Append, Space;

FROM SYSTEM IMPORT
    (* PROCS *) VAL;

FROM Text IMPORT
    (* PROCS *) WriteString, WriteLn, Write,
                WritePg, Read, EOL;

FROM TimeConvert IMPORT
    (* PROCS *) DateToText, TimeToText;

CONST BuffLaenge    = 82; (* Inklusive CR,LF *)
      ESC           = 33C;
      CR            = 15C;
      LF            = 12C;

TYPE ZeilenBuffer = ARRAY [0..BuffLaenge-1] OF CHAR;

VAR Zeile            : ZeilenBuffer;
    Datei, Pfad,
    Headstring       : String;
    Header, Endlos,
    Numbers, Button,
    MaxZeilen,
    ArgC, FileCounter,
    LineCounter, i   : CARDINAL;
    Ok, InitOk, FileOk,
    WithHeader,
    Endlospapier,
    NoNumbers        : BOOLEAN;
    FileToPrint, Prn : File;
    GHandle          : GemHandle;
    DHandle          : DeviceHandle;
    ArgV             : ARRAY [0..10] OF PtrArgStr;

(************************************************)

PROCEDURE InitPrinter;

(* Die File-Variable Prn wird global benutzt *)

BEGIN
    Open (Prn, "PRN:", appendSeqTxt);
    (* ESC g    : 15 cpi *)
    Write (Prn, ESC); Write (Prn, "g");
    (* ESC 1 20 : Linker Rand bei 14 *)
    Write (Prn, ESC); Write (Prn, "l"); Write (Prn, 16C);
    (* ESC A 6  : Zeilenvorschub 1/10 *)
    Write (Prn, ESC); Write (Prn, "A"); Write (Prn, 6C);
    (* ESC S 0  : Superscript an *)
    Write (Prn, ESC); Write (Prn, "S"); Write (Prn, 0C);
    Close (Prn);
END InitPrinter;

(************************************************)

PROCEDURE ResetPrinter;

(* Die File-Variable Prn wird global benutzt *) 

BEGIN
    Open (Prn, "PRN:", appendSeqTxt);
    (* Reset: ESC @                  *)
    Write (Prn, ESC); Write (Prn, 100C);
    (* ESC T : Superscript aus       *)
    Write (Prn, ESC); Write (Prn, "T");
    Close (Prn);
END ResetPrinter;

(************************************************)

PROCEDURE PrinterReady () : BOOLEAN;

CONST GEMDOS = 1;
      PrnOS  = 17; (* $11 *)

BEGIN
    RETURN VAL(BOOLEAN, CallSystem(GEMDOS,PrnOS)) 
END PrinterReady;

(************************************************)

PROCEDURE InitHeadLine ((* in  *) Datei      : String;
                    VAR (* out *) Headstring : String);

(* Die File-Variable FileToPrint wird global benutzt *)

CONST SingleSpace = " ";
      Maske       = "DD.MM.YY";

VAR SeitenString,
    Datumstring,
    ZeitString,
    Pfad, Name  : String;
    Datum       : Date;
    Zeit        : Time;
    Ok          : BOOLEAN;

BEGIN
    GetDateTime (FileToPrint, Datum,Zeit); 
    DateToText (Datum, Maske, Datumstring); 
    TimeToText (Zeit, ZeitString);
    SplitPath (Datei, Pfad, Name);
    Append (Space(32-Length(Name)), Name, Ok); 
    Concat (Name, ZeitString, Headstring, Ok); 
    Append (Space(4), Headstring, Ok);
    Append (Datumstring, Headstring, Ok);
END InitHeadLine;

(************************************************)

PROCEDURE WriteHeadLine ((* in *) Seitenzahl : INTEGER;
                         (* in *) Headstring : String);

(* Die File-Variable Prn wird global benutzt *)

CONST MinChar      = 2;
      InsertPos    = 20;
      DeleteLength = 2;

VAR SeitenString : String;
    Ok           : BOOLEAN;

BEGIN
    Delete (Headstring, InsertPos, DeleteLength, Ok);
    Insert (CardToStr(Seitenzahl, MinChar), InsertPos, Headstring, Ok);

    (* ESC P    : 10 cpi *)
    Write (Prn, ESC); Write (Prn, "P");
    (* ESC T    : Superscript off *)
    Write (Prn, ESC); Write (Prn, "T");
    (* ESC 1 13 : Linker Rand bei 13 *)
    Write (Prn, ESC); Write (Prn, "l"); Write (Prn, 15C);
    (* ESC G    : Fettdruck *)
    Write (Prn, ESC); Write (Prn, "G");

    WriteString (Prn, Headstring);
    WriteLn (Prn); WriteLn (Prn);
    WriteLn (Prn); WriteLn (Prn);

    (* ESC H : Fettdruck aus *)
    Write (Prn, ESC); Write (Prn, "H");
    (* ESC g : 15 cpi *)
    Write (Prn, ESC); Write (Prn, "g");
    (* ESC S 0 : Superscript on *)
    Write (Prn, ESC); Write (Prn, "S"); Write (Prn, 0C);
    (* ESC 1 20 : Linker Rand bei 14 *)
    Write (Prn, ESC); Write (Prn, "l"); Write (Prn, 16C);
END WriteHeadLine;

(************************************************)

PROCEDURE CreateFileName ( (* in    *) Pfad  : String;
                       VAR (* inout *) Datei : String);

VAR Index : CARDINAL;
    Ok    : BOOLEAN;

BEGIN
    IF Pfad[0] <> 0C THEN (* Kein leerer Pfadstring? *)
        Index := Length(Pfad) - 1;
        WHILE (Index > 0) AND (Pfad[Index] <> "\")DO 
            DEC (Index);
        END (* WHILE *);
        (* Maske im Pfad löschen *)
        Delete (Pfad, Index+1,(Length(Pfad)-Index-1), Ok);
        Concat (Pfad, Datei, Datei, Ok);
    END (* IF *);
END CreateFileName;

(************************************************)

PROCEDURE ReadUntilCR ( (* out *) VAR Zeile : ZeilenBuffer; 
                        (* out *) VAR EOLReached: BOOLEAN);

(* Die File-Variable FileToPrint wird global benutzt *)

VAR Index : INTEGER;

BEGIN
    Index := 0;

    LOOP
        Read (FileToPrint,Zeile[Index]);
        IF EOL(FileToPrint) THEN 
            Zeile[Index] := CR;
            Zeile[Index+1] := LF;
            Zeile[Index+2] := 0C;
            EOLReached := TRUE;
            EXIT
        ELSIF Zeile[Index] = 236C THEN
            (* Pesetas-Zeichen (anstatt scharfes s)? *) 
            Zeile[Index] := 341C;   (* Beta ausgeben *)
        END (* IF *);
        INC (Index);
        IF (Index = BuffLaenge - 2) THEN 
            Zeile[Index] := CR;
            Zeile[Index+1] := LF;
            EOLReached := FALSE;
            EXIT 
        END (* IF *);
    END (* LOOP *)

END ReadUntilCR;

(************************************************)

PROCEDURE PrintIt ( (* in *) WithHeader,
                       Endlospapier,
                       NoNumbers    : BOOLEAN;
              (* in *) Pfad, Datei  : String): BOOLEAN;

(* Die File-Variable FileToPrint wird global benutzt *)

CONST EndlosOffset = 10;
      MinZeilen    = 96;
      Offset       = 6;

VAR Zeilenzahl, Seitenzahl,
    ZeilenNummer            : CARDINAL;
    FirstPart, EOLReached   : BOOLEAN;

    (************************************************)

    PROCEDURE NummerOderSpaceDrucken;
    (* Zeilennummer oder Spaces drucken *)

    BEGIN
        IF NoNumbers THEN
            WriteString (Prn, "      ");
        ELSE
            WriteCard (Prn, ZeilenNummer, 4); 
            WriteString (Prn, ": ");
            INC (ZeilenNummer);
        END (* IF *);
    END NummerOderSpaceDrucken;

    (************************************************)

BEGIN
    ZeilenZahl := 0;
    Seitenzahl := 1;
    ZeilenNummer := 1;
    FirstPart := TRUE;

    Open (Prn, "PRN:", appendSeqTxt);

    CreateFileName (Pfad, Datei);
    Open (FileToPrint, Datei, readSeqTxt);
    IF State(FileToPrint) < 0 THEN 
        RETURN FALSE 
    END (* IF *);

    IF WithHeader THEN
        MaxZeilen := MinZeilen;
        InitHeadLine (Datei,Headstring);
        WriteHeadLine (Seitenzahl, Headstring);
    ELSE
        MaxZeilen := MinZeilen + Offset;
    END (* IF *);
    IF Endlospapier THEN
        INC (MaxZeilen, EndlosOffset);
    END (* IF *);

    LOOP
        ReadUntilCR (Zeile, EOLReached);

        IF EOF (FileToPrint) THEN 
            EXIT 
        END (* IF *);

        IF EOLReached THEN 
            IF FirstPart THEN
                NummerOderSpaceDrucken;
                WriteString (Prn, Zeile);
                INC (ZeilenZahl);
            ELSE
                (* Zeile eventuell unterdrücken *)
                IF Zeile[0] <> CR THEN
                    WriteString (Prn, " ");
                    WriteString (Prn, Zeile);
                    INC (ZeilenZahl);
                END (* IF *);
                FirstPart := TRUE;
            END (* IF *);
        ELSE
            IF FirstPart THEN
                NummerOderSpaceDrucken;
                FirstPart := FALSE;
            ELSE
                WriteString (Prn, "      ");
            END (* IF *);
            WriteString (Prn, Zeile);
            INC (ZeilenZahl);
        END (* IF EOLReached *);

        IF ZeilenZahl = MaxZeilen THEN 
            ZeilenZahl := 0;
            WritePg (Prn);
            INC (Seitenzahl);

            IF WithHeader THEN
                WriteHeadLine (Seitenzahl, Headstring); 
            END (* IF *);
        END (* IF *);

    END (* LOOP *);

    IF ZeilenZahl <> 0 THEN 
        WritePg (Prn);
    END (* IF *);

    Close (FileToPrint);
    Close (Prn);

    RETURN TRUE 
END PrintIt;

(************************************************)

BEGIN

    InitArgCV (ArgC, ArgV);
    IF ArgC = 1 THEN (* Kein Argument erhalten *)

        InitGem (RC, DHandle, Ok);
        GHandle := CurrGemHandle ();

        (* Drucker bereit? *)
        InitOk := FALSE;
        REPEAT
            IF PrinterReady() THEN 
                InitPrinter;
                InitOk := TRUE;
            ELSE
                FormAlert (1,"[1][Drucker meldet sich nicht!|Entweder einschalten |oder abbrechen!][Ok|Abbruch]", Button);
            END (* IF *) ;
        UNTIL (Button = 2) OR InitOk;

        (* Pfad für FileSelectorBox holen *) 
        GetCurrentDir (DefaultDrive(), Pfad);
        Append ("\*.*", Pfad, Ok);

        IF InitOk THEN 
            REPEAT 
                Datei := "";
                SelectFile (Pfad,Datei,FileOk);

                IF FileOk THEN
                    FormAlert (1,"[2][Ausdruck mit Filename,|Seitenzahl und Datum ?][Ja|Nein]", Header); 
                    FormAlert (1,"[2][Ausdruck auf|Endlospapier][Ja|Nein]", Endlos);
                    FormAlert (2,"[2][Ausdruck mit|Zeilennummern][Ja|Nein]", Numbers);
                    Ok := PrintIt (Header=1, Endlos=1,Numbers=2, Pfad, Datei);
                END (* IF *);

            UNTIL NOT FileOk;
        END (* IF *);

        IF PrinterReady() THEN 
            ResetPrinter;
        END (* IF *);

        ExitGem (GHandle);

    ELSE (* Argumentzeile auswerten *)

        IF PrinterReady() THEN 
            InitPrinter;
            WithHeader   := TRUE;
            Endlospapier := TRUE;
            NoNumbers    := TRUE;
            FileCounter  := 1;
            IF ArgV[1]^[0] = "-" THEN
                (* N: Kein Header E: Einzelblatt Z: Mit Zeilennummern *)
                FOR i := 1 TO Length(ArgV[1]^) - 1 DO 
                    CASE CAP(ArgV[1]^[i]) OF
                        "N" : WithHeader   := FALSE;
                      | "E" : Endlospapier := FALSE;
                      | "Z" : NoNumbers    := FALSE;
                    ELSE
                        (* Do nothing *)
                    END (* CASE *);
                END (* FOR i *);
                INC (FileCounter);
            END (* IF *);

            Assign (ArgV[FileCounter]^, Datei, Ok); 
            WHILE (FileCounter < ArgC) AND PrintIt (WithHeader,Endlospapier, NoNumbers, "",Datei) DO 
                INC (FileCounter);
                Assign (ArgV[FileCounter]^, Datei, Ok); 
            END (* WHILE *);
            ResetPrinter;

        END (* IF *);
    END (* IF *);
END SmallPrint.

Martin Wunderli
Aus: ST-Computer 06 / 1989, Seite 94

Links

Copyright-Bestimmungen: siehe Über diese Seite