<<>> <> <> <> <> DIRECTORY Atom USING [GetPName], Convert, EditSpan USING [CannotDoEdit, Move], Imager, IO, MessageWindow, NodeProps, NodeStyle, NodeStyleOps, ProcessProps, TiogaIO USING [FromRope, ToRope], Real, Rope, RuntimeError USING [UNCAUGHT], Scaled, TEditDocument USING [Selection], TEditFormat, TEditInput USING [CommandProc, currentEvent, Register], TEditInputOps, TEditSelection USING [Alloc, Copy, Free, GrowSelection, MakeSelection, pSel], TextEdit, TextNode, TextLooks USING [allLooks, noLooks], TiogaImager; ArtworkCenteredImpl: CEDAR PROGRAM IMPORTS Atom, Convert, EditSpan, Imager, IO, MessageWindow, NodeProps, NodeStyle, NodeStyleOps, ProcessProps, TiogaIO, Real, Rope, RuntimeError, Scaled, TEditFormat, TEditInput, TEditInputOps, TEditSelection, TextEdit, TextNode, TiogaImager ~ BEGIN ROPE: TYPE ~ Rope.ROPE; Cant: ERROR [cantMessage: ROPE ¬ NIL] ~ CODE; IsCenteredDisplay: PROC [node: TextNode.Ref] RETURNS [BOOL] ~ { prop: REF ~ NodeProps.GetProp[node, $Artwork]; WITH prop SELECT FROM a: ATOM => RETURN [a = $CenteredDisplay]; r: ROPE => RETURN [Rope.Equal[r, "CenteredDisplay"]]; ENDCASE => RETURN [FALSE]; }; <> MakeBranchSelection: PROC ~ { tSel: TEditDocument.Selection; IF TEditSelection.pSel=NIL OR TEditSelection.pSel.viewer=NIL THEN RETURN; WHILE TEditSelection.pSel.granularity # branch DO TEditSelection.GrowSelection[]; ENDLOOP; tSel ¬ TEditSelection.Alloc[]; TEditSelection.Copy[source: TEditSelection.pSel, dest: tSel]; tSel.pendingDelete ¬ TRUE; tSel.granularity ¬ char; -- necessary for inserting text without destroying node properties! tSel.insertion ¬ after; TEditSelection.MakeSelection[new: tSel]; TEditSelection.Free[tSel] }; DoWithLocks: PROC [proc: PROC[root: TextNode.Ref, tSel: TEditDocument.Selection]] ~ { error: {none, cant, uncaught} ¬ none; errorMessage: ROPE ¬ NIL; TEditInputOps.CallWithLocks[proc, write ! Cant => {error ¬ cant; errorMessage ¬ cantMessage; CONTINUE}; RuntimeError.UNCAUGHT => {error ¬ uncaught; CONTINUE}; ]; SELECT error FROM none => NULL; cant => { TEditInputOps.EditFailed[errorMessage]; }; uncaught => proc[NIL, TEditSelection.Alloc[]]; -- do without lock for easy debug ENDCASE => ERROR; }; OpenCenteredDisplayOp: TEditInput.CommandProc ~ { <> OpenCenteredDisplay: PROC [root: TextNode.Ref, tSel: TEditDocument.Selection] ~ { displayNode: TextNode.Ref; MakeBranchSelection[]; displayNode ¬ TEditSelection.pSel.start.pos.node; IF displayNode = NIL OR NOT IsCenteredDisplay[displayNode] THEN Cant["Not a proper CenteredDisplay node (artwork property missing)"] ELSE { displayRope: ROPE ¬ GetCDRope[displayNode]; IF NOT Rope.Match["Artwork=CenteredDisplay*", displayRope] THEN Cant["Not a proper CenteredDisplay node (begin with Artwork=CenteredDisplay)"] ELSE { branch: TextNode.Ref ¬ TiogaIO.FromRope[rope: displayRope]; branchSpan: TextNode.Span ¬ TextNode.MakeNodeSpan[TextNode.FirstChild[branch], TextNode.LastWithin[branch]]; result: TextNode.Span ¬ EditSpan.Move[destRoot: TextNode.Root[displayNode], sourceRoot: branch, dest: TextNode.MakeNodeLoc[displayNode], source: branchSpan, where: after, nesting: 1, event: TEditInput.currentEvent ! EditSpan.CannotDoEdit => Cant[]]; UnCacheCD[displayNode]; tSel.pendingDelete ¬ FALSE; tSel.start.pos ¬ [displayNode, 0]; tSel.end.pos ¬ [result.end.node, MAX[TextEdit.Size[result.end.node]-1, 0]]; tSel.granularity ¬ branch; TEditSelection.MakeSelection[new: tSel, selection: primary]; }; }; }; DoWithLocks[OpenCenteredDisplay]; quit ¬ TRUE; }; CloseCenteredDisplayOp: TEditInput.CommandProc ~ { <> CloseCenteredDisplay: PROC [root: TextNode.Ref, tSel: TEditDocument.Selection] ~ { displayNode: TextNode.Ref; displayRope: ROPE; MakeBranchSelection[]; displayNode ¬ TEditSelection.pSel.start.pos.node; IF displayNode = TEditSelection.pSel.end.pos.node THEN Cant["Please select a branch"]; IF NodeProps.GetProp[displayNode, $Artwork] # NIL THEN Cant["Already an Artwork node (of some kind)."]; displayRope ¬ TiogaIO.ToRope[displayNode]; IF NOT Rope.Match["Artwork=CenteredDisplay*", displayRope] THEN Cant["Not a proper CenteredDisplay node (begin with Artwork=CenteredDisplay)"]; TEditInputOps.ChangeCaretLooks[add: TextLooks.noLooks, remove: TextLooks.allLooks]; TEditInputOps.InsertRope[displayNode.rope]; UnCacheCD[displayNode]; NodeProps.PutProp[displayNode, $Artwork, Atom.GetPName[$CenteredDisplay]]; NodeProps.PutProp[displayNode, $CenteredDisplayRope, displayRope]; [] ¬ GetCDBox[displayNode]; }; DoWithLocks[CloseCenteredDisplay]; quit ¬ TRUE; }; <> Format: TEditFormat.FormatProc ~ { <<[lineInfo: TEditFormat.LineInfo, node: TextEdit.RefTextNode, startOffset: TextEdit.Offset, nodeStyle: NodeStyle.Ref, lineWidth: Scaled.Value, doLigsAndKern: BOOLEAN _ FALSE, kind: NodeStyleOps.OfStyle _ screen]>> <> Inner: PROC ~ { formattedBox: TiogaImager.Box ¬ GetCDBox[node, lineWidth, nodeStyle]; IF formattedBox # NIL THEN { extents: Imager.Box ¬ formattedBox.bounds; lineInfo.artworkData ¬ formattedBox; <> lineInfo.xmin ¬ 0; lineInfo.ymin ¬ 0; lineInfo.xmax ¬ Ceiling[extents.xmax-extents.xmin]; lineInfo.ymax ¬ Ceiling[extents.ymax-extents.ymin]; { <> extraIndent: REAL ~ nodeStyle.GetReal[$firstIndent]; leftIndent: REAL ~ nodeStyle.GetReal[$leftIndent] + extraIndent; rightIndent: REAL ~ nodeStyle.GetReal[$rightIndent]; clippedLineWidth: REAL ~ PositiveMin[nodeStyle.GetReal[$lineLength], Scaled.Float[lineWidth]]; trimmedLineWidth: REAL ~ MAX[clippedLineWidth-leftIndent-rightIndent, 0]; SELECT nodeStyle.GetLineFormatting[] FROM FlushLeft => {lineInfo.xOffset ¬ Scaled.FromReal[leftIndent];}; FlushRight => {lineInfo.xOffset ¬ Scaled.FromReal[MAX[Scaled.Float[lineWidth]-leftIndent-extents.xmax, 0]]}; Centered, Justified => {lineInfo.xOffset ¬ Scaled.FromReal[leftIndent+(trimmedLineWidth-extents.xmax)/2]}; ENDCASE => ERROR; }; }; }; lineInfo.startPos ¬ [node, 0]; lineInfo.nextPos ¬ [node, TextEdit.Size[node]]; lineInfo.nChars ¬ TextEdit.Size[node]; DoWithCatch[Inner]; }; DoWithCatch: PROC [proc: PROC] ~ { error: {none, cant, uncaught} ¬ none; errorMessage: ROPE ¬ NIL; Log: PROC [msg: ROPE] ~ { WITH ProcessProps.GetProp[$StdOut] SELECT FROM errout: IO.STREAM => { IO.PutRope[errout, msg]; IO.PutRope[errout, "\n"]; }; ENDCASE => { MessageWindow.Append[message: msg, clearFirst: TRUE]; MessageWindow.Blink[]; }; }; proc[ ! Cant => {error ¬ cant; errorMessage ¬ cantMessage; CONTINUE}; RuntimeError.UNCAUGHT => {error ¬ uncaught; CONTINUE}; ]; SELECT error FROM none => NULL; cant => { Log[errorMessage]; }; uncaught => proc[]; ENDCASE => ERROR; }; pointsPerMeter: REAL ¬ Imager.pointsPerInch/Imager.metersPerInch; infiniteDepth: REAL ¬ LAST[INT]; UnCacheCD: PROC [node: TextNode.Ref] ~ { NodeProps.RemProp[node, $Artwork]; NodeProps.RemProp[node, $Postfix]; NodeProps.RemProp[node, $CenteredDisplayRope]; NodeProps.RemProp[node, $CenteredDisplayRef]; NodeProps.RemProp[node, $CenteredDisplayBox]; }; GetCDRope: PROC [node: TextNode.Ref] RETURNS [displayRope: ROPE] ~ { ref: REF ¬ NodeProps.GetProp[node, $CenteredDisplayRope]; WITH ref SELECT FROM r: ROPE => displayRope ¬ r; ENDCASE => displayRope ¬ NIL; }; GetCDRef: PROC [node: TextNode.Ref, styleName: ATOM] RETURNS [branch: TextNode.Ref] ~ { ref: REF ¬ NodeProps.GetProp[node, $CenteredDisplayRef]; WITH ref SELECT FROM n: TextNode.Ref => branch ¬ n; ENDCASE => branch ¬ NIL; IF branch = NIL THEN { displayRope: ROPE ¬ GetCDRope[node]; IF displayRope.IsEmpty THEN RETURN [NIL] ELSE { branch ¬ TiogaIO.FromRope[rope: displayRope]; NodeProps.PutProp[branch, $Prefix, NodeProps.DoSpecs[$Prefix, Rope.Cat["\"", Atom.GetPName[styleName], "\" style"]]]; NodeProps.PutProp[branch, $Format, NodeProps.DoSpecs[$Format, "root"]]; NodeProps.RemProp[branch, $Postfix]; NodeProps.PutProp[node, $CenteredDisplayRef, branch]; }; }; }; GetCDBox: PROC [cdNode: TextNode.Ref, lineWidth: Scaled.Value ¬ Scaled.zero, nodeStyle: NodeStyle.Ref ¬ NIL] RETURNS [box: TiogaImager.Box] ~ { <> GetStyleAndWidth: PROC [node: TextNode.Ref] RETURNS [nodeStyle: NodeStyle.Ref, lineWidth: Scaled.Value] ~ { nodeStyle ¬ NodeStyleOps.Create[]; NodeStyleOps.ApplyAll[nodeStyle, node, print]; { nColumns: INT ~ MAX[nodeStyle.GetInt[$columns], 1]; columnGap: REAL ~ GetStyleParam[nodeStyle, $columnGap, 36]; pageWidth: REAL ~ nodeStyle.GetReal[$pageWidth]; pageLength: REAL ~ nodeStyle.GetReal[$pageLength]; leftMargin: REAL ~ nodeStyle.GetReal[$leftMargin]; rightMargin: REAL ~ nodeStyle.GetReal[$rightMargin]; bindingMargin: REAL ~ nodeStyle.GetReal[$bindingMargin]; totalWidth: REAL ~ MAX[pageWidth-leftMargin-rightMargin-bindingMargin, 0.0]; lineWidth ¬ Scaled.FromReal[MAX[(totalWidth-(nColumns-1)*columnGap)/nColumns, 0.0]]; }; }; ref: REF ¬ NodeProps.GetProp[cdNode, $CenteredDisplayBox]; WITH ref SELECT FROM b: TiogaImager.Box => box ¬ b; ENDCASE => box ¬ NIL; IF box = NIL THEN { branch: TextNode.Ref; IF nodeStyle = NIL THEN [nodeStyle, lineWidth] ¬ GetStyleAndWidth[cdNode]; branch ¬ GetCDRef[cdNode, nodeStyle.GetName[$style]]; box ¬ FormatBranchToBox[branch, lineWidth, nodeStyle]; NodeProps.PutProp[cdNode, $CenteredDisplayBox, box]; NodeProps.PutProp[cdNode, $Postfix, NodeProps.DoSpecs[$Postfix, Rope.Concat[Convert.FtoRope[box.bounds.ymax-box.bounds.ymin, 2], " pt topLeading the topLeading topIndent 0 pt bottomLeading"]]]; }; }; FormatBranchToBox: PROC [branch: TextNode.Ref, lineWidth: Scaled.Value, nodeStyle: NodeStyle.Ref] RETURNS [box: TiogaImager.Box] ~ { extraIndent: REAL ~ nodeStyle.GetReal[$firstIndent]; leftIndent: REAL ~ nodeStyle.GetReal[$leftIndent] + extraIndent; rightIndent: REAL ~ nodeStyle.GetReal[$rightIndent]; clippedLineWidth: REAL ~ PositiveMin[nodeStyle.GetReal[$lineLength], Scaled.Float[lineWidth]]; trimmedLineWidth: REAL ~ MAX[clippedLineWidth-leftIndent-rightIndent, 0]; <> inBounds: Imager.VEC ¬ [trimmedLineWidth, infiniteDepth]; formatted: TiogaImager.FormattedNodes ¬ TiogaImager.FormatNodes[start: [TextNode.FirstChild[branch], 0], bounds: inBounds, filter: NIL, sep: NIL]; depth: REAL ¬ GetDepth[branch, nodeStyle]; box ¬ TiogaImager.BoxFromList[list: TiogaImager.UnBox[formatted.box], yFix: IF depth = infiniteDepth THEN [] ELSE [expansion, depth]]; }; GetStyleParam: PROC [style: NodeStyle.Ref, param: ATOM, default: REAL] RETURNS [REAL] ~ { <> val: REAL ¬ default; val ¬ NodeStyleOps.GetStyleParam[s: style, name: param, styleName: style.name[style], kind: style.kind]; RETURN [val] }; GetDepth: PROC [node: TextNode.Ref, nodeStyle: NodeStyle.Ref] RETURNS [depth: REAL ¬ 0.0] ~ { <> rope: ROPE ~ node.rope; size: INT ~ Rope.Size[rope]; IF size # 0 THEN { i: INT ¬ -1; Getc: PROC RETURNS [CHAR] ~ {i¬i+1; RETURN [IF i> startTok: INT ¬ 0; sizeTok: INT ¬ 0; GetTok: PROC ~ { startTok ¬ i; UNTIL c = ' DO c ¬ Getc[] ENDLOOP; sizeTok ¬ i-startTok; WHILE c = ' AND i=size DO GetTok[]; -- ignored GetTok[]; SELECT TRUE FROM Match["depth"] => depth ¬ GetReal[]; ENDCASE => depth ¬ infiniteDepth; ENDLOOP; } ELSE { depth ¬ infiniteDepth; }; }; PositiveMin: PROC [a, b: REAL] RETURNS [REAL] ~ { IF a < 0 THEN RETURN [MAX[b, 0.01]]; IF b < 0 THEN RETURN [a]; RETURN [MIN[a, b]] }; Floor: PROC [r: REAL] RETURNS [i: INT] ~ { i ¬ Real.Round[r]; IF i > r THEN i ¬ i-1; }; Ceiling: PROC [r: REAL] RETURNS [i: INT] ~ { i ¬ Real.Round[r]; IF i < r THEN i ¬ i+1; }; Resolve: TEditFormat.ResolveProc ~ { loc ¬ lineInfo.startPos; xmin ¬ lineInfo.xmin+Scaled.Floor[lineInfo.xOffset]; width ¬ lineInfo.xmax; rightOfLine ¬ FALSE; }; CharPosition: TEditFormat.CharPositionProc ~ { x ¬ lineInfo.xmin+Scaled.Floor[lineInfo.xOffset]; width ¬ lineInfo.xmax; }; BoundingBox: TEditFormat.BoundingBoxProc ~ { RETURN [[lineInfo.xmin, lineInfo.ymin, lineInfo.xmax, lineInfo.ymax]] }; Paint: TEditFormat.PaintProc ~ { <<[lineInfo: TEditFormat.LineInfo, context: Imager.Context]>> Action: PROC ~ { box: TiogaImager.Box ¬ NARROW[lineInfo.artworkData]; Imager.Trans[context]; TiogaImager.Render[box, context, [box.bounds.xmin, -box.bounds.ymin]]; }; Imager.DoSave[context, Action]; }; <> centeredDisplayClass: TEditFormat.ArtworkClass ~ NEW[TEditFormat.ArtworkClassRep ¬ [ name: $CenteredDisplay, format: Format, paint: Paint, resolve: Resolve, charPosition: CharPosition, boundingBox: BoundingBox ]]; TEditFormat.RegisterArtwork[centeredDisplayClass]; NodeProps.DeclarePropertyAttribute[name: $CenteredDisplayRope, attribute: $ClientOnly]; NodeProps.DeclarePropertyAttribute[name: $CenteredDisplayRef, attribute: $ClientOnly]; NodeProps.DeclarePropertyAttribute[name: $CenteredDisplayBox, attribute: $ClientOnly]; TEditInput.Register[name: $OpenCenteredDisplay, proc: OpenCenteredDisplayOp, before: TRUE]; TEditInput.Register[name: $CloseCenteredDisplay, proc: CloseCenteredDisplayOp, before: TRUE]; END.