<<>> <> <> <> <> <<>> DIRECTORY Args, Buttons, Commander, G2dContour, G2dTool, Draw2d, FileNames, FS, G2dBasic, G2dMatrix, G2dOutline, G2dPopUp, G2dVector, Imager, ImagerBackdoor, ImagerBox, ImagerColor, ImagerFont, ImagerInterpress, ImagerMaskCapture, ImagerTransformation, InterpressInterpreter, IO, MessageWindow, PPreView, PPreViewClient, Real, Rope, RuntimeError, SF, TextNode, TiogaAccess, TiogaAccessViewers, TiogaImager, TIPUser, Vector2, ViewerClasses, ViewerOps, ViewerTools; G2dStrokeWarpCmdImpl: CEDAR PROGRAM IMPORTS Args, Buttons, G2dContour, G2dTool, Draw2d, FileNames, FS, G2dBasic, G2dMatrix, G2dOutline, G2dPopUp, G2dVector, Imager, ImagerBackdoor, ImagerBox, ImagerColor, ImagerFont, ImagerInterpress, ImagerMaskCapture, ImagerTransformation, InterpressInterpreter, IO, MessageWindow, PPreViewClient, Real, Rope, RuntimeError, TiogaAccess, TiogaAccessViewers, TiogaImager, TIPUser, ViewerOps, ViewerTools ~ BEGIN <> ButtonProc: TYPE ~ Buttons.ButtonProc; Pair: TYPE ~ G2dBasic.Pair; PairSequence: TYPE ~ G2dBasic.PairSequence; Matrix: TYPE ~ G2dMatrix.Matrix; Triple: TYPE ~ G2dMatrix.Triple; TransformProc: TYPE ~ G2dOutline.TransformProc; Box: TYPE ~ Imager.Box; Context: TYPE ~ Imager.Context; Rectangle: TYPE ~ Imager.Rectangle; Color: TYPE ~ ImagerColor.Color; Font: TYPE ~ ImagerFont.Font; Master: TYPE ~ InterpressInterpreter.Master; PreviewData: TYPE ~ PPreView.Data; ROPE: TYPE ~ Rope.ROPE; Reader: TYPE ~ TiogaAccess.Reader; VEC: TYPE ~ Vector2.VEC; Viewer: TYPE ~ ViewerClasses.Viewer; metersPerPoint: REAL ~ 0.0254/72.0; <> cmdOut: IO.STREAM; Coons: TYPE ~ RECORD [ box: Box, pu0, p1v, pu1, p0v: PairSequence, du, dv: NAT, ddu, ddv, uscale, vscale: REAL, c00, c10, c11, c01: Pair]; GetPersp: PROC [p1, p2, p3, p4: VEC, b: Box] RETURNS [m: Matrix] ~ { ENABLE Real.RealException, G2dMatrix.singular => { m ¬ [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]; GOTO Quit; }; r: Rectangle ¬ ImagerBox.RectangleFromBox[b]; m ¬ G2dMatrix.Invert[G2dMatrix.QuadrilateralToRectangle[p1, p2, p3, p4, r]]; EXITS Quit => NULL; }; GetCoons: PROC [c0, c1, c2, c3: PairSequence, b: Box] RETURNS [c: Coons] ~ { c.du ¬ c.dv ¬ 1000; c.ddu ¬ 1.0/REAL[c.du]; c.ddv ¬ 1.0/REAL[c.dv]; c.box ¬ b; c.uscale ¬ REAL[c.du]/(c.box.xmax-c.box.xmin); c.vscale ¬ REAL[c.dv]/(c.box.ymax-c.box.ymin); c.pu0 ¬ G2dContour.ResamplePairs[c3, c.dv+1]; c.p1v ¬ G2dContour.ResamplePairs[c0, c.dv+1]; c.pu1 ¬ G2dContour.ResamplePairs[c1, c.du+1]; c.p0v ¬ G2dContour.ResamplePairs[c2, c.dv+1]; [] ¬ G2dVector.ReverseSequence[c.pu0, c.pu0]; [] ¬ G2dVector.ReverseSequence[c.p0v, c.p0v]; c.c00 ¬ G2dVector.Midpoint[c.pu0[0], c.p0v[0]]; c.c10 ¬ G2dVector.Midpoint[c.pu0[c.du], c.p1v[0]]; c.c11 ¬ G2dVector.Midpoint[c.pu1[c.du], c.p1v[c.dv]]; c.c01 ¬ G2dVector.Midpoint[c.pu1[0], c.p0v[c.dv]]; }; Transformer: TransformProc ~ { t: Tool ¬ NARROW[clientData]; IF t.op = persp THEN { p: Triple ¬ G2dMatrix.Transform[[v.x, v.y, 1.0], t.persp]; RETURN[[p.x/p.z, p.y/p.z]]; } ELSE { c: Coons ¬ t.coons; ui: NAT ¬ Real.Fix[c.uscale*(v.x-c.box.xmin)]; vi: NAT ¬ Real.Fix[c.vscale*(v.y-c.box.ymin)]; vf: REAL ¬ v.y*c.ddv; uf: REAL ¬ v.x*c.ddu; mvf: REAL ¬ 1.0-vf; muf: REAL ¬ 1.0-uf; mufmvf: REAL ¬ muf*mvf; ufmvf: REAL ¬ uf*mvf; ufvf: REAL ¬ uf*vf; mufvf: REAL ¬ muf*vf; xv.x ¬ -0.5*t.pvRect.w+ mvf*c.pu0[ui].x+ vf*c.pu1[ui].x+ muf*c.p0v[vi].x+ uf*c.p1v[vi].x- mufmvf*c.c00.x- ufmvf*c.c10.x- mufvf*c.c01.x- ufvf*c.c11.x; xv.y ¬ -0.5*t.pvRect.h+ mvf*c.pu0[ui].y+ vf*c.pu1[ui].y+ muf*c.p0v[vi].y+ uf*c.p1v[vi].y- mufmvf*c.c00.y- ufmvf*c.c10.y- mufvf*c.c01.y- ufvf*c.c11.y; }; }; GetBounds: PROC [t: Tool] RETURNS [box: Box ¬ [0, 0, 0, 0]] ~ { SELECT TRUE FROM t.source = reader AND t.reader # NIL => { loc: TextNode.Location ¬ [NARROW[TiogaAccess.GetLocation[t.reader].node], 0]; box ¬ TiogaImager.FormatNodes[loc, [1000, 1000]].box.bounds; }; t.source = interpress AND t.interpressFile # NIL => { Operator: PROC [context: Imager.Context] ~ { Imager.SetAmplifySpace[context, 1.0]; InterpressInterpreter.DoPage[master, 1, context, NIL]; }; master: Master ¬ InterpressInterpreter.Open[t.interpressFile, NIL]; m: Imager.Transformation ¬ ImagerTransformation.Scale[150.0/0.0254]; -- ~150 pixels/inch n: Imager.Transformation ¬ ImagerTransformation.PostScale[m, 0.0254]; -- back to inches b: SF.Box ¬ ImagerMaskCapture.CaptureBounds[Operator, m ! ImagerMaskCapture.Cant => RESUME]; r: ImagerBox.Rectangle ¬ ImagerTransformation.InverseTransformRectangle[ n, ImagerBox.RectangleFromBox[[b.min.s, b.min.f, b.max.s, b.max.f]]]; box ¬ [r.x, r.y, r.x+r.w, r.y+r.h]; InterpressInterpreter.Close[master]; }; ENDCASE; }; PreviewPaint: ViewerClasses.PaintProc ~ { ENABLE UNWIND => GOTO Bad; t: Tool ¬ NARROW[whatChanged]; IF t.box = [0, 0, 0, 0] THEN t.box ¬ GetBounds[t]; <> <> IF t.box = [0, 0, 0, 0] THEN { Blink["Select some text or an Interpress name"]; RETURN; }; IF t.op = persp THEN t.persp ¬ GetPersp[t.perspPts[0], t.perspPts[1], t.perspPts[2], t.perspPts[3], t.box] ELSE t.coons ¬ GetCoons[t.coonsCrvs[0], t.coonsCrvs[1], t.coonsCrvs[2], t.coonsCrvs[3], t.box]; t.pvRect ¬ ImagerBackdoor.GetBounds[context ! Imager.Error => CONTINUE]; SELECT t.source FROM reader => G2dOutline.TransformSelected[t.reader, Transformer, context, 0.05, t]; interpress => { <> ipContext: Context ¬ G2dOutline.MakeTransformContext[Transformer, context, t]; cRect: Rectangle ¬ ImagerBackdoor.GetBounds[ipContext]; ipRect: Rectangle ¬ ImagerBox.RectangleFromBox[t.box]; Imager.SetColor[ipContext, Imager.black]; Imager.SetStrokeWidth[ipContext, 1.0]; Imager.ScaleT[ipContext, 1.0/Imager.metersPerPoint]; <> <> <> <> t.ipMaster ¬ InterpressInterpreter.Open[t.interpressFile, NIL]; InterpressInterpreter.DoPage[t.ipMaster, 1, ipContext, NIL]; InterpressInterpreter.Close[t.ipMaster]; }; ENDCASE; EXITS Bad => Blink["Error while painting"]; }; <> TransformText: TransformProc ~ { t: Triple ¬ G2dMatrix.Transform[[v.x, v.y, 1.0], NARROW[clientData, REF Matrix]­]; RETURN[[t.x/t.z, t.y/t.z]]; }; WarpText: PROC [x, y: REAL, text, out: ROPE, p1, p2, p3, p4: VEC, font: Font, rgb: Color] ~ { IPAction: PROC [context: Context] ~ { Imager.ScaleT[context, metersPerPoint]; Imager.TranslateT[context, [0.0, 0.5*11.0*Imager.pointsPerInch]]; G2dOutline.TransformRope[text, TransformText, context, x, y, font, rgb, 0.05, refM]; }; b: Box ¬ ImagerBox.BoxFromExtents[ImagerFont.RopeBoundingBox[font, text]]; refM: REF Matrix ¬ NEW[Matrix ¬ GetPersp[p1, p2, p3, p4, b]]; ref: ImagerInterpress.Ref ¬ ImagerInterpress.Create[out]; ImagerInterpress.DoPage[ref, IPAction]; ImagerInterpress.Close[ref]; }; <> StrokeWarpCommand: Commander.CommandProc ~ { cmdOut ¬ cmd.out; IF Args.NArgs[cmd] > 0 THEN { x, y, text, ipOut, x1, y1, x2, y2, x3, y3, x4, y4, r, g, b, fontA, size: Args.Arg; [x, y, text, ipOut, x1, y1, x2, y2, x3, y3, x4, y4, r, g, b, fontA, size] ¬ Args.ArgsGet[cmd, "%rrssrrrrrrrr-color%r[rr-font%s-size%r" ! Args.Error => {msg ¬ reason; GOTO Bad}]; { p1: VEC ¬ [x1.real, y1.real]; p2: VEC ¬ [x2.real, y2.real]; p3: VEC ¬ [x3.real, y3.real]; p4: VEC ¬ [x4.real, y4.real]; red: REAL ¬ IF r.ok THEN r.real ELSE 1.0; grn: REAL ¬ IF g.ok THEN g.real ELSE red; blu: REAL ¬ IF b.ok THEN b.real ELSE grn; ptSize: REAL ¬ IF size.ok THEN size.real ELSE 14.0; color: Color ¬ ImagerColor.ColorFromRGB[[red, grn, blu]]; fName: ROPE ¬ IF fontA.ok THEN fontA.rope ELSE "helvetica-mrr"; font: Font ¬ Imager.FindFontScaled[Rope.Concat["xerox/pressfonts/", fName], ptSize]; WarpText[x.real, y.real, text.rope, ipOut.rope, p1, p2, p3, p4, font, color ! Imager.Warning => {msg ¬ IO.PutFR1["no font: %g", IO.rope[fName]]; GOTO Bad}]; }; } ELSE { BCreate: PROC [name: ROPE, proc: ButtonProc, x, y: NAT, guarded: BOOL ¬ FALSE] ~ { [] ¬ Buttons.Create[info: [parent: t.parent, name: name, wx: x, wy: y], proc: proc, clientData: t, paint: TRUE, guarded: guarded]; }; t: Tool ¬ NEW[ToolRep]; t.directory ¬ FileNames.CurrentWorkingDirectory[]; t.parent ¬ ViewerOps.CreateViewer[ flavor: $StrokeWarp, info: [column: right, name: "StrokeWarp", openHeight: 334, data: t, iconic: TRUE]]; t.border ¬ ViewerOps.CreateViewer[ flavor: $Border, info: [parent: t.parent, name: "StrokeWarp", wx: 20, wy: 10, ww: 300, wh: 300, data: t, scrollable: FALSE]]; ViewerOps.OpenIcon[t.parent]; t.preViewer ¬ PPreViewClient.CreatePreViewerFromData[ NEW[PPreView.Rep ¬ [clientPaint: PreviewPaint, clientData: t, kind: ip[], fileInfo: NEW[PPreView.FileInfoRep ¬ [fullFName: "StrokeWarp"]]]], TRUE]; BCreate["WARP", WarpButton, 340, 285]; BCreate["Get Text", GetTextButton, 340, 255]; BCreate["Get IP", GetIPButton, 340, 225]; BCreate["Clear", ClearButton, 340, 195, TRUE]; t.typeButton ¬ Buttons.Create[info: [parent: t.parent, name: " Persp ", wx: 340, wy: 165], proc: TypeButton, clientData: t, paint: TRUE]; BCreate["Smooth", SmoothButton, 340, 135]; BCreate["HELP!", HelpButton, 340, 105]; ViewerOps.PaintViewer[t.border, client]; }; EXITS Bad => RETURN[$Failure, msg]; }; <> Tool: TYPE ~ REF ToolRep; ToolRep: TYPE ~ RECORD [ box: Box ¬ [0, 0, 0, 0], directory: ROPE, reader: Reader, interpressFile: ROPE, ipMaster: Master, preViewer: Viewer, parent: Viewer, border: Viewer, typeButton: Viewer, drawContext: Context, warpContext: Context, pvRect: Rectangle, op: {coons, persp} ¬ persp, source: {reader, interpress} ¬ reader, selected: NAT ¬ 0, persp: Matrix, perspIndex: INTEGER ¬ -1, coons: Coons, coonsIndex: INTEGER ¬ -1, coonsDone: BOOL ¬ FALSE, coonsCrvs: ARRAY [0..4) OF PairSequence, perspPts: ARRAY [0..4) OF Pair]; ClearButton: ButtonProc ~ { t: Tool ¬ NARROW[clientData]; IF t.op = coons THEN { FOR n: NAT IN [0..t.coonsIndex] DO t.coonsCrvs[n].length ¬ 0; ENDLOOP; t.coonsIndex ¬ -1; t.coonsDone ¬ FALSE; } ELSE t.perspIndex ¬ -1; ViewerOps.PaintViewer[t.border, client, FALSE, NIL]; }; GetTextButton: ButtonProc ~ { t: Tool ¬ NARROW[clientData]; t.source ¬ reader; t.reader ¬ TiogaAccessViewers.FromSelection[]; t.box ¬ [0, 0, 0, 0]; }; GetIPButton: ButtonProc ~ { master: Master; t: Tool ¬ NARROW[clientData]; t.interpressFile ¬ ViewerTools.GetSelectionContents[]; IF Rope.Find[t.interpressFile, "/"] = -1 AND Rope.Find[t.interpressFile, ">"] = -1 THEN t.interpressFile ¬ Rope.Concat[t.directory, t.interpressFile]; master ¬ InterpressInterpreter.Open[t.interpressFile, NIL ! FS.Error => {t.interpressFile ¬ NIL; Blink["Bad IP name"]; CONTINUE}]; IF master # NIL THEN {InterpressInterpreter.Close[master]; t.source ¬ interpress}; t.box ¬ [0, 0, 0, 0]; }; WarpButton: ButtonProc ~ { ViewerOps.PaintViewer[NARROW[clientData, Tool].preViewer, client]; }; TypeButton: ButtonProc ~ { t: Tool ¬ NARROW[clientData]; t.op ¬ IF t.op = persp THEN coons ELSE persp; Buttons.ReLabel[t.typeButton, IF t.op = persp THEN " Pesp " ELSE "Coons"]; ViewerOps.PaintViewer[t.border, client, FALSE, NIL]; }; SmoothButton: ButtonProc ~ { t: Tool ¬ NARROW[clientData]; IF t.op = persp THEN Blink["Must be in Coons mode"] ELSE { FOR i: NAT IN [0..4) DO [] ¬ G2dContour.SmoothPairs[t.coonsCrvs[i], t.coonsCrvs[i]]; ENDLOOP; ViewerOps.PaintViewer[t.border, client, FALSE, NIL]; }; }; HelpButton: ButtonProc ~ {G2dPopUp.Help["StrokeWarp"]}; PaintBorder: ViewerClasses.PaintProc ~ { Action: PROC ~ { IF t.op = coons THEN SELECT TRUE FROM t.coonsIndex = -1 => Draw2d.Clear[context]; whatChanged # NIL => { c: PairSequence ¬ t.coonsCrvs[t.coonsIndex]; IF c # NIL AND c.length > 1 THEN Draw2d.Line[context, c[c.length-2], c[c.length-1]]; }; ENDCASE => FOR n: NAT IN [0..t.coonsIndex] DO c: PairSequence ¬ t.coonsCrvs[n]; IF c # NIL THEN FOR n: NAT IN [1..c.length) DO Draw2d.Line[context, c[n-1], c[n]]; ENDLOOP; ENDLOOP ELSE SELECT TRUE FROM t.perspIndex = -1 => Draw2d.Clear[context]; ENDCASE => { FOR n: NAT IN [0..t.perspIndex) DO Draw2d.Line[context, t.perspPts[n], t.perspPts[n+1]]; ENDLOOP; IF t.perspIndex = 3 THEN Draw2d.Line[context, t.perspPts[3], t.perspPts[0]]; }; Draw2d.Label[context, [45.0, 280.0], "CW order: left, top, right, bottom"]; }; t: Tool ¬ NARROW[self.data]; Draw2d.DoWithBuffer[context, Action, whatChanged = NIL]; }; NotifyBorder: ViewerClasses.NotifyProc ~ { MovePersp: PROC ~ { t.perspPts[t.selected] ¬ p; ViewerOps.PaintViewer[t.border, client, FALSE, NIL]; }; AddPoint: PROC [] ~ { ENABLE RuntimeError.BoundsFault => GOTO Bad; IF t.op = coons THEN t.coonsCrvs[t.coonsIndex] ¬ G2dBasic.AddToPairSequence[t.coonsCrvs[t.coonsIndex], p] ELSE t.perspPts[t.perspIndex] ¬ p; ViewerOps.PaintViewer[t.border, client, FALSE, $NewPoint]; EXITS Bad => { Blink[IO.PutFR["Curve %g length exceeded at %g points", IO.int[t.coonsIndex], IO.int[t.coonsCrvs[t.coonsIndex].length]]]; t.coonsCrvs[t.coonsIndex].length ¬ 0; -- restart curve, about the best we can do here }; }; t: Tool ¬ NARROW[self.data]; mouse: TIPUser.TIPScreenCoords ¬ NARROW[input.first]; p: Pair ¬ [mouse.mouseX, mouse.mouseY]; SELECT input.rest.first FROM $leftDown => SELECT t.op FROM coons => SELECT TRUE FROM t.coonsDone => Blink["Already have four Coons curves!"]; t.coonsIndex = 3 => t.coonsDone ¬ TRUE; ENDCASE => { t.coonsIndex ¬ t.coonsIndex+1; AddPoint[]; }; ENDCASE => IF t.perspIndex = 3 THEN { min: REAL ¬ 100000.0; FOR n: NAT IN [0..t.perspIndex] DO d: REAL ¬ G2dVector.SquareDistance[p, t.perspPts[n]]; IF d < min THEN {min ¬ d; t.selected ¬ n}; ENDLOOP; t.perspPts[t.selected] ¬ p; } ELSE { t.selected ¬ t.perspIndex ¬ t.perspIndex+1; AddPoint[]; }; $leftHeld => IF t.op = persp THEN MovePersp[] ELSE IF t.coonsIndex IN [0..4) AND NOT t.coonsDone THEN AddPoint[]; $leftUp => IF t.op = coons THEN { IF t.coonsIndex = 3 THEN t.coonsDone ¬ TRUE; IF t.coonsCrvs[t.coonsIndex].length < 2 THEN { Blink[IO.PutFR1["Too few points, redraw curve %g", IO.int[t.coonsIndex]]]; t.coonsCrvs[t.coonsIndex].length ¬ 0; }; }; ENDCASE; }; <> Blink: PROC [r: ROPE] ~ { MessageWindow.Append[Rope.Concat["\t\t\t\t\t", r], TRUE]; MessageWindow.Blink[]; }; <> strokeWarpUsage: ROPE ~ " StrokeWarp (for interactive use) -- or -- StrokeWarp [-option] Options: [-color []] default is black [-font ] default is helvetica-mrr [-size ] default is 14 (pixels) x, y in pixels (72 per inch); fonts from ///7.0/Fonts/Xerox/Pressfonts/."; ViewerOps.RegisterViewerClass[$StrokeWarp, NEW[ViewerClasses.ViewerClassRec]]; ViewerOps.RegisterViewerClass[$Border, NEW[ViewerClasses.ViewerClassRec ¬ [ notify: NotifyBorder, paint: PaintBorder, tipTable: TIPUser.InstantiateNewTIPTable["G2dStrokeWarp.tip"] ]]]; G2dTool.Register["StrokeWarp", StrokeWarpCommand, strokeWarpUsage]; END.