G2dStrokeWarpCmdImpl.mesa
Copyright Ó 1987, 1992 by Xerox Corporation. All rights reserved.
Loosely based on Michael Plass's /Ivy/Plass/Overnight/RefitImpl.mesa.
Bloomenthal, September 10, 1992 4:12 pm PDT
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
Types and Constants
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;
Warping Tioga
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];
IO.PutF[cmdOut, "box = [%g, %g, %g, %g]\n",
IO.real[t.box.xmin], IO.real[t.box.ymin], IO.real[t.box.xmax], IO.real[t.box.ymax]];
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 ← context;
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];
Imager.TranslateT[ipContext, [cRect.w/2, cRect.h/2]];
IF cRect.w # 0 AND cRect.h # 0
THEN Imager.ScaleT[ipContext, MIN[ipRect.w/cRect.w, ipRect.h/cRect.h]];
Imager.TranslateT[ipContext, [-(ipRect.x+ipRect.w/2), -(ipRect.y+ipRect.h/2)]];
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"];
};
Warping Text
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];
};
Warp Command
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];
};
Warp Tool
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;
};
Support
Blink: PROC [r: ROPE] ~ {
MessageWindow.Append[Rope.Concat["\t\t\t\t\t", r], TRUE];
MessageWindow.Blink[];
};
Start Code
strokeWarpUsage: ROPE ~ "
StrokeWarp (for interactive use) -- or --
StrokeWarp <x y text ipOut x1 y1 x2 y2 x3 y3 x4 y4> [-option]
 Options:
  [-color <r> [<g b>]] default is black
  [-font <fontname>] default is helvetica-mrr
  [-size <pointsize>] 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.