--ContourJaM.mesa
-- Last Changed: Maureen Stone December 12, 1984 12:42:27 pm PST
-- Written by: Maureen Stone
DIRECTORY
Imager,
ImagerBasic USING [PixelArray, PixelBufferRep, PixelBuffer, DeviceRectangle, ColorRep, PathMapType],
ImagerTransform USING [InverseTransform, Concat, Transform, Translate, Rotate],
ImagerPixelMaps USING [PixelMap, DeviceRectangle, Create, ShiftMap, Clear, Trim, Copy, Window],
ImagerMasks USING [PixelArrayFromPixelMap],
Vector USING [Vec],
Cubic USING [Bezier],
FitJaM USING [defaultFitState, InitProc, RegisterInit],
FitState USING [ResetData, AddSample, NewContour, NextContour, AddLink, EnumerateLinks, Handle],
FS USING [Error, StreamOpen],
Real USING [RoundLI],
Scaled USING [FromReal, zero],
SampledColors USING[bitmap, intensity, PixelArrayFromAIS, DrawImage],
UFFileManager USING[KeyOf],
UFPressFontReader USING[Size, Range, Family, Face, Resolution, GetCharInfo, GetCharRaster, CharInfo, GetCharOutline, FontKey, NumberOfFontsInFile],
Font USING [Key, Create, FONT, Box, Pair, FontBoundingBox, WidthVector],
Outline,
JaMImager USING[Painter],
JaM,
Rope USING [ROPE, Fetch, FromChar],
Basics USING [LowByte, LowHalf],
AIS USING [Error],
IO USING [STREAM, Close],
SDtoSF USING [PathToStream];
ContourJaM: CEDAR PROGRAM
IMPORTS Imager, SampledColors, JaMImager, AIS, JaM, Rope, FitState, ImagerTransform, Outline, FS, FitJaM, Real, Scaled, UFFileManager, UFPressFontReader, Font, ImagerPixelMaps, ImagerMasks, Basics, SDtoSF, IO
EXPORTS = BEGIN
acFont: InternalFont;
sdFont: RECORD [font: Font.FONT, fontKey: UFPressFontReader.FontKey, scale: REAL];
sfFont: IO.STREAM;
pixels: ImagerBasic.PixelArray;
colorOperator: ATOM;
outline: Outline.Handle ← NEW[Outline.Rec];
OpenAIS: PROCEDURE[state: JaM.State] = {
ENABLE AIS.Error =>{JaM.ExecuteRope[state, "(AIS file error) .print" ! JaM.Stop => CONTINUE]; CONTINUE};
[pixels, colorOperator] ← SampledColors.PixelArrayFromAIS[JaM.PopRope[state]];
outline.tValue ← 128.5;
outline.border ← 255;
SetWindowFromPixels[];
};
OpenSF: PROC [state: JaM.State] = {
sfFont ← FS.StreamOpen[JaM.PopRope[state], $append! FS.Error =>
IF error.group=user
THEN JaM.ExecuteRope[state, "(Invalid file name) .print" ! JaM.Stop => CONTINUE]];
};
CloseSF: PROC [state: JaM.State] = {IO.Close[sfFont]};
WriteSFChar: PROC [state: JaM.State] = {
s: REAL ← JaM.PopReal[state]; -- pointsize/resolution to make it a "1 point font"
char: CHAR ← Rope.Fetch[JaM.PopRope[state],0];
PathMap: ImagerBasic.PathMapType = {
fitState: FitState.Handle ← NARROW[data];
newContour: PROC[x,y: REAL] = {move[[s*x,s*y]]};
newCubic: PROC[c: Cubic.Bezier] = {
curve[[s*c.b1.x, s*c.b1.y], [s*c.b2.x, s*c.b2.y], [s*c.b3.x, s*c.b3.y]]};
FitState.EnumerateLinks[fitState, newContour, newCubic];
};
SDtoSF.PathToStream[
dest: sfFont, pathMap: PathMap, pathData: FitJaM.defaultFitState,
family: acFont.family, face: "M R R", charCode: VAL[char],
widthx: acFont.charRep[char].fWidth, widthy: acFont.charRep[char].sWidth];
};
FontNotFound: SIGNAL = CODE;
OpenACFont: PROCEDURE[state: JaM.State] = {
ENABLE FontNotFound =>{JaM.ExecuteRope[state, "(File not found or file not an AC font) .print" ! JaM.Stop => CONTINUE]; CONTINUE};
acFont ← LoadAC[JaM.PopRope[state]];
JaM.PushRope[state,"A"];
GetACChar[state];
};
OpenSDFont: PROCEDURE[state: JaM.State] = {
ENABLE FS.Error =>{JaM.ExecuteRope[state, "(file not found) .print" ! JaM.Stop => CONTINUE]; CONTINUE};
scale: REAL ← JaM.PopReal[state];
name: Rope.ROPE ← JaM.PopRope[state];
font: Font.FONT ← Font.Create[name, Imager.Scale[scale]];
sdFont ← [font, [font.graphicsKey, 0],scale];
};
GetACChar: PROCEDURE[state: JaM.State] = {
IF acFont=NIL THEN {
JaM.ExecuteRope[state, "(Please open a font) .print"! JaM.Stop => CONTINUE]; RETURN};
pixels ← acFont.charRep[Rope.Fetch[JaM.PopRope[state],0]].pixels;
SetWindowFromPixels[];
colorOperator ← SampledColors.bitmap;
outline.tValue ← 1;
outline.border ← 0;
};
GetSDChar: PROC [state: JaM.State] ~ {
IF sdFont.font=NIL THEN {
JaM.ExecuteRope[state, "(Please open a font) .print"! JaM.Stop => CONTINUE]; RETURN};
pixels ← PAFromSDChar[Rope.Fetch[JaM.PopRope[state],0]];
SetWindowFromPixels[];
colorOperator ← SampledColors.bitmap;
outline.tValue ← 1;
outline.border ← 0;
};
ShowWindow: PROCEDURE[state: JaM.State] = {
path: Imager.Trajectory ← Imager.MoveTo[[outline.xStart, outline.yStart]];
Paint: PROC[dc: Imager.Context] = {
Imager.MaskStrokeClosed[dc, path, 0];
};
path ← Imager.LineTo[path, [outline.xStart, outline.yStart+outline.yPixels]];
path ← Imager.LineTo[path, [outline.xStart+outline.xPixels, outline.yStart+outline.yPixels]];
path ← Imager.LineTo[path, [outline.xStart+outline.xPixels, outline.yStart]];
JaMImager.Painter[Paint, state];
};
ResetWindow: PROCEDURE[state: JaM.State] = {
SetWindowFromPixels[];
};
SetWindowFromPixels: PROC = { --find an upright box in client space
sx, sy, ex, ey: REAL;
[[sx, sy]] ← ImagerTransform.Transform[[0,0],pixels.m];
[[ex,ey]] ← ImagerTransform.Transform[[pixels.xPixels,pixels.yPixels],pixels.m];
outline.xStart ← Real.RoundLI[MIN[sx,ex]];
outline.xPixels ← Real.RoundLI[MAX[sx,ex]]-outline.xStart;
outline.yStart ← Real.RoundLI[MIN[sy,ey]];
outline.yPixels ← Real.RoundLI[MAX[sy,ey]]-outline.yStart;
};
Copied out of RasterFontWriterImpl. Changed PixelMap to PixelArray
InternalFont: TYPE ~ REF InternalFontRep;
InternalFontRep: TYPE ~ RECORD [
family: Rope.ROPENIL,
face: [0..255] ← 0,
bitsPerEmQuad: REAL ← 0,
charRep: ARRAY CHAR OF InternalCharRep
];
InternalCharRep: TYPE ~ RECORD [
fWidth, sWidth: REAL,
pixels: ImagerBasic.PixelArray
];
LoadAC: PROC [fileName: Rope.ROPE] RETURNS [internalFont: InternalFont] ~ TRUSTED {
fileKey: Font.Key ← UFFileManager.KeyOf[fileName];
sizeInMeters: REAL;
bc, ec: CHAR;
IF UFPressFontReader.NumberOfFontsInFile[fileKey] = 0 THEN SIGNAL FontNotFound;
[bc, ec] ← UFPressFontReader.Range[[fileKey, 0]];
sizeInMeters ← UFPressFontReader.Size[[fileKey, 0]];
internalFont ← NEW [InternalFontRep];
internalFont.family ← UFPressFontReader.Family[[fileKey, 0]];
internalFont.face ← UFPressFontReader.Face[[fileKey, 0]];
internalFont.bitsPerEmQuad ← sizeInMeters*UFPressFontReader.Resolution[[fileKey, 0]].xRes/0.0254;
FOR char: CHAR IN [bc..ec] DO
info: UFPressFontReader.CharInfo ← UFPressFontReader.GetCharInfo[[fileKey, 0], char];
pixelArray: ImagerBasic.PixelArray ← UFPressFontReader.GetCharRaster[[fileKey, 0], char];
internalFont.charRep[char] ← [
fWidth: info.widthX*internalFont.bitsPerEmQuad,
sWidth: -info.widthY*internalFont.bitsPerEmQuad,
pixels: pixelArray
];
ENDLOOP;
};
Window: PROCEDURE[state: JaM.State] = {
y2: REAL ← JaM.PopReal[state];
x2: REAL ← JaM.PopReal[state];
y1: REAL ← JaM.PopReal[state];
x1: REAL ← JaM.PopReal[state];
will be in client coordinates if came from .touch
dx1, dx2, dy1, dy2: REAL;
IF y2<y1 THEN {t: REAL ← y2; y2 ← y1; y1← t};
IF x2<x1 THEN {t: REAL ← x2; x2 ← x1; x1← t};
Now have a rectangle with x1, y1 in the lower left, x2, y2 in the upper right
Transform from View to Client space.
[[dx1, dy1]] ← ImagerTransform.InverseTransform[[x1, y1],clientToView];
[[dx2, dy2]] ← ImagerTransform.InverseTransform[[x2, y2],clientToView];
outline.xStart ← Real.RoundLI[dx1];
outline.yStart ← Real.RoundLI[dy1];
outline.xPixels ← Real.RoundLI[dx2-dx1];
outline.yPixels ← Real.RoundLI[dy2-dy1];
};
GetValue: PROCEDURE[state: JaM.State] = {
y: REAL ← JaM.PopReal[state];
x: REAL ← JaM.PopReal[state];
dx,dy: REAL;
v: INT;
[[dx, dy]] ← ImagerTransform.InverseTransform[[x, y],clientToView];
v ← Get[state,Real.RoundLI[dx],Real.RoundLI[dy]];
JaM.PushInt[state,v];
};
debug: BOOLEANFALSE;
buffer: ImagerBasic.PixelBuffer ← NEW[ImagerBasic.PixelBufferRep[1]];
Get: PROC[client: REF, x,y: INT] RETURNS[INT] = {
Paint: PROC[dc: Imager.Context] = {
IF buffer[0]=0 THEN RETURN; --binary case
IF buffer[0] <= outline.tValue THEN {
Imager.SetColor[dc, Imager.black];
Imager.MaskRectangle[dc,x,y,1,1];
};
};
s, f: REAL;
[[s, f]] ← ImagerTransform.InverseTransform[[x, y],pixels.m];
pixels.get[pixels, buffer,1, 0, Scaled.FromReal[s], Scaled.FromReal[f], Scaled.zero, Scaled.zero];
IF debug THEN JaMImager.Painter[Paint, NARROW[client]];
RETURN[buffer[0]];
};
clientToView: Imager.Transformation ← Imager.Rotate[0];
ShowPA: PROCEDURE [state: JaM.State]= {
Paint: PROC[dc: Imager.Context] = {
SampledColors.DrawImage[dc,pixels,colorOperator];
};
JaMImager.Painter[Paint, state];
};
paint: BOOLEANTRUE;
NewEdge: PROCEDURE [client: REF, x0,y0,x1,y1: REAL] RETURNS [abort: BOOLEANFALSE] = {
state: JaM.State ← NARROW[client];
Paint: PROC[dc: Imager.Context] = {
do: PROC = {
Imager.ConcatT[dc, pixels.m];
Imager.SetColor[dc, Imager.black];
Imager.MaskVector[dc,[x0,y0],[x1,y1]];
};
Imager.DoSaveAll[dc, do];
};
IF paint THEN JaMImager.Painter[Paint, state];
RETURN[JaM.GetAbort[state]];
};
nContours: INT;
OutlineEdge: PROC [state: JaM.State] = {
outline.client ← state;
nContours ← Outline.OutlineEdge[outline];
JaM.PushInt[state, nContours];
};
OutlineBlackCenter: PROC [state: JaM.State] = {
outline.client ← state;
nContours ← Outline.OutlineBlackCenter[outline];
JaM.PushInt[state, nContours];
};
OutlineBlackEdge: PROC [state: JaM.State] = {
outline.client ← state;
nContours ←Outline.OutlineBlackEdge[outline];
JaM.PushInt[state, nContours];
};
ODrawContour: PROCEDURE[state: JaM.State] = {
ODrawContourNumber[state, JaM.PopInt[state]];
};
ODrawAllContours: PROCEDURE[state: JaM.State] = {
FOR cn: INT IN[0..nContours) DO ODrawContourNumber[state, cn]; ENDLOOP;
};
ODrawContourNumber: PROCEDURE [state: JaM.State, cn: INT] = {
Paint: PROC[dc: Imager.Context] = {
x0, y0: REAL;
first: BOOLEANTRUE;
newPt: PROC[x,y: REAL] = {
IF first THEN first ← FALSE
ELSE Imager.MaskVector[dc, [x0, y0], [x, y]];
x0 ← x; y0 ← y;
};
Outline.GetOutline[outline.data, newPt, cn];
};
JaMImager.Painter[Paint, state];
};
PAFromSDChar: PROC [char: CHAR] RETURNS [pa: ImagerBasic.PixelArray] ~ {
bb: Font.Box ← Font.FontBoundingBox[sdFont.font];
widthVector: Font.Pair ← Font.WidthVector[sdFont.font, char];
dbb: ImagerPixelMaps.DeviceRectangle ← [Real.RoundLI[-bb.ymax], Real.RoundLI[+bb.xmin], Real.RoundLI[bb.ymax]-Real.RoundLI[bb.ymin], Real.RoundLI[bb.xmax]-Real.RoundLI[bb.xmin]];
pixelMap: ImagerPixelMaps.PixelMap ← ImagerPixelMaps.Create[0, [0, 0, dbb.sSize+4, dbb.fSize+4]];
context: Imager.Context ← Imager.Create[$LFDisplay, NEW[ImagerPixelMaps.PixelMap ← pixelMap]];
pixelMap ← pixelMap.ShiftMap[dbb.sMin-2, dbb.fMin-2];
context.state.T ← Imager.Translate[2-bb.xmin, 2+dbb.sMin+dbb.sSize];
context.SetFont[sdFont.font];
pixelMap.Clear;
context.SetXY[[0,0]];
context.ShowChar[char];
pixelMap ← pixelMap.Trim[0];
Create a pixelarray and set pixels
dbb ← pixelMap.Window[];
pixelMap ← pixelMap.ShiftMap[-dbb.sMin, -dbb.fMin].Copy; --for PixelArrayFromPixelMap
pa ← ImagerMasks.PixelArrayFromPixelMap[pixelMap];
pa.m ← ImagerTransform.Concat[
pre: ImagerTransform.Translate[dbb.sMin, dbb.fMin],
post: ImagerTransform.Rotate[-90]];
};
SetSDContour: PROC [state: JaM.State] = {
s: REAL ← sdFont.scale; --sdfonts are all 1 point tall
bezier: Cubic.Bezier;
b0: Vector.Vec ← [0,0];
move: PROCEDURE [x, y: REAL] = {
FitState.NewContour[FitJaM.defaultFitState];
b0 ← [s*x,s*y];
};
line: PROCEDURE [x, y: REAL] = {
x ← x*s; y ← y*s;
bezier ← [b0, [(2*b0.x+x)/3, (2*b0.y+y)/3], [(b0.x+2*x)/3, (b0.y+2*y)/3], [x,y]];
FitState.AddLink[FitJaM.defaultFitState, bezier];
b0 ← bezier.b3;
};
curve: PROCEDURE [x1, y1, x2, y2, x3, y3: REAL] ={ --b1.x, b1.y, b2.x, b2.y, b3.x, b3.y
bezier ← [b0, [x1*s, y1*s], [x2*s, y2*s], [x3*s, y3*s]];
FitState.AddLink[FitJaM.defaultFitState, bezier];
b0 ← bezier.b3;
};
draw: PROCEDURE = {
FitState.NextContour[FitJaM.defaultFitState];
};
UFPressFontReader.GetCharOutline[sdFont.fontKey, Rope.Fetch[JaM.PopRope[state],0], move, line, curve, draw];
};
OSetSamples: PROC [state: JaM.State] = {
cn: INTEGER ← JaM.PopInt[state];
OSetSamplesNumber[cn];
};
OSetAllSamples: PROC [state: JaM.State] = {
FOR cn: INT IN[0..nContours) DO
OSetSamplesNumber[cn];
IF cn#nContours-1 THEN FitState.NewContour[FitJaM.defaultFitState];
ENDLOOP;
};
OSetSamplesNumber: PROCEDURE [cn: INT] = {
newPt: PROC[x,y: REAL] = {
FitState.AddSample[FitJaM.defaultFitState, x,y];
};
FitState.ResetData[FitJaM.defaultFitState, samples];
Outline.GetOutline[outline.data, newPt, cn];
};
SetOutlineT: PROCEDURE[state: JaM.State] = {outline.tValue ← JaM.PopReal[state]};
CVChar: PROCEDURE[state: JaM.State] = {
c: CHARVAL[Basics.LowByte[Basics.LowHalf[JaM.PopInt[state]]]];
JaM.PushRope[state, Rope.FromChar[c]];
};
Init: FitJaM.InitProc = {
JaM.Register[state,"Contour.openSF",OpenSF]; --open the SF output file
JaM.Register[state,"Contour.closeSF",CloseSF]; --close the SF output file
JaM.Register[state,"Contour.writeSFChar",WriteSFChar]; --write the current contour
JaM.Register[state,"Contour.openAIS",OpenAIS]; --open the ais file (will close current file)
JaM.Register[state,"Contour.openACFont", OpenACFont]; --(name) => open a font file
JaM.Register[state,"Contour.openSDFont", OpenSDFont]; --(name) size => open a font file
JaM.Register[state,"Contour.showPA",ShowPA]; --use Imager to display the current file
JaM.Register[state,"Contour.getACChar", GetACChar]; --(char) => sets up a character for outlining
JaM.Register[state,"Contour.getSDChar", GetSDChar]; --(char) => scan converts an SD char at the current size and sets it up for outlining
JaM.Register[state,"Contour.setSDContour", SetSDContour]; --(char) => creates contour from an sd char
JaM.Register[state,"Contour.windowPA", Window]; --x1 y1 x2 y2 => Set a window on the ais image
JaM.Register[state,"Contour.showWindow", ShowWindow]; --draws outline of window
JaM.Register[state,"Contour.resetWindow", ResetWindow]; --resets window to the size of the pixel array
JaM.Register[state,"Contour.getValue", GetValue]; --x, y => returns the value in the ps
JaM.Register[state,"Outline.tvalue",SetOutlineT]; -- Outline threshhold value
JaM.Register[state,".outline",OutlineEdge]; --find the contours
JaM.Register[state,".outlineBlackCenter",OutlineBlackCenter]; --find the edge through the black region. Makes sense for binary files.
JaM.Register[state,".outlineBlackEdge",OutlineBlackEdge]; --find the edge around the black region. Makes sense for binary files.
JaM.Register[state,"Outline.drawContour",ODrawContour]; --number .drawContour
JaM.Register[state,"Outline.drawAllContours",ODrawAllContours]; --.drawAllContour
JaM.Register[state,"Outline.setsa",OSetSamples]; --send numbered contour's samples to FitState
JaM.Register[state,"Outline.setallsa",OSetAllSamples]; --send all contours to FitState
JaM.Register[state,".cvchar",CVChar]; --Convert a number to a character
};
outline.get ← Get;
outline.newEdge ← NewEdge;
FitJaM.RegisterInit[$ContourJaM, Init];
END.