NectarineImpl.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Giordano Bruno Beretta, April 11, 1986 7:57:41 pm PST
gbb October 22, 1986 4:10:38 pm PDT
Implements an alternate way to produce Interpress masters from ChipNDale drawings. The difference is in the user interface. It is similar to the one in programs by Imaging Folks, and it is simpler to use for casual users.
Fuzzless and sweeter than peaches (Suzanne Williams)
DIRECTORY
Atom USING [MakeAtom],
BasicTime USING [GMT, Now, OutOfRange, Period, Update],
CD USING [CreateDrawRef, Design, DrawProc, DrawRectProc, DrawRef, Instance, InstanceList, Layer, LayerKey, Number, Object, Orientation, original, Position, Rect, undefLayer],
CDBasics USING [BaseOfAnyRect, Intersection, NonEmpty, SizeOfRect],
CDDirectory USING [EachEntryAction, Enumerate],
CDColors USING [ColorTable, DisplayMode, DisplayType, globalColors],
CDCurves USING [CurvePtr],
CDInstances USING [NewInst],
CDOps USING [DrawDesign, InstList],
CDOrient USING [CreateTransform, mirrorX, rotate180, rotate180X, rotate270X, rotate90, rotate90X],
CDTexts USING [TextPtr],
CDValue USING [Fetch],
Checksum USING [ComputeChecksum],
CStitching USING [all, Area, ChangeEnumerateArea, ChangeRect, DumpCache, EnumerateArea, NewTesselation, RectProc, Region, ResetTesselation, Tesselation, Tile, TileProc],
FS USING [ComponentPositions, Error, ExpandName, Position, SetKeep],
HashTable USING [Create, EachPairAction, Fetch, GetSize, Insert, Key, Pairs, Table],
Imager USING [black, ClipRectangle, Color, ColorOperator, ConcatT, Context, ConstantColor, DoSave, Font, MaskFillTrajectory, MaskRectangle, MaskStrokeTrajectory, Rectangle, ScaleT, SetColor, SetFont, SetPriorityImportant, SetStrokeEnd, SetStrokeJoint, SetStrokeWidth, SetXY, ShowRope, StrokeEnd, StrokeJoint, Trans, TranslateT, VEC],
ImagerBrick USING [Brick, BrickRep],
ImagerColor USING [ColorFromRGB, RGB],
ImagerColorPrivate USING [ComponentFromColor],
ImagerColorOperator USING [ColorOperator, RGBLinearColorModel],
ImagerExtras USING [MaskDashedStrokeTrajectory],
ImagerFont USING [Modify],
ImagerInterpress USING [Close, Create, DeclareColor, DeclareColorOperator, DeclareFont, DoPage, Ref],
ImagerPath USING [LineToX, LineToY, MoveTo, Trajectory],
ImagerPDPublic USING [Toner],
ImagerTransformation USING [Cat, Create, Invert, Rotate, Scale2, Transformation, Translate],
Interpress USING [classAppearanceError, classAppearanceWarning, classComment, classMasterError, classMasterWarning, LogProc],
IO USING [atom, int, PutFR1, real, time],
MessageWindow USING [Append, Blink, Clear],
Nectarine USING [],
NodeStyle USING [FontFace],
NodeStyleFont USING [FontFromStyleParams],
PeachPrint USING [DoPeachPrintCommand, PupAborted],
PrincOpsUtils USING [],
PrintFileConvert USING [InterpressToPD, ParamsFromPrinterType, PDParams, ProgressProc],
Process USING [CheckForAbort, Detach, priorityBackground, SetPriority],
Real USING [Fix, Float],
RefTab USING [Create, Delete, EachPairAction, Fetch, GetSize, Insert, Pairs, Ref],
Rope USING [Cat, Equal, Fetch, IsEmpty, Length, Replace, ROPE, SkipTo, Substr],
TerminalIO USING [TOS, WriteF, WriteInt, WriteRope],
UserProfile USING [Boolean, CallWhenProfileChanges, ProfileChangedProc, Token];
NectarineImpl: CEDAR PROGRAM
IMPORTS Atom, BasicTime, CD, CDBasics, CDColors, CDDirectory, CDInstances, CDOps, CDOrient, CDValue, Checksum, CStitching, FS, HashTable, Imager, ImagerColor, ImagerColorPrivate, ImagerColorOperator, ImagerExtras, ImagerFont, ImagerInterpress, ImagerPath, ImagerTransformation, IO, MessageWindow, NodeStyleFont, PeachPrint, PrintFileConvert, Process, Real, RefTab, Rope, TerminalIO, UserProfile
EXPORTS Nectarine ~ BEGIN
OPEN Real;
Implementation note on clipping: If bands have to be created because of the size of the design, true clipping to bands must be done when traversing the design. The penalty on the corner stitched data structure may be swallowed, since it is an overnight job anyway. However, leaving clipping to the Imager, you will have all those rectangles in the Interpress master with their multiciplity. A large disk can hold only about 90% of a thing like the Cross-RAM, and every single file on your disk will have been flushed off when you come in in the morning.
If you insist on leaving the clipping to the imager, then you must be very careful deciding where to place it. The reason is that clipping in the Imager is relative, and therefore the window must be specified inside Do-Save-Simple-Body, in order not to disturb the global clipping window. The places would be EnumerateGeometry and EnumerateTextInDesign.
break: SIGNAL = CODE;  -- for debugging
invalidPrinter: PUBLIC SIGNAL = CODE;
communicationsFailure: PUBLIC SIGNAL = CODE;
tooComplex: PUBLIC SIGNAL = CODE;
Font: TYPE = Imager.Font;
Color: TYPE = Imager.Color;
Transformation: TYPE = ImagerTransformation.Transformation;
State: TYPE = REF StateRec;
StateRec: TYPE = RECORD [
previousColour: Color ← NIL,
previousStrokeWidth: REAL ← -1.0,
previousFont: Font ← NIL,
previousTextOrientation: CD.Orientation ← CD.original,
cardTile: CARD ← 0, -- |{tile}|
startTime: BasicTime.GMT,
context: Imager.Context,
design: CD.Design,
interpress: ImagerInterpress.Ref,
tess: Tess,
abort: REF BOOL,
clip: BOOLFALSE, -- Wysiwyg or selectedOnly
cdClip: CD.Rect, -- Wysiwyg or selectedOnly
selectedOnly: BOOL,
band: REF CD.Rect ← NIL];
medium: Imager.Rectangle ~ [x: 0.0, y: 0.0, w: 215.9, h: 279.4]; -- in mm
field: Imager.Rectangle ~ [x: medium.x + 10.0, y: medium.y + 10.0, w: medium.w - 20.0, h: medium.h - 20.0]; -- in mm
[Artwork node; type 'ArtworkInterpress on' to command tool]  Insert caption here 
mmXin: REAL ~ 25.4;
statistics: BOOLFALSE;
debug: BOOLFALSE;
remindFontConventions: BOOLTRUE;
risk: INT ← 2; -- decrease if Nectarine runs out of VM. More risk mean much more speed. Geometric interpretation: consider a horizontal straight line through the design; you state that you have a rectangle only each risk l. At zero risk, Nectarine uses only 14 MB of memory in the worst case.
Geometry
Region: TYPE ~ LIST OF REF CStitching.Region;
Tess: TYPE ~ CStitching.Tesselation;
Tile: TYPE ~ CStitching.Tile;
empty: REF ~ NIL;
nothing: REF INT ~ NEW [INT];
StartDecomposition: PROC [state: State] RETURNS [dec: Tess] ~
{RETURN [CStitching.NewTesselation[stopFlag: state.abort]]};
FlushDecomposition: PROC [dec: Tess] ~ BEGIN
Does what FreeTesselation supposedly did once upon a time.
CStitching.ResetTesselation [dec]
END; -- FlushDecomposition
PrintAlignedTile: CStitching.TileProc ~ BEGIN
PROC [tile: REF Tile, data: REF]
This version is much slower than PrintTile. It uses 4 additional operations and 12 additional bytes. This buys that all rectangles get aligned to pixels. This makes that all wires in schematics are printed with the same width.
state: State ~ NARROW [data];
b: Blend ~ NARROW [tile.value];
r: CD.Rect ~ CStitching.Area [tile]; -- not the area but the rectangle !
ir: Imager.Rectangle ~ ImagerRect [r];
MaskAlignedRectangle: PROC ~ BEGIN
state.context.SetXY [[ir.x, ir.y]]; state.context.Trans [];
state.context.MaskRectangle [[0, 0, ir.w, ir.h]]
END; -- MaskAlignedRectangle
ChangeColour [state, b.blend];
state.context.DoSave [MaskAlignedRectangle];
IF state.abort^ THEN ERROR ABORTED;
IF statistics THEN BEGIN
l: CD.Number ~ state.design.technology.lambda;
b.area ← b.area + ((r.x2 - r.x1) / l) * ((r.y2 - r.y1) / l);
state.cardTile ← SUCC [state.cardTile]
END
END; -- PrintAlignedTile
PrintTile: CStitching.TileProc ~ BEGIN
PROC [tile: REF Tile, data: REF]
state: State ~ NARROW [data];
b: Blend ~ NARROW [tile.value];
r: CD.Rect ~ CStitching.Area [tile]; -- not the area but the rectangle !
ChangeColour [state, b.blend];
state.context.MaskRectangle [ImagerRect [r]];
IF state.abort^ THEN ERROR ABORTED;
IF statistics THEN BEGIN
l: CD.Number ~ state.design.technology.lambda;
b.area ← b.area + ((r.x2 - r.x1) / l) * ((r.y2 - r.y1) / l);
state.cardTile ← SUCC [state.cardTile]
END
END; -- PrintTile
PrintBand: PROC [state: State] ~ BEGIN
Prints a band. If there is only one band and no clipping is necessary, setting clip to false will make it faster.
decomposition: Tess ← StartDecomposition [state];
gdr: CD.DrawRef ~ CD.CreateDrawRef [[]];
EnumerateGeometry: PROC ~ BEGIN
state.context.SetPriorityImportant [FALSE]; -- works for the devices at PARC today
decomposition.EnumerateArea [rect: CStitching.all, eachTile: PrintAlignedTile, data: state, skip: empty]
END; -- EnumerateGeometry
Process.CheckForAbort []; IF state.abort^ THEN ERROR ABORTED;
IF (state.clip OR (state.band # NIL)) THEN
gdr.interestClip ← IF (state.band = NIL) THEN state.cdClip
ELSE CDBasics.Intersection [state.cdClip, state.band^];
state.tess ← decomposition;
gdr.drawRect ← NewRect; gdr.devicePrivate ← state;
IF state.selectedOnly THEN DrawSelection [state.design, gdr]
ELSE CDOps.DrawDesign [state.design, gdr]; -- pass 2
TerminalIO.WriteRope ["."];
BlendAllColours []; TerminalIO.WriteRope ["."]; -- pass 3
state.previousColour ← NIL; -- needed by Interpress
state.context.DoSave [EnumerateGeometry]; -- pass 4
FlushDecomposition [decomposition];
TerminalIO.WriteRope [". "]; -- state.tess ← decomposition ← NIL
END; -- PrintBand
NewRect: CD.DrawRectProc ~ BEGIN
[r: Rect, l: Layer, pr: DrawRef]
state: State ~ NARROW [pr.devicePrivate, State];
rect: CD.Rect ← IF (state.band = NIL) THEN r ELSE CDBasics.Intersection [r, state.band^];
IF (CDBasics.NonEmpty [rect]) THEN BEGIN
Note that merging works correctly because of the use of the colour table.
dec: Tess ~ state.tess;
InsertRect: CStitching.RectProc = BEGIN
[plane: REF Tesselation, rect: Rect, oldValue: REF, data: REF]
WITH oldValue SELECT FROM
b: Blend => IF NOT b.flavours[l] THEN dec.ChangeRect [rect: rect, new: ColourTile [b, l]];
ENDCASE => BEGIN
b: Blend ← NEW [BlendRec];
dec.ChangeRect [rect: rect, new: ColourTile [b, l]]
END
END; -- InsertRect
dec.ChangeEnumerateArea [rect: rect, eachRect: InsertRect, skip: nothing];
IF state.abort^ THEN ERROR ABORTED
END
END; -- NewRect
Annotation
mirrorY: Transformation ~ ImagerTransformation.Scale2 [[-1, 1]];
rot180: Transformation ~ ImagerTransformation.Rotate [180];
SpecialHack: PROC [obj: CD.Object, or: CD.Orientation] RETURNS [t: Transformation] ~ BEGIN
This is a special temporary hack that hacks out the transformation required to print the same thing as ChipNDale displays in the class $FlipText.
adjustX: Transformation ~ ImagerTransformation.Translate [[-obj.size.x, 0]];
adjustXY: Transformation ~ ImagerTransformation.Translate [[-obj.size.x, -obj.size.y]];
IF (obj.class.objectType # $FlipText) THEN ERROR; -- tocca ferro
t ← SELECT or FROM
CDOrient.mirrorX, CDOrient.rotate90X => ImagerTransformation.Cat [adjustX, mirrorY],
CDOrient.rotate180X, CDOrient.rotate270X => ImagerTransformation.Cat [adjustXY, rot180, adjustX, mirrorY],
CDOrient.rotate180, CDOrient.rotate90 => ImagerTransformation.Cat [adjustXY, rot180],
ENDCASE => ImagerTransformation.Create [1, 0, 0, 0, 1, 0]
END; -- SpecialHack
DrawText: CD.DrawProc ~ BEGIN
[inst: CD.Instance, pos: CD.Position, orient: CD.Orientation, pr: CD.DrawRef]
state: State ~ NARROW [pr.devicePrivate];
context: Imager.Context ~ state.context;
text: CDTexts.TextPtr ~ NARROW [inst.ob.specificRef];
offset: Imager.VEC ~ text.cdFont.xy;
transf, invTransf: Transformation;
Process.CheckForAbort []; IF state.abort^ THEN ERROR ABORTED;
transf ← CDOrient.CreateTransform [cellSize: inst.ob.size, cellInstOrient: orient, cellInstPos: pos];
IF (inst.ob.class.objectType = $FlipText) THEN
transf ← ImagerTransformation.Cat [SpecialHack [inst.ob, orient], transf];
invTransf ← ImagerTransformation.Invert [transf];
context.ConcatT [transf]; context.SetXY [offset];
ChangeColour [state, LayerColour [inst.ob.layer]];
ChangeFont [state, text.cdFont.font];
context.ShowRope [text.text]; context.ConcatT [invTransf]
END; -- DrawText
DrawPath: CD.DrawProc ~ BEGIN
[inst: CD.Instance, pos: CD.Position, orient: CD.Orientation, pr: CD.DrawRef]
state: State ~ NARROW [pr.devicePrivate];
context: Imager.Context ~ state.context;
curve: CDCurves.CurvePtr ~ NARROW [inst.ob.specificRef];
transf: Transformation ~ CDOrient.CreateTransform [cellSize: inst.ob.size, cellInstOrient: orient, cellInstPos: pos];
invTransf: Transformation ~ ImagerTransformation.Invert [transf];
Process.CheckForAbort []; IF state.abort^ THEN ERROR ABORTED;
context.ConcatT [transf]; -- context.SetXY [offset];
ChangeColour [state, LayerColour [inst.ob.layer]];
ChangeStrokeWidth [state, Float [curve.w]];
context.MaskStrokeTrajectory [curve.path]; context.ConcatT [invTransf]
END; -- DrawPath
DrawArea: CD.DrawProc ~ BEGIN
[inst: CD.Instance, pos: CD.Position, orient: CD.Orientation, pr: CD.DrawRef]
state: State ~ NARROW [pr.devicePrivate];
context: Imager.Context ~ state.context;
curve: CDCurves.CurvePtr ~ NARROW [inst.ob.specificRef];
transf: Transformation ~ CDOrient.CreateTransform [cellSize: inst.ob.size, cellInstOrient: orient, cellInstPos: pos];
invTransf: Transformation ~ ImagerTransformation.Invert [transf];
Process.CheckForAbort []; IF state.abort^ THEN ERROR ABORTED;
context.ConcatT [transf]; -- context.SetXY [offset];
ChangeColour [state, LayerColour [inst.ob.layer]];
ChangeStrokeWidth [state, Float [curve.w]];
context.MaskFillTrajectory [curve.path, TRUE]; context.ConcatT [invTransf]
END; -- DrawArea
DrawObjectBorder: CD.DrawRectProc ~ BEGIN
[r: Rect, l: Layer, pr: DrawRef]
The border is drawn outside the cell, since this is the way they are used by Rick Barth, currently the only creator of documents using boxes.
state: State ~ NARROW [pr.devicePrivate, State];
pen: REAL ~ Float [state.design.technology.lambda];
halfPen: REAL ~ pen / 2.0;
object: Imager.Rectangle ~ ImagerRect [r];
border: ImagerPath.Trajectory;
Process.CheckForAbort []; IF state.abort^ THEN ERROR ABORTED;
ChangeColour [state, black]; ChangeStrokeWidth [state, pen];
Convert the object border into an Imager trajectory.
border ← ImagerPath.MoveTo [[object.x - halfPen, object.y - halfPen]];
border ← border.LineToX [object.x + object.w + halfPen]; -- South
border ← border.LineToY [object.y + object.h + halfPen]; -- East
border ← border.LineToX [object.x - halfPen];  -- North
border ← border.LineToY [object.y - halfPen];  -- West
Draw it !
Note that the stroke ends and joints are round.
state.context.MaskStrokeTrajectory [trajectory: border]
END; -- DrawObjectBorder
DrawDashedObjectBorder: CD.DrawRectProc ~ BEGIN
[r: Rect, l: Layer, pr: DrawRef]
The border is drawn outside the cell, since this is the way they are used by Rick Barth, currently the only creator of documents using boxes.
state: State ~ NARROW [pr.devicePrivate, State];
pen: REAL ~ Float [state.design.technology.lambda / 2];
halfPen: REAL ~ pen / 2.0;
object: Imager.Rectangle ~ ImagerRect [r];
border: ImagerPath.Trajectory;
patternElements: NAT ~ 8;
DotDotDotDash: PROC [i: NAT] RETURNS [REAL] ~ BEGIN
pattern: ARRAY [0 .. patternElements) OF REAL ~ [0, 5*pen, 0, 5*pen, 0, 10*pen, 30*pen, 10*pen];
RETURN [pattern[i]]
END; -- DotDotDotDash
Process.CheckForAbort []; IF state.abort^ THEN ERROR ABORTED;
ChangeColour [state, black]; ChangeStrokeWidth [state, pen];
Convert the object border into an Imager trajectory.
border ← ImagerPath.MoveTo [[object.x - halfPen, object.y - halfPen]];
border ← border.LineToX [object.x + object.w + halfPen]; -- South
border ← border.LineToY [object.y + object.h + halfPen]; -- East
border ← border.LineToX [object.x - halfPen];  -- North
border ← border.LineToY [object.y - halfPen];  -- West
Draw it !
Note that the stroke ends and joints are round.
ImagerExtras.MaskDashedStrokeTrajectory [context: state.context, trajectory: border, offset: 0, length: 2*(object.w+object.h), patternLen: patternElements, pattern: DotDotDotDash]
END; -- DrawDashedObjectBorder
DrawObjectBorder: CD.DrawRectProc ~ BEGIN
[r: Rect, l: Layer, pr: DrawRef]
The border is drawn outside the cell, since this is the way they are used by Rick Barth, currently the only creator of documents using boxes.
state: State ~ NARROW [pr.devicePrivate, State];
pen: REAL ~ Float [state.design.technology.lambda / 2];
object, border: Imager.Rectangle;
Process.CheckForAbort []; IF state.abort^ THEN ERROR ABORTED;
ChangeColour [state, black];
object ← ImagerRect [r];
South:
object.x ← object.x - pen; object.y ← object.y - pen;
object.w ← object.w + 2 * pen; object.h ← object.h + 2 * pen;
border ← [x: object.x, y: object.y, w: object.w , h: pen];
state.context.MaskRectangle [border];
West:
border ← [x: object.x, y: object.y, w: pen, h: object.h];
state.context.MaskRectangle [border];
East:
border ← [x: object.x + object.w - pen, y: object.y, w: pen, h: object.h];
state.context.MaskRectangle [border];
North:
border ← [x: object.x, y: object.y + object.h - pen, w: object.w, h: pen];
state.context.MaskRectangle [border]
END; -- DrawObjectBorder
DrawObject: CD.DrawProc ~ BEGIN
[inst: Instance, pos: Position, orient: Orientation, pr: REF DrawInformation]
Process.CheckForAbort [];
SELECT inst.ob.class.objectType FROM
$Text, $RigidText, $FlipText => DrawText [inst, pos, orient, pr];
$Spline0, $Line0 => DrawPath [inst, pos, orient, pr];
$FilledCurve0, $Polygon0 => DrawArea [inst, pos, orient, pr];
The procedure for rectangles is called through the recursion step.
ENDCASE => inst.ob.class.drawMe [inst, pos, orient, pr]
END; -- DrawObject
Interpress
DoInterpress: PUBLIC PROC [design: CD.Design, chipNDaleWindow: CD.Rect, clip, onlySel: BOOLFALSE, abortFlag: REF BOOL] RETURNS [masterName: Rope.ROPE, usedField: Imager.Rectangle] ~ BEGIN
Produces an Interpress master of the design. The master is scaled such that it fits a whole page. chipNDaleWindow is either the bounding box of the design or a window in it. In the latter case, clip must be set to TRUE, and objects completetly outside the window will be ommitted from the Interpress master (because of things like mitering, actual clipping can be performed only when a bitmap is created).
The usedField is specified in millimetres. It is useful for subsequent processing of the Interpress master as long as this field cannot be specified in the preamble of Interpress masters themselves.
If onlySel is true, only the selected object are included in the Interpress master.
May return signal tooComplex if it is believed that there might be too many rectangles in a horizontal cross section. The used criterion is the width of the design; if you do not agree, use the Interpreter to increase the risk.
interpress: ImagerInterpress.Ref;
state: State ~ NEW [StateRec];
rgbLinear: Imager.ColorOperator ~ ImagerColorOperator.RGBLinearColorModel [255];
OpenIPMaster: PROC [] RETURNS [ImagerInterpress.Ref] ~ BEGIN
Creates the interpress master.
ENABLE {FS.Error => GOTO failure};
subDir: Rope.ROPE ~ "[]<>Temp>Nectarine>"; -- follows the religion
pos: FS.ComponentPositions;
ext: Rope.ROPE ~ ".dummy"; -- anything, just not to lose the dot
IF NOT design.name.IsEmpty[] THEN masterName ← design.name.Cat [ext]
ELSE BEGIN
cdName: Rope.ROPE ~ NARROW [CDValue.Fetch [design, $CDxLastFile]];
IF cdName.IsEmpty[] THEN masterName ← Rope.Cat ["UnnamedMaster", ext]
ELSE BEGIN
shortFName: Rope.ROPE;
[fullFName: masterName, cp: pos] ← FS.ExpandName [cdName, subDir];
shortFName ← masterName.Substr [start: pos.base.start, len: pos.base.length];
masterName ← shortFName.Cat [ext]
END
END;
[fullFName: masterName, cp: pos] ← FS.ExpandName [masterName, subDir];
masterName ← masterName.Replace [pos.ext.start, pos.ext.length, "Interpress"];
RETURN [ImagerInterpress.Create [masterName]];
EXITS
failure => BEGIN
masterName ← "[]<>Temp>Nectarine>UnnamedMaster.Interpress";
RETURN [ImagerInterpress.Create [masterName]]
END
END; -- OpenIPMaster
DrawToIP: PROC [context: Imager.Context] ~ BEGIN
Called back by the Interpress machinery.
tdr: CD.DrawRef ~ CD.CreateDrawRef [[]];
window: Imager.Rectangle ~ ImagerRect [chipNDaleWindow]; -- in the CG sense
ratioW, ratioH, y0: REAL;
iterations, bandSize: INT; -- number and size of bands
chronos: BOOL;
lap, end: BasicTime.GMT;
EnumerateTextInDesign: PROC ~ BEGIN
state.context.SetPriorityImportant [TRUE]; -- see EnumerateGeometry
IF state.selectedOnly THEN DrawSelection [state.design, tdr]
ELSE CDOps.DrawDesign [state.design, tdr]
END; -- EnumerateTextInDesign
state.context ← context; state.clip ← clip;state.selectedOnly ← onlySel;
state.cdClip ← chipNDaleWindow;
SetLayerColourTable [];
ratioW ← field.w / window.w; ratioH ← field.h / window.h;
usedField.x ← field.x; usedField.y ← field.y; -- in mm
IF ratioH < ratioW THEN
{usedField.w ← window.w * ratioH; usedField.h ← field.h}
ELSE {usedField.w ← field.w; usedField.h ← window.h * ratioW};
Preview considers the top of the page to be most important, because we write text from top to bottom. Also designers want have images to be flush to the top of the field, so they can use the bottom of the medium for hand-annotations. Unfortunately, this causes a severe problem when producing a PD file. In fact there is said to be a hack fixing a hard bug in the Peach software. This hack is said to be a white rectangle at the lower left corner of the medium. On the Versatec this means that with every image in landscape format you would get metres of white paper. This is why images are positioned in the lower left corner of the field.
y0 ← IF ratioH < ratioW THEN 0.0 ELSE field.h - window.h * ratioW;
y0 ← 0.0;
context.TranslateT [[field.x, y0 + field.y]]; context.ScaleT [MIN [ratioW, ratioH]];
context.TranslateT [[-window.x, -window.y]];
context.SetStrokeJoint [mitered]; -- I hope that's faster for rectangles
IF statistics THEN CleanColourTable [state];
state.startTime ← BasicTime.Now [];
IF clip THEN context.ClipRectangle [window]; -- global to context !!!
Rectangles:
Magic numbers: Assume 14 MB of memory may be used. Each tile requires 16 words of storage (14 words for the tile and 2 words Cedar overhead), hence 106 tiles can be stored. In the current worst case, a die is 10 mm wide. If a gate can be 2 mm long, in the worst case there is a rectangle each mm. In the average there are as many space tiles as there are colored tiles.
bandSize ← Fix [(1000000.0 * (risk+1)) / (window.w / design.technology.lambda)];
IF bandSize < 4 * design.technology.lambda THEN SIGNAL tooComplex;
iterations ← Fix [window.h] / bandSize; -- "zero relative" !
IF (iterations > 5) THEN BEGIN
TerminalIO.WriteRope [" There will be "];
TerminalIO.WriteInt [iterations]; TerminalIO.WriteRope [" sets of 3 dots "]
END
ELSE {IF debug THEN TerminalIO.WriteInt [iterations]};
IF ((iterations = 0) AND (NOT state.clip)) THEN PrintBand [state] -- shortcut
ELSE BEGIN
halftime: INT ~ iterations / 2;
currentBand: REF CD.Rect ~ NEW [CD.Rect ← chipNDaleWindow];
The intersection of the current band with the clipping window is performed by PrintBand. The field state.cdClip is assigned earlier.
state.band ← currentBand;
chronos ← iterations > 5;
FOR b: INT DECREASING IN [0 .. iterations] DO
Produce bands from high to low y-coordinate values in order to avoid paging in succeeding software.
currentBand.y1 ← chipNDaleWindow.y1 + b * bandSize;
currentBand.y2 ← MIN [chipNDaleWindow.y2, currentBand.y1 + bandSize + 1];
IF chronos THEN BEGIN
The first bands are not typical, because they are not dense.
fudge: INT ~ 8;
IF (b = iterations - 3) THEN lap ← BasicTime.Now [];
IF (b = iterations - 4) THEN BEGIN
duration: INT ~ BasicTime.Period [lap, BasicTime.Now[]];
end ← BasicTime.Update [lap, fudge * duration * (b + 2) !
BasicTime.OutOfRange => GOTO endless];
TerminalIO.WriteF ["\nInterpress master will be ready ca. %g\n", IO.time [end]];
chronos ← FALSE
END
END;
PrintBand [state];
IF (b = halftime) AND (iterations > 10) THEN BEGIN
fudge: INT ~ 3; -- more than 2 because of memory fragmentation
duration: INT ~ BasicTime.Period [state.startTime, BasicTime.Now[]];
end ← BasicTime.Update [state.startTime, fudge * duration !
BasicTime.OutOfRange => GOTO endless];
TerminalIO.WriteF ["\nInterpress master will be ready ca. %g\n", IO.time [end]]
END
ENDLOOP;
state.band ← NIL
END;
IF (state.tess # NIL) THEN {CStitching.DumpCache []; state.tess ← NIL};
Text:
context.SetStrokeEnd [round]; context.SetStrokeJoint [round];
The stroke joint must be round, because a ChipNDale staightline segment [a, b] is made out of a spline [a, b, a'], where d (a, a') < e.
IF clip THEN tdr.interestClip ← chipNDaleWindow;
tdr.drawChild ← DrawObject;
tdr.drawOutLine ← DrawObjectBorder; tdr.borders ← TRUE;
tdr.devicePrivate ← state;
state.previousColour ← NIL; -- needed by Interpress
state.previousStrokeWidth ← -1.0; -- needed by Interpress
context.DoSave [EnumerateTextInDesign]; TerminalIO.WriteRope [". "]; -- pass 5
TerminalIO.WriteRope [TimeToRope [state.startTime, BasicTime.Now[]]];
TerminalIO.WriteRope ["\n"];
EXITS
endless => BEGIN
ImportantMessage [" Nectarine would not terminate before 2036"];
SIGNAL tooComplex
END
END; -- DrawToIP
Action: PROC [] ~ BEGIN
Does it at background priority.
DeclareColours: HashTable.EachPairAction ~ BEGIN
blendRec: Blend ~ NARROW [value];
IF (blendRec.blend # NIL) THEN interpress.DeclareColor [blendRec.blend];
RETURN [FALSE]
END;
DeclareFonts: RefTab.EachPairAction ~
{interpress.DeclareFont [NARROW [val,Font]]; RETURN [FALSE]};
ListFonts: RefTab.EachPairAction ~
{TerminalIO.WriteRope [NARROW[val,Font].name]; TerminalIO.WriteRope ["\n"];
RETURN [FALSE]};
state.interpress ← interpress ← OpenIPMaster [];
TerminalIO.WriteRope [Rope.Cat ["Producing Interpress master ", masterName, "\n"]];
Produce preamble.
interpress.DeclareColorOperator [rgbLinear];
It is not possible to declare all colours in the preamble, because the size of the Interpress frame is 50 entries (this is defined in the Standard).
IF (colourTable.GetSize[] + fontMap.GetSize[] < 50) THEN
[] ← colourTable.Pairs [DeclareColours]; -- this is a terrible hack, since the colours in the table are those from the previuos run. A request to provide a clean solution has been filed with the Imaging People.
Create a table of the fonts and them in the preamble.
UpdateFontMap [state]; -- pass 1
IF (fontMap.GetSize[] < 50) THEN [] ← fontMap.Pairs [DeclareFonts];
TerminalIO.WriteRope [". "];
IF debug THEN BEGIN
TerminalIO.WriteRope ["\nThe following fonts are in the preamble:\n"];
[] ← fontMap.Pairs [ListFonts]
END;
Produce the page. All coordinates will be in millimetres.
TRUSTED {Process.SetPriority [Process.priorityBackground]};
interpress.DoPage [action: DrawToIP, scale: 0.001];
Close.
interpress.Close [];
The keep of Interpress masters is 1. Unfortunately PeachPrint does not lock a file between the time it posts a request and the request is served. This can not only cause the files to get lost, but it may even hang some print servers. The solution implemented here, i.e., changing the keep to 3, it to be considered as a mild temporary hack until a proper solution becomes available in 3 weeks from the time these lines are written.
FS.SetKeep [masterName, 3];
Calling FS.SetKeep will have the side effect of deleting files that are obsolete relative to the new keep, even when the keep is not changed.
IF (statistics OR debug) THEN ListColourTable [state];
TerminalIO.WriteRope [Rope.Cat ["Interpress master ", masterName, " is ready.\n"]]
END; -- Action
state.design ← design;
state.abort ← IF abortFlag # NIL THEN abortFlag ELSE NEW [BOOLFALSE];
Action [] -- fork
END; -- DoInterpress
DrawSelection: PROC [design: CD.Design, d: CD.DrawRef] ~ BEGIN
Same as CDOps.DrawDesign, but visits only the selected objects.
FOR all: CD.InstanceList ← CDOps.InstList [design], all.rest WHILE all # NIL DO
IF all.first.selected THEN d.drawChild [all.first, all.first.location, all.first.orientation, d]
ENDLOOP
END; -- DrawSelection
EnumerateObjects: PROC [design: CD.Design, d: CD.DrawRef] ~ BEGIN
Same as CDOps.DrawDesign, but visits objects only once.
DoObject: CDDirectory.EachEntryAction ~ BEGIN
PROC [name: Rope.ROPE, ob: CD.Object] RETURNS [quit: BOOL�LSE]
i: CD.Instance ~ CDInstances.NewInst [ob];
d.drawChild [i, [0,0], 0, d]
END; -- DoObject
[] ← CDDirectory.Enumerate [design, DoObject]
END; -- EnumerateObjects
ChangeColour: PROC [state: State, colour: Color] ~ INLINE BEGIN
At this point we know that only one colour representation is used. Must be fast as a bullet.
context: Imager.Context ~ state.context;
a: ImagerColor.RGB ~ RGBFromColour [colour];
b: ImagerColor.RGB ~ RGBFromColour [state.previousColour];
IF (a.R # b.R) OR (a.G # b.G) OR (a.B # b.B) OR (state.previousColour = NIL) THEN BEGIN
Draw black using the grey colour model, so that it is placed in the black colour separation.
IF (a.R = 0.0) AND (a.G = 0.0) AND (a.B = 0.0) THEN context.SetColor [blackBlack]
ELSE context.SetColor [colour];
state.previousColour ← colour
END
END; -- ChangeColour
ChangeStrokeWidth: PROC [state: State, w: REAL] ~ INLINE BEGIN
w < 0 means that it has not yet been set in this body. Must be fast as a bullet.
context: Imager.Context ~ state.context;
IF (w < 0) OR (w # state.previousStrokeWidth) THEN BEGIN
context.SetStrokeWidth [w]; state.previousStrokeWidth ← w
END
END; -- ChangeStrokeWidth
ChangeFont: PROC [state: State, skFont: Font] ~ INLINE BEGIN
Must be fast as a bullet
context: Imager.Context ~ state.context;
IF (skFont # state.previousFont) THEN BEGIN
prFont: Font ← NARROW [fontMap.Fetch[skFont].val];
IF (prFont = NIL) THEN BEGIN
This is nasty code. The trade off was between sort of a hack, 20 minutes of CPU on full chips, and this fix here. I wanted to find all fonts appearing in the design in order to declare them in the preamble. The most stupid thing somebody could do, would be to use a ChipNDale drawProc to visit all text objects [this can take 20 minutes of CPU]. In fact you only need to visit once every object. In ChipNDale you do this by enumerating the directory. Unfortunately ChipNDale has one object which is never in the directory, namely the root cell. In order to avoid the unesthetic special code for the root, the fonts used in the root object are not cached. In the normal case, no fonts are missed, because if there are non-cell objects in the root then usually they have some buddy inside an official cell. So all this was to justify why the print font can be L and has to be handled here at this stange place. All in all I don't care, so if you want to do something different just go ahead (the quickest would be to bypass the ChipNDale directory enumeration procedure and to do your own; this is easy if you use my table called visitedCells).
prFont ← Mapping [skFont]; [] ← fontMap.Insert [skFont, prFont]
END;
context.SetFont [prFont]; state.previousFont ← skFont
END
END; -- ChangeFont
LogInterpress: Interpress.LogProc ~ BEGIN
Called for errors during interpress execution. [class: INT, code: ATOM, explanation: ROPE]
TerminalIO.WriteRope [SELECT class FROM
Interpress.classMasterError => "Master Error: ",
Interpress.classMasterWarning => "Master Warning: ",
Interpress.classAppearanceError => "Appearance Error: ",
Interpress.classAppearanceWarning => "Appearance Warning: ",
Interpress.classComment => "Comment: ",
ENDCASE
=> IO.PutFR1 ["Class %g error: ", IO.int [class]]];
TerminalIO.WriteRope [explanation]; TerminalIO.WriteRope [" . . . \n"]
END; -- LogInterpress
ProgressLog: PrintFileConvert.ProgressProc ~ BEGIN
[begin: BOOL, page: INT]
IF begin THEN TerminalIO.WriteRope [IO.PutFR1 ["[%g", IO.int [page]]]
ELSE TerminalIO.WriteRope ["] "]
END; -- ProgressLog
Color Blending
Data is global
Bitset: TYPE ~ PACKED ARRAY CD.Layer OF BOOLEANALL [FALSE];
BlendKey: TYPE ~ REF Bitset;
Blend: TYPE ~ REF BlendRec;
BlendRec: TYPE ~ RECORD [count: CD.Layer ← 0,
flavours: Bitset,
blend: Color ← black,
area: CARD ← 0];
colourTable: HashTable.Table ~ HashTable.Create [557, Match, Hash]; -- or 997
layerColorTable: ARRAY CD.Layer OF Color; -- must be set for each task
ColourTile: PROC [old: Blend, l: CD.Layer] RETURNS [new: Blend] ~ BEGIN
Sets up a blending record for a tile. Must be fast as a bullet.
key: BlendKey ~ NEW [Bitset ← old.flavours];
key[l] ← TRUE;
new ← NARROW [colourTable.Fetch[key].value, Blend];
IF (new = NIL) THEN BEGIN
copy: Blend ← NEW [BlendRec ← [flavours: old.flavours]];
copy.count ← SUCC [old.count];
copy.flavours[l] ← TRUE;
new ← copy;
IF NOT colourTable.Insert [key, copy] THEN ERROR
END
END; -- ColourTile
black: Color ~ ImagerColor.ColorFromRGB [[0.0, 0.0, 0.0]];
blackBlack: Color ~ Imager.black;
blue: Color ~ ImagerColor.ColorFromRGB [[0.0, 0.0, 1.0]];
unColour: ImagerColor.RGB ~ [0.0, 0.0, 0.0];
yellowRGB: ImagerColor.RGB ~ [1.0, 1.0, 0.0];
magentaRGB: ImagerColor.RGB ~ [1.0, 0.0, 1.0];
cyan: Color ~ ImagerColor.ColorFromRGB [[0.0, 1.0, 1.0]];
lightYellow: Color ~ ImagerColor.ColorFromRGB [[6.0/7.0, 6.0/7.0, 3.0/7.0]];
lightMagenta: Color ~ ImagerColor.ColorFromRGB [[5.0/7.0, 0.0, 5.0/7.0]];
BlendColours: HashTable.EachPairAction ~ BEGIN
Takes the layers covering a tile and blend an RBG-colour out of them.
[key: Key, value: Value] RETURNS [quit: BOOLEANFALSE]
components: Blend ~ NARROW [value];
n: REAL ← Float [components.count];
comp: PACKED ARRAY CD.Layer OF BOOLEAN ← components.flavours;
mix: ImagerColor.RGB ← unColour;
IF (components.blend # black) THEN RETURN [FALSE]; -- caching across sessions
SELECT n FROM
0 => ERROR; -- should never have been allocated
1 => BEGIN
i: CD.Layer ← 0;
WHILE NOT comp[i] DO i ← SUCC [i] ENDLOOP;
components.blend ← LayerColour [i]
END;
ENDCASE => BEGIN
poly, diff, met, met2, cut, cut2, well: CD.Layer ← 0;
Find exception layers.
FOR i: CD.Layer IN CD.Layer DO
IF comp[i] THEN SELECT CD.LayerKey[i] FROM
$pol => poly ← i;
$ndif, $pdif => diff ← i;
$met => met ← i;
$met2 => met2 ← i;
$nwel => well ← i;
$cut => cut ← i;
$cut2 => {cut ← i; cut2 ← i};
ENDCASE => NULL
ENDLOOP;
Cuts always win.
IF (cut # 0) THEN components.blend ← IF (cut2 # 0) THEN blue ELSE black
ELSE BEGIN-- Assume: all other colours have the same weight.
IF (poly # 0) AND (diff # 0) THEN BEGIN-- Handle gates.
Reinitialize mix by yellow and eliminate poly and diff.
mix ← yellowRGB; n ← n - 1.0;
comp[poly] ← comp[diff] ← FALSE
END;
IF (poly # 0) AND (met # 0) AND (diff = 0) THEN BEGIN-- Handle metal over poly.
mix ← magentaRGB; n ← n - 1.0;
comp[poly] ← comp[met] ← FALSE
END;
FOR i: CD.Layer IN CD.Layer DO-- Compute mean colour.
IF comp[i] THEN BEGIN
v: ImagerColor.RGB ~ RGBFromColour [LayerColour[i]];
mix.R ← mix.R + v.R; mix.G ← mix.G + v.G; mix.B ← mix.B + v.B
END
ENDLOOP;
IF (met2 # 0) THEN BEGIN-- make metal-2 transparent
mix.R ← mix.R - 4.0/7.0; mix.B ← mix.B - 4.0/7.0; n ← n - 4.0/7.0
END;
IF (well # 0) THEN BEGIN-- make wells transparent
mix.R ← mix.R - 5.0/7.0; mix.G ← mix.G - 5.0/7.0; n ← n - 5.0/7.0;
mix.B ← mix.B - (3.0/7.0 * 2.0/7.0) -- take out well lightener
END;
mix.R ← mix.R / n; mix.G ← mix.G / n; mix.B ← mix.B / n;
components.blend ← ImagerColor.ColorFromRGB [mix]
END
END;
RETURN [FALSE]
END; -- BlendColours
LayerColour: PROC [l: CD.Layer] RETURNS [Color] ~ INLINE BEGIN
Ensure that only one colour representation is used.
RETURN [layerColorTable[l]]
END; -- LayerColour
SetLayerColourTable: PROC ~ BEGIN
Spread out hues & make "more subtractive".
FOR i: CD.Layer IN CD.Layer DO
SELECT CD.LayerKey[i] FROM
$nwel => layerColorTable[i] ← lightYellow;
$met => layerColorTable[i] 𡤌yan;
$met2 => layerColorTable[i] ← lightMagenta;
ENDCASE => layerColorTable[i] ← CDColors.globalColors[bit8][normal].cols[i]
ENDLOOP
END; -- SetLayerColourTable
BlendAllColours: PROC ~ BEGIN
The logarithm of the number of colours is two, that of the rectangles is six.
[] ← colourTable.Pairs [BlendColours]
END; -- BlendAllColours
Hash: PROC [k: HashTable.Key] RETURNS [CARDINAL] ~ BEGIN
PROC [Key] RETURNS [CARDINAL]
TRUSTED BEGIN
RETURN [Checksum.ComputeChecksum [0, SIZE [BlendKey], LOOPHOLE [k]]]
END
END; -- Hash
Match: PROC [a, b: HashTable.Key] RETURNS [BOOL] ~ BEGIN
HashTable.EqualProc
k1: BlendKey ~ NARROW [a, BlendKey]; k2: BlendKey ~ NARROW [b, BlendKey];
RETURN [(k1^ = k2^)]
END; -- Match
Conversions
Data is global
fontMap: RefTab.Ref ~ RefTab.Create []; -- global
fontPrefix: ATOM ~ Atom.MakeAtom ["xerox/pressfonts/"];
visitedCells: RefTab.Ref;
DrawFilter: CD.DrawProc ~ BEGIN
[inst: Instance, pos: Position, orient: Orientation, pr: REF DrawInformation]
SELECT inst.ob.class.objectType FROM
$Text, $RigidText, $FlipText => FindFont [inst, pos, orient, pr];
$Cell =>
There is no facility offered by ChipNDale to visit only a cell without recursing to its subcells, so here is a homebrew hack.
IF visitedCells.Insert [inst.ob, $hack] THEN inst.ob.class.drawMe [inst, pos, orient, pr];
ENDCASE => NULL
END; -- DrawFilter
FindFont: CD.DrawProc ~ BEGIN
[inst: Instance, pos: Position, orient: Orientation, pr: REF DrawInformation]
text: CDTexts.TextPtr ~ NARROW [inst.ob.specificRef];
font: Font ~ text.cdFont.font;
IF NOT fontMap.Fetch[font].found THEN [] ← fontMap.Insert [font, Mapping [font]]
END; -- FindFont
UpdateFontMap: PROC [state: State] ~ BEGIN
The map is global, because fonts rarely change across designs.
tdr: CD.DrawRef ~ CD.CreateDrawRef [[]];
Evict: RefTab.EachPairAction ~ {[] ← visitedCells.Delete [key]; RETURN [FALSE]};
remindFontConventions ← TRUE;
tdr.drawChild ← DrawFilter; tdr.devicePrivate ← state;
IF state.selectedOnly THEN DrawSelection [state.design, tdr]
ELSE BEGIN
DrawDesign is an overkill, which easily takes 20 minutes on the CrossRAM. There is no facility offered by ChipNDale to visit only a cell without recursing to its subcells, so here is a homebrew hack.
visitedCells ← RefTab.Create [557];
EnumerateObjects [state.design, tdr];
[] ← visitedCells.Pairs [Evict]; visitedCells ← NIL
END
END; -- UpdateFontMap
Mapping: PROC [sk: Font] RETURNS [mr: Font] ~ BEGIN
Translates from strike to spline fonts.
shortFName, subDir, fullFName, family, attributes: Rope.ROPE;
cp: FS.ComponentPositions;
sizePos, facePos: INTEGER;
face: NodeStyle.FontFace ← Regular;
size: REAL;
[fullFName, cp] ← FS.ExpandName [name: sk.name];
subDir ← fullFName.Substr [start: cp.subDirs.start, len: cp.subDirs.length];
SELECT TRUE FROM
subDir.Equal ["Xerox>PressFonts", FALSE] => BEGIN
IF remindFontConventions THEN BEGIN
TerminalIO.WriteRope ["Nectarine takes care of all the font substitutions. You can use the strike fonts for the layout.\n"];
remindFontConventions ← FALSE
END;
mr ← sk
END;
subDir.Equal ["Xerox>TiogaFonts", FALSE] => BEGIN
Construct the old style name:
shortFName ← fullFName.Substr [start: cp.base.start, len: cp.base.length];
Find the size and face from the old style name:
sizePos ← shortFName.SkipTo [0, "0123456789"];
attributes ← shortFName.Substr [sizePos, shortFName.Length[]-sizePos];
facePos ← attributes.SkipTo [0, "bBiI"];
Compute the size (assume: there always is a size):
size ← (ORD [attributes.Fetch[0]] - ORD ['0]);
FOR i: INT IN [1 .. facePos) DO
size ← size * 10.0 + (ORD [attributes.Fetch[i]] - ORD ['0])
ENDLOOP;
Determine the face:
IF (facePos # attributes.Length[]) THEN BEGIN
it: BOOL ~ (attributes.SkipTo [0, "iI"] # attributes.Length[]);
b: BOOL ~ (attributes.SkipTo [0, "bB"] # attributes.Length[]);
SELECT TRUE FROM
it AND b => face ← BoldItalic;
it AND NOT b => face ← Italic;
NOT it AND b => face ← Bold;
ENDCASE => ERROR
END;
family ← shortFName.Substr [0, sizePos];
mr ← NodeStyleFont.FontFromStyleParams [prefix: fontPrefix, family: Atom.MakeAtom[family], face: face, size: size, alphabets: CapsAndLower];
mr ← ImagerFont.Modify [mr, sk.charToClient]
END;
ENDCASE => BEGIN
TerminalIO.WriteRope [Rope.Cat [fullFName, "font class", subDir, " unknown, not substituted.\n"]];
mr ← sk
END
END; -- Mapping
ImagerRect: PROC [r: CD.Rect] RETURNS [Imager.Rectangle] ~ BEGIN
base: CD.Position ~ CDBasics.BaseOfAnyRect [r];
size: CD.Position ~ CDBasics.SizeOfRect [r];
RETURN [[Float[base.x], Float[base.y], Float[size.x], Float[size.y]]]
END; -- ImagerRect
ImagerVec: PROC [p: CD.Position] RETURNS [Imager.VEC] ~ BEGIN
RETURN [[Float[p.x], Float[p.y]]]
END; -- ImagerVec
CdPos: PROC [v: Imager.VEC] RETURNS [CD.Position] ~ BEGIN
RETURN [[Round[v.x], Round[v.y]]]
END; -- CdPos
RGBFromColour: PROC [c: Color] RETURNS [rgb: ImagerColor.RGB] ~ INLINE BEGIN
Assume that LayerColour had previously been called.
WITH c SELECT FROM
constant: Imager.ConstantColor => BEGIN
rgb.R ← ImagerColorPrivate.ComponentFromColor [constant, $Red];
rgb.G ← ImagerColorPrivate.ComponentFromColor [constant, $Green];
rgb.B ← ImagerColorPrivate.ComponentFromColor [constant, $Blue]
END;
ENDCASE => rgb ← unColour;
RETURN [rgb]
END; -- RGBFromColour
ImportantMessage: PROC [msg: Rope.ROPE] ~ BEGIN
Writes a message in the ChipNDale terminal viewer and in the Message Window at the top of the LF screen and makes it blink.
TerminalIO.WriteRope [msg]; TerminalIO.WriteRope ["\n"];
MessageWindow.Clear []; MessageWindow.Append [msg]; MessageWindow.Blink []
END; -- ImportantMessage
Printing
PeachProcess: PROC [masterName, peachName: Rope.ROPE, printerKey: ATOM, copies: INT, sizeHint: REF Imager.Rectangle ← NIL] ~ BEGIN
This is a mess. The religion asks me to propagate the error I get from PeachPrint to my client. However, PeachPrint just sits there until all printing is done or aborts locking up ChipNDale. Therefore, until PeachPrint is not fixed and forks off a process by its own, I do it myself and swallow that bloody event.
If the image is in portrait format and the printer uses roll paper, the rectangle usedField from DoInterpress can be specified as a sizeHint to increase the scale so a to use the full roll width.
Note: the origin in PDParams refers to the field, not to the medium.
ENABLE {PeachPrint.PupAborted => GOTO failure};
serverName: Rope.ROPE;
deviceParameters: PrintFileConvert.PDParams;
scale: REAL;
FillSample: PROC [s: ImagerBrick.Brick, a, b, c, d: REAL] ~ BEGIN
Should be in Cedar.
IF (s.size # 4 )THEN ERROR;
s.sSize ← 2; s.fSize ← 2; s.phase ← 0; s.u ← 0; s.v ← 0;
s.samples[0] ← a; s.samples[1] ← b;
s.samples[2] ← c; s.samples[3] ← d
END; -- FillSample
TRUSTED {Process.SetPriority [Process.priorityBackground]};
SELECT printerKey FROM
$NVersatec => BEGIN
Roll paper.
deviceParameters ← PrintFileConvert.ParamsFromPrinterType [$versatec];
IF (sizeHint # NIL) AND (sizeHint.h > sizeHint.w) THEN BEGIN
scale (mmXin * deviceParameters.pageFSize) / sizeHint.w;
IF (scale * sizeHint.h > 2000) THEN
ImportantMessage [" Your plot will be longer than 2 metres."]
END
ELSE scale (mmXin * deviceParameters.pageFSize) / field.w;
serverName ← UserProfile.Token ["Nectarine.Versatec", "Sleepy"]
END;
$NColorVersatec, $NPeachExpand => BEGIN
Roll paper.
brickB: ImagerBrick.Brick ← NEW [ImagerBrick.BrickRep[4]];
brickC: ImagerBrick.Brick ← NEW [ImagerBrick.BrickRep[4]];
brickM: ImagerBrick.Brick ← NEW [ImagerBrick.BrickRep[4]];
brickY: ImagerBrick.Brick ← NEW [ImagerBrick.BrickRep[4]];
deviceParameters ← PrintFileConvert.ParamsFromPrinterType [$colorVersatec];
IF (sizeHint # NIL) AND (sizeHint.h > sizeHint.w) THEN BEGIN
scale (mmXin * deviceParameters.pageFSize) / sizeHint.w;
IF (scale * sizeHint.h > 2000) THEN
ImportantMessage [" Your plot will be longer than 2 metres."]
END
ELSE scale (mmXin * deviceParameters.pageFSize) / field.w;
serverName ← IF (printerKey = $NColorVersatec) THEN
UserProfile.Token ["Nectarine.ColorVersatec", "Sleepy"]
ELSE UserProfile.Token ["Nectarine.PeachExpand", "Kearsarge"];
FillSample [brickB, 0.6, 0.4, 0.2, 0.8];
FillSample [brickC, 0.2, 0.8, 0.6, 0.4];
FillSample [brickM, 0.6, 0.4, 0.2, 0.8];
FillSample [brickY, 0.42, 0.15, 0.7, 0.3]; -- delicate !
Use the following three statements to obtain very fast colors:
deviceParameters.bricks [black] ← brickB;
deviceParameters.bricks [cyan] ← brickC;
deviceParameters.bricks [magenta] ← brickM;
deviceParameters.bricks [yellow] ← brickY
Or use the following statement to obtain quality colors (rotated screens):
deviceParameters.ppd ← 2.0
END;
$NBw400 => BEGIN
Roll paper.
deviceParameters ← PrintFileConvert.ParamsFromPrinterType [$bw400];
scale ← (mmXin * deviceParameters.pageFSize) / field.w;
serverName ← UserProfile.Token ["Nectarine.Bw400", "MtFuji"]
END;
$NColor400 => BEGIN
Roll paper.
deviceParameters ← PrintFileConvert.ParamsFromPrinterType [$color400];
deviceParameters.ppd ← 4.0;
scale ← (mmXin * deviceParameters.pageFSize) / field.w;
serverName ← UserProfile.Token ["Nectarine.Color400", "MtFuji"]
END;
$NPlateMaker => BEGIN
Cut paper.
deviceParameters ← PrintFileConvert.ParamsFromPrinterType [$plateMaker];
deviceParameters.tonerUniverse ← LIST [black, cyan, magenta, yellow];
Implementation notice: because of some possible arcane bug in the compiler, if the scales in x and y would have been first defined either as contants or variables, the compiler would set them to the value 0.0. Therefore you should not change the next line.
scale ← MIN [(mmXin*deviceParameters.pageFSize)/field.w, (mmXin*deviceParameters.pageSSize)/field.h]
END;
$NRaven300 => serverName ← UserProfile.Token ["Nectarine.Raven300", "Quoth"];
ENDCASE => NULL; -- can never happen
The adjustement of the field to the left end of the printer head has to be scaled, because the field is included in the coordinates in the Interpress master.
SELECT printerKey FROM
$NRaven300 => NULL;
$NPlateMaker => BEGIN
tx: REAL ~ - (field.x * scale) / mmXin;
ty: REAL ~ - (field.y * scale) / mmXin;
separation: Rope.ROPE ← peachName.Cat ["-Black"];
deviceParameters.toners ← LIST [black];
PrintFileConvert.InterpressToPD [inputName: masterName,
outputName: separation,
params: deviceParameters,
sx: scale, sy: scale, tx: tx, ty: ty,
logProc: LogInterpress, progressProc: ProgressLog];
TerminalIO.WriteRope [Rope.Cat ["Black separation written on ", separation, "\n"]];
separation ← peachName.Cat ["-Cyan"];
deviceParameters.toners ← LIST [cyan];
PrintFileConvert.InterpressToPD [inputName: masterName,
outputName: separation,
params: deviceParameters,
sx: scale, sy: scale, tx: tx, ty: ty,
logProc: LogInterpress, progressProc: ProgressLog];
TerminalIO.WriteRope [Rope.Cat ["Cyan separation written on ", separation, "\n"]];
separation ← peachName.Cat ["-Magenta"];
deviceParameters.toners ← LIST [magenta];
PrintFileConvert.InterpressToPD [inputName: masterName,
outputName: separation,
params: deviceParameters,
sx: scale, sy: scale, tx: tx, ty: ty,
logProc: LogInterpress, progressProc: ProgressLog];
TerminalIO.WriteRope [Rope.Cat ["Magenta separation written on ", separation, "\n"]];
separation ← peachName.Cat ["-Yellow"];
deviceParameters.toners ← LIST [yellow];
PrintFileConvert.InterpressToPD [inputName: masterName,
outputName: separation,
params: deviceParameters,
sx: scale, sy: scale, tx: tx, ty: ty,
logProc: LogInterpress, progressProc: ProgressLog];
TerminalIO.WriteRope [Rope.Cat ["Yellow separation written on ", separation, "\n"]];
TerminalIO.WriteRope ["Copy these files onto [Indigo]<Platemaker>your name> and message the operator <ELarson.PA>. Please advise him that the separations make use of the full device size.\n\n"]
END;
ENDCASE => BEGIN
PrintFileConvert.InterpressToPD [inputName: masterName,
outputName: peachName,
params: deviceParameters,
sx: scale, sy: scale,
tx: - (field.x * scale) / mmXin, ty: - (field.y * scale) / mmXin,
logProc: LogInterpress, progressProc: ProgressLog];
The keep of PD files is 1. Unfortunately PeachPrint does not lock a file between the time it posts a request and the request is served. This can not only cause the files to get lost, but it may even hang some print servers. The solution implemented here, i.e., changing the keep to 3, it to be considered as a mild temporary hack until a proper solution becomes available in 3 weeks from the time these lines are written.
FS.SetKeep [peachName, 3]
Calling FS.SetKeep will have the side effect of deleting files that are obsolete relative to the new keep, even when the keep is not changed.
END;
IF (printerKey # $NPlateMaker) THEN BEGIN
TerminalIO.WriteRope [Rope.Cat ["Sending ", peachName, " to ", serverName, "\n."]];
PeachPrint.DoPeachPrintCommand [serverName, peachName, TerminalIO.TOS[], FALSE, copies]
END;
IF (printerKey = $NPeachExpand) THEN BEGIN
pos: FS.ComponentPositions ~ FS.ExpandName[peachName].cp;
server2Name: Rope.ROPE ~ UserProfile.Token ["Nectarine.ExpandedPeach", "Sleepy"];
simpleName: Rope.ROPE ~ peachName.Substr [pos.base.start, pos.base.length];
peachName ← Rope.Cat ["[", serverName, "]<Cedar>PD>", simpleName, "-1.PD"];
TerminalIO.WriteRope [Rope.Cat ["Sending ", peachName, " to ", server2Name, "\n."]];
PeachPrint.DoPeachPrintCommand [server2Name, peachName, TerminalIO.TOS[], FALSE, copies]
END;
EXITS
failure => BEGIN
ImportantMessage [Rope.Cat ["Peach communications failure. File saved on ", peachName, " for manual retry."]];
SIGNAL communicationsFailure
END
END; -- PeachProcess
Print: PUBLIC PROC [masterName: Rope.ROPE, printerKey: ATOM, copies: INT ← 1, sizeHint: REF Imager.Rectangle ← NIL] RETURNS [peachName: Rope.ROPE] ~ BEGIN
Produces (if necessary) a PD file from an Interpress master and ships it to the printer. Valid printer keys are $NVersatec, $NColorVersatec, $NPeachExpand, $NBw400, $NColor400, $NPlateMaker, $NRaven300. May return signals invalidPrinter.
If the image is in portrait format and the printer uses roll paper, the rectangle usedField from DoInterpress can be specified as a sizeHint to increase the scale so a to use the full roll width.
IF (masterName = NIL) THEN TerminalIO.WriteRope ["Produce an Interpress master first.\n"]
ELSE BEGIN
pos: FS.ComponentPositions ~ FS.ExpandName[masterName].cp;
SELECT printerKey FROM
$NVersatec, $NColorVersatec, $NBw400, $NColor400, $NPeachExpand, $NPlateMaker =>
peachName ← masterName.Replace [pos.ext.start, pos.ext.length, "PD"];
$NRaven300 => peachName ← masterName; -- understands Interpress
ENDCASE => SIGNAL invalidPrinter;
Process.CheckForAbort [];
TRUSTED BEGIN
Process.Detach [FORK PeachProcess [masterName, peachName, printerKey, copies, sizeHint]]
END;
TerminalIO.WriteRope ["Peach printing process forked off.\n"]
END
END; -- Print
Statistics
GetStatisticsToggle: UserProfile.ProfileChangedProc ~ BEGIN
statistics ← UserProfile.Boolean [key: "Nectarine.Statistics"]
END;
ListColourTable: PROC [state: State] ~ BEGIN
Lists the statistics on the colour table.
totalArea: REAL ← 0.0; -- It is the way it is because of numerical stability.
ComputeArea: HashTable.EachPairAction ~ BEGIN
data: Blend ~ NARROW [value];
totalArea ← totalArea + Float [data.area]
END; -- ComputeArea
ListEntry: HashTable.EachPairAction ~ BEGIN
data: Blend ~ NARROW [value];
comp: ImagerColor.RGB ~ RGBFromColour [data.blend];
IF totalArea = 0.0 THEN totalArea ← 1.0;
TerminalIO.WriteF ["%g\t%g\t%g\t\t%g\t", IO.real[comp.R], IO.real[comp.G], IO.real[comp.B], IO.real[Float[data.area]/totalArea]];
FOR i: CD.Layer IN CD.Layer DO
IF data.flavours[i] THEN TerminalIO.WriteF [" %g", IO.atom [CD.LayerKey[i]]]
ENDLOOP;
TerminalIO.WriteRope ["\n"]
END; -- ListEntry
TerminalIO.WriteF ["Statistical data gathered by Nectarine:\n\tSize of color table: %g; number of tiles: %g\n\tColor (R, G, B), relative area and layers\n", IO.int [colourTable.GetSize[]], IO.int [state.cardTile]];
[] ← colourTable.Pairs [ComputeArea];
IF debug THEN BEGIN
[] ← colourTable.Pairs [ListEntry];
TerminalIO.WriteRope ["Colors with null area were intermediate.\n"]
END
END; -- ListColourTable
CleanColourTable: PROC [state: State] ~ BEGIN
We initialize only the area so as to cache colour blendings across sessions.
ResetArea: HashTable.EachPairAction ~ {rec: Blend ~ NARROW [value]; rec.area ← 0};
[] ← colourTable.Pairs [ResetArea]; state.cardTile ← 0
END; -- CleanColourTable
TimeToRope: PROC [from, to: BasicTime.GMT] RETURNS [time: Rope.ROPE] ~ BEGIN
Although the INT returned BasicTime.Period is the same as GMT and might hence be loopholed to use IO.time from Conversions, the latter uses BasicTime.Unpack which allows only values that give a valid date.
tmp: Rope.ROPE;
sec: INT = BasicTime.Period [from, to];
min: INT = sec / 60;
h: INT = min / 60;
tmp ← IO.PutFR1 [value: IO.int [h]];
time ← SELECT h FROM
= 0 => "00",
< 10 => Rope.Cat ["0", tmp],
ENDCASE => tmp;
tmp ← IO.PutFR1 [value: IO.int [min MOD 60]];
time ← Rope.Cat [time, ":", SELECT min FROM
= 0 => "00",
< 10 => Rope.Cat ["0", tmp],
ENDCASE => tmp];
tmp ← IO.PutFR1 [value: IO.int [sec MOD 60]];
time ← Rope.Cat [time, ":", SELECT sec FROM
= 0 => "00",
< 10 => Rope.Cat ["0", tmp],
ENDCASE => tmp]
END; -- TimeToRope
Initialization
UserProfile.CallWhenProfileChanges [GetStatisticsToggle]
END.
gbb September 4, 1986 1:49:44 pm PDT
Made a change to avoid anti-aliasing. All rectangles are now printed with the lower left corner starting on a pixel.
changes to: DIRECTORY, PrintAlignedTile, MaskAlignedRectangle (local of PrintAlignedTile), PrintTile, PrintBand, EnumerateGeometry (local of PrintBand)
gbb September 4, 1986 5:51:47 pm PDT
Changed the border of cells from a sequence of rectangles to a trajectory.
changes to: DIRECTORY, IMPORTS, DrawObjectBorder, DotDotDotSpace (local of DrawObjectBorder), DrawObject
gbb September 5, 1986 11:09:19 am PDT
Added the parameter sizeHint to the print procedure.
changes to: PeachProcess, Print
gbb September 19, 1986 7:14:38 pm PDT
Black toner for color Versatec had gone lost somewhen.
changes to: PeachProcess
gbb September 22, 1986 3:26:33 pm PDT
Added capability to produce tone separations for PlateMaker.
changes to: DIRECTORY, PeachProcess, Print
gbb September 23, 1986 6:04:17 pm PDT
Addded filled trajectories.
changes to: DIRECTORY, DrawArea, DrawObjectBorder, DrawObject
gbb September 24, 1986 3:47:17 pm PDT
Fixed scaling for devices with cut paper. Changed colour model used to print black. Added 1 to the height of bands.
changes to: DIRECTORY, DrawToIP (local of DoInterpress), black, unColour, BlendColours, PeachProcess.
gbb September 25, 1986 8:00:34 pm PDT
Bypassed an arcane bug in the compiler.
changes to: PeachProcess: see implementation note.
gbb October 2, 1986 1:01:19 pm PDT
Introduced a mild temporary hack to fix a server locking problem until the new print server software becomes available in about three to four weeks.
Finished adjusting and testing all the little details to print on Platemaker at 1200 lines per inch resolution. Instructions are written in the Terminal viewer. When submitting the separations, please advise the operator that the separations make use of the full device size.
changes to: Action (local of DoInterpress), PeachProcess
gbb October 7, 1986 3:36:11 pm PDT
Tweeking the rendition of colours on the Versatec.
changes to: PeachProcess
gbb October 9, 1986 1:47:12 pm PDT
Handle gracefully the limit of 50 entries in the Interpress frame.
changes to: DIRECTORY, Action (local of DoInterpress)