'PUBPRINT.BAS Created by Steve Gartrell.  Last mod 8/4/92.
'USAGE:  QBX/QB.QLB must be loaded if run in the environment.
'        QBX/QB.LIB must be LINKed if done from the command line.
'        Portrait and Landscape subs take no parameters and
'        return no values.  VidConfig returns -1 if a non-graphics
'        mode is in effect.  Various routines pass the printer file
'        number amongst themselves.  Set the Aspect% SHARED variable
'        to True% if you wish aspect correct to be done.
'
'        Released to the Public Domain 8/4/92 by Stephen K. Gartrell.
'
'$DYNAMIC
DEFINT A-Z
'$INCLUDE: 'qbx.bi'       'for PDS; use 'qb.bi' if using QB 4.5
DECLARE SUB LandScape ()
DECLARE SUB Portrait ()
DECLARE SUB PrintIt (Char%(), PrinterFile%, FixUp%)
DECLARE FUNCTION InitPrinter% ()
DECLARE SUB ResetPrinter (PrinterFile%)
DECLARE FUNCTION VidConfig% ()

CONST False% = 0, True% = NOT False%

DIM SHARED Rows%, Cols%, GrayScale%(1 TO 4, 0 TO 8)
DIM SHARED LandDPI%, PortDPI%, Bits%(0 TO 7), Aspect%
DIM SHARED PortHoriEx%, LandHoriEx%

' These are the "IBM standard" codes as listed by my
' Tandy 9-pin printer manual

' 75  =   60 DPI density
' 76  =  120 DPI density half speed  (better row alignment)
' 89  =  120 DPI density normal speed
' 90  =  240 DPI density

'Pre-calculate the bit mask needed to check for non-zero bits
' in an 8-pixel byte

FOR BitWeight% = 0 TO 7
   Bits%(BitWeight%) = 2 ^ BitWeight%
NEXT

'Place the colors to print here!!  Although the array is
' DIMmed for 4 colors, counting the background (blank),
' you are dealing with 5 colors.
' Remember, at least one mode only has four colors, which
' are returned by POINT as 0, 1, 2, and 3.  The two color
' [B&W] modes only have _1_ color and the background. POINT
' returns a 0 or a 1, in that case.  Coincidentally, testing
' for blue ("1" in 16 color modes) reveals whether a pixel
' is on in B&W modes.
' Additional colors could be added by increasing the number
' of elements in the GrayScale% array, and dreaming up your
' own bit masks.  I have found that 10 to 12 colors are quite
' feasible without losing your ability to differentiate the
' printed output.  And that is on a 9-pin printer...

GrayScale%(1, 8) = 1    'blue will be tested for
GrayScale%(2, 8) = 2    'green will be tested for
GrayScale%(3, 8) = 4    'red will be tested for
GrayScale%(4, 8) = 14   'yellow will be tested for

'Construct the dot pattern masks to emulate "newsphoto" grayscaling.

FOR cnt% = 0 TO 7
  GrayScale%(1, cnt%) = &HFF
NEXT

GrayScale%(2, 0) = &HAA
FOR cnt% = 1 TO 7

  'NOT &HAA yields (basically) &H55- the MOD is not necessary, just tidy!

  GrayScale%(2, cnt%) = NOT (GrayScale%(2, cnt% - 1)) MOD 256
NEXT

GrayScale%(3, 0) = &H88
GrayScale%(3, 1) = &H0
GrayScale%(3, 2) = &H22
GrayScale%(3, 3) = &H0
GrayScale%(3, 4) = &H88
GrayScale%(3, 5) = &H0
GrayScale%(3, 6) = &H22
GrayScale%(3, 7) = &H0

GrayScale%(4, 0) = &H0
GrayScale%(4, 1) = &H20
GrayScale%(4, 2) = &H0
GrayScale%(4, 3) = &H0
GrayScale%(4, 4) = &H0
GrayScale%(4, 5) = &H0
GrayScale%(4, 6) = &H2
GrayScale%(4, 7) = &H0

SCREEN 7

'This call to VidConfig is just made so that Rows% and Cols%
' (both of which are global variables) are initialized, to make
' it easier to change SCREENs and experiment without modifying
' the pattern generator variables.

Garbage% = VidConfig

'Borrowed this random pattern generator from Rich Geldreich!!
'Modified it to limit it to the four chosen colors, and used
'CIRCLEs to better emphasize ASPECT effects.

FOR A = 1 TO 100
  RANDOMIZE TIMER
  Radius% = RND * 50: Colour% = GrayScale%(CINT(RND * 3) + 1, 8)
  x% = RND * Cols%: y% = RND * Rows%
  CIRCLE (x%, y%), Radius%, Colour%
  PAINT (x%, y%), GrayScale%(CINT(RND * 3) + 1, 8), Colour%
NEXT

'This sequence of calls will demonstrate the value of Aspect
' correction by printing the same screen in Portrait and Landscape
' modes, first without correction, and then with.  The DO:LOOP at
' the end is so that you can get the results from the printer and
' compare them to the screen.  Of course, you've already made sure
' your printer had at least four sheets available!!!

Aspect% = False%

Portrait

LPRINT
LPRINT "Rows = "; Rows%, "Columns = "; Cols%
LPRINT "Portrait"
LPRINT "Aspect correction OFF."
LPRINT CHR$(12);

LandScape

LPRINT
LPRINT "Rows = "; Rows%, "Columns = "; Cols%
LPRINT "Landscape"
LPRINT "Aspect correction OFF."
LPRINT CHR$(12);

Aspect% = True%

Portrait

LPRINT
LPRINT "Rows = "; Rows%, "Columns = "; Cols%
LPRINT "Portrait"
LPRINT "Aspect correction ON."
LPRINT CHR$(12);

LandScape

LPRINT
LPRINT "Rows = "; Rows%, "Columns = "; Cols%
LPRINT "Landscape"
LPRINT "Aspect correction ON."
LPRINT CHR$(12);

DO: LOOP UNTIL LEN(INKEY$): SCREEN 0: WIDTH 80: END

FUNCTION InitPrinter%

'Open the printer as a file, set up line feed spacing, and return the
' file number it was opened under.

PrinterFile% = FREEFILE

OPEN "LPT1:BIN" FOR OUTPUT AS PrinterFile%

'1/9" LF
PRINT #PrinterFile%, CHR$(27); CHR$(51); CHR$(24);

'CR/LF
PRINT #PrinterFile%,

InitPrinter% = PrinterFile%

END FUNCTION

SUB LandScape

'If VidConfig% function returns a value, it's a bad SCREEN mode, so
' quit!

IF VidConfig% THEN
   BEEP
   EXIT SUB
END IF

'If the global Aspect% variable is set, aspect correction is on.  Set
' FixUp% to -1 to differentiate LandScape mode from Portrait mode.

IF Aspect% THEN FixUp% = -1

'Call InitPrinter% function, and store returned filenumber

PrinterFile% = InitPrinter%

'Initialize printer bits-byte (?!)

Pixels% = 0

FOR x% = 0 TO Cols% STEP 8

  'Setup storage for screen pixel color values

  REDIM ColorArray%(x% TO x% + 7, 0 TO Rows%)
  
  'Read screen pixel values

  FOR PixColumn% = x% TO x% + 7
   FOR PixRow% = 0 TO Rows%

    'POINT returns a -1 if bad coordinates are passed, which makes
    ' life infinitely easier...

    ColorArray%(PixColumn%, PixRow%) = POINT(PixColumn%, PixRow%)
   NEXT
  NEXT

  'Setup storage for printer output bytes

  REDIM Char%(0 TO Rows%)

  'Dependent upon the number of colors you've setup masks for,
  ' read the colors from the array, AND them with the appropriate
  ' mask, and then OR the result into the output printer byte array.

  FOR Hue% = 1 TO UBOUND(GrayScale%, 1)
   cell% = 0
   FOR y% = Rows% TO 0 STEP -1
    BitWeight% = 7
    FOR PixelColumn% = x% TO x% + 7
      IF ColorArray%(PixelColumn%, y%) = GrayScale%(Hue%, 8) THEN
       Pixels% = Pixels% OR Bits%(BitWeight%)
      END IF
      BitWeight% = BitWeight% - 1
    NEXT PixelColumn%

         'The "y% MOD 8" ensures that we cycle through each of the
         ' bit masks for this color, and eliminate 'striping'!

    Pixels% = Pixels% AND GrayScale%(Hue%, y% MOD 8)
    Char%(cell%) = Char%(cell%) OR Pixels%
    cell% = cell% + 1
    Pixels% = 0

   NEXT y%
  NEXT Hue%
  
  'Send the array of mask-ANDed, ORed-together printer bytes for printing

  CALL PrintIt(Char%(), PrinterFile%, FixUp%)

NEXT x%

'All done...Return the printer to normal draft mode, and close the file

ResetPrinter PrinterFile%

END SUB

SUB Portrait

IF VidConfig% THEN
   BEEP
   EXIT SUB
END IF

IF Aspect% THEN FixUp% = 1

PrinterFile% = InitPrinter%

Pixels% = 0

FOR y% = 0 TO Rows% STEP 8

  REDIM ColorArray%(y% TO y% + 7, 0 TO Cols%)
  FOR PixRow% = y% TO y% + 7
   FOR PixColumn% = 0 TO Cols%
    ColorArray%(PixRow%, PixColumn%) = POINT(PixColumn%, PixRow%)
   NEXT
  NEXT

  REDIM Char%(0 TO Cols%)

  FOR Hue% = 1 TO UBOUND(GrayScale%, 1)
   cell% = 0
   FOR x% = 0 TO Cols%
    BitWeight% = 0
    
    FOR PixelRow% = y% + 7 TO y% STEP -1
     IF ColorArray%(PixelRow%, x%) = GrayScale%(Hue%, 8) THEN
      Pixels% = Pixels% OR Bits%(BitWeight%)
     END IF
     BitWeight% = BitWeight% + 1
    NEXT PixelRow%

    Pixels% = Pixels% AND GrayScale%(Hue%, x% MOD 8)
    Char%(cell%) = Char%(cell%) OR Pixels%

    cell% = cell% + 1
    Pixels% = 0
   NEXT x%
  NEXT Hue%

  CALL PrintIt(Char%(), PrinterFile%, FixUp%)

NEXT y%

ResetPrinter PrinterFile%

END SUB

SUB PrintIt (Char%(), PrinterFile%, FixUp%)

STATIC regs AS RegType

'Derive line length from size of printer byte array

CellCnt% = UBOUND(Char%)

'If aspect correction is on then...

IF Aspect% THEN
   
   'Set output printer DPI and number of times to duplicate output
   ' bytes (printer columns) to the values of whichever print
   ' orientation routine called us.

   IF FixUp% < 1 THEN
      DPI% = LandDPI%
      Hor% = LandHoriEx%
   ELSE
      DPI% = PortDPI%
      Hor% = PortHoriEx%
   END IF

   'Tell the printer what DPI to use, and how many consecutive
   ' graphics bytes (printer columns) to expect.

   PRINT #PrinterFile%, CHR$(27); CHR$(DPI%);
   PRINT #PrinterFile%, CHR$(((CellCnt% + 1) * Hor%) MOD 256);
   PRINT #PrinterFile%, CHR$(((CellCnt% + 1) * Hor%) \ 256);

   'Avoid QB/PDS quirks, and use BIOS interrupt &H17 to get our
   ' bytes to the printer.  Bytes duplicated as often as needed
   ' to achieve good aspect correction.

   FOR cell% = 0 TO CellCnt%
     FOR HoriCnt% = 1 TO Hor%
        regs.ax = Char%(cell%)
        regs.dx = 0
        CALL Interrupt(&H17, regs, regs)
     NEXT
   NEXT
   
   'Emit a LF/CR combination

   PRINT #PrinterFile%,

'Aspect correction isn't on, so use 120 DPI half speed printing, and
' just shoot the output array to the printer via INT &H17.

ELSE

   DPI% = 76

   'Still have to tell the printer what DPI, and how many bytes.

   PRINT #PrinterFile%, CHR$(27); CHR$(DPI%);
   PRINT #PrinterFile%, CHR$((CellCnt% + 1) MOD 256);
   PRINT #PrinterFile%, CHR$((CellCnt% + 1) \ 256);

   FOR cell% = 0 TO CellCnt%
     regs.ax = Char%(cell%)
     regs.dx = 0
     CALL Interrupt(&H17, regs, regs)
   NEXT

   PRINT #PrinterFile%,
END IF

END SUB

REM $STATIC
SUB ResetPrinter (PrinterFile%)

'Restore the passed in file number, which is the printer, to
' draft mode, and close the file

'back to 1/6" LF
PRINT #PrinterFile%, CHR$(27); CHR$(50);

'select standard font
PRINT #PrinterFile%, CHR$(27); CHR$(73); CHR$(1);

'select 10 CPI
PRINT #PrinterFile%, CHR$(27); CHR$(77);

'select bidirectional printing
PRINT #PrinterFile%, CHR$(27); CHR$(85); CHR$(0);

CLOSE #PrinterFile%

END SUB

REM $DYNAMIC
FUNCTION VidConfig%

'Gotta find out what mode we're in, and set global variables
' accordingly.  If it's a bad mode, return a value so calling
' routine ducks out.

DIM regs AS RegType

' video driver interrupt

intnum% = &H10

' get video mode function

regs.ax = &HF00
CALL Interrupt(intnum%, regs, regs)

'Number of columns returned in ah
' multiply by 8 pixels per column, then sub 1 so that
' result agrees with QB/PDS graphics coordinate system.

Cols% = (regs.ax \ 256) * 8 - 1
CurrentMode% = regs.ax AND 255

'Go through the possible screens, and set the global variables
' for the number of rows.  Also set the values for the printer
' DPI and number of printer column repetitions to be used in
' the event that aspect correction is desired.

SELECT CASE CurrentMode%
  CASE &H4, &H6, &HD, &HE, &H13    'Screen 1, 2, 7, 8, 13 in order
   Rows% = 199
   IF CurrentMode% = &H6 OR CurrentMode% = &HE THEN
      LandHoriEx% = 7
      LandDPI% = 90
      PortHoriEx% = 2
      PortDPI% = 90
   ELSE
      LandHoriEx% = 4
      LandDPI% = 90
      PortHoriEx% = 3
      PortDPI% = 90
   END IF

  CASE &HF, &H10                   'Screen 10 & 9, RESPECTIVELY!!
   Rows% = 349
   LandHoriEx% = 4
   LandDPI% = 90
   PortHoriEx% = 1
   PortDPI% = 76

  CASE &H11, &H12                  'Screen 11 & 12, respectively
   Rows% = 479
   LandHoriEx% = 3
   LandDPI% = 90
   PortHoriEx% = 3
   PortDPI% = 90

  CASE ELSE

   ' Either a text mode or not valid return; set
   ' the return value so that the calling routine
   ' knows something is wrong.

   VidConfig% = True%
   EXIT FUNCTION

END SELECT

'Made it here; must be a good SCREEN mode

VidConfig% = False%

END FUNCTION
