~
BEGIN
testPattern: ARRAY [0..10) OF REAL ← ALL[10.0];
TestPattern: PROC [i: INT] RETURNS [REAL] ~ {RETURN [testPattern[i]]};
TestPath: PathProc ~ {
moveTo[[100, 100]];
lineTo[[100, 200]];
lineTo[[200, 100]];
arcTo[[200, 200],[100, 200]];
};
Context: TYPE ~ Imager.Context;
PathProc: TYPE ~ ImagerPath.PathProc;
Bezier:
TYPE ~
ARRAY [0..4)
OF
VEC;
Add:
PROC [v1, v2:
VEC]
RETURNS [
VEC] ~ {
RETURN[[v1.x+v2.x, v1.y+v2.y]] };
Sub:
PROC [v1, v2:
VEC]
RETURNS [
VEC] ~ {
RETURN[[v1.x-v2.x, v1.y-v2.y]] };
RealPlusOrMinus:
TYPE ~
RECORD [value:
REAL, error:
REAL];
nPts: NAT ← 6;
AverageSpeed:
PROC [p: Bezier]
RETURNS [RealPlusOrMinus] ~ {
Computes an approximation to the average speed of the point moving along the curve as a function of its parameter, along with estimated error bounds. Since the curve is parameterized from zero to one, its average speed is numerically equal to its arc length.
nReal: REAL ~ nPts;
min: REAL ← Real.LargestNumber;
max: REAL ← 0;
delta:
ARRAY [0..3)
OF
VEC ~ [Sub[p[1], p[0]], Sub[p[2], p[1]], Sub[p[3], p[2]]];
The deltas form a Bezier description of the velocity profile. We want its average magnitude.
FOR i:
NAT
IN [0..nPts]
DO
t: REAL ~ i/nReal;
s: REAL ~ 1.0-t;
d01: VEC ← Add[Vector2.Mul[delta[0], t], Vector2.Mul[delta[1], s]];
d12: VEC ← Add[Vector2.Mul[delta[1], t], Vector2.Mul[delta[2], s]];
d012: VEC ← Add[Vector2.Mul[d01, t], Vector2.Mul[d12, s]];
sqr: REAL ~ Vector2.Square[d012];
IF sqr > max THEN max ← sqr;
IF sqr < min THEN min ← sqr;
ENDLOOP;
max ← RealFns.SqRt[max];
min ← RealFns.SqRt[min];
RETURN [[(max+min)*1.5, (max-min)*1.5]]
};
sigBits: NAT ← 7;
DivideUntilSmooth:
PROC [p: Bezier, piece:
PROC [p: Bezier, speed:
REAL], fullScale:
REAL] ~ {
a: RealPlusOrMinus ← AverageSpeed[p];
UNTIL Real.FScale[a.error, sigBits] <= fullScale
DO
Mid: PROC [a,b: VEC] RETURNS [VEC] ~ INLINE {RETURN [[Real.FScale[a.x+b.x, -1], Real.FScale[a.y+b.y, -1]]]};
p01: VEC ~ Mid[p[0],p[1]];
p12: VEC ~ Mid[p[1],p[2]];
p23: VEC ~ Mid[p[2],p[3]];
p012: VEC ~ Mid[p01,p12];
p123: VEC ~ Mid[p12,p23];
p0123: VEC ~ Mid[p012,p123];
DivideUntilSmooth[[p[0], p01, p012, p0123], piece, fullScale];
p ← [p0123, p123, p23, p[3]];
a ← AverageSpeed[p];
ENDLOOP;
piece[p, a.value];
};
SubDivide:
PROC [b: Bezier, t:
REAL, hi:
BOOL]
RETURNS [Bezier] ~ {
s: REAL ~ 1-t;
Interpolate:
PROC [a, b:
VEC]
RETURNS [
VEC] ~
INLINE {
RETURN [[a.x*s+b.x*t, a.y*s+b.y*t]]
};
q1 : VEC ~ Interpolate[b[0], b[1]];
q2: VEC ~ Interpolate[b[1], b[2]];
q3: VEC ~ Interpolate[b[2], b[3]];
qp1: VEC ~ Interpolate[q1, q2];
qp2: VEC ~ Interpolate[q2, q3];
q: VEC ~ Interpolate[qp1, qp2];
RETURN [IF hi THEN [q, qp2, q3, b[3]] ELSE [b[0], q1, qp1, q]]
};
SubPiece:
PROC [b: Bezier, t0, t1:
REAL]
RETURNS [Bezier] ~ {
IF t1 # 1.0
THEN {
b ← SubDivide[b, t1, FALSE];
t0 ← t0/t1;
t1 ← 1;
};
IF t0 # 0.0 THEN b ← SubDivide[b, t0, TRUE];
RETURN [b];
};
MeasurePath:
PUBLIC
PROC [path: PathProc]
RETURNS [sum:
REAL ← 0.0] ~ {
lp: VEC ← [0,0];
move:
PROC [p0:
VEC] ~ {
lp ← p0;
};
line:
PROC [p1:
VEC] ~ {
sum ← sum + Vector2.Length[Sub[p1, lp]];
lp ← p1;
};
piece:
PROC [p: Bezier, speed:
REAL] ~ {
sum ← sum + speed;
};
curve:
PROC [p1, p2, p3:
VEC] ~
TRUSTED {
p: Bezier ~ [lp, p1, p2, p3];
fullScale: REAL ~ AverageSpeed[p].value;
IF fullScale > 0.0 THEN DivideUntilSmooth[p, piece, fullScale];
lp ← p3;
};
conic:
PROC [p1, p2:
VEC, r:
REAL] ~ {
ImagerPath.ConicToCurves[lp, p1, p2, r, curve]
};
arc: PROC [p1, p2: VEC] ~ {ImagerPath.ArcToConics[lp, p1, p2, conic]};
path[moveTo: move, lineTo: line, curveTo: curve, conicTo: conic, arcTo: arc];
};
MaskDashedStroke:
PUBLIC
PROC [context: Context, path: PathProc, patternSize:
INT, pattern:
PROC [i:
INT]
RETURNS [
REAL], offset:
REAL, length:
REAL ← 0.0] ~ {
pathUnits: REAL ~ MeasurePath[path];
stretch: REAL ~ IF length = 0.0 OR pathUnits = 0.0 THEN 1.0 ELSE pathUnits/length;
lp: VEC ← [0,0];
index: INT ← 0; -- index of currently active pattern element.
residual: REAL ← pattern[0]*stretch; -- remaining part of current pattern element, in master units.
used: REAL ← 0.0; -- amount of pattern used, in master units.
on: BOOL ← TRUE;
Advance:
PROC [patternOffset:
REAL, startAction, stopAction:
PROC ←
NIL] ~ {
UNTIL used >= patternOffset
DO
IF residual > 0.0
THEN {
Still have part of the current piece to use up.
IF used+residual <= patternOffset
THEN {used ← used+residual; residual ← 0.0}
ELSE {residual ← used+residual - patternOffset; used ← patternOffset};
}
ELSE {
The current piece is all used up; go on to the next.
IF on AND stopAction#NIL THEN stopAction[];
index ← index + 1; IF index = patternSize THEN index ← 0;
residual ← pattern[index]*stretch;
on ← NOT on;
IF on AND startAction#NIL THEN startAction[];
};
ENDLOOP;
};
DashedPath: PathProc ~ {
move: PROC [p0: VEC] ~ {lp ← p0; IF on THEN moveTo[lp]};
line:
PROC [p1:
VEC] ~ {
delta: VEC ~ Vector2.Sub[p1, lp];
d: REAL ~ Vector2.Length[delta];
segmentStart: REAL ~ used;
segmentEnd: REAL ~ used + d;
start:
PROC ~ {
s: REAL ← (used-segmentStart)/d;
moveTo[Vector2.Add[lp, Vector2.Mul[delta, s]]];
};
stop:
PROC ~ {
s: REAL ← (used-segmentStart)/d;
lineTo[Vector2.Add[lp, Vector2.Mul[delta, s]]];
};
Advance[segmentEnd, start, stop];
IF on THEN lineTo[p1];
lp ← p1;
};
curve:
PROC [p1, p2, p3:
VEC] ~ {
piece:
PROC [p: Bezier, speed:
REAL] ~ {
segmentStart: REAL ~ used;
segmentEnd: REAL ~ used + speed;
dashStartParam: REAL ← 0;
needMove: BOOL ← FALSE;
start:
PROC ~ {
dashStartParam ← (used-segmentStart)/speed;
needMove ← TRUE;
};
stop:
PROC ~ {
dashEndParam: REAL ← (used-segmentStart)/speed;
b: Bezier ← SubPiece[p, dashStartParam, dashEndParam];
IF needMove THEN moveTo[b[0]];
curveTo[b[1], b[2], b[3]];
needMove ← FALSE;
};
Advance[segmentEnd, start, stop];
IF on THEN stop[];
};
p: Bezier ~ [lp, p1, p2, p3];
fullScale: REAL ~ AverageSpeed[p].value;
IF fullScale > 0.0 THEN DivideUntilSmooth[p, piece, fullScale];
lp ← p3;
};
conic:
PROC [p1, p2:
VEC, r:
REAL] ~ {
ImagerPath.ConicToCurves[lp, p1, p2, r, curve]
};
arc: PROC [p1, p2: VEC] ~ {ImagerPath.ArcToConics[lp, p1, p2, conic]};
path[moveTo: move, lineTo: line, curveTo: curve, conicTo: conic, arcTo: arc];
};
patternTotal: REAL ← 0.0;
FOR i:
INT
IN [0..patternSize)
DO
p: REAL ← pattern[i];
IF p < 0 THEN ERROR;
patternTotal ← patternTotal + pattern[i];
ENDLOOP;
IF patternTotal <= 0 THEN ERROR;
WHILE offset < 0 DO offset ← offset + 2*patternTotal ENDLOOP;
Advance[offset*stretch];
Imager.MaskStroke[context, DashedPath];
};