// PreScan.bcpl
//
// modified by Ramshaw, March 30, 1981 4:39 PM
// - add switch for Bob Hunt, to make CoordsConvertBox either
// behave like it should (see below) or else try for local fidelity
// so that SIL line drawings look better on a low-resolution printer.
//
// modified by Ramshaw, March 11, 1981 10:54 AM
// - change CoordsConvertBox to make rectangles behave just
// like the corresponding objects.
//
// modified by Ramshaw, December 11, 1980 12:45 PM
// - make sure that CoordsConvertBox always returns a box that is
// at least one bit in each dimension, since all kinds of other
// code has its termination checks at the END of loops (instead
// of at the beginning where they belong).
//
// modified by Butterfield, October 13, 1980 3:35 PM
// - ResolutionB, ResolutionS, 1X instead of 10X - 10/13/80
//
// errors 800
//
//Prescan phase of press 3100 routine
//
//PreScanInit()
//PreScanClose()
//
Called to begin and end pre scan pass.
//ShowPage(pg,part)
//
Called to pre-scan a page. pg is a PageG structure
//
that will be appropriately filled up. part is a pointer to
//
a PE describing the part of the file.
//ShowEntity(e)
//
Called internally to process each entity
//
//CoordsUpdate()
//
Called if CoordsInvalid to update CurSCoord,CurBCoord.
//CoordsConvert(x,y,lvS,lvB,absolute,changed)
//
Given coordinate (x,y), convert it to (double precision) bit
//
coordinates (lvS,lvB). If absolute, the x,y pair is absolute,
//
otherwise relative. Changed is optional--a set of flags to say
//
which of the coordintes have changed:
//
2X changed -- only update corresponding bit coord
//
1Y changed -- "
//CoordsConvertF(lvx,lvy,acS,acB,absolute)
//
Called to convert floating point numbers (lvx,lvy) to bit coordinates.
//
Results are left in AC’s (acS,acB).
//CoordsConvertBox(width,height,lvSmin,lvSmax,lvBmin,lvBmax)
//
Returns true if rectangular thing fits on page -- S,B values filled.
//CoordsBound(sl,sr,bl,br)
//
Four numbers are a box around something to be displayed.
//
Expand bounding box for page. Returns true if thing is
//
OK (i.e., will lie entirely within clipping area)



get "pressinternals.df"
get "pressparams.df"
get "pressfile.df"

// outgoing procedures
external
[
PreScanInit
PreScanClose
ShowPage
CoordsUpdate
CoordsConvert
CoordsConvertBox
CoordsConvertF
CoordsBound
]

// outgoing statics
external
[
PreScratchW//Window on scratch file
PreGodW//Window on god file
DL//Window on DL of press file
ScratchLen//Length of scratch file

Entity//Entity being processed
CurSCoord//Current S and B coordinates
CurBCoord// (double precision integer,fraction)
CoordsInvalid//Flags of which coords set since before
CurSMax//Bounding box
CurSMin
CurBMax
CurBMin
PSStats//Statistics record (structure PSStat)

SimplePage//True if only characters on page

//Spruce-like font load stuff
ICCUses
FontSizePageNew
FontSizePageOld
maxBandRecsSoFar
maxFontSizeSoFar
nFontLoads
maxPrintPassRecs
]
static
[
PreScratchW
PreGodW
DL
ScratchLen

Entity
CurSCoord
CurBCoord
CoordsInvalid
CurSMax
CurSMin
CurBMax
CurBMin
PSStats

SimplePage

//Spruce-like font load stuff
ICCUses
FontSizePageNew
FontSizePageOld
maxBandRecsSoFar
maxFontSizeSoFar
nFontLoads
maxPrintPassRecs
]

// incoming procedures
external
[
//Related PRESCAN procedures
ShowCharsInit
ShowCharsClose
ShowChars
ShowCharsSet
ShowCharsFont
ShowCharsSetSpace
PreFSGet

ShowObject
ShowRectangle
ShowDots

//Band writing procedures
BandWriteInit
BandWriteClose
BandWriteBeginPage
BandWriteEndPage
BandWrite
BandOnCopy
BandSync
BandRecords

//WINDOW
WindowInit
WindowClose
WindowRead
WindowReadBlock
WindowReadByte
WindowRead2Bytes
WindowSetPosition

//PARTS
SetPartBounds
SetPositioninPart
SetBytePositioninPart
GetPositioninPart
SkipinPart

//FONTMAKE
MakeFI
FontMakeup

//PRESS
PressError
FSGetX
FSGet
FSPut
DblShift
GetTime

//PRESSML
DoubleAdd; DoubleSub; DoubleCop
TGr

//OS
MoveBlock
SetBlock; Zero

//FLOAT
DPAD; DPSB; FNEG
FSTDP; FLD; FST; FLDI; FAD; FSB; FML; FDV

//CURSOR
CursorChar
CursorDigit
CursorToggle

//METER
MeterBlock
MeterTime
]

// incoming statics
external
[
EL//Window on EL of press file

PressFile//Files to use...
BandFile
BandWindow
ScratchFile
GodFile

XOffset
YOffset
ScaleOffset
SoftScan
LocalFidelity

//color stuff from PreBands
BandCurHue
BandCurSat
BandCurBright
nPrinterColors

ResolutionS
ResolutionB
portrait
nScanLines
nBitsPerScan

Report

ICCtot
PermanentBottom
OverlayBottom
nPages
FA
]

// internal statics
static
[
CopyUsed//True if <OnlyOnCopy> used
ColorUsed//True if <SetHue>,<SetSaturation> used
MicaX//Last <set x>
MicaY//Last <set y>
ResFactorS//Multiplier for S resolution
ResFactorB//Multiplier for B resolution (micas=>bits)
]

// File-wide structure and manifest declarations.

structure EHC :
//EH + some stuff
[
@EH
nextword//Pointer to next entity
ELCPosword 2//Part pos of entity commands
]

// Procedures

let PreScanInit() be
[ compileif ReportSw then [ GetTime(lv Report>>REP.PreTime) ]
CursorChar($S)

DL=WindowInit(PressFile,1)
//Windows for reading it.
PreScratchW=WindowInit(ScratchFile,1)
PreGodW=WindowInit(GodFile,1)

//Set up coordinates and conversion data
let a=FSGetX(10)
//Grumble
CurSCoord=a
CurBCoord=a+2
ResFactorS=a+4
ResFactorB=a+6
FLDI(1, 2540);
if ScaleOffset then [ FLDI(0,ScaleOffset); FDV(1,0) ]
FLDI(0,ResolutionS); FDV(0,1); FST(0,ResFactorS)
FLDI(0,ResolutionB); FDV(0,1); FST(0,ResFactorB)
if portrait then [ let t=XOffset; XOffset=YOffset; YOffset=t ]

//Get length of scratch file (will go away someday...).
ScratchLen=a+8
WindowReadBlock(PreScratchW,ScratchLen,2)

//Now, compute number of records available to printing pass
let csiz=PermanentBottom-OverlayBottom-nPages*(size PageG/16+size FontG/16)
//what is this stuff??????????????? csiz=csiz-(OverlayTable!2-OverlayTable!1)*256
maxPrintPassRecs=csiz rshift BandFile>>F.LogPagesize

let icc=ICCtot
ICCUses=FSGetX(icc)
Zero(ICCUses,icc) //0=>not assigned
nFontLoads=0
maxFontSizeSoFar=4 //Account for dummy character
maxBandRecsSoFar=0

BandWriteInit()
//Start up band writing
ShowCharsInit()
//And character fonts
]

and PreScanClose() be
[
WindowClose(DL)
BandWriteClose()//doesn’t close BandWindow
MakeFI()
FSPut(ICCUses)
ShowCharsClose()
FontMakeup()
WindowClose(BandWindow)
WindowClose(PreScratchW)
WindowClose(PreGodW)
FSPut(CurSCoord)
compileif ReportSw then [ GetTime(lv Report>>REP.PreTime) ]
]

and ShowPage(pg,part) be
[ CursorDigit(pg>>PageG.PageNumber)
let PsRec=vec size PSStat/16
compileif MeterSw then
[ PSStats=PsRec; Zero(PsRec,size PSStat/16)
PSStats>>PSStat.TimeIn=MeterTime()
]

let frec=part>>PE.pStart
//First record
let nrec=part>>PE.pRecs
SetPartBounds(EL,frec,nrec)
//Limit the EL
SetPartBounds(DL,frec,nrec)

let t=vec 1
t!0=0; t!1=nrec
DblShift(t,-LogPressRecordSize)
//Length of page part
let s=vec 1
s!0=0; s!1=part>>PE.Padding+1
//Prepare to read length entry
DoubleSub(t,s)
// t is pos in part
SetPositioninPart(EL,t)

FontSizePageOld=0
FontSizePageNew=0

//Now read all entities
let Elist=0
[
let Elen=vec 1
Elen!0=0; Elen!1=WindowRead(EL)
if Elen!1 eq 0 then break
//Last entity has length 0

compileif MeterSw then
[ PSStats>>PSStat.Entities=PSStats>>PSStat.Entities+1 ]
let c=vec 1
GetPositioninPart(EL,c)
// c => just beyond entity

let p=PreFSGet(size EHC/16)
p>>EHC.next=Elist
//Chain new one on list
Elist=p

let d=vec 1
DoubleCop(d,c)
DoubleSub(d,table [ 0;size EH/16 ]) // d => beginning of EH
SetPositioninPart(EL,d)
//At beginning of EH
WindowReadBlock(EL,p,size EH/16) //Read EH

DoubleSub(c,Elen)
// c is head of entity commands
DoubleCop(lv p>>EHC.ELCPos,c)
//Save it

DoubleSub(c, table [ 0;1 ])
//Next length position
SetPositioninPart(EL,c)
] repeat

//Initialize bounding box.
CurSMin=nScanLines+1; CurSMax=-1
CurBMin=nBitsPerScan+1; CurBMax=-1
CopyUsed=false
SimplePage=not SoftScan
//true unless we’re not using ORbit
ColorUsed=false

//Start off bands
BandWriteBeginPage()

//Process all entities in order
while Elist do
[
CursorToggle(0)
SetPositioninPart(EL,lv Elist>>EHC.ELCPos)
ShowEntity(Elist)
//Go interpret the entity
let n=Elist>>EHC.next
FSPut(Elist)
Elist=n
]

//Compute area of page used
let bm=CurBMin&(-16)
//Margin offsets are all in "words"
let bc=(CurBMax-bm+32)/32
//Count of double words
pg>>PageG.BitMargin=bm
pg>>PageG.BitWc=bc*2
pg>>PageG.CopyCrucial=CopyUsed
pg>>PageG.SimplePage=SimplePage
pg>>PageG.ColorUsed=ColorUsed

//Fill in min and max bands used. The reason for divides rather than
// shifts in this code is that if CurSMax is still -1 (no stuff entered),
// we wish PageG.FirstBand to be greater than PageG.LastBand
pg>>PageG.FirstBand=CurSMin/BANDWidth
pg>>PageG.LastBand=CurSMax/BANDWidth
//And finish off the bands.
BandWriteEndPage(pg)


compileif MeterSw then
[
MoveBlock(lv PSStats>>PSStat.PG,pg,size PageG/16)
PSStats>>PSStat.TimeOut=MeterTime()
MeterBlock(METERShowPage,PSStats,size PSStat/16)
]

// If this page can be folded into the last one (for purposes of
// font planning), it must be the cas
// that 2*maxBandRecs+nFontRecs is small enough to fit
// (le maxPrintPassRecs)
let m=maxBandRecsSoFar
//not including this page
let nBandRecs=pg>>PageG.nRecords
let nFontRecs=BandRecords(maxFontSizeSoFar+FontSizePageNew+ICCtot)

// If it does not fit, push out the earlier font load
if nFontRecs+m*2 gr maxPrintPassRecs then
[ if maxBandRecsSoFar then MakeFI() //push out current font load
maxFontSizeSoFar=FontSizePageOld+4
for i=0 to ICCtot-1 do if ICCUses!i gr 0 then ICCUses!i=0
maxBandRecsSoFar=0
]
for p=pg to pg+(size PageG/16)*(nPrinterColors-1) by size PageG/16 do
p>>PageG.fontLoad=nFontLoads
for i=0 to ICCtot-1 do if ICCUses!i ls 0 then ICCUses!i=1
maxFontSizeSoFar=maxFontSizeSoFar+FontSizePageNew
if nBandRecs gr maxBandRecsSoFar then maxBandRecsSoFar=nBandRecs
]

and ShowEntity(e) be
[
Entity=e

//Set up for reading DL for this entity
SetBytePositioninPart(DL,lv e>>EH.Dstart)
//Position DL

//Coordinate defaults
MicaX=0
MicaY=0
CoordsInvalid=3
//Say that both coords are wrong
let AlternativeDone=false
//not inside Alternative selection initially

//Color and copy defaults
BandSync(0,0,0)
//Default color
BandOnCopy(0)

//Font defaults
ShowCharsSet(e>>EH.Fontset)
//Set
ShowCharsFont(0)

//Reset-space
ShowCharsSetSpace(0)

EL>>W.ByteCount=-(e>>EH.Length-(size EH/16))*2

while EL>>W.ByteCount ls 0 do
[
CursorToggle(1)
let Com=WindowReadByte(EL)
test Com le EShortMax then

switchon Com rshift 3 into
[
//Show characters short: Com is # of characters - 1
case EShowShort/8:
case EShowShort/8+1:
case EShowShort/8+2:
case EShowShort/8+3:
ShowChars(Com+1)
endcase
//Skip characters short: Com% is #of characters - 1
case ESkipShort/8:
case ESkipShort/8+1:
case ESkipShort/8+2:
case ESkipShort/8+3:
for ch=1 to (Com%)+1 do
WindowReadByte(DL)
endcase
//Show characters and skip one: Com% is number-1
case EShowSkip/8:
case EShowSkip/8+1:
case EShowSkip/8+2:
case EShowSkip/8+3:
ShowChars((Com%)+1)
WindowReadByte(DL)
endcase
//Set space x&y short: (Com+new byte)䕱 is length
case ESpaceXShort/8:
case ESpaceYShort/8:
[
let oth=WindowReadByte(EL)
oth=oth+(Com&3) lshift 8
ShowCharsSetSpace( (((Com rshift 3) eq ESpaceXShort/8)? 1,2),oth)
]
endcase
//Font change
case EFont/8:
case EFont/8+1:
ShowCharsFont(Com)
endcase
default: endcase
]
or
switchon Com into
[
//Alternative: followed by 3 words: mask, ELbytes, DLbytes
case EAlternative:
[ let mask=WindowRead2Bytes(EL)
let ELbytes,DLbytes=vec 1,vec 1
ELbytes!0=WindowRead2Bytes(EL)
ELbytes!1=WindowRead2Bytes(EL)
DLbytes!0=WindowRead2Bytes(EL)
DLbytes!1=WindowRead2Bytes(EL)
if (mask+ELbytes!0+ELbytes!1+DLbytes!0+DLbytes!1) eq 0 then
[ AlternativeDone=false//no more alternatives to skip
endcase
]
if AlternativeDone then
[ if (ELbytes!0 ne 0)%(ELbytes!1 ls 0) then PressError(804)
EL>>W.ByteCount=EL>>W.ByteCount+ELbytes!1
SkipinPart(EL,3,ELbytes)
SkipinPart(DL,3,DLbytes)
endcase
]
//should check here to make sure we can do next alternative
//but since this is the all powerful program, we must be ok
AlternativeDone=true
]
endcase

//OnlyOnCopy: next byte is copy number
case EOnlyOnCopy:
[
BandOnCopy(WindowReadByte(EL))
CopyUsed=true
]
endcase
//Set x: next word is new x as signed integer
case ESetX:
[
MicaX=WindowRead2Bytes(EL)
CoordsInvalid=CoordsInvalid%2//X changed
]
endcase
//Set y: next word is new y as signed integer
case ESetY:
[
MicaY=WindowRead2Bytes(EL)
CoordsInvalid=CoordsInvalid%1//Y changed
]
endcase
//Show characters: next entity byte is # of characters
case EShow:
ShowChars(WindowReadByte(EL))
endcase
//Skip characters: next entity byte is number
case ESkip:
SkipinPart(DL,0,WindowReadByte(EL))
endcase
//Skip control bytes: skip next three bytes
case ESkipControlImmediate:
for i=1 to WindowReadByte(EL) do WindowReadByte(EL)
endcase
case ESkipControl:
[
SkipinPart(DL,0,WindowRead2Bytes(EL))
WindowReadByte(EL)//Type of control info
]
endcase
//Show character immediate
case EShowImmediate:
ShowChars(1,WindowReadByte(EL))
endcase
//Set space x
case ESpaceX:
ShowCharsSetSpace(1,WindowRead2Bytes(EL))
endcase
//Set space y
case ESpaceY:
ShowCharsSetSpace(2,WindowRead2Bytes(EL))
endcase
//Reset-space
case EResetSpace:
ShowCharsSetSpace(4)
ShowCharsSetSpace(0)
endcase
//Space
case ESpace:
ShowChars(1,#40)
endcase
//Brightness,Hue,Saturation
case ESetBright:
BandSync(BandCurHue,BandCurSat,WindowReadByte(EL))
ColorCheck()
endcase
case ESetHue:
BandSync(WindowReadByte(EL),BandCurSat,BandCurBright)
ColorCheck()
endcase
case ESetSat:
BandSync(BandCurHue,WindowReadByte(EL),BandCurBright)
ColorCheck()
endcase
//Show object
case EShowObject:
SimplePage=SimplePage&ShowObject(WindowRead2Bytes(EL))
endcase
//Show dots (two flavors)
case EShowDots:
case EShowDotsOpaque:
[
let c=vec 1
c!0=WindowRead2Bytes(EL)
c!1=WindowRead2Bytes(EL)
ShowDots(c,Com eq EShowDotsOpaque)
SimplePage=false
]
endcase
//Show rectangle (rule)
case EShowRectangle:
[
ShowRectangle(WindowRead2Bytes(EL),
WindowRead2Bytes(EL))
]
endcase
//Nop
case ENop:
endcase
default:
PressError(801,Com)
endcase
]//switchon
]
//while loop

if EL>>W.ByteCount ne 0 then PressError(802)
ShowCharsSetSpace(4)
//Put widths back in font
]

and ColorCheck() be
[ if (BandCurSat ne 0)&(BandCurBright ne 0) then ColorUsed=true
if ((BandCurHue rem 40) ne 0)%
((BandCurSat ne 0)&(BandCurSat ne 255))%
((BandCurBright ne 0)&(BandCurBright ne 255)) then SimplePage=false
]
// COORDINATE STUFF

and CoordsUpdate() be
[
let x=MicaX+Entity>>EH.Xe
let y=MicaY+Entity>>EH.Ye
CoordsConvert(x,y,CurSCoord,CurBCoord,true,CoordsInvalid)
CoordsInvalid=0
]

and CoordsConvert(x,y,lvS,lvB,absolute,whochanged; numargs n) be
[
if n eq 5 then whochanged=3
if whochanged eq 0 then PressError(803)
if portrait then [ whochanged=(table [ 0;2;1;3 ] )!whochanged
let t=x; x=y; y=t ]
if absolute then [ x=x+XOffset; y=y+YOffset ]

if (whochanged&2) ne 0 then
[
FLDI(0,x); FML(0,ResFactorS)
//X scaled
if portrait then FNEG(0)
FSTDP(0,lvS)
//X changed
if portrait&absolute then lvS!0=nScanLines-1+lvS!0
]
if (whochanged&1) ne 0 then
[
FLDI(0,y); FML(0,ResFactorB)
//Y scaled
FSTDP(0,lvB)
//Y changed
if absolute then lvB!0=lvB!0+(FA lshift 4)
//add FA for ORbit
]
]

and CoordsConvertF(lvx,lvy,acS,acB,absolute) be
[
if portrait then [ let t=lvx; lvx=lvy; lvy=t ]
FLD(acS,lvx)
if XOffset & absolute then [ FLDI(0,XOffset); FAD(acS,0) ]
FML(acS,ResFactorS)
if portrait then
[
FNEG(acS)
if absolute then [ FLDI(0,nScanLines-1); FAD(acS,0) ]
]
FLD(acB,lvy)
if YOffset & absolute then [ FLDI(0,YOffset); FAD(acB,0) ]
FML(acB,ResFactorB)
if absolute&FA then [ FLDI(0,FA lshift 4);FAD(acB,0)]
]

// Locate a "box" with given width and height relative to the
// current location, and return its S,B dimensions. Returns
// "true" if thing fits on page.
// There is rather careful handling of the box to try to calculate
// a somewhat larger box than the user asks for. The reason is
// that we wish to insure two things:
//
1. Rectangles that are supposed to meet actually do so,
//
in spite of roundoff error.
//
2. On low resolution devices, rectangles with the same W
//
or H have the same widths or heights, in spite of their
//
alignment with the coordinate grid.

and CoordsConvertBox(w,h,lvSmin,lvSmax,lvBmin,lvBmax) = valof
[
let GetMinMax(cur,delta,lvMin,lvMax) be
// The dimension of the rectangle is "d". When delta>0, this is
// derived by Floor(delta+1/2). When delta<0, we want Floor(-delta+1/2),
// which is -Floor(delta+1/2) because Floor(x)=-Floor(-x+1-epsilon).
//JTM note: while this argument may or may not be correct, the sad truth is that
//
taking the left half of a negative double word integer is not the Floor
//
but rather the Ceiling. So, change the above to be Ceiling(delta-1/2)
// Also: e has been rounded down (not ok if negative delta)
//
Max is e+d, which may be short if e has been rounded down

//
let half= table [ 0; #100000 ]
//
test delta!0 ge 0 then
//
[ DoubleAdd(delta,half)
//
@lvMin=cur!0
//
DoubleAdd(delta,cur)
//
@lvMax=delta!0-1
//
]
//
or[ DoubleSub(delta,half)
//
@lvMax=cur!0+((cur!1 ls 0)?1,0)
//
DoubleAdd(delta,cur)
//
@lvMin=delta!0+1
//
]
//
]

//Ramshaw this time: I think that JTM was just confused. Bob Sproull
// had valid goals, but I am going to vote for purity over everything:
// the following code treats rectangles just like rectinlinear objects,
// computing the edge coordinates precisely and then ceiling’ing them to
// an integral number of pixels. This has some bad effects: two rules
// with the same mica width may not turn out to be the same number of
// pixels. But the good effects outweigh the bad: no holes or double
// coverage.

//Ramshaw one more time: Well, lets be less hard-core about our purity,
// and give people the choice. Test the LocalFidelity switch, and do the
// right thing depending.
[
test LocalFidelity ifso //try for local fidelity by Sproull’s rules
[
let half= table [ 0; #100000 ]
DoubleAdd(delta, half)
let d=delta!0//take floor
test d ge 0 then
[
@lvMin=cur!0+1
@lvMax=cur!0+d
]
or[
@lvMax=cur!0
@lvMin=cur!0+d+1
]
]
ifnot //global fidelity above everything: round each edge independently
[
test delta!0 ge 0 then
[
@lvMin=cur!0+1
DoubleAdd(delta, cur)
@lvMax=delta!0
]
or[
@lvMax=cur!0
DoubleAdd(delta,cur)
@lvMin=delta!0+1
]
]
if @lvMax ls @lvMin then @lvMax=@lvMin //guarantee that size=0 doesn’t
// happen, since none of the code is ready to handle it! (Ramshaw)
]

if CoordsInvalid then CoordsUpdate()
let dS=vec 1; let dB=vec 1
let b,bt,s,st=nil,nil,nil,nil
CoordsConvert(w, h, dS, dB, false)
//Get scan,bit version

GetMinMax(CurBCoord,dB,lv b,lv bt)
GetMinMax(CurSCoord,dS,lv s,lv st)

@lvSmin=s; @lvSmax=st; @lvBmin=b; @lvBmax=bt

resultis CoordsBound(s,st,b,bt)
]

// Check four coordinates against legal limits for page, and
// expand bounding box for this page if necessary. Note that
// the tests for "bit" are done first, and only if they are satisfied
// is the "scan" direction enlarged. Thus CurSMin > CurSMax is
// a fine indication that the page is empty!

and CoordsBound(sl,sr,bl,br) = valof
[
if bl ls CurBMin then
[
if bl ls 0 then resultis false
CurBMin=bl
]
if br gr CurBMax then
[
if (br-FA*16) ge nBitsPerScan then resultis false
CurBMax=br
]

if sl ls CurSMin then
[
if sl ls 0 then resultis false
CurSMin=sl
]
if sr gr CurSMax then
[
if sr ge nScanLines then resultis false
CurSMax=sr
]
resultis true
]