-- texdisp.mesa -- last written by Doug Wyatt, February 5, 1980 11:02 PM -- Display output routines for TEX DIRECTORY TexDefs: FROM "TexDefs", TexDispDefs: FROM "TexDispDefs", TexFontDefs: FROM "TexFontDefs" USING [CharHt,CharDp], TexGlueDefs: FROM "TexGlueDefs", TexIODefs: FROM "TexIODefs", TexNodeDefs: FROM "TexNodeDefs", TexMemDefs: FROM "TexMemDefs", XGraphicsDefs: FROM "XGraphicsDefs", InlineDefs: FROM "InlineDefs"; TexDisp: PROGRAM IMPORTS TexMemDefs,TexIODefs,TexFontDefs,XGraphicsDefs,InlineDefs EXPORTS TexDispDefs = BEGIN OPEN TexDefs, TexNodeDefs, TexGlueDefs, TexDispDefs; DEBUG: BOOLEAN = FALSE; shrinkcharsEnabled: BOOLEAN = TRUE; micasPerInch: Dimn=2540; leftMargin: Dimn=micasPerInch; -- one inch topMargin: Dimn=micasPerInch; -- also one inch TexDispConfusion: PUBLIC SIGNAL = CODE; listDir: Direction ← vlist; -- {vlist, hlist} DDimn: TYPE = LONG INTEGER; -- Display dimension, measured in micas DDArray: TYPE = DESCRIPTOR FOR ARRAY OF DDimn; nilDDArray: DDArray = DESCRIPTOR[NIL,0]; scalingFactor: LONG INTEGER ← unitScaling; Scale: PROCEDURE[d: Dimn] RETURNS[DDimn] = INLINE BEGIN RETURN [(d*scalingFactor)/unitScaling]; END; micasPerDot: INTEGER=32; Round: PROCEDURE[d: DDimn] RETURNS[DDimn] = BEGIN RETURN [ IF d>=0 THEN ((d+micasPerDot/2)/micasPerDot)*micasPerDot ELSE ((d-micasPerDot/2)/micasPerDot)*micasPerDot]; END; RoundDown: PROCEDURE[d: DDimn] RETURNS[DDimn] = BEGIN RETURN [ IF d>=0 THEN (d/micasPerDot)*micasPerDot ELSE ((d-micasPerDot+1)/micasPerDot)*micasPerDot]; END; RoundUp: PROCEDURE[d: DDimn] RETURNS[DDimn] = BEGIN RETURN [ IF d>=0 THEN ((d+micasPerDot-1)/micasPerDot)*micasPerDot ELSE (d/micasPerDot)*micasPerDot]; END; RS: PROCEDURE[d: Dimn] RETURNS[DDimn] = INLINE BEGIN RETURN [Round[Scale[d]]]; END; displayFonts: DisplayFonts; bitMap: POINTER TO XGraphicsDefs.Bitmap; screenTop, screenRight, screenBottom, screenLeft: DDimn; charString: STRING ← [1]; -- holds single charcter for PutTextInBitmap -- font initialization InitDisplayFont: PUBLIC PROCEDURE[ fontname: STRING, mode: XGraphicsDefs.textMode, f: Font, fonts: DisplayFonts] = BEGIN fonts[f].mode←mode; WHILE (fonts[f].strike←XGraphicsDefs.GetStrikeHandle[fontname])=NIL DO BEGIN OPEN TexIODefs; UseDisplay; Ws["HELP! Cannot find AL file for "]; Ws[fontname]; SIGNAL TexDispConfusion; END; ENDLOOP; END; -- display routines DisplayPage: PUBLIC PROCEDURE[p: BoxNodePtr, x, y: Dimn, scaling: INTEGER] = BEGIN -- p is the vlist box for the page -- x,y are page-relative coordinates for top,left of rectangle to display -- scaling is factor by which all dimensions are expanded -- integer treated as fixed point with 10 bit fraction screenx, screeny, screenw, screenh: CARDINAL; b: POINTER TO XGraphicsDefs.Bitmap; -- screenx and screeny are dot numbers in bitmap for x,y of page -- screenh and screeny are height and width in dots of rectangle in bitmap -- displayFonts maps from TEX font numbers to strike fonts for display -- b is a pointer to the bitmap for the display screenx2, screeny2: CARDINAL; b←XGraphicsDefs.GetDefaultBitmapHandle[]; screenx←screeny←0; screenw←b.nBits; screenh←b.nLines; -- set up program variables scalingFactor←scaling; bitMap←b; charString.length←1; IF screenx>=b.nBits OR screeny>=b.nLines OR screenh<= 0 OR screenw<=0 THEN RETURN; screenTop←DotsToDDimn[screeny]; screenLeft←DotsToDDimn[screenx]; screenRight←DotsToDDimn[screenx2←MIN[b.nBits,screenx+screenw]]; screenBottom←DotsToDDimn[screeny2←MIN[b.nLines,screeny+screenh]]; -- *** for now, everything is a single font! InitDisplayFont["texscreenfont", XGraphicsDefs.normal, 0, displayFonts]; -- now do it XGraphicsDefs.EraseAreaInBitmap[screenx,screeny,screenx2-1,screeny2-1,b]; DisplayVlist[p,RS[leftMargin-x]+screenLeft,RS[topMargin-y]+screenTop] END; BoxNodePtrCheck: PROCEDURE[pp: NodePtr] RETURNS[BoxNodePtr] = BEGIN -- verify that pp points to a box Node WITH node:pp SELECT FROM box => RETURN [@node]; ENDCASE => NULL; SIGNAL TexDispConfusion; RETURN [NIL]; END; DisplayHlist: PROCEDURE[ pp: NodePtr, x,y: DDimn, sizes: DDArray ← nilDDArray] = -- x,y are left,baseline for box in screen coordinate system BEGIN freeSizes: BOOLEAN; p: BoxNodePtr ← BoxNodePtrCheck[pp]; qptr, qq: NodePtr; prevdir: Direction←listDir; nchild: CARDINAL; -- use as index into widths -- get the sizes for display IF p.dir#hlist THEN SIGNAL TexDispConfusion; listDir←hlist; IF sizes = nilDDArray THEN BEGIN sizes←GetSizes[p]; freeSizes←TRUE; END ELSE freeSizes←FALSE; -- go through the list doing the display qq←p.head; nchild←0; WHILE qq#NIL DO qptr←qq; -- qptr is used so that q is not invalidated if qq changes BEGIN WITH q:qptr SELECT FROM char => DisplayCharacter[q.c, x, y]; string => BEGIN i: CARDINAL; f: Font←q.font; FOR i IN[0..q.length) DO DisplayCharacter[[f,q.text[i]], x, y]; x←x+sizes[nchild]; nchild←nchild+1; ENDLOOP; GOTO Done; END; glue, space, kern => NULL; rule => BEGIN h: DDimn←Scale[IF q.height<0 THEN p.height ELSE q.height]; d: DDimn←Scale[IF q.depth<0 THEN p.depth ELSE q.depth]; w: DDimn←Scale[q.width]; -- use this instead of sizes[nchild] -- since sizes may have been increased to stretch things DisplayRectangle[x,Round[y-h],RoundUp[h+d],RoundUp[w]]; END; box => SELECT q.dir FROM vlist => DisplayVlist[@q,x,y-RS[q.height-q.shiftamt]]; hlist => DisplayHlist[@q,x,y+RS[q.shiftamt]]; ENDCASE => ERROR; leader => BEGIN s: DDimn←sizes[nchild]; -- space to be filled with leaders gptr: NodePtr←q.link; -- pointer to node following leader Node IF gptr=NIL THEN BEGIN SIGNAL TexDispConfusion; GOTO Exit END; WITH gptr SELECT FROM -- should be glue node glue => qq←gptr; -- so that skip this glue on next loop ENDCASE => BEGIN SIGNAL TexDispConfusion; GOTO Exit END; WITH b:q.p SELECT FROM -- should be rule or box box => BEGIN lsizes: DDArray; -- sizes array for leader box xx: DDimn; -- starting place for box leaders xend: DDimn←x+s; ww: DDimn←RS[b.width]; -- box width on display IF ww<=0 THEN GOTO Exit; xx←ww*((x/ww)+1); -- the smallest suitable multiple of ww SELECT b.dir FROM hlist => BEGIN lsizes←GetSizes[@b]; WHILE xx+ww<=xend DO DisplayHlist[@b,xx,y,lsizes]; xx←xx+ww ENDLOOP; END; vlist => BEGIN yy: DDimn ← y-RS[b.height]; listDir←vlist; lsizes←GetSizes[@b]; listDir←hlist; WHILE xx+ww<=xend DO DisplayVlist[@b,xx,yy,lsizes]; xx←xx+ww ENDLOOP; END; ENDCASE => ERROR; FreeSizes[lsizes]; END; rule => -- variable horizontal rule -- *** now uses RoundUp for height .. DKW DisplayRectangle[x,y-RS[b.height], RoundUp[Scale[b.height+b.depth]],s]; -- note: ignores width of rule -- doesn't check for negative height or depth either ENDCASE => BEGIN SIGNAL TexDispConfusion; GOTO Exit END; EXITS Exit => NULL; END; ENDCASE => GOTO Done; -- ignore all other types of nodes x←x+sizes[nchild]; nchild←nchild+1; EXITS Done => NULL END; qq←qq.link; -- NOT q.link; qq may have been changed! ENDLOOP; IF freeSizes THEN FreeSizes[sizes]; listDir←prevdir; END; DisplayVlist: PROCEDURE[ pp: NodePtr, x,y: DDimn, sizes: DDArray ← nilDDArray] = -- x,y are left,top for box in screen coordinate system BEGIN p: BoxNodePtr ← BoxNodePtrCheck[pp]; freeSizes: BOOLEAN; qptr, qq: NodePtr; prevdir: Direction←listDir; nchild: CARDINAL; -- use as index into y's -- get the sizes for display IF p.dir#vlist THEN SIGNAL TexDispConfusion; listDir←vlist; IF sizes = nilDDArray THEN BEGIN sizes←GetSizes[p]; freeSizes←TRUE; END ELSE freeSizes←FALSE; -- go through the list doing the display qq←p.head; nchild←0; WHILE qq#NIL DO qptr←qq; -- qptr is used so that q is not invalidated if qq changes WITH q:qptr SELECT FROM char => DisplayCharacter[q.c, x, y+CharacterDisplayHeight[q.c]]; glue, space, kern => NULL; rule => BEGIN h: DDimn←Scale[q.height+q.depth]; -- use this instead of sizes[nchild] -- since sizes may have been increased to stretch things w: DDimn←Scale[IF q.width<0 THEN p.width ELSE q.width]; DisplayRectangle[x,y,RoundUp[h],RoundUp[w]]; END; box => SELECT q.dir FROM vlist => DisplayVlist[@q,x+RS[q.shiftamt],y]; hlist => DisplayHlist[@q,x+RS[q.shiftamt],y+RS[q.height]]; ENDCASE => ERROR; leader => BEGIN s: DDimn←sizes[nchild]; -- space to be filled with leaders gptr: NodePtr←q.link; -- pointer to node following leader Node IF gptr=NIL THEN BEGIN SIGNAL TexDispConfusion; GOTO Exit END; WITH gptr SELECT FROM -- should be glue node glue => qq←gptr; -- so that skip this glue on next loop ENDCASE => BEGIN SIGNAL TexDispConfusion; GOTO Exit END; WITH b:q.p SELECT FROM -- should be rule or box box => BEGIN lsizes: DDArray; -- sizes array for leader box yy: DDimn; -- starting place for box leaders yend: DDimn←y+s; hh: DDimn←RS[b.height+b.depth]; IF hh<=0 THEN GOTO Exit; yy←hh*(y/hh+1); -- the smallest suitable multiple of hh SELECT b.dir FROM hlist => BEGIN boxh: DDimn ← RS[b.height]; listDir←hlist; lsizes←GetSizes[@b]; listDir←vlist; WHILE yy+hh<=yend DO DisplayHlist[@b,x,yy+boxh,lsizes]; yy←yy+hh ENDLOOP; END; vlist => BEGIN lsizes←GetSizes[@b]; WHILE yy+hh<=yend DO DisplayVlist[@b,x,yy,lsizes]; yy←yy+hh ENDLOOP; END; ENDCASE => ERROR; FreeSizes[lsizes]; END; rule => -- variable horizontal rule -- *** now uses RoundUp for width .. DKW DisplayRectangle[x,y,s,RoundUp[Scale[b.width]]]; -- note: ignores width of rule -- doesn't check for negative width either ENDCASE => BEGIN SIGNAL TexDispConfusion; GOTO Exit END; EXITS Exit => NULL; END; ENDCASE => BEGIN qq←qq.link; LOOP; END; -- ignore all other types of nodes qq←qq.link; -- NOT q.link; qq may have been changed! y←y+sizes[nchild]; nchild←nchild+1; ENDLOOP; IF freeSizes THEN FreeSizes[sizes]; listDir←prevdir; END; -- top level size routines GetSizes: PROCEDURE[p: BoxNodePtr] RETURNS[sizes: DDArray]= BEGIN head: NodePtr ← p.head; flex: ARRAY FlexDir OF Flex; size, err: DDimn; children: CARDINAL; -- count the children that have sizes to remember children←CountItems[head]; IF children=0 THEN RETURN[DESCRIPTOR[NIL,0]]; -- allocate the array to hold the sizes sizes←DESCRIPTOR[ TexMemDefs.AllocMem[SIZE[DDimn]*children],children]; -- calculate and save the natural sizes; save max flex info [flex,size]←NaturalSizes[head,sizes]; -- decide whether to stretch or shrink SELECT err←RS[BoxSize[p]]-size FROM >0 => StretchSizes[head,sizes,err, flex[str].val,flex[str].order]; <0 => IF NOT (p.glueset.dir=shr AND p.glueset.order=regular AND p.glueset.num=p.glueset.den) THEN -- not an overfull box ShrinkSizes[head,sizes,-err,flex[shr].val,flex[shr].order] ELSE IF DEBUG THEN TexIODefs.Ps["<Overfull box on display>"]; ENDCASE => NULL; -- no flex needed; just use natural widths END; FreeSizes: PROCEDURE[sizes:DDArray] = BEGIN children: CARDINAL←LENGTH[sizes]; IF children>0 THEN TexMemDefs.FreeMem[BASE[sizes],SIZE[DDimn]*children]; END; CountItems: PROCEDURE[list:NodePtr] RETURNS[n:CARDINAL] = BEGIN -- count char, box, rule, space, kern, glue nodes n←0; WHILE list#NIL DO WITH pp:list SELECT FROM char, box, rule, space, kern, glue => n←n+1; string => n←n+pp.length; -- don't count leader; it's covered by following glue node ENDCASE => NULL; list←list.link; ENDLOOP; END; GlueShrink: PROCEDURE[g: GluePtr] RETURNS[shrink: Dimn] = BEGIN -- increase nonzero shrink for display minSpaceSize: INTEGER=micasPerDot; IF (shrink←g.flex[shr].val)#0 THEN shrink←MAX[shrink,MIN[g.space/2,g.space-minSpaceSize]]; -- on display, allow glue to shrink to 1/2 space -- if that is at least minSpaceSize -- unless specified shrink is even greater. -- this keeps proportional spacing where possible -- while making sure that spaces don't vanish (unless scaling down) END; NaturalSizes: PROCEDURE[list: NodePtr, sizes: DDArray] RETURNS[flex: ARRAY FlexDir OF Flex, size: DDimn] = BEGIN -- calculate and save the natural sizes -- return max nonzero flex orders, unscaled flexibility, and total size nchild: CARDINAL; c: DDimn; ord: FlexOrder; val: Dimn; stretch: FlexVec ← [0,0,0]; shrink: FlexVec ← [0,0,0]; nchild←0; size←0; WHILE list#NIL DO BEGIN WITH q:list SELECT FROM char => c←CharacterDisplaySize[q.c]; string => BEGIN i: INTEGER; f: Font←q.font; ch: FChar; FOR i IN[0..q.length) DO ch←[f,q.text[i]]; size←size+(sizes[nchild]←CharacterDisplaySize[ch]); nchild←nchild+1; ENDLOOP; GOTO Done; END; box => c←RS[BoxSize[@q]]; rule => c←RS[RuleSize[@q]]; space => c←RS[q.s]; kern => c←RoundDown[Scale[q.s]]; glue => BEGIN g: GluePtr ← q.g; gflex: Flex; space: Dimn←g.space; c←RS[space]; -- stretch gflex←g.flex[str]; stretch[gflex.order]←stretch[gflex.order]+gflex.val; -- shrink gflex←g.flex[shr]; shrink[gflex.order]←shrink[gflex.order]+GlueShrink[g]; END; ENDCASE => BEGIN list←list.link; LOOP; END; size←size+(sizes[nchild]←c); nchild←nchild+1; EXITS Done => NULL END; list←list.link; ENDLOOP; flex←[zeroFlex,zeroFlex]; FOR ord DECREASING IN FlexOrder DO IF (val←stretch[ord])#0 THEN BEGIN flex[str]←[ord,val]; EXIT; END; ENDLOOP; FOR ord DECREASING IN FlexOrder DO IF (val←shrink[ord])#0 THEN BEGIN flex[shr]←[ord,val]; EXIT; END; ENDLOOP; END; BoxSize: PROCEDURE [p: BoxNodePtr] RETURNS[Dimn] = INLINE BEGIN RETURN [IF listDir=vlist THEN p.height+p.depth ELSE p.width]; END; RuleSize: PROCEDURE [p: POINTER TO rule Node] RETURNS[Dimn] = INLINE BEGIN RETURN [IF listDir=vlist THEN p.height+p.depth ELSE p.width]; END; CharacterDisplaySize: PROCEDURE[c: FChar] RETURNS[DDimn] = INLINE BEGIN RETURN [IF listDir=vlist THEN CharacterDisplayHeight[c]+CharacterDisplayDepth[c] ELSE CharacterDisplayWidth[c]]; END; -- stretch routines StretchSizes: PROCEDURE[ head: NodePtr, sizes: DDArray, err: DDimn, flex: Dimn, maxstretch: FlexOrder] = BEGIN qq: NodePtr; old, new: DDimn; nchild: CARDINAL; IF DEBUG THEN BEGIN OPEN TexIODefs; UseDisplay; Ps["<+box "]; Wn[InlineDefs.LowHalf[err/micasPerDot]]; Ws[">"]; END; IF flex=0 THEN BEGIN StretchNonGlue[head,sizes,err]; RETURN END; -- store the stretched glue sizes qq←head; nchild←0; DO -- reduce the error and the flexibility each time stretch some glue WITH q:qq SELECT FROM char, box, rule, space, kern => nchild←nchild+1; string => nchild←nchild+q.length; glue => BEGIN IF q.g.flex[str].order = maxstretch THEN BEGIN old←sizes[nchild]; sizes[nchild]←new←Round[StretchGlue[q.g,err,flex]]; err←err+old-new; IF (flex←flex-q.g.flex[str].val)=0 THEN -- this is the last glue BEGIN sizes[nchild]←sizes[nchild]+err; IF DEBUG THEN ReportFlex[str,new-old+err]; EXIT END; IF DEBUG THEN ReportFlex[str,new-old]; END; nchild←nchild+1; END; ENDCASE => NULL; qq←qq.link; ENDLOOP; END; StretchNonGlue: PROCEDURE[head: NodePtr, sizes: DDArray, err: DDimn] = BEGIN -- stretch the char, box, and rule children evenly by total amount=err -- effect is to add white space after each -- don't stretch last child (so end of box looks okay) -- don't change kerning amounts -- don't change glue (usually won't be any in the list) nchild: CARDINAL; qq: NodePtr; nstretch,n: CARDINAL; r0, r1, diff, x, stretch: DDimn; StretchSize: PROCEDURE[i: CARDINAL] = BEGIN r1←RoundDown[(x←x+err)/nstretch]; IF (diff←r1-r0) # 0 THEN BEGIN sizes[i]←sizes[i]+diff; stretch←stretch+diff; r0←r1; END; END; -- count number of children to stretch qq←head; nstretch←0; WHILE qq#NIL DO WITH q:qq SELECT FROM char, box, rule => nstretch←nstretch+1; string => nstretch←nstretch+q.length; ENDCASE => NULL; qq←qq.link; ENDLOOP; IF nstretch<=1 THEN RETURN; -- nothing in list to stretch (?!) nstretch←nstretch-1; -- don't want to stretch last child -- distribute the stretch evenly qq←head; nchild←0; r0←RoundDown[(x←err)/nstretch]; stretch←0; n←0; WHILE n<nstretch DO WITH q:qq SELECT FROM space, kern, glue => nchild←nchild+1; char, box, rule => BEGIN StretchSize[nchild]; nchild←nchild+1; n←n+1 END; string => THROUGH [0..q.length) WHILE n<nstretch DO StretchSize[nchild]; nchild←nchild+1; n←n+1 ENDLOOP; ENDCASE => NULL; qq←qq.link; ENDLOOP; IF stretch#err THEN ERROR; -- should have exact fit by now IF DEBUG THEN BEGIN OPEN TexIODefs; UseDisplay; Ws["<Stretch nonglue "]; Wn[InlineDefs.LowHalf[stretch/micasPerDot]]; Ws[" dots over "]; Wn[nstretch]; Ws[" items>"]; END; END; StretchGlue: PROCEDURE[g:GluePtr, err:DDimn, flex:Dimn] RETURNS[DDimn] = INLINE BEGIN -- err is scaled, flex is not RETURN [Scale[g.space]+(g.flex[str].val*err)/flex]; END; -- shrink routines ShrinkSizes: PROCEDURE[ head: NodePtr, sizes: DDArray, err: DDimn, flex: Dimn, maxshrink: FlexOrder] = BEGIN -- err is the (scaled, positive) amount by which to shrink sizes -- flex is the (unscaled, positive) amount by which glue can shrink qq: NodePtr; old, new, shrinkchars, scaledflex, vshrink: DDimn; nchild: CARDINAL; IF DEBUG THEN BEGIN OPEN TexIODefs; UseDisplay; Ps["<-box "]; Wn[InlineDefs.LowHalf[err/micasPerDot]]; Ws[">"]; END; shrinkchars←err-(scaledflex←RS[flex]); IF shrinkcharsEnabled AND maxshrink=regular AND shrinkchars>0 THEN -- glue can't shrink enough BEGIN -- shrink chars and lower error goal so don't overshrink rem: DDimn ← ShrinkChars[head,sizes,shrinkchars,scaledflex]; err←scaledflex; IF rem<0 THEN err←err+rem; -- overshot in shrinking the chars END; IF flex=0 THEN RETURN; -- store the shrunken glue sizes IF listDir=vlist THEN -- decide how much to shrink each glue -- for vlist, want same spacing (leading); ok to overshrink a little -- in hlist can have different width spaces; want final err=0 BEGIN num: CARDINAL←0; -- total number of maxshrink glue nodes qq←head; nchild←0; UNTIL qq=NIL DO WITH q:qq SELECT FROM glue => IF q.g.flex[shr].order = maxshrink THEN num←num+1; ENDCASE => NULL; qq←qq.link; ENDLOOP; IF num=0 THEN ERROR; vshrink ← (err+num-1)/num; END; qq←head; nchild←0; UNTIL qq=NIL DO WITH q:qq SELECT FROM char, box, rule, space, kern => nchild←nchild+1; string => nchild←nchild+q.length; glue => BEGIN IF q.g.flex[shr].order = maxshrink THEN BEGIN old←sizes[nchild]; IF listDir=hlist THEN BEGIN -- reduce the error and the flexibility -- each time shrink some glue shrink: Dimn ← GlueShrink[q.g]; sizes[nchild]←new←Round[ShrinkGlue[q.g,err,shrink,flex]]; err←err-old+new; IF (flex←flex-shrink)=0 THEN -- this is the last glue BEGIN sizes[nchild]←sizes[nchild]-err; IF DEBUG THEN ReportFlex[shr,old-new+err]; EXIT END; END ELSE BEGIN sizes[nchild]←new←old-vshrink; err←err-old+new; END; IF DEBUG THEN ReportFlex[shr,old-new]; END; nchild←nchild+1; END; ENDCASE => NULL; qq←qq.link; ENDLOOP; END; ShrinkChars: PROCEDURE[head: NodePtr, sizes: DDArray, err, glueflex: DDimn] RETURNS[DDimn] = BEGIN -- shrink selected nodes by total of err -- okay to overshoot err by as much as glueflex, but minimize overshoot -- return err-actual shrink; may be negative if overshoot -- sequences consist of chars, boxes, and kern nodes -- include boxes because might hold an accented character -- include kern nodes because want uniform spacing in words with kerning -- shrink longer sequences in preference to shorter ones -- minimize variance in shrink between sequences and within them -- never shrink by more than node's width so don't get neg width nchild, nshrink: CARDINAL; num: INTEGER; q: NodePtr; remaining, cs, shrink, target: DDimn; ShrinkNode: TYPE = RECORD [ -- have one of these per sequence of chars to shrink link: ShrinkPtr, -- points to next ShrinkNode node: NodePtr, -- points to first node in the sequence nchild: CARDINAL, -- sizes index for the first node in sequence num: CARDINAL, -- number of nodes to shrink in this sequence sz: DDimn, -- total size of this sequence minsz: DDimn, -- size of smallest node to shrink in this sequence done: BOOLEAN -- starts FALSE, set TRUE when shrink this sequence ]; ShrinkPtr: TYPE = POINTER TO ShrinkNode; shrinklist,shrinknode: ShrinkPtr ← NIL; AllocShrinkNode: PROCEDURE RETURNS[p: ShrinkPtr] = INLINE BEGIN RETURN[TexMemDefs.AllocMem[SIZE[ShrinkNode]]]; END; FreeShrinkNode: PROCEDURE[p: ShrinkPtr] = INLINE BEGIN TexMemDefs.FreeMem[p,SIZE[ShrinkNode]]; END; FreeShrinkList: PROCEDURE = INLINE BEGIN WHILE shrinklist#NIL DO shrinknode←shrinklist.link; FreeShrinkNode[shrinklist]; shrinklist←shrinknode; ENDLOOP; END; FindLongest: PROCEDURE RETURNS [ShrinkPtr] = INLINE BEGIN longest, list: ShrinkPtr; sz: DDimn; -- sz for current longest longest←NIL; sz←0; list←shrinklist; WHILE list#NIL DO IF list.done=FALSE AND list.sz>sz THEN BEGIN longest←list; sz←list.sz; END; list←list.link; ENDLOOP; IF longest#NIL THEN longest.done←TRUE; RETURN [longest]; END; first: BOOLEAN←TRUE; StartShrinkNode: PROCEDURE[node: NodePtr, nchild: CARDINAL] = BEGIN shrinknode←AllocShrinkNode[]; shrinknode↑←[shrinklist,node,nchild,0,0,LAST[DDimn],FALSE]; shrinklist←shrinknode; first←FALSE; END; UpdateShrinkNode: PROCEDURE[size: DDimn] = BEGIN nshrink←nshrink+1; cs←MIN[cs,size]; shrinknode.num←shrinknode.num+1; shrinknode.minsz←MIN[shrinknode.minsz,size]; shrinknode.sz←shrinknode.sz+size; END; LookAhead: PROCEDURE[q: NodePtr, nchild: CARDINAL] = BEGIN size: DDimn←sizes[nchild]; nxtchild: CARDINAL ← nchild+1; nxt: NodePtr←q.link; -- pointer to following node(s) WHILE nxt#NIL DO WITH nxt SELECT FROM glue, space, rule => EXIT; -- end of sequence kern => size←size+sizes[nxtchild]; -- adjust for kerning char, box, string => BEGIN IF first THEN StartShrinkNode[q,nchild]; UpdateShrinkNode[size]; EXIT; END; ENDCASE => NULL; nxt←nxt.link; nxtchild←nxtchild+1; ENDLOOP; RETURN; END; ApplyShrink: PROCEDURE[shrinknode: ShrinkPtr, shrink: DDimn] = BEGIN q: NodePtr; nchild: CARDINAL←shrinknode.nchild; i: CARDINAL←shrinknode.num; ShrinkOne: PROCEDURE = BEGIN sizes[nchild]←sizes[nchild]-shrink; i←i-1; nchild←nchild+1; END; FOR q←shrinknode.node,q.link UNTIL q=NIL OR i=0 DO WITH qq:q SELECT FROM char, box => ShrinkOne; string => THROUGH[0..qq.length) WHILE i>0 DO ShrinkOne ENDLOOP; kern => nchild←nchild+1; ENDCASE => NULL; ENDLOOP; END; Report: PROCEDURE [nchild, num: CARDINAL, cs: DDimn] = BEGIN OPEN TexIODefs; Ws["<- "]; PrintSequence[head,nchild,num]; IF cs>micasPerDot THEN BEGIN Ws[" "]; Wn[InlineDefs.LowHalf[cs/micasPerDot]]; END; Wc['>]; END; remaining←err; -- decrement this as reduce sizes -- create shrinklist, set nshrink←SUM of num's; set cs←MIN of minsz's q←head; nchild←0; nshrink←0; first←TRUE; cs←LAST[DDimn]; WHILE q#NIL DO WITH qq:q SELECT FROM glue, space, rule => -- these break a sequence BEGIN nchild←nchild+1; first←TRUE; END; kern => nchild←nchild+1; -- these can be imbedded inside a sequence char, box => -- these can be part of a sequence BEGIN LookAhead[q,nchild]; nchild←nchild+1 END; string => -- so can these BEGIN len: StringLength←qq.length; IF len>1 THEN BEGIN IF first THEN StartShrinkNode[q,nchild]; THROUGH[0..len-1) -- for all but the last character DO UpdateShrinkNode[sizes[nchild]]; nchild←nchild+1 ENDLOOP; END; LookAhead[q,nchild]; nchild←nchild+1; -- for the last character END; ENDCASE => NULL; q←q.link; ENDLOOP; IF nshrink=0 THEN RETURN[err]; -- first try to do uniform shrink across entire list IF (shrink←MIN[cs,RoundDown[err/nshrink]])<cs -- can still increase shrink AND nshrink*(target←RoundUp[shrink+1])<=err+(glueflex/3) -- allow shrink beyond err, but don't overshoot by more -- than 1/3 glueflex, else get overshrunk chars. THEN shrink←target; IF shrink>0 THEN BEGIN FOR shrinknode←shrinklist,shrinknode.link WHILE shrinknode#NIL DO ApplyShrink[shrinknode,shrink]; shrinknode.minsz←shrinknode.minsz-shrink; ENDLOOP; IF DEBUG THEN BEGIN OPEN TexIODefs; Ws["<-"]; Wn[nshrink]; IF shrink>micasPerDot THEN BEGIN Wc['*]; Wn[InlineDefs.LowHalf[shrink/micasPerDot]]; END; Wc['>]; END; IF (remaining←remaining-nshrink*shrink)<=0 THEN BEGIN FreeShrinkList[]; RETURN[remaining]; END; END; -- distribute the error keeping shrink within a sequence uniform -- don't sort list since usually only shrink a few sequences target←RoundUp[remaining/nshrink]; -- want to shrink by this WHILE remaining>0 AND (shrinknode←FindLongest[])#NIL DO num←shrinknode.num; cs←MIN[target,shrinknode.minsz,RoundDown[(remaining+glueflex)/num]]; -- don't shrink by more than target -- don't have shrink in this sequence > minsz -- don't overshoot remaining+glueflex IF cs>0 THEN BEGIN ApplyShrink[shrinknode,cs]; remaining←remaining-num*cs; IF DEBUG THEN Report[shrinknode.nchild,num,cs]; END; ENDLOOP; -- give up now, even if that's not enough shrinking FreeShrinkList[]; RETURN [remaining]; END; ShrinkGlue: PROCEDURE[g:GluePtr, err:DDimn, shrink,flex:Dimn] RETURNS[DDimn] = INLINE BEGIN -- err is scaled, flex and shrink are not RETURN [Scale[g.space]-(shrink*err)/flex]; END; -- character size information CharacterDisplayHeight: PROCEDURE [c: FChar] RETURNS[DDimn] = BEGIN RETURN [RS[TexFontDefs.CharHt[c]]]; END; CharacterDisplayWidth: PROCEDURE [c: FChar] RETURNS[DDimn] = BEGIN font: DisplayFont ← displayFonts[--c.font-- 0 --for now only one font--]; xarray: POINTER TO ARRAY [0..0) OF CARDINAL ← font.strike.xInSegment; char: CARDINAL ← LOOPHOLE[c.char,CARDINAL]; w: CARDINAL ← xarray[char+1]-xarray[char]; IF font.mode = XGraphicsDefs.bold THEN w←w+1; RETURN [DotsToDDimn[w]]; END; DotsToDDimn: PROCEDURE [dots: CARDINAL] RETURNS[DDimn] = INLINE BEGIN RETURN [dots*micasPerDot]; END; CharacterDisplayDepth: PROCEDURE [c: FChar] RETURNS[DDimn] = BEGIN RETURN [RS[TexFontDefs.CharDp[c]]]; END; Strike: PROCEDURE [c: FChar] RETURNS[POINTER TO XGraphicsDefs.StrikeFont] = INLINE BEGIN RETURN [displayFonts[--c.font-- 0 --for now only one font--].strike]; END; -- routines that actually put bits on the screen DisplayCharacter: PROCEDURE [c: FChar, x, y: DDimn] = BEGIN -- x,y are scaled and rounded left,baseline for character -- takes care of synthetic italic and boldface font: DisplayFont ← displayFonts[--c.font-- 0 --for now only one font--]; -- for now, skip character if not fully on screen rather than clip IF x < screenLeft THEN RETURN; IF (x+CharacterDisplayWidth[c]) > screenRight THEN RETURN; IF (y-CharacterDisplayHeight[c]) < screenTop THEN RETURN; IF (y+CharacterDisplayDepth[c]) > screenBottom THEN RETURN; charString[0]←c.char; []←XGraphicsDefs.PutTextInBitmap[charString, DotNum[x], DotNum[y], font.strike, font.mode, bitMap]; END; DisplayRectangle: PROCEDURE [x, y, h, w: DDimn] = BEGIN -- x,y,h,w are scaled and rounded left,top,height,width x1, x2, y1, y2: DDimn; x1 ← MAX[screenLeft,x]; x2 ← MIN[screenRight,x+w]; y1 ← MAX[screenTop,y]; y2 ← MIN[screenBottom,y+h]; IF x1>=x2 OR y1>=y2 THEN RETURN; XGraphicsDefs.PutAreaInBitmap[DotNum[x1], DotNum[y1], DotNum[x2]-1, DotNum[y2]-1, bitMap]; END; DotNum: PROCEDURE[d: DDimn] RETURNS[CARDINAL] = INLINE BEGIN -- doesn't check for dimension out of range RETURN [InlineDefs.LowHalf[d]/micasPerDot]; END; -- miscellaneous routines used in debugging PrintSequence: PROCEDURE [head: NodePtr, first,num: CARDINAL] = BEGIN i: CARDINAL; FOR i IN [first..first+num] DO PrintChar[head,i]; ENDLOOP; END; PrintChar: PROCEDURE [head: NodePtr, nchild: CARDINAL] = BEGIN WHILE head#NIL DO WITH p:head SELECT FROM glue, space, kern, box, rule => nchild←nchild-1; char => IF nchild=0 THEN -- this is it BEGIN TexIODefs.Wc[p.c.char]; RETURN; END ELSE nchild←nchild-1; ENDCASE; head←head.link; ENDLOOP; END; ReportFlex: PROCEDURE [dir: FlexDir, amount: DDimn] = BEGIN OPEN TexIODefs; Ws[IF dir=shr THEN "<-g " ELSE "<+g "]; Wn[InlineDefs.LowHalf[amount/micasPerDot]]; Ws[">"]; END; AllocDisplayFonts: PROCEDURE[numfonts: CARDINAL] RETURNS[dispfonts: DisplayFonts] = INLINE BEGIN dispfonts←DESCRIPTOR[ TexMemDefs.AllocMem[SIZE[TexDispDefs.DisplayFont]*numfonts], numfonts]; END; FreeDisplayFonts: PROCEDURE[dispfonts: DisplayFonts] = INLINE BEGIN TexMemDefs.FreeMem[BASE[dispfonts], SIZE[TexDispDefs.DisplayFont]*LENGTH[dispfonts]]; END; setup: BOOLEAN←TRUE; SetUpDisplay: PUBLIC PROCEDURE = BEGIN DCBHead: POINTER TO CARDINAL = LOOPHOLE[420B]; nextDCB: POINTER TO ARRAY[0..3] OF CARDINAL ← LOOPHOLE[DCBHead↑]; DCBtop: POINTER TO ARRAY [0..3] OF CARDINAL ← nextDCB; nScanLines: CARDINAL ← 608; -- UNTIL nextDCB[0] = 0 DO -- nScanLines ← nScanLines - nextDCB[3]*2; -- nextDCB ← LOOPHOLE[nextDCB[0]]; -- ENDLOOP; -- nScanLines ← nScanLines - nextDCB[3]*2; -- IF nScanLines<=0 THEN nScanLines←1; XGraphicsDefs.SetDefaultBitmap[608,nScanLines]; [] ← XGraphicsDefs.TurnOnGraphics[]; nextDCB ← LOOPHOLE[DCBHead↑]; nextDCB[0] ← LOOPHOLE[DCBtop,CARDINAL]; setup←FALSE; END; InitDisp: PUBLIC PROCEDURE = BEGIN IF setup THEN SetUpDisplay; -- allocate display font array displayFonts←AllocDisplayFonts[nFonts]; END; CloseDisp: PUBLIC PROCEDURE = BEGIN -- deallocate FreeDisplayFonts[displayFonts]; END; END.