NewImager.mesa
Copyright © 1984 Xerox Corporation. All rights reserved.
Michael Plass, February 20, 1984 9:15:15 am PST
Doug Wyatt, August 15, 1984 12:18:04 pm PDT
The Imager provides a rich set of facilities for creating two-dimensional images on a variety of devices; images can be specified in a way that is independent of any particular imaging device.
DIRECTORY
Font USING [FONT],
ImagerColor USING [Color, ConstantColor, SampledColor],
ImagerPath USING [Path, PathProc],
ImagerPixelArray USING [PixelArray],
ImagerTransformation USING [Transformation],
Prop USING [PropList],
Rope USING [ROPE],
Vector2 USING [VEC];
Imager: CEDAR DEFINITIONS
~ BEGIN
The imaging model
Prose.
Contexts and state
Context: TYPE ~ REF ContextRep;
ContextRep: TYPE ~ RECORD[
class: Class, -- procedures for the context class
data: REF, -- instance data (type depends on the class)
props: Prop.PropList -- instance property list
];
The state of the imager is contained in a number of imager variables:
cp: Pair -- current position, in device coordinates
T: Transformation -- client-to-device transformation
font: FONT -- current font
color: Color -- current color
strokeWidth: REAL -- stroke width for MaskStroke and friends
strokeEnd: StrokeEnd -- stroke end treatment for MaskStroke and friends
underlineStart: REAL -- starting x recorded by StartUnderline
amplifySpace: REAL -- factor applied to widths of "amplified" characters
correctShrink: REAL -- allowable shrink when correcting spaces
correctMeasure: Pair -- intended line measure for Correct
correctTolerance: Pair -- tolerable deviation from correctMeasure
priorityImportant: BOOL -- preserve priority order of mask operations
noImage: BOOL -- don't let mask operations change the output
clipOutline: Outline -- clipping outline
props: Prop.PropList -- a property list (of arbitrary [key: REF, value: REF] pairs)
DoSave: PROC[context: Context, body: PROC];
DoSaveAll: PROC[context: Context, body: PROC];
Call the "body" procedure, then restore imager variables to their state prior to the call.
DoSave preserves changes to the "persistent" imager variables, cp and correctMeasure.
DoSaveAll saves and restores all imager variables.
PutProp: PROC[context: Context, key: REF, value: REF];
Put (key, value) on the context's property list.
GetProp: PROC[context: Context, key: REF] RETURNS[value: REF];
Get key's value from the context's property list. Returns NIL if key is not found.
RemProp: PROC[context: Context, key: REF];
Remove key from the context's property list.
Errors
Error: ERROR[errorCode: ErrorCode];
ErrorCode: TYPE ~ {
Bug, -- detected an internal inconsistency
Unimplemented, -- operation not provided for this context
NotYetImplemented, -- part of the Imager implementation is incomplete
ZeroDivideInCorrectSpace, -- CorrectSpace calculation tried to divide by zero
UnableToProperlyAdjustMaskPositions, -- Correct failed to achieve target measure
UnknownSpecialColor, -- unrecognized atom for a special Color
UnknownColorModel, -- unrecognized colorOperator for a sampled color
MustBeRopeOrRefText, -- characters argument is not ROPE or REF TEXT
UnimplementedSpecialOp, -- unrecognized op for SpecialOp
NonexistentFont, -- could not find requested font
InvalidOutline, -- outline is wrong type or NIL
InvalidStroke, -- stroke is wrong type or NIL
GrayParameterOutOfRange, -- for SetGray or MakeGray, f<0 or f>1
WrongType -- variable value has wrong type for Set* or Get*
};
Transformations
For simple applications, the operations here should suffice; for more general Transformation operations, see the ImagerTransformation interface.
Transformation: TYPE ~ ImagerTransformation.Transformation;
ConcatT: PROC[context: Context, m: Transformation];
Premultiply the context's current transformation by m. T ← m.Concat[T]
micasToMeters: REAL ~ 0.00001;
inchesToMeters: REAL ~ micasToMeters*2540;
pointsToMeters: REAL ~ inchesToMeters/72.27;
These are common scale factors for ScaleT.
ScaleT: PROC[context: Context, s: REAL];
Equivalent to context.ConcatT[ImagerTransformation.Scale[s]].
Scale2T: PROC[context: Context, sx, sy: REAL];
Equivalent to context.ConcatT[ImagerTransformation.Scale2[sx, sy]].
RotateT: PROC[context: Context, a: REAL];
Equivalent to context.ConcatT[ImagerTransformation.Rotate[a]].
Angle a is in degrees counterclockwise.
TranslateT: PROC[context: Context, x, y: REAL];
Equivalent to context.ConcatT[ImagerTransformation.Translate[x, y]]
Move: PROC[context: Context];
Translate the origin to the current position. [T.c, T.f] ← cp
Trans: PROC[context: Context];
Translate the origin to the grid point nearest the current position. [T.c, T.f] ← DRound[cp]
Color
The color that will be deposited on the page image is determined by the value of the color variable when a mask operator is invoked. Wherever the mask allows it, the color specified by the imager variable color is deposited on the page image, obliterating any color previously laid down at the same position on the page. There are two ways to specify color: a constant color, and color sampled on a raster. A value of type Color fully specifies a color; a subtype ConstantColor is used for constant colors (ConstantColor widens to Color).
Color: TYPE ~ ImagerBasic.Color;
Constant color
ConstantColor: TYPE ~ ImagerBasic.ConstantColor;
MakeGray: PROC[f: REAL] RETURNS[ConstantColor];
Make a shade of gray specified by the fraction f: 1 means black, 0 means white.
black, white: READONLY ConstantColor; -- MakeGray[1] and MakeGray[0]
Sampled color
SampledColor: TYPE ~ ImagerBasic.SampledColor;
SetSampledColor: PROC[context: Context, pa: PixelArray, pixelT: Transformation, colorOperator: ATOM ← $Intensity];
SetSampledBlack: PROC[context: Context, pa: PixelArray, pixelT: Transformation, transparent: BOOLEANFALSE];
These operations set a sampled color with a transformation of
Concat[pixelT, context.state.T].
For example, if the current coordinate system were in meters, and the client wished to set a 10-by-10 stipple pattern that repeated every 1 cm, the following would work:
pattern: PACKED ARRAY [0..100) OF [0..1] ← [1, 0, 1, 1, ... 0];
pixelArray: PixelArray;
TRUSTED {pixelArray ← MakePixelArrayFromBits[@pattern, 10, 10, 10]};
SetSampledBlack[context, pixelArray, Scale[0.001]];
The current color
SetColor: PROC[context: Context, color: Color];
Set the value of the color variable.
SetGray: PROC[context: Context, f: REAL];
Equivalent to context.SetColor[MakeGray[f]]
Masks
The mask operators are the central focus of the imager, for they determine the shapes of primitive images that are laid down on the image. Mask operators are available to make images of rectangles, line drawings, or filled outlines, and to use a pixel array to specify samples of the mask.
When a mask operator is executed, the page image is altered. The operation of a mask operator is controlled in part by its arguments and in part by imager variables:
— The current transformation, T, transforms the specified mask shape to determine the coordinates of the mask on the image.
— The current color governs the color of the object that will be placed on the image.
— If priorityImportant is TRUE, the priority order of objects laid down is preserved.
— If noImage is TRUE, mask operators will have no effect on the image, although they will have the proper effect on the imager variables.
Shapes are defined geometrically in terms of segments, trajectories and paths. A segment is a directed segment of a straight line, cubic curve, or conic section; it has a start point and an end point. A trajectory is a sequence of connected segments; the end point of a segment coincides with the start point ot the next one. A path may contain a number of trajectories. When a path is regarded as an outline to be filled, each trajectory is implicitly closed by a straight-line segment linking the end point of the last segment with the start point of the first segment.
Pair: TYPE ~ Vector2.VEC;
RECORD[x, y: REAL]
PathProc: TYPE ~ ImagerPath.PathProc;
PROC[
pathData: REF,
moveTo: PROC[p: Pair],
lineTo: PROC[p: Pair],
curveTo: PROC[p1, p2, p3: Pair],
conicTo: PROC[p1, p2: Pair, r: REAL],
arcTo: PROC[p1, p2: Pair]
];
PathProc examples:
MaskFillTriangle: PROC[context: Context, p0, p1, p2: Pair] ~ {
MapTriangle: PathProc ~ { moveTo[p0]; lineTo[p1]; lineTo[p2] };
context.MaskFill[MapTriangle];
};
Polygon: TYPE ~ REF PolygonRep;
PolygonRep: TYPE ~ RECORD[SEQUENCE size: NAT OF Pair];
MapPolygon: PathProc ~ {
polygon: Polygon ~ NARROW[pathData];
IF polygon.size=0 THEN RETURN ELSE moveTo[polygon[0]];
FOR i: NAT IN[1..polygon.size) DO lineTo[polygon[i]] ENDLOOP;
};
MaskFillPolygon: PROC[context: Context, polygon: Polygon] ~ {
context.MaskFill[MapPolygon, polygon];
};
MaskFill: PROC[context: Context, pathProc: PathProc, pathData: REFNIL];
Fill the region outlined by the path with the current color.
Points with nonzero winding number are "inside" the path.
MaskRectangle: PROC[context: Context, x, y, w, h: REAL];
MaskRectangleI: PROC[context: Context, x, y, w, h: INTEGER]
~ INLINE { context.class.MaskRectangleI[context, x, y, w, h] };
Fill a rectangular area with the given origin, width and height.
Equivalent to
MapRectangle: PathProc ~ {
moveTo[[x, y]]; lineTo[[x+w, y]]; lineTo[[x+w, y+h]]; lineTo[[x, y+h]] };
context.MaskFill[MapRectangle];
Box: TYPE ~ RECORD[xmin, ymin, xmax, ymax: REAL];
MaskBox: PROC[context: Context, box: Box];
A convenience for former users of Graphics.DrawBox.
Equivalent to
context.MaskRectangle[
x: box.xmin, y: box.ymin, w: box.xmax-box.xmin, h: box.ymax-box.ymin];
SetStrokeWidth: PROC[context: Context, strokeWidth: REAL];
Establish the width for following strokes.
StrokeEnd: TYPE ~ {square, butt, round};
square: Square off the end after extending the stroke by half its width
butt: Square off the end flush with the endpoint
round: Round the end with a semicircular cap
SetStrokeEnd: PROC[context: Context, strokeEnd: StrokeEnd];
Establish endpoint treatment for following strokes.
MaskStroke: PROC[context: Context, pathProc: PathProc, pathData: REFNIL];
The stroke is first broadened to have uniform width strokeWidth, fitted with the endpoints specified by strokeEnd, then transformed by the current transformation T, and used as a mask to alter the image. Joints between segments of the trajectory are mitered, i.e., sides of the stroke are extended until they meet. Segments of a trajectory meeting at an acute angle will thus generate long, sharp corners.
If square or butt end geometry is undetermined because the trajectory starts or ends with a segment whose start and end points coincide, an error occurs.
To generate strokes with rounded joints between segments, rather than mitered joints, call MaskStroke separately for each segment, using round stroke ends.
MaskStrokeClosed: PROC[context: Context, pathProc: PathProc, pathData: REFNIL];
Like MaskStroke, except that each trajectory is closed if necessary with a straight line back to its starting point, and all joints between segments are mitered. The current strokeEnd setting has no effect.
MaskVector: PROC[context: Context, p1, p2: Pair];
Draw a straight line segment, using the current stroke width and ends.
Equivalent to
MapVector: PathProc ~ { moveTo[p1]; lineTo[p2] };
context.MaskStroke[MapVector]
PixelArray: TYPE ~ ImagerPixelArrays.PixelArray;
MaskPixel: PROC[context: Context, pa: PixelArray];
Bitmap: TYPE ~ RECORD[
base: LONG POINTER, wpl: NAT, -- starting address, words per line
w, h: NAT, -- width and height, in bits
x, y: NAT,
ox, oy: INTEGER
];
MaskBitmap: PROC[context: Context, x, y: INTEGER, bitmap: Bitmap];
SetPriorityImportant: PROC[context: Context, priorityImportant: BOOL];
Set the current value of priorityImportant.
A change to the image induced by a mask operator is said to be "ordered" if priorityImportant is TRUE, and "unordered" if priorityImportant is FALSE. The rule is that the priority order of all ordered image changes must be preserved; the imager is allowed to alter priority order among unordered changes, or between ordered and unordered changes. Because preserving priority order may require more computation than allowing arbitrary reordering of objects, creators should leave priorityImportant FALSE if possible (this is the default). [[Change default to TRUE?]]
SetNoImage: PROC[context: Context, noImage: BOOL];
While noImage=TRUE, mask operations will not affect the output.
Text
Current position operators
The imaging operators make it easy to locate a graphical object such as a character at the current position. The current position is measured in the view coordinate system, and is recorded in a persistent imager variables. It is by altering the current position that an operator displaying a character specifies where the next character on the text line should usually lie. The following operators change the current position.
SetXY: PROC[context: Context, p: Pair];
SetXYI: PROC[context: Context, x, y: INTEGER]
~ INLINE { context.class.SetXYI[context, x, y] };
Set the current position.
SetXYRel: PROC[context: Context, v: Pair];
SetXYRelI: PROC[context: Context, x, y: INTEGER]
~ INLINE { context.class.SetXYRelI[context, x, y] };
Add a relative displacement to the current position.
SetXRel: PROC[context: Context, x: REAL];
SetXRelI: PROC[context: Context, x: INTEGER]
~ INLINE { context.class.SetXYRelI[context, x, 0] };
Equivalent to context.SetXYRel[x, 0]
SetYRel: PROC[context: Context, y: REAL];
SetYRelI: PROC[context: Context, y: INTEGER]
~ INLINE { context.class.SetXYRelI[context, 0, y] };
Equivalent to context.SetXYRel[0, y]
Character operators
The most common shapes are those used to make images of characters; these masks are specified in sets of pre-defined operators called fonts.
FONT: TYPE ~ Font.FONT;
ROPE: TYPE ~ Rope.ROPE;
MakeFont: PROC[name: ROPE, size: REAL] RETURNS[FONT];
Equivalent to Font.ModifyFont[Font.FindFont[name], Scale[size]]
SetFont: PROC[context: Context, font: FONT];
ShowRope: PROC[context: Context, rope: ROPE, start: INT ← 0, len: INTINT.LAST];
ShowText: PROC[context: Context, text: REF READONLY TEXT, start: NAT ← 0, len: NATNAT.LAST];
ShowRope and ShowText treat \377 as an escape code; see Interpress, section 2.5.3.
ShowChar: PROC[context: Context, char: CHAR];
ShowCharCode: PROC[context: Context, charCode: CARDINAL];
SetAmplifySpace: PROC[context: Context, amplifySpace: REAL];
Underlining
Character strings can be underlined by placing a rectangle of appropriate width and height just below the string. The width of the rectangle will be determined by the width of the character string. The position of the underline along the baseline will be determined by the current position, but because of spacing corrections the current position cannot be anticipated accurately when the master is created. The operators StartUnderline and MaskUnderline are provided to help position underlines accurately. They assume a master coordinate system in which the baseline is oriented in the positive x direction.
StartUnderline: PROC[context: Context];
Remember the x component of the current position as the starting point for an underline.
MaskUnderline: PROC[context: Context, dy, h: REAL];
MaskUnderlineI: PROC[context: Context, dy, h: INTEGER]
~ INLINE { context.class.MaskUnderlineI[context, dy, h] };
The text starting at the point previously identified by StartUnderline and ending at the current position will be underlined with a rectangle of height h and top a distance dy below the current position. For example, to underline the word Hello, the client might call:
context.StartUnderline[]; context.ShowRope["Hello"]; context.MaskUnderline[4, 1];
Spacing correction
CorrectMask: PROC[context: Context];
CorrectSpace: PROC[context: Context, v: Pair];
CorrectSpaceXY: PROC[context: Context, x, y: REAL];
Correct: PROC[context: Context, body: PROC];
SetCorrectMeasure: PROC[context: Context, v: Pair];
SetCorrectMeasureXY: PROC[context: Context, x, y: REAL];
SetCorrectTolerance: PROC[context: Context, v: Pair];
SetCorrectToleranceXY: PROC[context: Context, x, y: REAL];
SetCorrectShrink: PROC[context: Context, correctShrink: REAL];
Space: PROC[context: Context, x: REAL];
SpaceI: PROC[context: Context, x: INTEGER]
~ INLINE { context.class.SpaceI[context, x] };
Equivalent to { context.SetXRel[x]; context.CorrectSpaceXY[x, 0] }.
Clipping
ClipOutline: PROC[context: Context, pathProc: PathProc, pathData: REFNIL];
ClipOutlinePath: PROC[context: Context, path: Path];
ExcludeOutline: PROC[context: Context, pathProc: PathProc, pathData: REFNIL];
ExcludeOutlinePath: PROC[context: Context, path: Path];
ClipRectangle: PROC[context: Context, x, y, w, h: REAL];
ClipRectangleI: PROC[context: Context, x, y, w, h: INTEGER];
ExcludeRectangle: PROC[context: Context, x, y, w, h: REAL];
ExcludeRectangleI: PROC[context: Context, x, y, w, h: INTEGER];
Private details
Key: TYPE ~ {
DCScpx, DCScpy, correctMX, correctMY, T, priorityImportant, font, color, noImage, strokeWidth, strokeEnd, underlineStart, amplifySpace, correctPass, correctShrink, correctTX, correctTY, clipper, spare1, spare2, spare3};
Class: TYPE ~ REF ClassRep;
ClassRep: TYPE ~ RECORD[
type: ATOM,
DoSave: PROC[context: Context, body: PROC] ←,
DoSaveAll: PROC[context: Context, body: PROC] ←,
SetRef: PROC[context: Context, key: Key, value: REF] ←,
SetReal: PROC[context: Context, key: Key, value: REAL] ←,
SetInt: PROC[context: Context, key: Key, value: INT] ←,
GetRef: PROC[context: Context, key: Key] RETURNS[REF] ←,
GetReal: PROC[context: Context, key: Key] RETURNS[REAL] ←,
GetInt: PROC[context: Context, key: Key] RETURNS[INT] ←,
ConcatT: PROC[context: Context, m: Transformation] ←,
Scale2T: PROC[context: Context, sx, sy: REAL] ←,
RotateT: PROC[context: Context, a: REAL] ←,
TranslateT: PROC[context: Context, x, y: REAL] ←,
Move: PROC[context: Context] ←,
Trans: PROC[context: Context] ←,
SetFont: PROC[context: Context, font: FONT] ←,
ShowRope: PROC[context: Context, rope: ROPE, start, len: INT] ←,
ShowText: PROC[context: Context, text: REF READONLY TEXT, start, len: NAT] ←,
ShowCharCode: PROC[context: Context, charCode: CARDINAL] ←,
SetXY: PROC[context: Context, p: Pair] ←,
SetXYI: PROC[context: Context, x, y: INTEGER] ←,
SetXYRel: PROC[context: Context, v: Pair] ←,
SetXYRelI: PROC[context: Context, x, y: INTEGER] ←,
GetCP: PROC[context: Context] RETURNS[Pair] ←,
MaskFill: PROC[context: Context, pathProc: PathProc, pathData: REF] ←,
MaskStroke: PROC[context: Context, pathProc: PathProc, pathData: REF, closed: BOOL] ←,
MaskRectangle: PROC[context: Context, x, y, w, h: REAL] ←,
MaskRectangleI: PROC[context: Context, x, y, w, h: INTEGER] ←,
MaskVector: PROC[context: Context, p1, p2: Pair] ←,
MaskVectorI: PROC[context: Context, x1, y1, x2, y2: INTEGER] ←,
StartUnderline: PROC[context: Context] ←,
MaskUnderline: PROC[context: Context, dy, h: REAL] ←,
MaskUnderlineI: PROC[context: Context, dy, h: INTEGER] ←,
MaskPixel: PROC[context: Context, pa: PixelArray] ←,
SetColor: PROC[context: Context, color: Color] ←,
SetGray: PROC[context: Context, f: REAL] ←,
SetSampledColor: PROC[context: Context, pa: PixelArray, pixelT: Transformation, colorOperator: ATOM] ←,
SetSampledBlack: PROC[context: Context, pa: PixelArray, pixelT: Transformation, transparent: BOOL] ←,
ClipOutline: PROC[context: Context, pathProc: PathProc, pathData: REF, exclude: BOOL] ←,
ClipRectangle: PROC[context: Context, x, y, w, h: REAL, exclude: BOOL] ←,
ClipRectangleI: PROC[context: Context, x, y, w, h: INTEGER, exclude: BOOL] ←,
CorrectMask: PROC[context: Context] ←,
CorrectSpace: PROC[context: Context, v: Pair] ←,
Correct: PROC[context: Context, body: PROC] ←,
SetCorrectMeasure: PROC[context: Context, v: Pair] ←,
SetCorrectTolerance: PROC[context: Context, v: Pair] ←,
Space: PROC[context: Context, x: REAL] ←,
SpaceI: PROC[context: Context, x: INTEGER] ←,
props: Prop.PropList ← NIL
];
END.