.Student Forth source code for ROTPAGES, which prints "PAGE xy" files sideways.
@ROTPAGES.src
by Hank Wilkinson
@Using Student Forth to crop and rotate Paint PAGES files.
GEOS, Paint DRIVERS and
Paint PAGES are Berkeley Softworks
'64 is is Commodore Business Machines
PStudent Forth
@Anticipatory Design Science
Freeman Building, Suite 303, 612 Pasteur Dr.
Greensboro, NC 27403
A booklet made from an 8-1/2 X
5-1/2
page is comfortable to handle. Folded accordian style, computer paper is perfect for this.
Here are two sheets of computer paper with the sprocket holes still attached.
Fold these two pages the way being described and you'll end up with four half-sized pages. Here is the idea with the sprocket holes removed.
Obviously, these pages may be folded all the way up to close like a book. Just as obvious, you don't have to use exactly two sheets. .
The size of book is pleasing to hold, flip through and read. Some of its appeal may be because the ratio of its sides is very close to the golden ratio (ie., ~1 to ~.616).
The size of book is pleasing to hold, flip through and read. Some of its appeal may be because the ratio of its sides is very close to the golden ratio (ie., ~1 to ~.616).
It's also practical. When I am through with a rough draft, I have only printed on one side. By folding it as I have above I can write on the back easily. Rather than throw paper in the trash can, I put all used computer paper in a box for use later
The half-size page is also practical for the way I program. I break a task up into the smallest practical size. A small page encourages this. Using the back of a used piece of paper keeps me from worrying about "wasting" paper to figure out my programs.
So anyway, that is how I ended up thinking about folding paper this way. The task of rotating and clipping a page to fit half a page is made easier by Paint PAGES.
The GEOWRITE file is actually clipped when made into a GEOPAINT page by Paint PAGES.
The GEOWRITE file is actually clipped when made into a GEOPAINT page by Paint PAGES. My GEOWRITE files (for my FX-80 printer driver) are clipped off at the bottom for Paint PAGES. My GEOWRITE uses 94 print rows per page, GEOPAINT uses 90.
We will need to clip this GEOPAINT page to 80 card rows in order to rotate it 90 degrees.
FThis is the idea. By using the facility of Paint PAGES that we already have, a program that clips and rotates, putting two half pages per page is not that difficult. All we have to do is set our margins, headers and footers correctly, and Paint PAGES will generate the images for us.
We just have to make the clipping and rotating program that prints two half pages per page. This is made easier by doing pieces of a page out to a temporary file in stages.
Any confusion you have about setting margins, headers or footers will be cleared up the first time you use this routine. On my set-up, GEOWRITE ma
Any confusion you have about setting margins, headers or footers will be cleared up the first time you use this routine. On my set-up, GEOWRITE may make one page contain 94 rows of 80 cards per row. Without a whimper, error, or any other warning, Paint PAGES will trim this down to 90 rows of 80 cards per row.
When
@ROTPAGES
gets through with it, your page will pass only 80 rows of 40 cards per. That is what I meant by your confusion being cleared up quickly. Using 40 cards means we clip the entire right half of the image, and some more off the bottom.
Just like Paint PAGES leaves your GEOWRITE file alone,
@ROTPAGES
leaves the Paint PAGES files alone. It writes no permanent changes back to disk.
The image screen in GEOS is a large 8000 byte buffer. Ignoring color, this huge buffer shows, pixel by pixel, what is in the buffer. The way the '64 is made, and the way GEOS sets it up, there may be two of these: the background screen and the foreground screen.
Because of the way things are hooked up inside our '64s, the image screen is further divide into cards. A card is eight consecutive bytes, the first of which lies exactly on the addressing boundary. The cards appear to a programmer as eight bytes stacked on top of each other.
GThe "cards" are arranged next to each other in this 8000 byte image buffer, or screen, so that 40 cards make up one screen image row.
HEach image scr
GThe "cards" are arranged next to each other in this 8000 byte image buffer, or screen, so that 40 cards make up one screen image row.
HEach image screen's card row is 320 bytes. You may realize this to be exactly half as wide as a printer's row and GEOPAINT row.
It may be hard to visualize, but it must be even harder to explain because I'm getting nowhere fast.
Let's pause a minute, I have something to do here.
{ : copyright! ." Hank Wilkinson 1990" ; }
Okay, let's go on.
I'll say it this way. Each GEOPAINT page is stored in a VLIR file as 45 records representing
horizontal rows, each of which is 80 cards wide. The actual data is not only compressed, but the c
I'll say it this way. Each GEOPAINT page is stored in a VLIR file as 45 records representing
horizontal rows, each of which is 80 cards wide. The actual data is not only compressed, but the color information is there also. We will ignore color.
This means that the GEOPAINT page represents a grid of pixels 640 wide in the horizontal direction and 720 high. Our
@ROTPAGES
is going to take one of the pages and clip it into 320 pixels wide and 640 pixels high. That way, the resultant clipped image will fit, if rotated 90 degrees, on a half sheet of paper.
The easiest way I see of doing that is to fill a GEOS screen completely up with an image 320 pixels wide and 200 pixels high. Then we rotate that entire screen out to a temporary disk file. We repeat this procedure 2 and 1/5 more times to complete a half page. This is then sent to the printer.
@This
is repeated until we have printed out all the Paint PAGES images that are on a disk.
We are ready to start defining procedure now.
1) Fill screen with Paint PAGES GEOPAINT image.
2) Rotate screen image 90 degrees.
3) Save rotated image to disk.
4) Print image.
Fill screen is furt
Fill screen is further broken down into smaller parts.
1) Read image double row into buffer.
2) Expand the compacted image into another buffer.
3) Move image row from buffer into screen.
Rotate screen image is divided into rotating a card 90 degrees, rotating a full column 90 degrees and rotating a partial column 90 degrees. The screen card rotation has been written in assembly language and attached to
@Student Forth
before any of the other programming is done.
In fact, just the action part of the word was written and assembled but not linked. A short, one page, procedure was written that attached it into
@Student Forth
and it may be called by the word
@RotCard
@RotCard
needs the
address and the
address on the stack. Let's say we are rotating the first card on the screen to the first card position in a buffer. Here is the syntax of that theoretical task,
@SCREENBASE BUFFER RotCard
@SCREENBASE
is the "from". That is where
@RotCard
will start.
@BUFFER
is the "to". That is where
@RotCard
will put the rotated card.
Along with
@RotC
Along with
@RotCard
we also made another word that rotates the 25 cards in a column of a GEOS screen. Like
@RotCard
@RotCol
needs a "from" and "to" address.
@RotCol
will rotate the whole screen column.
GEOS is persnickety about entering the deskTop sometimes, like when you enter from drive 8 and exit from drive 10. A variable may be used to save the initial, or entry drive. Then, upon exit, this same drive will be set.
{ 0 Variable entry/ExitDisk }
Also, with a variable, it is easier to keep track of which drive is being used. For example, the "PAGE xy" may be on a disk drive, but we want to do our cropping and rotation on the RAM disk.
{ 0 Variable RAM 0 Variable Disk }
Any error needing to return to the deskTop should return with the disks set correctly.
: GoBye
( first, an empty loop giving user time to react )
10000 0 Do Loop
entry/ExitDisk C@ SetDevice Drop
OpenDisk Drop
Bye
The next few pages contain the basic GEOPAINT VLIR file reading and expansion commands. If you want them explained, read my "PRINTPAGES.SRC" found on Q-Link. Skip on over to page 14.
0 Variable #Printed
0 Variable PAGECount
21912 Constant pic
0 Variable picPtr
: picAddress ( -- address ) pic picPtr @ + ;
: pic@ ( -- value_pointed_to ) picAddress C@ ;
: NextpicPtr ( -- ) 1 picPtr +! ;
23912 Constant row
0 Variable rowPtr
: rowAddress ( -- address ) row rowPtr @ + ;
: row! ( byte -- ) rowAddress C! ;
: NextrowPtr ( -- ) 1 rowPtr +! ;
: Type1 ( -- )
pic@ 128 - rowAddress Over
NextpicPtr pic@ NextpicPtr Fill rowPtr +! ;
: Type2 ( -- )
: Type1 ( -- )
pic@ 128 - rowAddress Over
NextpicPtr pic@ NextpicPtr Fill rowPtr +! ;
: Type2 ( -- )
pic@ NextpicPtr
63 And 0 Do
picAddress rowAddress 8 CMove
8 rowPtr +!
8 picPtr +!
: Type3 ( -- )
pic@ >R NextpicPtr
picAddress rowAddress R CMove
R picPtr +!
R> rowPtr +!
: UnCompact ( -- )
Case 128 > Of Type1 Exit Then
Case 63 > Of Type2 Exit Then
Type3
: Expandpic ( -- )
: Expandpic ( -- )
0 picPtr !
0 rowPtr !
Begin
UnCompact
rowPtr @ 960 > Until
: Getpic ( -- true=read,false=empty )
FN$ OpenVLIR
If ." Couldn't open " FN$ 16 Type CR GoBye Then
PointRecord
If ." Couldn't point to record" CloseVLIR
GoBye
Then
pic 2000 ReadRecord
If ." Couldn't read" CloseVLIR
GoBye
Then
( true )
Else
row 1280 Erase
FALSE
Then
CloseVLIR
: PreparePixels ( GEOPAINT_row# -- )
GetPic ( row# -- flag )
If ExpandPic Then
?Terminal If
GoBye
Then
: PAGE$ ." PAGE "
: InitializeName
FN$ 17 Erase
: PAGE$ ." PAGE "
: InitializeName
FN$ 17 Erase
' PAGE$ 2 + Count FN$ Swap CMove
: SetName
PAGECount @ S->D <# #S #>
Dup 1 > If
Else
Then
FN$ + Swap CMove
These command words from "PRINTPAGES" are used
as is
. This is the "copy and modify" method of programming. As a utility, understand them this way:
@PreparePixels
will read a compressed GEOPAINT VLIR record and expand it into two 640 byte printer rows at the buffer named
Each row of little squares above stands for 640 bytes. The entire file needs to be "cropped" to rows 320 bytes wide. This will be accomplished by moving only the first 320 bytes of each row to the GEOS image screen. The screen (the foreground screen) will serve as a large buffer to gather together 25 half rows together for the 90 degree rotation.
Conceptually then, the GEO
Conceptually then, the GEOS image screen may be accessed as 25 rows, of 320 bytes of pixels. By numbering the image row from 0 to 24, the offset into
@SCREENBASE
is computed by
@index*320
. Adding this offset to
@SCREENBASE
, the effective address is obtained.
: ToScreen ( address_in_row screen_row_0:24 -- )
320 * SCREENBASE + 320 CMove ;
@ToScreen
needs either the address of
@row+640
and the screen row from 0 to 24. Given that much it will move the 1/2 printer row to the GEOS image screen.
Basically, every time we read another VLIR record, we have two printer rows. Because there are 25 image screen rows, the movement from the
buffer to the screen doesn't equal. In other words, 12-
double printer rows fill the image screen. It is apparent that a double row will be split between image screens from time to time.
We either move the 1st half of the double row to the image screen or the second half.
: 1stHalf ( screen_row_0:24 -- )
row Swap ToScreen ;
Realizing each printer row is 640 bytes, adding 640 computes the offset to the second half of the image slice. Note the difference betwe
Realizing each printer row is 640 bytes, adding 640 computes the offset to the second half of the image slice. Note the difference between
@1stHalf
and
@2ndHalf
is merely "640 +".
: 2ndHalf ( screen_row_0:24 -- )
row 640 + Swap ToScreen ;
@1stHalf
and
@2ndHalf
now form an ugly, but writable basis of moving and thus clipping PAGES to the screen. In the following words, the loop index,
, is first used to prepare the pixels. Then it is used to compute the GEOS image screen rows to move the pixels, clipping them.
: 1stScreen
12 0 Do
I PreparePixels
I 2 * 1stHalf
I 2 * 1+ 2ndHalf
Loop
12 PreparePixels 24 1stHalf
Note the
@Do/Loop
is from 0 to 12, or 0 to 11 inclusive. At the end of the
loop we have only expanded and moved 24 screen rows. The 25th is handled after the loop.
In the next word, the left over half of the double row is send to the screen firs
In the next word, the left over half of the double row is send to the screen first. The loop indices are changed to reflect the VLIR file records being accessed. The math on indices correctly places the expanded images on the screen.
: 2ndScreen
0 2ndHalf
25 13 Do
I PreparePixels
I 13 - 2 * 1+ 1stHalf
I 13 - 2 * 2 + 2ndHalf
Loop
Note the "gyrations" taken to equalize filling 25 image rows from
pairs
of image rows. Again note the math on the index to aim at the correct screen rows (0:25) even though the VLIR file's record numbers range from 0:40.
: 3rdScreen
37 25 Do
I PreparePixels
I 25 - 2 * 1stHalf
I 25 - 2 * 1+ 2ndHalf
Loop
37 PreparePixels 24 1stHalf
The last word in the movement series reflect 1/5 of a screen, or five screen rows. Again the loop indices reflect this.
: 4thScreen
0 2ndHalf
40 38 Do
I PreparePixels
I 38 - 2 * 1+ 1stHalf
I 38 - 2 * 2 + 2ndHalf
Loop
: RotPartCol ( from -- )
PBP1 32 + PUTIT+1 !
5 0 Do
RotCard
320 8 +!
-8 PUTIT+1 +!
We now have commands which read and expand the VLIR records, and commands that clip the file and buffer a screen full. The next task would be to rotate the screen and save it in a temporary disk file.
Commands to perform rotation and storing, and subsequent recalling and printing need to be developed. We start with a few system calls.
Looking for
Looking for the printer driver, or the "PAGE xy" files might require changing drives. Any error encountered will stop the process after displaying the system call causing the error along with the actual error number.
: SetDrive ( drive# -- )
SetDevice -Dup If ." SetDevice Error # " .
GoBye Then
OpenDisk -Dup If ." OpenDisk Error # " .
GoBye Then
Once the actual type is know, these words may be used to connect to
Once the actual type is know, these words may be used to connect to the necessary drive. For speed, we want the clipped and rotated images on the RAM disk. We may want the "PAGE xy" files on a regular disk, if we fill up our RAM disk.
: ->RAM RAM C@ SetDrive ;
: ->Disk Disk C@ SetDrive ;
If an error was encountered changing drives, the following variable may be accessed to see which drive is the current one.
8489 Constant curDrive
Decimal
To check to see if there is enough room on a disk to do the cropping and rotating, we build a word that returns the number of blocks free on the stack.
C1DB Constant CalcBlksFree
Decimal
: CalcFree curDirHead r5 ! CalcBlksFree Jsr r4 @
The search for a RAM disk should be straight forward, if that is achievable. For readability, obtaining the actual type of a particular drive is contained in a command.
848e Constant driveType
Decimal
: DriveTypeA ( -- drive_type ) driveType C@ ;
: DriveTypeB ( -- drive_type ) driveType 1+ C@ ;
: DriveTypeC ( -- drive_type ) driveType 2 + C@ ;
To find the RAM disk, each drive is searched until it is found. The search is accomplished by reading the type byte and testing bit 7. (The RAM disk has bit 7 set.) The program stops if no RAM disk is found.
After finding the RAM disk, the amount of free space is checked. Again the process is stopped if there is not enough room.
: InitRAM/Disk
DriveTypeA 128 And
If 8 RAM C!
Else
DriveTypeB 128 And
If 9 RAM C!
Else DriveTypeC 128 And
If 10 RAM C!
Else 10 0 Do ." Need RAM Disk!!!" CR Loop
GoBye
Then
Then
->RAM CalcFree
124 < If 19 emit 10 0 Do
." Need 32k free on RAM Disk!!" CR
Loop
GoBye Then
To keep the disk file name "TEMP" in, a string buffer is made.
{ Create TEMP$ 17 Allot }
The string itself is written in memory.
{ : TMP$ ." TEMP" ; }
The string will be moved to the buffer later.
To create a file in GEOS, a 256 byte space is needed for the file header. Rather than have this buffer in the program space, it extended past the program memory.
To create a file in GEOS, a 256 byte space is needed for the file header. Rather than have this buffer in the program space, it extended past the program memory.
{ : putb Here 256 + ; }
Then the creation of the "TEMP" file is matter of following a list of things to do.
C1ED Constant SaveFile
C289 Constant AppendRecord
Decimal
: CreateTEMP
TEMP$ 16 Erase ' TMP$ 2 + Count TEMP$ Swap
CMove TEMP$ putb !
3 putb 2 + C! 21 putb 3 + C!
128 63 + putb 4 + C!
3 putb 68 + C! 13 putb 69 + C! 1 putb 70 + C!
0 putb 71 + !
-1 putb 73 + !
0 putb 75 + !
putb 20 ! 1 22 C! SaveFile Jsr
TEMP$ OpenVLIR Drop
120 0 Do
AppendRecord Jsr
CloseVLIR
Once used, the "TEMP" file has to be er
Once used, the "TEMP" file has to be erased, gaining back the disk space.
C238 Constant DeleteFile
: DeleteTEMP TEMP$ r0 ! DeleteFile Jsr ;
Decimal
The "TEMP" file is where we put the cropped and rotated image. Once made, it is printed out. The printing done by printer rows. First, put the data into the PBP1 print buffer and the following word will send it to the printer via
@PrintBuffer
, checking to see if the user wants to quit.
: PrintBuf ( -- )
PBP1 r0 ! PBP2 r1 ! 0 r2 ! PrintBuffer
?Terminal If CloseVLIR
GoBye
Then
There will be times a blank row is sent. This allows "flushing" the GEOS print buffers and alignment of two pages per page.
: SendBlankRow PBP1 640 Erase PrintBuf ;
With an "open" VLIR TEMP file, the record needs accessing. Any error should be acknowledged and solved.
: AimRecord ( record# -- )
PointRecord
-Dup If ." error # " . ." wh
With an "open" VLIR TEMP file, the record needs accessing. Any error should be acknowledged and solved.
: AimRecord ( record# -- )
PointRecord
-Dup If ." error # " . ." while pointing"
CloseVLIR GoBye Then
Drop
Also, what is stored in the TEMP file is a slice of the page.
: GetSlice ( address_to_put -- )
200 ReadRecord
-Dup If ." error # " . ." while reading"
CloseVLIR GoBye Then
Drop
By reading the "TEMP" file, one record at a time, the printing is accomplished. Here we read one record and send it to be printed.
: PutBuf ( 0:39 -- )
Dup 8 * SCREENBASE + RotPartCol
80 Over + AimRecord PBP1 40 + GetSlice
40 Over + AimRecord PBP1 240 + GetSl
: PutBuf ( 0:39 -- )
Dup 8 * SCREENBASE + RotPartCol
80 Over + AimRecord PBP1 40 + GetSlice
40 Over + AimRecord PBP1 240 + GetSlice
AimRecord PBP1 440 + GetSlice
PrintBuf
Then the whole process of printing the "TEMP" file is accomplished by looping.
: PrtTEMP ( -- )
TEMP$ OpenVLIR Drop
40 0 Do
I PutBuf
CloseVLIR
Writing records out to a VLIR file requires the beginning address and a count of the number of bytes to write to the record.
C28F Constant (WriteRecord)
Decimal
: WriteRecord ( from count -- error )
r7 !
0 0 0 0 (WriteRecord) Call ( a x y psw )
Drop Drop Swap
C28F Constant (WriteRecord)
Decimal
: WriteRecord ( from count -- error )
r7 !
0 0 0 0 (WriteRecord) Call ( a x y psw )
Drop Drop Swap Drop ( x=error )
: Rot1stScreen
1stScreen
TEMP$ OpenVLIR Drop
40 0 Do
I AimRecord
SCREENBASE I 8 * + PBP1 192 + RotCol
PBP1 200 WriteRecord Drop
CloseVLIR ?Terminal If GoBye Then
: Rot2ndScreen
2ndScreen
TEMP$ OpenVLIR Drop
80 40 Do
I PointRecord Drop Drop
SCREENBASE I 40 - 8 * + PBP1 192 + RotCol
PBP1 200 WriteRecord Drop
CloseVLIR ?Terminal If GoBye Then
: Rot3rdScreen
3rdScreen
TEMP$ OpenVLIR Drop
120 80 Do
I PointRecord Drop Drop
SCREENBASE I 80 - 8 * + PBP1 192 + RotCol
PBP1 200 WriteRecord Drop
CloseVLIR ?Terminal If
GoBye
Then
: RotPage
CreateTEMP
Rot1stScreen
Rot2ndScreen
Rot3rdScreen
4thScreen
PrtTEMP
Delet
: RotPage
CreateTEMP
Rot1stScreen
Rot2ndScreen
Rot3rdScreen
4thScreen
PrtTEMP
DeleteTEMP
Now and again we crash. I hate it, you hate it. We probably hate the mo
Now and again we crash. I hate it, you hate it. We probably hate the most a
@preventable
crash! One sure way to crash is to forget remove the "Paint PAGES" driver and put back your regular printer driver.
GEOS has a variable to keep the printer driver's file name in.
{ Hex
8465 Constant PrntFileName
Decimal }
Before we even load the printer driver, this location should be checked to be sure it contains our normal printer driver.
But we have no way of knowing what our normal printer driver is. On the other hand, we do know what it
shouldn't be!
"Paint PAGES" and "Paint OVERLAY" both begin with "Paint ". By simply adding up the ASCII value of "Paint ", our total to check for is 540, or $21C.
{ 540 Constant WRONGPRELUDE }
By reading and summing the first six bytes of the ASCII string found at
@PrntFileName
, a test may be performed ensuring we
don't
have "Paint whatever".
: ?PaintDriver ( -- )
: ?PaintDriver ( -- )
6 0 Do
PrntFileName I + C@ +
WRONGPRELUDE =
If 19 Emit
10 0 Do
." You have wrong printer driver!" CR
Loop
GoBye
Then
Simply put, this routine adds up the first six characters in the
@installed
printer driver and compares this to the ASCII sum of "Paint ". If the two sums are equal, the screen is cleared, a message is printed and the program
@Abort
s to the command line.
Once a suitable printer driver
is verified, the next task is to
@find
the printer driver. With GEOWRITE allowing 61 pages, space is a premium. Neither the printer driver nor
@ROTPAGES
should have to be on the RAM disk, allowing more room for our "PAGE xy" files.
Once a drive is set, it is a simple matter to see if our driver is there.
: ?PDriver ( -- true=found )
( -- false= not found )
PrntFileName FindFile 0=
If the driver is
Once a drive is set, it is a simple matter to see if our driver is there.
: ?PDriver ( -- true=found )
( -- false= not found )
PrntFileName FindFile 0=
If the driver is not on the drive we look at, we should change drives and look there. The following word needs either 8, 9 or 10.
: SearchDrive ( drive# -- true=found )
( drive# -- false=not found )
SetDevice If FALSE Exit Then
OpenDisk If FALSE Exit Then
?PDriver
Basically we should first check the "current" drive. If the driver isn't found we should check the other drives. The following word includes a redundant search, but the computer is doing the searching, not us.
: Look ( -- TRUE = found )
( -- FALSE = not found )
DriveTypeA If 8 SearchDrive -Dup If Exit Then Then
DriveTypeB If 9 SearchDrive -Dup If Exit Then Then
DriveTypeC If 10 SearchDrive Exit Then
Wrapping the idea up in a big command is simple. First check current
Wrapping the idea up in a big command is simple. First check current, if found we're though. Otherwise, check all three drives. If
@Look
couldn't find the driver, clear the screen and print the message four times. Keep doing that until the user puts in a disk with driver on it.