-- TexJust.mesa -- last written by Doug Wyatt, December 18, 1979 11:03 PM -- the TEX justification machinery DIRECTORY TexDebugDefs: FROM "TexDebugDefs", TexDefs: FROM "TexDefs", TexErrorDefs: FROM "TexErrorDefs", TexFontDefs: FROM "TexFontDefs", TexGlueDefs: FROM "TexGlueDefs", TexJustifyDefs: FROM "TexJustifyDefs", TexMemDefs: FROM "TexMemDefs", TexNodeDefs: FROM "TexNodeDefs", TexPackDefs: FROM "TexPackDefs", TexTableDefs: FROM "TexTableDefs", InlineDefs: FROM "InlineDefs"; TexJust: PROGRAM IMPORTS TexDebugDefs,TexErrorDefs,TexFontDefs,TexGlueDefs,TexJustifyDefs, TexMemDefs,TexNodeDefs,TexPackDefs,TexTableDefs,InlineDefs EXPORTS TexJustifyDefs = BEGIN OPEN TexGlueDefs,TexNodeDefs,TexDefs,TexJustifyDefs; BreakNode: TYPE = RECORD [ link: BreakPtr, -- points to prev break, sorted by target nlines: CARDINAL, -- number of lines so far, up to this break width: BigDimn, -- accumulated width up to this break flex: FlexSums, -- accumulated stretch/shrink up to this break node: NodePtr, -- points to place in hlist after which break occurs next: BreakPtr, -- points to breaknode for best preceding break totbad: TotBad, -- accumulated sum of badness squares target: BigDimn -- best curwd for the next break after here ]; -- N.B.: in this implementation, the interpretation of node differs -- from that of curbrk in the Sail version. Here node points to the -- last node to be INCLUDED in the current hlist. Another way to say -- this is that the break occurs between prevp and p (when TryBreak -- is called). In the Sail version, curbrk=p; in this version, -- node=prevp and node.link=p. BreakPtr: TYPE = POINTER TO BreakNode; -- allocation for break nodes jzone: TexMemDefs.ZonePtr_NIL; JustInit: PROCEDURE = BEGIN jzone_TexMemDefs.CreateZone[init: 2, grow: 9]; END; AllocBreakNode: PROCEDURE RETURNS[p: BreakPtr] = INLINE BEGIN RETURN[TexMemDefs.Alloc[jzone,SIZE[BreakNode]]] END; TotBad: TYPE = LONG INTEGER; -- sum of badness squares maxTotBad: TotBad = LAST[LONG INTEGER]; -- largest LONG INTEGER -- Note: TotBad is INTEGER instead of CARDINAL since the badness -- computation sometimes SUBTRACTS penalty^2 for a negative penalty ShortDimn: PROCEDURE[dd: BigDimn] RETURNS[Dimn] = --INLINE-- BEGIN -- ***** for debugging, perhaps this should test for overflow RETURN[InlineDefs.LowHalf[dd]]; END; DeltaFlex: PROCEDURE[a,b: FlexVecPtr] RETURNS[Flex] = BEGIN ord: FlexOrder; v: FlexVal; FOR ord DECREASING IN FlexOrder DO IF (v_a[ord]-b[ord])#0 THEN RETURN[[ord,v]] ENDLOOP; RETURN[zeroFlex]; END; ComputeTotBad: PROCEDURE[bad,pen: Penalty] RETURNS[TotBad] = --INLINE-- BEGIN -- ***** test for bad<0? t,u: LONG INTEGER; epsilon: INTEGER=1; -- *****???***** IF pen>=0 THEN BEGIN t_bad+pen+epsilon; RETURN[t*t] END ELSE BEGIN t_bad+epsilon; u_pen; RETURN[t*t-u*u] END; END; AddEliminatedNodes: PROCEDURE[p: NodePtr, q: BreakPtr] = --INLINE-- BEGIN r: NodePtr_p; -- add width, str, shr from nodes to be eliminated between lines WHILE r#NIL DO WITH rr:r SELECT FROM glue => BEGIN t: GluePtr_rr.g; q.width_q.width+t.space; SumFlex[t, @q.flex]; END; penalty,disc => NULL; kern,space => q.width_q.width+rr.s; eject => IF r#p THEN EXIT; ENDCASE => EXIT; r_r.link; ENDLOOP; END; Justification: PUBLIC PROCEDURE[list: NodeListPtr, LineWidth: LineWidthProc, NewLine: NewLineProc] RETURNS[lastwidth: Dimn] = BEGIN q, bestplace: BreakPtr_NIL; finalglue: GluePtr_NIL; first,widow,brokenline: BOOLEAN; inserts: NodeListPtr; b: BoxNodePtr_NIL; lines: CARDINAL; ragged: CARDINAL; -- raggedness justpar: Penalty; -- maximum penalty to be considered for a possible break penPen,hPen: Penalty; thresholdPenalty: Penalty=1000; topopen,botopen: BreakPtr_NIL; autobreaking: BOOLEAN; -- automatic line breaking not shut off by hyphnode curwd: BigDimn; curflex: FlexSums; p,prevp: NodePtr_NIL; -- current and preceding nodes in hlist TryBreak: PROCEDURE[w: BigDimn, pen: Penalty_0, eject: BOOLEAN_FALSE] = BEGIN -- decides if p might be a reasonable place to break, and updates -- the break node table. This procedure is called when p points -- to a permissible break in an hlist, corresponding to an additional -- penalty as specified. Parameter w is the current width to be used -- in badness calculations; it differs from curwd if an inserted -- hyphen would appear at the break. If this is a potentially useful -- break, a new break node is created. The value of botopen is -- adjusted to "close" break nodes that are no longer relevant. r: BreakPtr_topopen; prevr: BreakPtr_NIL; bestplace: BreakPtr_NIL; bestscore: TotBad_maxTotBad; delta: Dimn; -- difference between current target and w dir: FlexDir; flex: Flex; badness: Penalty; -- penalty for breaking at r -- Look at all open break nodes to see if there are any which lead to -- a badness<=justpar at the current position, and close break nodes -- using the fact that r.link.target<=r.target DO delta_ShortDimn[r.target-w]; dir_IF delta<0 THEN shr ELSE str; flex_DeltaFlex[@curflex[dir], @r.flex[dir]]; badness_GluePenalty[[dir: dir, order: flex.order, num: ABS[delta], den: flex.val]]; IF badness=maxPenalty AND dir=shr THEN BEGIN -- can't shrink this much IF prevr#NIL THEN botopen_prevr ELSE -- a bad break was inevitable BEGIN bestplace_botopen_r; bestscore_0; -- any value is ok here END; EXIT; -- no more feasible breaks END; -- now badness is the stretch/shrink penalty at the attempted break BEGIN totbad: TotBad; IF badness>justpar AND NOT eject THEN GOTO NoGood; totbad_r.totbad+ComputeTotBad[badness,pen]; IF eject AND bestplace=NIL AND totbad>bestscore THEN totbad_bestscore; -- additional penalty for two hyphens in a row, or in 2nd-last line IF r.node.link.type=disc AND (p=NIL OR p.link=NIL OR p.type=disc) THEN totbad_totbad+penPen; IF totbad<=bestscore THEN BEGIN bestplace_r; bestscore_totbad END; EXITS NoGood => NULL END; IF r=botopen THEN EXIT; prevr_r; r_r.link; ENDLOOP; IF p=NIL AND bestplace=NIL THEN bestplace_topopen; -- make the best of a bad case IF bestplace#NIL THEN AddNewBreak[bestplace, bestscore]; END; TryHyphenation: PROCEDURE = --INLINE-- BEGIN prevq,q: NodePtr; w: Dimn_0; f: Font; -- the font of the first letter n: CARDINAL_0; -- n will be the number of letters passed prevq_p; q_p.link; IF q=NIL THEN RETURN; WITH qq:q SELECT FROM char => f_qq.c.font; ENDCASE => RETURN; WHILE (q_prevq.link)#NIL DO WITH qq:q SELECT FROM char => IF qq.c.font=f AND qq.c.char IN['a..'z] THEN BEGIN w_w+TexFontDefs.CharWd[qq.c]; n_n+1 END ELSE EXIT; kern => w_w+qq.s; ENDCASE => EXIT; prevq_q; ENDLOOP; IF n>=5 AND EndsHyphWord[q] AND StraddlesTarget[w] THEN Hyphenate[p.link, n, HyphenChar[f]] ELSE BEGIN curwd_curwd+w; p_prevq END; END; StraddlesTarget: PROCEDURE[w: Dimn] RETURNS[BOOLEAN] = --INLINE-- BEGIN s: BigDimn_curwd+w; r: BreakPtr; FOR r_topopen, r.link UNTIL r.targetcurwd]; END; AddNewBreak: PROCEDURE[bestplace: BreakPtr, bestscore: TotBad] = --INLINE-- BEGIN -- a new break was found and bestplace is the best lead-in q: BreakPtr_AllocBreakNode[]; -- create a new break node q^_[link: , nlines: bestplace.nlines+1, width: curwd, flex: curflex, node: prevp --N.B.!--, next: bestplace, totbad: bestscore, target: ]; -- add width, str, shr from nodes to be eliminated between lines AddEliminatedNodes[p, q]; q.target_q.width+LineWidth[q.nlines]; IF q.target>=topopen.target THEN BEGIN q.link_topopen; topopen_q END ELSE BEGIN -- This case can arise only in weird circumstances due to -- changing line lengths or boxes of negative width p: BreakPtr_topopen; WHILE p#botopen AND p.link.target>q.target DO p_p.link ENDLOOP; q.link_p.link; p.link_q; IF p=botopen THEN botopen_q; END; END; ChooseFinalBreak: PROCEDURE RETURNS[bestplace: BreakPtr, finalglue: GluePtr] = --INLINE-- BEGIN -- choose either the break at the parfillglue ending the paragraph -- or break at the end of the paragraph q: BreakPtr_topopen; r: NodePtr_q.node.link; -- node following q's break g: GluePtr_NIL; TryBreak[curwd]; -- try a break including parfillglue IF r.link#NIL OR topopen.totbad g_rr.g; ENDCASE; RETURN[bestplace: q, finalglue: g]; END; PackageLine: PROCEDURE[q: BreakPtr, inserts: NodeListPtr] RETURNS[b: BoxNodePtr, broken: BOOLEAN] = --INLINE-- BEGIN brk,nextbrk: NodePtr; l: CARDINAL; -- line number line: NodeListPtr_RemoveNodeList[list,q.node]; -- if break is at a discretionary hyphen, add a real hyphen IF (brk_list.link)#NIL THEN WITH bb:brk SELECT FROM disc => BEGIN StoreNode[line,MakeCharNode[bb.c]]; broken_TRUE END; ENDCASE => broken_FALSE; -- now prune unwanted nodes at the break nextbrk_IF q.link#NIL THEN q.link.node ELSE NIL; PruneNodes[nextbrk]; -- and package the line into a box l_q.nlines-1; b_TexPackDefs.HPackage[list: line, desiredwidth: LineWidth[l], xpand: FALSE, inserts: inserts]; IF ragged>0 THEN MakeRagged[b,ragged]; END; PruneNodes: PROCEDURE[brk: NodePtr] = --INLINE-- BEGIN t: NodePtr; WHILE (t_list.link)#NIL DO IF t=brk THEN EXIT; -- don't delete a chosen break SELECT t.type FROM glue,kern,space,penalty,disc => DsNode[RemoveNode[list]]; ENDCASE => EXIT; ENDLOOP; END; -- the justification procedure starts here lastwidth_0; IF list.link=NIL THEN BEGIN FreeNodeList[list]; RETURN[lastwidth] END; -- empty hlist BEGIN OPEN TexTableDefs; r: TexPar_TexParam[ragged]; WITH rr:r SELECT ragged FROM ragged=>ragged_rr.ragged; ENDCASE; justpar_CurPenalty[jpar]; penPen_CurPenalty[penpen]; hPen_CurPenalty[hpen]; END; TexMemDefs.OpenZone[jzone]; lines_0; prevp_list; -- This code is fussier about prevp than the Sail version: -- it should always be true that prevp.link=p. autobreaking_TRUE; curwd_0; ClearFlexSums[@curflex]; topopen_botopen_AllocBreakNode[]; topopen^_[link: NIL, nlines: 0, width: curwd, flex: curflex, node: list, next: NIL, totbad: 0, target: LineWidth[0]]; -- we go through the hlist calling TryBreak at each permissible break WHILE (p_prevp.link)#NIL DO WITH pp:p SELECT FROM char => curwd_curwd+TexFontDefs.CharWd[pp.c]; box => curwd_curwd+pp.width; rule => curwd_curwd+pp.width; glue => BEGIN t: GluePtr=pp.g; IF autobreaking THEN SELECT prevp.type FROM char,box,hyph,disc,ins => TryBreak[curwd]; ENDCASE; curwd_curwd+t.space; SumFlex[t, @curflex]; IF autobreaking THEN TryHyphenation; END; leader,ins => NULL; kern,space => curwd_curwd+pp.s; -- ***** allow break at space Nodes? hyph => autobreaking_(pp.auto=on); penalty => BEGIN pen: Penalty_pp.pts; IF pen TryBreak[curwd+TexFontDefs.CharWd[pp.c], hPen]; eject => BEGIN -- this opens a new break for sure TryBreak[curwd, 0, TRUE]; botopen_topopen; -- and we also close all the others END; ENDCASE => ERROR TexErrorDefs.Confusion; -- bad Node type prevp_p; ENDLOOP; -- now choose either the break at the parfillglue ending the paragraph -- or break at the end of the paragraph [bestplace, finalglue]_ChooseFinalBreak[]; lines_bestplace.nlines; -- Now we have found the best sequence of breaks, namely to break into -- "lines" lines, the last break coming at "bestplace" and the break nodes -- telling where to break after that. The next step is to link up the -- break nodes in forward order, using their next fields for this purpose q_LinkForward[bestplace]; -- During the finishing-up phase, which breaks up the hlist into -- packages that go into the output vlist, q points to the break node -- specifying the end of the current line, and p heads the hlist of -- nodes remaining to be output b_NIL; WHILE (q_q.next)#NIL DO inserts_InitNodeList[]; [b, brokenline]_PackageLine[q, inserts]; first_q.nlines=1; widow_lines>1 AND (first OR q.nlines=lines-1); NewLine[b,inserts,[first:first, widow:widow, broken:brokenline]]; ENDLOOP; IF b#NIL THEN BEGIN lastwidth_b.width+b.shiftamt; IF finalglue#NIL THEN lastwidth_lastwidth-FixGlue[finalglue, b.glueset]; END; TexDebugDefs.ShowZone[jzone,"Justification"]; []_TexMemDefs.CloseZone[jzone]; -- free all the break nodes FreeNodeList[list]; RETURN[lastwidth]; END; EndsHyphWord: PROCEDURE[q: NodePtr] RETURNS[BOOLEAN] = --INLINE-- BEGIN OPEN TexTableDefs; IF q#NIL THEN WITH qq:q SELECT FROM char => RETURN[SfTable[qq.c.char]#sfOne]; glue => RETURN[TRUE]; ENDCASE; RETURN[FALSE]; END; HyphenChar: PROCEDURE[f: Font] RETURNS[FChar] = --INLINE-- BEGIN RETURN[[f,'-]] END; LinkForward: PROCEDURE[p: BreakPtr] RETURNS[BreakPtr] = --INLINE-- BEGIN q,r: BreakPtr; q_NIL; WHILE p#NIL DO r_p.next; p.next_q; q_p; p_r ENDLOOP; RETURN[q]; END; MakeRagged: PROCEDURE[b: BoxNodePtr, ragged: CARDINAL] = --INLINE-- BEGIN OPEN InlineDefs; n: Dimn_b.glueset.num; IF n>0 THEN b.glueset.num_LongDiv[LongMult[100,n],100+ragged]; END; JustInit; END.