-- 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[""]; 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 nchild_nchild+1; char, box, rule => BEGIN StretchSize[nchild]; nchild_nchild+1; n_n+1 END; string => THROUGH [0..q.length) WHILE n NULL; qq_qq.link; ENDLOOP; IF stretch#err THEN ERROR; -- should have exact fit by now IF DEBUG THEN BEGIN OPEN TexIODefs; UseDisplay; Ws[""]; 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]])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.