REBOL [
    Title: "Printing module using PCL"
    Author: "Steven White"
    Date: 27-MAY-2014
    File: %pclprt.r
    Purpose: {A COBOL-like method for printing basic
    text-oriented business reports.}
    library: [
        level: 'beginner
        platform: 'all
        type: [function]
        domain: [printing]
        tested-under: "view 2.7.8 on Windows"
        support: none
        licnese: none
        see-also: none
    ]
]

;; [---------------------------------------------------------------------------]
;; [ This is a module for primitive printing.                                  ]
;; [ It works in a specific situation, namely, where all printers are          ]
;; [ connected to a Windows print server, and all printers are made by         ]
;; [ Hewlett-Packard.                                                          ]
;; [ It also is designed for simple reports, lines of text only, like in       ]
;; [ the days of mainframe computers.                                          ]
;; [ The reason it works in this situation, and this situation is required     ]
;; [ to make it work, is that we can send stuff to a printer in a standard     ]
;; [ way, and what we send to the printer can contain embedded PCL codes       ]
;; [ to control the printer.                                                   ]
;; [ The module "prints" pre-formmated lines to a "file" which is just a       ]
;; [ big string in memory.  Then, it puts them on paper by writing to a        ]
;; [ printer through the print server.                                         ]
;; [ Without a network printer that is a Hewlett-Packard printer, this         ]
;; [ probably will not work.                                                   ]
;; [---------------------------------------------------------------------------]

;; [---------------------------------------------------------------------------]
;; [ These items are the ones that would have to adjusted for a particular     ]
;; [ installation.  They could be pulled out into a configuration file         ]
;; [ if desired.                                                               ]
;; [ In the working version of this module at the site where it was            ]
;; [ written, these items have meaningful values.                              ]
;; [ If you are looking at a copy of the real module, sanitized for its        ]
;; [ demonstration value, these item will have obviously dummy values.         ]
;; [---------------------------------------------------------------------------]

PCLPRT-INSTALLATION-NAME: "C I T Y    O F    B L O O M I N G T O N"
PCLPRT-SERVER: %/cob-print02/
PCLPRT-DEFAULT-PRINTER: "FILE"
PCLPRT-FILE-ID: %printfile.txt
PCLPRT-PRINTER-LIST: [
    FILE
    ASSESSING01
    ASSESSING02
    ASSESSING03
    ASSESSING04
    ASSESSING05
    BI02
    BI03
    COMSERV-PR01
    COMSERV-PR02
    COMSERV-PR03
    COMSERV-PR04
    FIN-ACCT01
    FIN-ACCT02
    FIN-ACCT03
    FIN-ACCT04
    FIN-ACCT05
    FIN-ACCT06
    FIN-CASHIER01
    FIN-UBILL01
    FIN-UBILL02
    IS01
    IS02
    IS-COPIER
    LEGAL-01
    LEGAL-02
    LEGAL-03
    PD13
    PD15
    PD17
    PD20
    PD23
    PLN01
    PLN02
    PLN03
    PLN04
    PLN06
]

;; [---------------------------------------------------------------------------]
;; [ A printer is controlled by putting codes ("escape sequences") into        ]
;; [ the data sent to the printer.                                             ]
;; [ These are the codes we use in this module, assembled here for             ]
;; [ organization.  They are commonly-used ones, but not even close to         ]
;; [ all of them.  There is a huge amount of stuff that this module            ]
;; [ does NOT do.                                                              ]
;; [---------------------------------------------------------------------------]

PCLPRT-RESET:            "^(esc)E"
PCLPRT-FORMFEED:         "^(page)"
PCLPRT-LANDSCAPE:        "^(esc)&l1O"
PCLPRT-FONT-LINEPRINTER: "^(esc)(10U^(esc)(s0p16.67h8.5v0s0b0T"
PCLPRT-MARGIN-LEFT-5:    "^(esc)&a5L"
PCLPRT-MARGIN-TOP-4:     "^(esc)&l4E"
PCLPRT-LPI-8:            "^(esc)&l8D"
PCLPRT-LINE-TERMINATION: "^(esc)&k2G"  ;; CR=CR, LF=CR-LF, FF=CR-FF

;; [---------------------------------------------------------------------------]
;; [ "Printing" is going to mean appending a print line to the end of          ]
;; [ this big string.  When we "close" the print "file," this big string       ]
;; [ will be written to a network printer.                                     ]
;; [---------------------------------------------------------------------------]

PCLPRT-FILE: ""

;; [---------------------------------------------------------------------------]
;; [ Here are some other important items, defined here so we can keep          ]
;; [ track of them.                                                            ]
;; [---------------------------------------------------------------------------]

PCLPRT-PRINTER: copy PCLPRT-DEFAULT-PRINTER
PCLPRT-PRINTER-PATH: none
PCLPRT-PAGE-SIZE: 57
PCLPRT-LINE-COUNT: 0

;; [---------------------------------------------------------------------------]
;; [ This procedure can be used to request a printer name if the default       ]
;; [ name is not acceptable, or if you want the operator to specify a          ]
;; [ printer.                                                                  ]
;; [---------------------------------------------------------------------------]

PCLPRT-REQUEST-PRINTER: does [
    PCLPRT-PRINTER: request-list "Select printer by name" PCLPRT-PRINTER-LIST
]

;; [---------------------------------------------------------------------------]
;; [ This procedure "opens" the print "file," which means we will clear        ]
;; [ out the string and put some initial printer control characters            ]
;; [ into it.                                                                  ]
;; [---------------------------------------------------------------------------]

PCLPRT-OPEN: does [
    PCLPRT-FILE: copy ""
    append PCLPRT-FILE PCLPRT-RESET
    append PCLPRT-FILE PCLPRT-LINE-TERMINATION
    PCLPRT-LINE-COUNT: 0
]

;; [---------------------------------------------------------------------------]
;; [ This procedure "closes" the print "file," which means we will             ]
;; [ put the appropriate printer reset characters at the end of the            ]
;; [ string and write it to the printer.                                       ]
;; [---------------------------------------------------------------------------]

PCLPRT-CLOSE: does [
    append PCLPRT-FILE PCLPRT-RESET
    either equal? "FILE" to-string PCLPRT-PRINTER  [
        write/binary PCLPRT-FILE-ID PCLPRT-FILE
    ] [
        PCLPRT-PRINTER-PATH: rejoin [
            PCLPRT-SERVER
            PCLPRT-PRINTER
        ]
        write/binary PCLPRT-PRINTER-PATH PCLPRT-FILE
    ]
]

;; [---------------------------------------------------------------------------]
;; [ This procedure causes a page skip by putting a form-feed character        ]
;; [ into the file.                                                            ]
;; [---------------------------------------------------------------------------]

PCLPRT-EJECT: does [
    append PCLPRT-FILE PCLPRT-FORMFEED 
    PCLPRT-LINE-COUNT: 0
]

;; [---------------------------------------------------------------------------]
;; [ This procedure "prints" a line passed to it, which means we will          ]
;; [ append the passed line to the file, and add a newline.                    ]
;; [ The refinement of "double" puts an extra newline at the end for           ]
;; [ double spacing.                                                           ]
;; [---------------------------------------------------------------------------]

PCLPRT-PRINT: func [
    PCLPRT-PRINT-LINE
    /DOUBLE
] [
    append PCLPRT-FILE PCLPRT-PRINT-LINE
    append PCLPRT-FILE newline
    PCLPRT-LINE-COUNT: PCLPRT-LINE-COUNT + 1
    if DOUBLE [
        append PCLPRT-FILE newline
        PCLPRT-LINE-COUNT: PCLPRT-LINE-COUNT + 1
    ]
]

;; [---------------------------------------------------------------------------]
;; [ Here is a separate procedure for sending the printer-reset codes.         ]
;; [---------------------------------------------------------------------------]

PCLPRT-RESET-PRINTER: does [
    append PCLPRT-FILE PCLPRT-RESET 
]

;; [---------------------------------------------------------------------------]
;; [ This procedure sets the font, etc., to a line-printer style.              ]
;; [ To find out what characters to use, use the control panel on the          ]
;; [ printer to get the PCL font list.  On the list are the exact escape       ]
;; [ sequences needed.                                                         ]
;; [---------------------------------------------------------------------------]

PCLPRT-SET-LINEPRINTER: does [
    append PCLPRT-FILE PCLPRT-LANDSCAPE
    append PCLPRT-FILE PCLPRT-FONT-LINEPRINTER
    append PCLPRT-FILE PCLPRT-MARGIN-LEFT-5
    append PCLPRT-FILE PCLPRT-MARGIN-TOP-4
    append PCLPRT-FILE PCLPRT-LPI-8
]

;; [---------------------------------------------------------------------------]
;; [ This procedure emits the characters to set the orientation to             ]
;; [ landscape.                                                                ]
;; [---------------------------------------------------------------------------]

PCLPRT-SET-LANDSCAPE: does [
    append PCLPRT-FILE PCLPRT-LANDSCAPE
]

;; [---------------------------------------------------------------------------]
;; [ The procedures below use the procedures above for printing in a           ]
;; [ classic COBOL manner.  The print headings automatically, checks for       ]
;; [ page skips, and so on.                                                    ]
;; [ The caller of this module should "do" it early in the program to define   ]
;; [ the items below, and then set the following items to desired values:      ]
;; [ LP-PROGRAM:  Name of the program making the report.                       ]
;; [ LP-REPORT:   50-character report description.                             ]
;; [ LP-SUBTITLE: not used until we figure out how to center it.               ] 
;; [ What these procedures are going to give you is a report of text lines     ]
;; [ in a fixed-width font, like the line printer of the COBOL days.           ]
;; [---------------------------------------------------------------------------]

;; -- Items to be loaded before first use
PCLPRT-LP-PROGRAM: ""
PCLPRT-LP-REPORT: ""
PCLPRT-LP-SUBTITLE: ""
PCLPRT-LP-PAGE-COUNT: 1
PCLPRT-LP-TITLE: copy PCLPRT-INSTALLATION-NAME 
PCLPRT-LP-HEADING-1: ""
PCLPRT-LP-HEADING-2: ""
PCLPRT-LP-USER-HEADING-1: ""
PCLPRT-LP-USER-HEADING-2: ""
PCLPRT-LP-USER-HEADING-3: ""
PCLPRT-LP-USER-HEADING-COUNT: 0
PCLPRT-LP-PROG-LGH: 0
PCLPRT-LP-REPT-LGH: 0
PCLPRT-LP-PROG-20: ""
PCLPRT-LP-REPT-50: ""

;; -- Helper functions for the main printing functions
PCLPRT-SUBSTRING: func [
    "Return a substring from the start position to the end position"
    INPUT-STRING [series!] "Full input string"
    START-POS    [number!] "Starting position of substring"
    END-POS      [number!] "Ending position of substring"
] [
    if END-POS = -1 [END-POS: length? INPUT-STRING]
    return skip (copy/part INPUT-STRING END-POS) (START-POS - 1)
]

PCLPRT-FILLER: func [
    "Return a string of a given number of spaces"
    SPACE-COUNT [integer!]
    /local FILLER 
] [
    FILLER: copy ""
    loop SPACE-COUNT [
        append FILLER " "
    ]
    return FILLER
]

PCLPRT-SPACEFILL: func [
    "Left justify a string, pad with spaces to specified length"
    INPUT-STRING
    FINAL-LENGTH
    /local TRIMMED-STRING
           LENGTH-OF-TRIMMED-STRING
           NUMBER-OF-SPACES-TO-ADD
           FINAL-PADDED-STRING
] [
    TRIMMED-STRING: copy ""
    TRIMMED-STRING: trim INPUT-STRING
    LENGTH-OF-TRIMMED-STRING: length? TRIMMED-STRING
    either (LENGTH-OF-TRIMMED-STRING < FINAL-LENGTH) [
        NUMBER-OF-SPACES-TO-ADD: (FINAL-LENGTH - LENGTH-OF-TRIMMED-STRING)
        FINAL-PADDED-STRING: copy TRIMMED-STRING
        loop NUMBER-OF-SPACES-TO-ADD [
            append FINAL-PADDED-STRING " "
        ]
    ] [
        FINAL-PADDED-STRING: COPY ""
        FINAL-PADDED-STRING: PCLPRT-SUBSTRING TRIMMED-STRING 1 FINAL-LENGTH
    ]
]

;; -- Main printing functions 
PCLPRT-LP-PRINT-USER-HEADINGS: does [
    PCLPRT-LP-USER-HEADING-COUNT: 0
    if (PCLPRT-LP-USER-HEADING-1 <> "") [
        PCLPRT-PRINT PCLPRT-LP-USER-HEADING-1
        PCLPRT-LP-USER-HEADING-COUNT: PCLPRT-LP-USER-HEADING-COUNT + 1
    ]
    if (PCLPRT-LP-USER-HEADING-2 <> "") [
        PCLPRT-PRINT PCLPRT-LP-USER-HEADING-2
        PCLPRT-LP-USER-HEADING-COUNT: PCLPRT-LP-USER-HEADING-COUNT + 1
    ]
    if (PCLPRT-LP-USER-HEADING-3 <> "") [
        PCLPRT-PRINT PCLPRT-LP-USER-HEADING-3
        PCLPRT-LP-USER-HEADING-COUNT: PCLPRT-LP-USER-HEADING-COUNT + 1
    ]
    if (PCLPRT-LP-USER-HEADING-COUNT > 0) [
        PCLPRT-PRINT ""
    ]
] 
  
PCLPRT-LP-OPEN: does [
    PCLPRT-OPEN
    PCLPRT-SET-LINEPRINTER
    PCLPRT-LP-PAGE-COUNT: 1
    PCLPRT-LP-PROG-LGH: length? PCLPRT-LP-PROGRAM
    either (PCLPRT-LP-PROG-LGH >= 20) [
        PCLPRT-LP-PROG-20: PCLPRT-SUBSTRING PCLPRT-LP-PROGRAM 1 20
    ] [
        PCLPRT-LP-PROG-20: PCLPRT-SPACEFILL PCLPRT-LP-PROGRAM 20
    ]
    PCLPRT-LP-REPT-LGH: length? PCLPRT-LP-REPORT
    either (PCLPRT-LP-REPT-LGH >= 50) [
        PCLPRT-LP-REPT-50: PCLPRT-SUBSTRING PCLPRT-LP-REPORT 1 50
    ] [
        PCLPRT-LP-REPT-50: PCLPRT-SPACEFILL PCLPRT-LP-REPORT 50
    ]
    PCLPRT-LP-HEADING-1: rejoin [
        PCLPRT-LP-PROG-20
        PCLPRT-FILLER 43
        PCLPRT-LP-TITLE
        PCLPRT-FILLER 52
        now/date
    ]
    PCLPRT-LP-HEADING-2: rejoin [
        PCLPRT-LP-REPT-50
        PCLPRT-FILLER 13
        PCLPRT-FILLER 39    ;; subtitle, eventually
        PCLPRT-FILLER 52
        "Page "
        to-string PCLPRT-LP-PAGE-COUNT
    ]
    PCLPRT-PRINT PCLPRT-LP-HEADING-1
    PCLPRT-PRINT/DOUBLE PCLPRT-LP-HEADING-2
    PCLPRT-LP-PRINT-USER-HEADINGS
]

PCLPRT-LP-CLOSE: does [
    PCLPRT-CLOSE
]

PCLPRT-LP-PRINT: func [
    PCLPRT-LP-PRINT-LINE
    /DOUBLE  ;; not used at this time 
] [
    if (PCLPRT-LINE-COUNT >= PCLPRT-PAGE-SIZE) [
        PCLPRT-LINE-COUNT: 0
        PCLPRT-LP-PAGE-COUNT: PCLPRT-LP-PAGE-COUNT + 1
        PCLPRT-LP-HEADING-2: copy ""
        PCLPRT-LP-HEADING-2: rejoin [
            PCLPRT-LP-REPT-50
            PCLPRT-FILLER 13
            PCLPRT-FILLER 39    ;; subtitle, eventually
            PCLPRT-FILLER 52
            "Page "
            to-string PCLPRT-LP-PAGE-COUNT
        ]
        PCLPRT-EJECT
        PCLPRT-PRINT PCLPRT-LP-HEADING-1 
        PCLPRT-PRINT/DOUBLE PCLPRT-LP-HEADING-2
        PCLPRT-LP-PRINT-USER-HEADINGS
    ]
    PCLPRT-PRINT PCLPRT-LP-PRINT-LINE
]