MODULE Outline; IMPORT Offset, Text, PS, R2, Circle, Show, TextList, Type; IMPORT TypeLinesC, TypeLinesL, TypeLinesR; PRIVATE VAR north, south, east, west; (* This module maintains a "current bounding box", which other procedures in the module use to locate the parts of the outline. The bounding box is set by the "Start" procedure below. *) /* These four numerc variables determine the current bounding box for the outline. */ CONST TopMDefault = 30, LeftMDefault = 20; PRIVATE VAR TopM := TopMDefault, LeftM := LeftMDefault; (* This module maintains a top and left margin, which specify how much below and to the right of the northwest corner of the bounding box the current point should be positioned at the start of each outline. *) PROC SetTopM(val) IS TopM := val END; PROC SetLeftM(val) IS LeftM := val END; UI SetTool(SetTopM); UI SetTool(SetLeftM); (* Set the current top/left margin to "val". *) UI Param(SetTopM, TopMDefault); UI Param(SetTopM, 0); UI Param(SetTopM, 15); UI Param(SetTopM, 30); UI Param(SetTopM, 45); UI Param(SetLeftM, LeftMDefault); UI Param(SetLeftM, 0); UI Param(SetLeftM, 10); UI Param(SetLeftM, 20); UI Param(SetLeftM, 30); PRIVATE CONST LeftMargin = 15; PROC Start(corner1, corner2) IS IF VAR x1, y1, x2, y2 IN corner1 = (x1, y1) AND corner2 = (x2, y2) -> north := MAX(y1, y2); south := MIN(y1, y2); east := MAX(x1, x2); west := MIN(x1, x2); PS.MoveTo((west + LeftM, north - TopM)) END FI END; UI PointTool(Start); (* Set the current bounding box so "corner1" and "corner2" are its opposite corners and set the current point according to the current top and left margin values. This procedure must be called before any calls to the other procedures in this module. *) PROC Center(txt) IS VAR w, h, pt IN w, h := TextList.Size(TextList.FromText(txt)); pt := PS.CurrentPoint(); TypeLinesC.North(((west + east) / 2, CDR(pt)), txt); PS.MoveTo(R2.Plus(pt, (0, -h))) END END; UI TextTool(Center); (* Center "txt" horzontally in the current bounding box so its north edge is horizontal with the current point, and advance the current point down by the height of the text. The text is rendered in the current font face and size. If "txt" contains "\n" characters, the lines are typeset as a block. *) PROC SkipTo(p) IS VAR curr IN curr := PS.CurrentPoint(); PS.MoveTo((CAR(curr), CDR(p))) END END; UI PointTool(SkipTo); (* Set the current point to the point horizontal with "p" and vertical with the current point. *) VAR InterpointSkip := (1.1, 0); VAR IntersubpointSkip := (0.3, 0); (* If the InterpointSkip is "(k, d)", then the gap before each new outermost item will be k*fs+d, where fs is the font point size. The IntersubpointSkip is interpreted similarly, but applies after subpoints (of any level). *) PRIVATE CONST TabChar = Text.GetChar("\t", 0); /* TabChar = '\t'. It is the character that users type to indent subitems of the outline. */ PRIVATE CONST TabRatio = 1; /* TabRatio is the ratio between the pointsize of the font and the amount by which subpoints are indented. */ PRIVATE PROC (i, dx, dy):ProcessIndent(txt) IS VAR lineskip = InterpointSkip IN DO i < Text.Length(txt) AND Text.GetChar(txt, i) = TabChar -> dx := dx + PS.GetFontPtSize() * TabRatio; i := i + 1; lineskip := IntersubpointSkip OD; dy := dy - (PS.GetFontPtSize() * CAR(lineskip) + CDR(lineskip)) END END; PRIVATE CONST BulletChar = Text.GetChar("*", 0); /* "BulletChar" is the character typed to get a bullet on a point. If the next character is 'o', the result will be a black circle. If the next character is '-', the result will be an em dash. Otherwise the next character will be the bullet. */ PRIVATE PROC i:ProcessBullet(txt, dx, dy) IS IF i + 1 < Text.Length(txt) AND Text.GetChar(txt, i) = BulletChar -> DrawBullet(Text.GetChar(txt, i + 1), dx, dy); i := i + 2 | SKIP FI END; PRIVATE CONST DotChar = 183, DashChar = Text.GetChar("_", 0); PRIVATE PROC DrawBullet(ch, dx, dy) IS VAR bullet, yfudge, p, w IN IF ch = Text.GetChar("o", 0) -> bullet := DotChar; yfudge := 0 | ch = Text.GetChar("-", 0) -> bullet := DashChar; yfudge := 0.45 * PS.GetFontPtSize() | bullet := ch; yfudge := 0 FI; p := PS.CurrentPoint(); w := PS.StringWidth(" "); p := R2.Plus(p, (dx - w, dy + yfudge)); DrawDot(p, bullet) END END; PRIVATE PROC DrawDot(p, ch) IS IF ch = DotChar -> VAR asc, dec, w IN asc, dec := PS.FontHeight(); w := PS.StringWidth("i"); FilledCirc(R2.Minus(p, (w / 2, 0.65 * asc)), asc / 8) END | TypeLinesR.NE(p, Text.FromChar(ch)) FI END; PRIVATE PROC FilledCirc(p, rad) IS SAVE PS IN Circle.Draw(p, R2.Plus(p, (rad, 0))); PS.Fill() END END; PRIVATE PROC (i, dx, dy):ProcessBody(txt) IS VAR suffix IN suffix := Text.Sub(txt, i, 9999); TypeLinesL.NW(R2.Plus(PS.CurrentPoint(), (dx, dy)), suffix); VAR width, height IN width, height := TextList.Size(TextList.FromText(suffix)); dy := dy - height END END END; PROC Item(txt) IS SAVE Offset IN Offset.SetHorVer(0, 0); VAR i = 0, dx = 0, dy = 0 IN (i, dx, dy):ProcessIndent(txt); (i):ProcessBullet(txt, dx, dy); (i, dx, dy):ProcessBody(txt); PS.MoveTo(R2.Plus(PS.CurrentPoint(), (0, dy))) END END END; UI TextTool(Item); (* Display "txt" as an item or sub-item. If "txt" begins with a tab ("\t") character, then the remaining characters are displayed as a sub-item. Otherwise, all of "txt" is displayed as an item. If "txt" is displayed as an item, the current point is first moved down according to the current value of "InterpointSkip". The item is then processed as described below. If "txt" is displayed as a sub-item, then the current point is first moved down according to the current value of "IntersubpointSkip". The sub-item is then processed as described below, but indented by a fixed amount. If the leading character of an item or sub-item is an asterisk ("*"), then the item or sub-item will be bulleted. The next character determines the kind of bullet: if it is a small oh ("o"), the bullet is a filled round dot; if it is a dash ("-"), the bullet is a dash; if it is any other character, that character serves as the bullet. The bullet is outdented to the left, so that the left edges of bulleted and unbulleted items will be aligned. Within an item, newline ("\n") characters cause line breaks. Each new line is left-justified with the text of the first line of the item. *) PRIVATE PROC (i, dx, dy):ProcessBodyCL(txt, cl) IS VAR suffix IN suffix := Text.Sub(txt, i, 9999); SAVE PS IN VAR asc, dec IN asc, dec := PS.FontHeight(); PS.MoveTo(R2.Plus(PS.CurrentPoint(), (dx, dy-asc))) END; Show.L(suffix); APPLY(cl) END; VAR width, height IN width, height := TextList.Size(TextList.FromText(suffix)); dy := dy - height END END END; PROC ItemCL(txt, cl) IS SAVE Offset IN Offset.SetHorVer(0, 0); VAR i = 0, dx = 0, dy = 0 IN (i, dx, dy):ProcessIndent(txt); (i):ProcessBullet(txt, dx, dy); (i, dx, dy):ProcessBodyCL(txt, cl); PS.MoveTo(R2.Plus(PS.CurrentPoint(), (0, dy))) END END END; (* Like "Item" above, but newlines are not recognized in "txt", and after "txt" is rendered, "cl" is applied with the current point set just after the rendered "txt". *)