<<>> <> <> <> <> <> <> <<>> DIRECTORY Basics, ImagerManhattan USING [CreateFromBoxes], ImagerPath USING [ConicToProc, CurveToProc, LineToProc, MoveToProc, PathProc, Transform], ImagerSample USING [EdgeAction, FillBoxes, SampleMap], ImagerScanConverter USING [], ImagerScanConverterExtras USING [], ImagerScanConverterPrivate USING [Area, AreaRep, AreaState, Edge, EdgeRep, Edges, ExternalHalfPlane, Pair, RealBox, Region], ImagerTransformation USING [Transformation], RealFns USING [SqRt], RealInline, ImagerScaled, SF USING [Box, BoxAction, BoxGenerator, Intersect, maxBox, Nonempty], Vector2 USING [VEC]; ImagerScanConverterImpl: CEDAR MONITOR IMPORTS Basics, ImagerManhattan, ImagerPath, RealFns, RealInline, ImagerScaled, SF, ImagerSample EXPORTS ImagerScanConverter, ImagerScanConverterExtras ~ BEGIN OPEN ImagerScanConverterPrivate; PathProc: TYPE ~ ImagerPath.PathProc; Transformation: TYPE ~ ImagerTransformation.Transformation; VEC: TYPE ~ Vector2.VEC; checking: BOOL ¬ TRUE; <> <> RealAbsDif: PROC [x, y: REAL] RETURNS [REAL] = INLINE { RETURN [RealInline.Abs[x - y]]; }; flatHack: REAL ¬ 1.0; <> SetFlatHack: PROC [millis: INT] = { IF millis IN [0..10000] THEN flatHack ¬ millis * 0.001; }; SetScanConversionMode: PUBLIC PROC [devicePath: DevicePath, mode: INTEGER] ~ { devicePath.mode ¬ mode }; Union: PROC [a, b: Region] RETURNS [Region] ~ INLINE { RETURN [LOOPHOLE[Basics.BITOR[LOOPHOLE[a], LOOPHOLE[b]]]]; }; Odd: PROC [i: INTEGER] RETURNS [BOOL] ~ INLINE { RETURN [VAL[LOOPHOLE[i, CARDINAL] MOD 2]] }; IEEEFloat: TYPE = MACHINE DEPENDENT RECORD [ signAndExpt: [0..200H), fraction: [0..CARD32.LAST/200H] ]; hiddenBit: CARD32 ~ LOOPHOLE[IEEEFloat[signAndExpt: 1, fraction: 0]]; Round: PROC [r: REAL] RETURNS [INT] ~ { <> <> f: IEEEFloat ~ LOOPHOLE[r]; c: CARDINAL ~ LOOPHOLE[149 - f.signAndExpt]; Valid: TYPE ~ [0..31); IF c < 31 THEN { k: Valid ~ LOOPHOLE[c, Valid]; RETURN LOOPHOLE[ Basics.BITRSHIFT[hiddenBit + f.fraction + Basics.BITLSHIFT[1, k], k+1] ] }; IF LOOPHOLE[r, CARD] > 0 THEN RETURN [0]; <> RETURN [RealInline.MCFloor[r+0.5]] -- Note this would give the wrong answer for r = 0.5-ulp, because adding 0.5 causes a roundup. }; ScaledFromReal: PROC [r: REAL] RETURNS [SCALED] ~ { Valid: TYPE ~ [0..31); f: IEEEFloat ~ LOOPHOLE[r]; c: CARDINAL ¬ LOOPHOLE[(f.signAndExpt + 16) - 150]; IF c < 8 THEN { RETURN LOOPHOLE[Basics.BITLSHIFT[hiddenBit + f.fraction, LOOPHOLE[c, Valid]]] }; c ¬ Basics.BITNOT[c]; IF c < 31 THEN { k: Valid ~ LOOPHOLE[c, Valid]; RETURN LOOPHOLE[ Basics.BITRSHIFT[hiddenBit + f.fraction + Basics.BITLSHIFT[1, k], k+1]] }; IF LOOPHOLE[r, INT] >= 0 THEN RETURN LOOPHOLE[0]; RETURN LOOPHOLE[RealInline.MCFloor[r*65536.0+0.5]] }; scratch1: Area ¬ NIL; scratch2: Area ¬ NIL; defaultTolerance: REAL ¬ 0.5; dLimit: INTEGER ¬ 15; depthExceededCounter: CARD ¬ 0; nonStop: BOOL ¬ FALSE; -- set to TRUE to get old (possibly runaway) behavior. DepthExceeded: PROC [stop: BOOL ¬ TRUE] RETURNS [BOOL] ~ { depthExceededCounter ¬ depthExceededCounter + 1; -- Set break here to tinker. RETURN [stop AND NOT nonStop] }; TooDeep: PROC [limit: INTEGER] RETURNS [BOOL] ~ INLINE { RETURN[IF limit > 0 THEN FALSE ELSE DepthExceeded[]]; }; defaultMode: INTEGER ¬ 0; Create: PUBLIC ENTRY PROC RETURNS [DevicePath] ~ { area: Area ¬ NIL; SELECT TRUE FROM scratch1 # NIL => {area ¬ scratch1; scratch1 ¬ NIL}; scratch2 # NIL => {area ¬ scratch2; scratch2 ¬ NIL}; ENDCASE => { area ¬ NEW[AreaRep ¬ [ bounds: [], realBounds: [], tightBounds: [], state: nil, moveCount: 0, firstPt: [0, 0], firstRegion: ALL[FALSE], lastPt: [0, 0], lastRegion: ALL[FALSE], totalCrossings: 0, increasingEdges: NIL, decreasingEdges: NIL, freeEdges: NIL, mode: defaultMode, tolerance: defaultTolerance, cLimit: dLimit, pLimit: dLimit ]]; }; area.tolerance ¬ defaultTolerance; <> RETURN [area] }; Destroy: PUBLIC ENTRY PROC [devicePath: DevicePath] ~ { IF devicePath # NIL THEN { devicePath.mode ¬ defaultMode; devicePath.tolerance ¬ defaultTolerance; devicePath.cLimit ¬ dLimit; devicePath.pLimit ¬ dLimit; devicePath.increasingEdges ¬ FreeEdges[devicePath, devicePath.increasingEdges]; devicePath.decreasingEdges ¬ FreeEdges[devicePath, devicePath.decreasingEdges]; SELECT TRUE FROM scratch1 = NIL => {scratch1 ¬ devicePath}; scratch2 = NIL => {scratch2 ¬ devicePath}; ENDCASE => NULL; }; }; FreeEdges: PROC [area: Area, edges: Edges] RETURNS [nil: Edges ¬ NIL] ~ { IF edges # NIL THEN { last: Edges ¬ edges; DO last.f0 ¬ ImagerScaled.zero; last.df ¬ ImagerScaled.zero; IF last.link = NIL THEN EXIT ELSE last ¬ last.link; ENDLOOP; last.link ¬ area.freeEdges; area.freeEdges ¬ edges; }; }; SetBounds: PUBLIC PROC [devicePath: DevicePath, box: SF.Box] ~ { area: Area ~ devicePath; IF box.min.s < -32767 THEN box.min.s ¬ -32767; IF box.max.s < box.min.s THEN box.max.s ¬ box.min.s; IF box.max.s > 32767 THEN box.max.s ¬ 32767; IF box.min.f < -32767 THEN box.min.f ¬ -32767; IF box.max.f < box.min.f THEN box.max.f ¬ box.min.f; IF box.max.f > 32767 THEN box.max.f ¬ 32767; area.bounds ¬ box; area.realBounds ¬ [min: [s: box.min.s, f: box.min.f], max: [s: box.max.s, f: box.max.f]]; area.tightBounds ¬ [min: [s: LAST[INTEGER], f: LAST[INTEGER]], max: [s: FIRST[INTEGER], f: FIRST[INTEGER]]]; area.state ¬ awaitMove; area.moveCount ¬ 0; area.firstPt ¬ [0, 0]; area.lastPt ¬ [0, 0]; area.totalCrossings ¬ 0; area.increasingEdges ¬ FreeEdges[area, area.increasingEdges]; area.decreasingEdges ¬ FreeEdges[area, area.decreasingEdges]; area.cLimit ¬ dLimit; area.pLimit ¬ dLimit; }; RegionOf: PROC [area: Area, pt: Pair] RETURNS [Region] ~ INLINE { region: Region ~ [ sLo: pt.s < area.realBounds.min.s, sHi: pt.s > area.realBounds.max.s, fLo: pt.f < area.realBounds.min.f, fHi: pt.f > area.realBounds.max.f ]; RETURN [region] }; MoveTo: PUBLIC PROC [devicePath: DevicePath, pt: Pair] ~ { area: Area ~ devicePath; Close[area]; area.firstPt ¬ area.lastPt ¬ pt; area.firstRegion ¬ area.lastRegion ¬ RegionOf[area, pt]; area.state ¬ awaitLine; area.moveCount ¬ area.moveCount + 1; }; Close: PROC [area: Area] ~ { IF area.state = awaitLine THEN LineTo[area, area.firstPt]; area.state ¬ awaitMove; }; LineTo: PUBLIC PROC [devicePath: DevicePath, pt: Pair] ~ { InLineTo[devicePath, pt] }; InLineTo: PROC [area: Area, pt: Pair] ~ INLINE { <> IF area.lastRegion = ALL[FALSE] AND pt.s >= area.realBounds.min.s AND pt.s <= area.realBounds.max.s AND pt.f >= area.realBounds.min.f AND pt.f <= area.realBounds.max.f THEN { <> AppendSeg[area, area.lastPt, pt]; area.lastPt ¬ pt; } ELSE { region: Region ~ RegionOf[area, pt]; ClipSeg[area: area, p0: area.lastPt, r0: area.lastRegion, p1: pt, r1: region]; area.lastPt ¬ pt; area.lastRegion ¬ region; }; }; Half: PROC [r: REAL] RETURNS [REAL] ~ INLINE { RETURN [r*0.5] }; Mid: PROC [p, q: Pair] RETURNS [Pair] ~ INLINE { RETURN [[Half[p.s+q.s], Half[p.f+q.f]]]; }; maxd: NAT ~ 16; -- power of two for fast MOD optRatio: REAL ~ 0.5857864; -- area ratio between optimized two-segment approximation and naive two-segment approximation Tolerance: PROC [i: INT] RETURNS [old: INT] ~ { old ¬ Round[defaultTolerance*100.0]; defaultTolerance ¬ i/100.0; }; ParToDepth: PROC [area: Area, p0, p1, p2: Pair] RETURNS [[0..maxd]] ~ { <> s01: REAL ~ p0.s-p1.s; f01: REAL ~ p0.f-p1.f; s21: REAL ~ p2.s-p1.s; f21: REAL ~ p2.f-p1.f; i: CARDINAL ¬ 0; a: REAL ¬ ABS[s01*f21-f01*s21]; -- twice the area of control triangle budget: REAL ¬ area.tolerance * RealInline.AbsMax[p2.s-p0.s, p2.f-p0.f]; IF budget < 0.25 THEN budget ¬ 0.25; -- A minimum budget, for real small stuff. IF a > budget THEN { i ¬ i + 1; a ¬ a * (0.25 * optRatio); }; WHILE a > budget AND i < maxd DO i ¬ i + 1; a ¬ a * 0.25; ENDLOOP; IF i < 4 AND s01*s21 + f01*f21 > 0.0 THEN { <<-- dot product indicates angle is acute - do at least 16 segments in this (probably rare) case>> i ¬ 4; }; RETURN [i] }; ParTo: PUBLIC PROC [devicePath: DevicePath, p1, p2: Pair] ~ { area: Area ~ devicePath; p0: Pair ¬ area.lastPt; stack: ARRAY [0..maxd) OF ARRAY [0..1] OF Pair; n: ARRAY [0..maxd) OF CARDINAL; -- This array could be eliminated by working out the right invariants. i: CARDINAL[0..maxd) ¬ 1; r0: Region ~ area.lastRegion; r1: Region ~ RegionOf[area, p1]; r2: Region ~ RegionOf[area, p2]; allVisible: BOOLEAN ~ Union[Union[r0, r1], r2] = ALL[FALSE]; n[0] ¬ ParToDepth[area, p0, p1, p2]; IF n[0] = 0 OR (area.lastRegion # ALL[FALSE] AND r1 = area.lastRegion AND r2 = area.lastRegion) THEN { <> IF allVisible THEN { AppendSeg[area, p0, p2]; area.lastPt ¬ p2; } ELSE { LineTo[area, p2] }; RETURN; }; stack[0] ¬ [p2, p1]; WHILE i # 0 DO i ¬ (CARDINAL[i] - 1) MOD maxd; -- note: the mod is present merely to skip bounds checks; it should never have an effect. p2 ¬ stack[i][0]; p1 ¬ stack[i][1]; IF n[i] = 1 THEN { <> w02: REAL ~ 0.1767766953; w1: REAL ~ 0.6464466094; pw: Pair ~ [ s: (p0.s+p2.s)*w02 + p1.s*w1, f: (p0.f+p2.f)*w02 + p1.f*w1]; IF allVisible THEN { AppendSeg[area, p0, pw]; AppendSeg[area, pw, p2]; area.lastPt ¬ p2; } ELSE { LineTo[area, pw]; LineTo[area, p2] }; p0 ¬ p2; } ELSE { p10: Pair ~ Mid[p0, p1]; p12: Pair ~ Mid[p1, p2]; p012: Pair ~ Mid[p10, p12]; newn: CARDINAL ~ n[i] - 1; stack[i][1] ¬ p12; -- same as stack[i] ¬ [p2, p12]; n[i] ¬ newn; i ¬ (CARDINAL[i] + 1) MOD maxd; stack[i] ¬ [p012, p10]; n[i] ¬ newn; i ¬ (CARDINAL[i] + 1) MOD maxd; }; ENDLOOP; }; CurveTo: PUBLIC PROC [devicePath: DevicePath, p1, p2, p3: Pair] ~ { area: Area ~ devicePath; R: PROC [p, q: REAL] RETURNS [REAL] ~ INLINE { RETURN [q + Half[q-p]] }; Ext: PROC [p, q: Pair] RETURNS [Pair] ~ INLINE { RETURN [[R[p.s, q.s], R[p.f, q.f]]] }; p0: Pair ~ area.lastPt; q1: Pair ~ Ext[p0, p1]; q2: Pair ~ Ext[p3, p2]; IF RealAbsDif[q1.s, q2.s]+RealAbsDif[q1.f, q2.f] < flatHack OR TooDeep[area.cLimit] THEN { ParTo[area, Mid[q1, q2], p3] } ELSE { p01: Pair ~ Mid[p0, p1]; p12: Pair ~ Mid[p1, p2]; p23: Pair ~ Mid[p2, p3]; p012: Pair ~ Mid[p01, p12]; p123: Pair ~ Mid[p12, p23]; p0123: Pair ~ Mid[p012, p123]; area.cLimit ¬ area.cLimit - 1; CurveTo[area, p01, p012, p0123]; CurveTo[area, p123, p23, p3]; area.cLimit ¬ area.cLimit + 1; }; }; ConicTo: PUBLIC PROC [devicePath: DevicePath, p1, p2: Pair, r: REAL] ~ { area: Area ~ devicePath; SELECT r FROM > 0.9999 => { LineTo[area, p1]; LineTo[area, p2] }; <= 0.0 => { LineTo[area, p2] }; ENDCASE => { p0: Pair ~ area.lastPt; p02: Pair ~ Mid[p0, p2]; m: Pair ~ [p1.s-p02.s, p1.f-p02.f]; flatness: REAL = (RealInline.Abs[m.s]+RealInline.Abs[m.f])*RealAbsDif[r, 0.5]; IF flatness < flatHack OR TooDeep[area.cLimit] THEN { ParTo[area, p1, p2] } ELSE { q: Pair ~ [m.s*r+p02.s, m.f*r+p02.f]; rNew: REAL ~ 1.0/(1.0+RealFns.SqRt[2.0*(1-r)]); area.cLimit ¬ area.cLimit - 1; ConicTo[area, [(p1.s-p0.s)*r+p0.s, (p1.f-p0.f)*r+p0.f], q, rNew]; ConicTo[area, [(p1.s-p2.s)*r+p2.s, (p1.f-p2.f)*r+p2.f], p2, rNew]; area.cLimit ¬ area.cLimit + 1; }; }; }; ScaledIntMul: PROC [s: ImagerScaled.Value, c: INTEGER] RETURNS [ImagerScaled.Value] ~ INLINE { num: Basics.LongNumber ¬ LOOPHOLE[s]; num.li ¬ num.li * c; RETURN [LOOPHOLE[num]] }; appendSegCount: CARD ¬ 0; AppendSegCount: PROC RETURNS [c: CARD] ~ { c ¬ appendSegCount; appendSegCount ¬ 0; }; wrapUnit: INTEGER ~ 2**(BITS[NAT]/2); AppendSeg: PROC [area: Area, point0, point1: Pair] ~ TRUSTED INLINE { IF area.mode # 0 THEN AppendFatSeg[area, @point0, @point1] ELSE AppendSegInner[area, @point0, @point1, wrapUnit] }; AppendFatSeg: UNSAFE PROC [area: Area, point0, point1: POINTER TO Pair] ~ UNCHECKED { AppendSegInner[area, point0, point1, wrapUnit]; IF point0.s > point1.s THEN { t: POINTER TO Pair ¬ point0; point0 ¬ point1; point1 ¬ t; }; BEGIN s0m: REAL ~ point0.s-0.5; f0m: REAL ~ point0.f-0.5; s0p: REAL ~ point0.s+0.5; f0p: REAL ~ point0.f+0.5; s1m: REAL ~ point1.s-0.5; f1m: REAL ~ point1.f-0.5; s1p: REAL ~ point1.s+0.5; f1p: REAL ~ point1.f+0.5; P: UNSAFE PROC [p0s, p0f, p1s, p1f: REAL, dw: INT] ~ UNCHECKED INLINE { p0: Pair ¬ [p0s, p0f]; p1: Pair ¬ [p1s, p1f]; AppendSegInner[area, @p0, @p1, dw]; }; IF point0.f < point1.f THEN { P[s0m, f0m, s0p, f0m, 1]; P[s0p, f0m, s1p, f1m, 1]; P[s0m, f0p, s1m, f1p, -1]; P[s1m, f1p, s1p, f1p, -1]; } ELSE { P[s0m, f0m, s1m, f1m, 1]; P[s1m, f1m, s1p, f1m, 1]; P[s0m, f0p, s0p, f0p, -1]; P[s0p, f0p, s1p, f1p, -1]; }; END; }; Low32: PROC [d: DREAL] RETURNS [INT32] = INLINE { <> RETURN [LOOPHOLE[d, PACKED ARRAY [0..1] OF INT32][1]]; }; FindMagic: PROC [unit: INT] RETURNS [DREAL] ~ { scaleMagic: DREAL ¬ 3; QRound: PROC [r: REAL] RETURNS [INT] ~ { d: DREAL ¬ r + scaleMagic; RETURN [Low32[d]]; }; UNTIL QRound[1] = unit DO scaleMagic ¬ scaleMagic+scaleMagic; ENDLOOP; RETURN [scaleMagic] }; SCALED: TYPE ~ INT32; scaledHalf: SCALED ~ LOOPHOLE[ImagerScaled.half]; scaledUnit: SCALED ~ LOOPHOLE[ImagerScaled.one]; sMagic: ARRAY [0..1] OF DREAL ¬ [FindMagic[1], FindMagic[scaledUnit]]; ScaledSlope: UNSAFE PROC [ p0, p1: POINTER TO Pair, scaleMagic: POINTER TO ARRAY [0..1] OF DREAL, sAdjust: POINTER TO ARRAY [0..1] OF SCALED, -- inputs fAdjust: POINTER TO ARRAY [0..1] OF SCALED -- outputs ] RETURNS [SCALED] ~ UNCHECKED INLINE { num: DREAL ~ DREAL[p1.f]-DREAL[p0.f]; denom: DREAL ~ DREAL[p1.s]-DREAL[p0.s]; slope: DREAL ~ num/denom; magic0: DREAL ~ scaleMagic[0]; p: DREAL ¬ slope + scaleMagic[1]; scaledSlope: SCALED ~ Low32[p]; p ¬ slope*sAdjust[0] + magic0; fAdjust[0] ¬ Low32[p]; p ¬ slope*sAdjust[1] + magic0; fAdjust[1] ¬ Low32[p]; RETURN [scaledSlope]; }; MCScaledSlope: UNSAFE PROC [p0, p1: POINTER TO Pair, scaleMagic: POINTER TO ARRAY [0..1] OF DREAL, sAdjust, fAdjust: POINTER TO ARRAY [0..1] OF SCALED] RETURNS [SCALED] ~ UNCHECKED MACHINE CODE { <> "+static int MCScaledSlope (p0, p1, scaleMagic, sAdjust, fAdjust) float p0[]; float p1[]; double scaleMagic[]; int sAdjust[]; int fAdjust[]; { double num = (double)(p1[1]) - (double)(p0[1]); double denom = (double)(p1[0]) - (double)(p0[0]); double slope = num/denom; double magic0 = scaleMagic[0]; double p = slope + scaleMagic[1]; int* pp = (int*)(&p); int scaledSlope = pp[1]; p = slope*sAdjust[0] + magic0; fAdjust[0] = pp[1]; p = slope*sAdjust[1] + magic0; fAdjust[1] = pp[1]; return(scaledSlope); } .MCScaledSlope"; }; AppendSegInner: UNSAFE PROC [area: Area, p0: POINTER TO Pair, p1: POINTER TO Pair, wrapDelta: INT] ~ UNCHECKED { sMin: INTEGER ¬ Round[p0.s]; sMax: INTEGER ¬ Round[p1.s]; appendSegCount ¬ appendSegCount + 1; IF sMin > sMax THEN { { p: POINTER TO Pair ¬ p0; p0 ¬ p1; p1 ¬ p }; { s: INTEGER ¬ sMin; sMin ¬ sMax; sMax ¬ s }; wrapDelta ¬ -wrapDelta; }; IF sMin < sMax THEN { sCount: INTEGER ~ sMax-sMin; sAdjust: ARRAY [0..1] OF SCALED ¬ [ sMin*scaledUnit + scaledHalf - ScaledFromReal[p0.s], (sCount-1)*scaledUnit ]; fAdjust: ARRAY [0..1] OF SCALED; -- set by next line to sAdjust * (p1.f-p0.f)/(p1.s-p0.s) <> scaledSlope: SCALED ~ ScaledSlope[p0, p1, @sMagic, @sAdjust, @fAdjust]; ef0: SCALED ~ ScaledFromReal[p0.f] + fAdjust[0] + scaledHalf; ef1: SCALED ~ ef0 + fAdjust[1]; fMin: INTEGER ¬ ImagerScaled.Floor[LOOPHOLE[ef0]]; fMax: INTEGER ¬ ImagerScaled.Floor[LOOPHOLE[ef1]]; new: Edges ¬ NIL; IF fMin > fMax THEN {t: INTEGER ¬ fMin; fMin ¬ fMax; fMax ¬ t}; area.totalCrossings ¬ area.totalCrossings + sCount; IF area.tightBounds.min.f > fMin THEN area.tightBounds.min.f ¬ fMin; IF area.tightBounds.max.f < fMax THEN area.tightBounds.max.f ¬ fMax; IF area.tightBounds.min.s > sMin THEN area.tightBounds.min.s ¬ sMin; IF area.tightBounds.max.s < sMax THEN area.tightBounds.max.s ¬ sMax; IF area.freeEdges = NIL THEN { new ¬ NEW[EdgeRep] } ELSE { new ¬ area.freeEdges; area.freeEdges ¬ new.link; }; new.sMin ¬ sMin; new.sCount ¬ LOOPHOLE[sCount]; new.f0 ¬ LOOPHOLE[ef0]; new.df ¬ LOOPHOLE[scaledSlope]; new.wrapDelta ¬ wrapDelta; IF wrapDelta > 0 THEN { new.link ¬ area.increasingEdges; area.increasingEdges ¬ new; } ELSE { new.link ¬ area.decreasingEdges; area.decreasingEdges ¬ new; }; }; }; Trouble: SIGNAL = CODE; -- raised when an error check fails; resuming may well work OK, but something isn't quite right. Check: PROC [truth: BOOL] ~ { IF NOT truth THEN SIGNAL Trouble }; Interpolate: PROC [x0, y0, x1, y1, x: REAL] RETURNS [y: REAL] ~ { <> dx0: REAL ~ x-x0; dx1: REAL ~ x1-x; IF checking THEN Check[ (x IN [x0..x1] OR x IN [x1..x0]) ]; IF RealInline.Abs[dx0] <= RealInline.Abs[dx1] THEN y ¬ y0+(y1-y0)*(dx0/(x1-x0)) ELSE y ¬ y1-(y1-y0)*(dx1/(x1-x0)); IF checking THEN Check[ (y IN [y0..y1] OR y IN [y1..y0]) ]; }; ClipSeg: PROC [area: Area, p0: Pair, r0: Region, p1: Pair, r1: Region] ~ { sRegion: Region ~ [sLo: TRUE, sHi: TRUE, fLo: FALSE, fHi: FALSE]; clip: ARRAY ExternalHalfPlane OF REAL ~ [ sLo: area.realBounds.min.s, sHi: area.realBounds.max.s, fLo: area.realBounds.min.f, fHi: area.realBounds.max.f ]; FOR b: ExternalHalfPlane IN ExternalHalfPlane DO SELECT TRUE FROM r0[b] AND r1[b] => { IF sRegion[b] THEN { p0.s ¬ p1.s ¬ clip[b] } ELSE { p0.f ¬ p1.f ¬ clip[b] }; r0[b] ¬ FALSE; r1[b] ¬ FALSE; }; r0[b] OR r1[b] => { cut: Pair ~ IF sRegion[b] THEN [s: clip[b], f: Interpolate[p0.s, p0.f, p1.s, p1.f, clip[b]]] ELSE [s: Interpolate[p0.f, p0.s, p1.f, p1.s, clip[b]], f: clip[b]]; IF r0[b] THEN { IF sRegion[b] THEN AppendSeg[area, [s: cut.s, f: p0.f], cut] ELSE AppendSeg[area, [s: p0.s, f: cut.f], cut]; p0 ¬ cut; r0 ¬ RegionOf[area, cut]; } ELSE { IF sRegion[b] THEN AppendSeg[area, cut, [s: cut.s, f: p1.f]] ELSE AppendSeg[area, cut, [s: p1.s, f: cut.f]]; p1 ¬ cut; r1 ¬ RegionOf[area, cut]; }; }; ENDCASE => NULL; ENDLOOP; IF checking THEN Check[ (Union[r0, r1] = ALL[FALSE]) ]; AppendSeg[area, p0, p1] }; SortS: PROC [e: Edges] RETURNS [Edges] ~ INLINE { <> RETURN [MSort[e: e, sCompare: TRUE, terminator: NIL]]; }; MSort: PROC [e: Edges, sCompare: BOOL, terminator: Edge] RETURNS [Edges] ~ { <> n: INT ¬ 0; x: Edges ¬ e; y: Edges ¬ terminator; tail: Edges ¬ e; WHILE x # terminator DO x ¬ x.link; n ¬ n + 1; IF x = terminator THEN EXIT; x ¬ x.link; n ¬ n + 1; tail ¬ tail.link; ENDLOOP; IF n <= 10 THEN RETURN [ISort[e, sCompare, terminator]]; x ¬ e; y ¬ tail.link; tail.link ¬ terminator; x ¬ MSort[x, sCompare, terminator]; y ¬ MSort[y, sCompare, terminator]; <> RETURN [Merge[x, y, sCompare, terminator]] }; Merge: PROC [x, y: Edges, sCompare: BOOL, terminator: Edge] RETURNS [Edges] ~ { GT: PROC [a, b: Edge] RETURNS [BOOL] ~ INLINE { RETURN [IF sCompare THEN (a.sMin > b.sMin) ELSE (ImagerScaled.Floor[a.f0] > ImagerScaled.Floor[b.f0])] }; new: Edges ¬ x; tail: Edges ¬ terminator; IF x = terminator THEN RETURN [y]; IF y = terminator THEN RETURN [x]; IF GT[x, y] THEN {new ¬ y; y ¬ x; x ¬ new}; <> DO <> DO tail ¬ x; x ¬ x.link; IF x = terminator THEN {tail.link ¬ y; RETURN[new]}; IF GT[x, y] THEN EXIT; ENDLOOP; tail.link ¬ y; <> DO tail ¬ y; y ¬ y.link; IF y = terminator THEN {tail.link ¬ x; RETURN[new]}; IF GT[y, x] THEN EXIT; ENDLOOP; tail.link ¬ x; ENDLOOP; }; ISort: PROC [e: Edges, sCompare: BOOL, terminator: Edge] RETURNS [Edges] ~ { <> unconsumed: Edges ¬ e; new: Edges ¬ terminator; WHILE unconsumed # terminator DO link: Edges ~ unconsumed.link; current: Edges ~ unconsumed; a: Edges ¬ new; v: INTEGER ~ IF sCompare THEN current.sMin ELSE ImagerScaled.Floor[current.f0]; p: Edges ¬ terminator; IF sCompare THEN { WHILE a#terminator AND v > a.sMin DO p ¬ a; a ¬ a.link ENDLOOP } ELSE { WHILE a#terminator AND v > ImagerScaled.Floor[a.f0] DO p ¬ a; a ¬ a.link ENDLOOP }; IF p = terminator THEN { current.link ¬ new; new ¬ current } ELSE { current.link ¬ p.link; p.link ¬ current }; unconsumed ¬ link; ENDLOOP; RETURN [new]; }; SortF: PROC [e: Edges, s: INTEGER] RETURNS [Edges] ~ { <> n: INT ¬ 0; null: Edges ¬ e; WHILE null # NIL AND null.sMin = s DO null ¬ null.link; n ¬ n + 1 ENDLOOP; IF n <= 10 THEN RETURN [ISort[e: e, sCompare: FALSE, terminator: null]]; RETURN [MSort[e: e, sCompare: FALSE, terminator: null]]; }; CountCrossings: PROC [e: Edges] RETURNS [n: INT ¬ 0] ~ { WHILE e#NIL DO n ¬ n + e.sCount; e ¬ e.link ENDLOOP; }; CheckMonotone: PROC [area: Area] ~ { sSize: INT ¬ 0; IF area.state = awaitLine THEN Close[area]; sSize ¬ area.tightBounds.max.s-area.tightBounds.min.s; IF sSize <= 0 OR (area.moveCount = 1 AND area.totalCrossings = sSize+sSize) THEN area.state ¬ completeMonotone ELSE area.state ¬ complete; area.increasingEdges ¬ SortS[area.increasingEdges]; area.decreasingEdges ¬ SortS[area.decreasingEdges]; IF checking AND CountCrossings[area.increasingEdges] # CountCrossings[area.decreasingEdges] THEN ERROR; }; AdvanceTo: PROC [e: EdgeRep, s: INTEGER] RETURNS [EdgeRep] ~ { WHILE e.sMin+e.sCount <= s AND e.link#NIL DO e ¬ e.link­; ENDLOOP; IF e.sMin+e.sCount <= s THEN { e.sCount ¬ 0; e.sMin ¬ LAST[INTEGER]; } ELSE { WHILE e.sMin < s DO e.sMin ¬ e.sMin + 1; e.sCount ¬ e.sCount - 1; e.f0 ¬ ImagerScaled.PLUS[e.f0, e.df]; ENDLOOP; }; RETURN [e]; }; CopyEdges: PROC [area: Area, src: Edges, sMin, sMax: INTEGER] RETURNS [result: Edges] ~ { head: Edges ~ IF area.freeEdges = NIL THEN NEW[EdgeRep] ELSE area.freeEdges; last: Edges ¬ head; WHILE src # NIL DO s0: INTEGER ¬ src.sMin; s1: INTEGER ¬ s0+src.sCount; IF s0 < sMin THEN s0 ¬ sMin; IF s1 > sMax THEN s1 ¬ sMax; IF s0 < s1 THEN { IF last.link = NIL THEN last.link ¬ NEW[EdgeRep]; last ¬ last.link; last.sMin ¬ src.sMin; last.sCount ¬ src.sCount; last.f0 ¬ src.f0; last.df ¬ src.df; last.wrapDelta ¬ src.wrapDelta; }; src ¬ src.link; ENDLOOP; { t: Edges ~ last.link; last.link ¬ NIL; result ¬ head.link; head.link ¬ t; area.freeEdges ¬ head; }; }; AdvanceEdge: PROC [p: Edges, bbMinS: INTEGER] = { WHILE p # NIL AND p.sMin < bbMinS DO edge: Edge ¬ p; sMin: INTEGER ¬ edge.sMin; IF sMin < bbMinS THEN { sCount: INTEGER ¬ edge.sCount; f0: ImagerScaled.Value ¬ edge.f0; df: ImagerScaled.Value = edge.df; WHILE sMin < bbMinS DO sMin ¬ sMin + 1; sCount ¬ sCount - 1; f0 ¬ ImagerScaled.PLUS[f0, df]; ENDLOOP; edge.f0 ¬ f0; edge.sCount ¬ LOOPHOLE[sCount]; -- Shouldn't trap here if CopyEdges did it's job edge.sMin ¬ sMin; }; p ¬ edge; p ¬ p.link; ENDLOOP; }; Validate: PROC [area: Area] RETURNS [nPieces: INT ¬ 0] ~ { <<-- This is to call from the dubugger; it can be commented out for production.>> nCross: PACKED ARRAY [0..500] OF INTEGER ¬ ALL[0]; s: INT ¬ INT.FIRST; FOR e: Edges ¬ area.increasingEdges, e.link UNTIL e = NIL DO FOR s: INT IN [e.sMin..e.sMin+e.sCount) DO IF s IN [0..500] THEN nCross[s] ¬ nCross[s] + 1; ENDLOOP; IF e.sMin < s THEN ERROR; s ¬ e.sMin; nPieces ¬ nPieces + 1; ENDLOOP; s ¬ INT.FIRST; FOR e: Edges ¬ area.decreasingEdges, e.link UNTIL e = NIL DO FOR s: INT IN [e.sMin..e.sMin+e.sCount) DO IF s IN [0..500] THEN nCross[s] ¬ nCross[s] - 1; ENDLOOP; IF e.sMin < s THEN ERROR; s ¬ e.sMin; nPieces ¬ nPieces + 1; ENDLOOP; FOR i: INT IN [0..500] DO IF nCross[i] # 0 THEN ERROR ENDLOOP; s ¬ INT.FIRST; }; Dummy: PROC [edges: Edges] RETURNS [EdgeRep] ~ INLINE { RETURN [[sMin: FIRST[INTEGER], sCount: 0, f0: ImagerScaled.zero, df: ImagerScaled.zero, wrapDelta: 0, link: edges]] }; ConvertMonotone: PROC [area: Area, boxAction: SF.BoxAction, clipBox: SF.Box ¬ SF.maxBox] ~ { bb: SF.Box ~ SF.Intersect[clipBox, area.tightBounds]; e: ARRAY [0..1] OF EdgeRep ¬ [ AdvanceTo[Dummy[area.increasingEdges], bb.min.s], AdvanceTo[Dummy[area.decreasingEdges], bb.min.s]]; s: INTEGER ¬ e[0].sMin; assert: BOOL[TRUE..TRUE] ¬ (s = e[1].sMin); currentBox: SF.Box ¬ []; WHILE s < bb.max.s DO f0, f1: INTEGER; IF ImagerScaled.Floor[e[0].f0] > ImagerScaled.Floor[e[1].f0] THEN { t: EdgeRep ¬ e[0]; e[0] ¬ e[1]; e[1] ¬ t; }; f0 ¬ MAX[ImagerScaled.Floor[e[0].f0], bb.min.f]; f1 ¬ MIN[ImagerScaled.Floor[e[1].f0], bb.max.f]; IF s = currentBox.max.s AND f0 = currentBox.min.f AND f1 = currentBox.max.f THEN { <> IF e[0].df=ImagerScaled.zero AND e[1].df=ImagerScaled.zero THEN { <> d0: INTEGER ~ e[0].sCount; d1: INTEGER ~ e[1].sCount; d: INTEGER ~ MIN[d0, d1, bb.max.s-s] - 1; e[0].sCount ¬ LOOPHOLE[d0-d]; e[1].sCount ¬ LOOPHOLE[d1-d]; s ¬ s + d; }; currentBox.max.s ¬ s + 1; } ELSE { <> IF SF.Nonempty[currentBox] THEN boxAction[currentBox]; currentBox ¬ [[s, f0], [s+1, f1]] }; IF (e[0].sCount ¬ e[0].sCount-1) = 0 THEN { IF e[0].link = NIL THEN EXIT ELSE e[0] ¬ e[0].link­ } ELSE { e[0].f0 ¬ ImagerScaled.PLUS[e[0].f0, e[0].df] }; IF (e[1].sCount ¬ e[1].sCount-1) = 0 THEN { IF e[1].link = NIL THEN EXIT ELSE e[1] ¬ e[1].link­ } ELSE { e[1].f0 ¬ ImagerScaled.PLUS[e[1].f0, e[1].df] }; s ¬ s + 1; ENDLOOP; IF SF.Nonempty[currentBox] THEN boxAction[currentBox]; }; Fill: PROC [dst: ImagerSample.SampleMap, area: Area, oddWrap: BOOL ¬ FALSE] ~ { boxes: SF.BoxGenerator ~ { BoxesFromArea[area, boxAction, SF.maxBox, oddWrap] }; ImagerSample.FillBoxes[dst, boxes]; }; <> DevicePath: TYPE ~ REF DevicePathRep; DevicePathRep: PUBLIC TYPE ~ AreaRep; CreatePath: PUBLIC PROC [path: PathProc, transformation: Transformation, clipBox: SF.Box] RETURNS [DevicePath] ~ { devicePath: DevicePath ~ Create[]; SetPath[devicePath, path, transformation, clipBox]; RETURN [devicePath] }; SetPath: PUBLIC PROC [devicePath: DevicePath, path: PathProc, transformation: Transformation ¬ NIL, clipBox: SF.Box] ~ { area: Area ~ devicePath; T: PROC [p: VEC] RETURNS [Pair] ~ INLINE { RETURN [[s: p.x, f: p.y]] }; moveTo: ImagerPath.MoveToProc ~ { MoveTo[area, T[p]] }; lineTo: ImagerPath.LineToProc ~ { InLineTo[area, T[p1]] }; curveTo: ImagerPath.CurveToProc ~ { CurveTo[area, T[p1], T[p2], T[p3]] }; conicTo: ImagerPath.ConicToProc ~ { ConicTo[area, T[p1], T[p2], r] }; SetBounds[area, clipBox]; ImagerPath.Transform[path: path, m: transformation, moveTo: moveTo, lineTo: lineTo, curveTo: curveTo, conicTo: conicTo]; }; ObjectsFromPath: PUBLIC PROC [path: PathProc, clipBox: SF.Box, objectProc: PROC [box: SF.Box, boxGenerator: SF.BoxGenerator], devicePath: DevicePath] ~ { area: Area ~ devicePath; T: PROC [p: VEC] RETURNS [Pair] ~ INLINE { RETURN [[s: p.x, f: p.y]] }; boxGenerator: SF.BoxGenerator ~ { BoxesFromArea[area, boxAction, clipBox, FALSE] }; moveTo: ImagerPath.MoveToProc ~ { Close[area]; IF SF.Nonempty[area.tightBounds] THEN { objectProc[area.tightBounds, boxGenerator]; SetBounds[area, clipBox]; }; MoveTo[area, T[p]]; }; lineTo: ImagerPath.LineToProc ~ { InLineTo[area, T[p1]] }; curveTo: ImagerPath.CurveToProc ~ { CurveTo[area, T[p1], T[p2], T[p3]] }; conicTo: ImagerPath.ConicToProc ~ { ConicTo[area, T[p1], T[p2], r] }; SetBounds[area, clipBox]; path[moveTo: moveTo, lineTo: lineTo, curveTo: curveTo, conicTo: conicTo, arcTo: NIL]; Close[area]; IF SF.Nonempty[area.tightBounds] THEN { objectProc[area.tightBounds, boxGenerator]; SetBounds[area, clipBox]; }; }; BoundEdges: PROC [area: Area, e: Edges] ~ INLINE { <> UNTIL e = NIL DO ef0: ImagerScaled.Value ~ e.f0; sCount: INTEGER ~ e.sCount; edf: ImagerScaled.Value ~ e.df; fMin: INTEGER ¬ ImagerScaled.Floor[ef0]; fMax: INTEGER ¬ ImagerScaled.Floor[ImagerScaled.PLUS[ef0, ScaledIntMul[edf, sCount-1]]]; IF fMin > fMax THEN {t: INTEGER ¬ fMin; fMin ¬ fMax; fMax ¬ t}; IF area.tightBounds.min.f > fMin THEN area.tightBounds.min.f ¬ fMin; IF area.tightBounds.max.f < fMax THEN area.tightBounds.max.f ¬ fMax; e ¬ e.link; ENDLOOP; }; BoundingBox: PUBLIC PROC [devicePath: DevicePath, clipBox: SF.Box] RETURNS [SF.Box] ~ { IF devicePath.state < complete THEN CheckMonotone[devicePath]; RETURN [SF.Intersect[devicePath.tightBounds, clipBox]] }; Monotone: PUBLIC PROC [devicePath: DevicePath] RETURNS [BOOL] ~ { IF devicePath.state < complete THEN CheckMonotone[devicePath]; RETURN [devicePath.state = completeMonotone] }; GenerateEdges: PUBLIC PROC [devicePath: DevicePath, edgeAction: ImagerSample.EdgeAction] ~ { IF devicePath.state < complete THEN CheckMonotone[devicePath]; { e: ARRAY [0..1] OF Edge ¬ [devicePath.increasingEdges, devicePath.decreasingEdges]; s0: INTEGER ¬ IF e[0] # NIL THEN e[0].sMin ELSE LAST[INTEGER]; s1: INTEGER ¬ IF e[1] # NIL THEN e[1].sMin ELSE LAST[INTEGER]; UNTIL e[0] = NIL AND e[1] = NIL DO IF s0 <= s1 THEN { edgeAction[which: 0, sMin: e[0].sMin, sCount: e[0].sCount, f0: e[0].f0, df: e[0].df]; e[0] ¬ e[0].link; s0 ¬ IF e[0] # NIL THEN e[0].sMin ELSE LAST[INTEGER]; } ELSE { edgeAction[which: 1, sMin: e[1].sMin, sCount: e[1].sCount, f0: e[1].f0, df: e[1].df]; e[1] ¬ e[1].link; s1 ¬ IF e[1] # NIL THEN e[1].sMin ELSE LAST[INTEGER]; }; ENDLOOP; }; }; NumberOfBoxes: PUBLIC PROC [devicePath: DevicePath] RETURNS [INT] ~ { RETURN [devicePath.totalCrossings/2] }; BoxesFromArea: PROC [area: Area, boxAction: SF.BoxAction, clipBox: SF.Box ¬ SF.maxBox, oddWrap: BOOL ¬ FALSE] ~ INLINE { ConvertToBoxes[devicePath: area, oddWrap: oddWrap, clipBox: clipBox, boxAction: boxAction]; }; ConvertToBoxes: PUBLIC PROC [devicePath: DevicePath, oddWrap: BOOL, clipBox: SF.Box, boxAction: SF.BoxAction] ~ { area: Area ~ devicePath; IF area.state < complete THEN CheckMonotone[area]; IF area.state = completeMonotone THEN ConvertMonotone[area, boxAction, clipBox] ELSE { wrap: INTEGER ¬ 0; wrapMask: CARDINAL ~ IF oddWrap THEN 2*wrapUnit-1 ELSE LAST[CARDINAL]; ActiveWrap: PROC RETURNS [BOOL] ~ INLINE { RETURN [Basics.BITAND[LOOPHOLE[wrap], wrapMask] = 0] }; needSort: BOOL ¬ TRUE; bbMinS: INTEGER = MAX[clipBox.min.s, area.tightBounds.min.s]; bbMinF: INTEGER = MAX[clipBox.min.f, area.tightBounds.min.f]; bbMaxS: INTEGER = MIN[clipBox.max.s, area.tightBounds.max.s]; bbMaxF: INTEGER = MIN[clipBox.max.f, area.tightBounds.max.f]; e: Edges ¬ Merge[ x: CopyEdges[area: area, src: area.increasingEdges, sMin: bbMinS, sMax: bbMaxS], y: CopyEdges[area: area, src: area.decreasingEdges, sMin: bbMinS, sMax: bbMaxS], sCompare: TRUE, terminator: NIL ]; <> AdvanceEdge[e, bbMinS]; WHILE e#NIL DO g: Edges ¬ e; s: INTEGER ~ g.sMin; f0: INTEGER ¬ LAST[INTEGER]; -- The start of a run of pixels fScaledPrev: Basics.LongNumber ¬ [li[0]]; -- previous fScaled p: Edges ¬ NIL; IF s >= bbMaxS THEN { [] ¬ FreeEdges[area, g]; RETURN; }; IF needSort THEN { e ¬ g ¬ SortF[g, s]; needSort ¬ FALSE; }; UNTIL g = NIL OR g.sMin > s DO residual: INTEGER ~ g.sCount - 1; fScaled: ImagerScaled.Value ¬ g.f0; f: INTEGER ¬ ImagerScaled.Floor[fScaled]; IF f < bbMinF THEN f ¬ bbMinF ELSE IF f > bbMaxF THEN f ¬ bbMaxF; IF ActiveWrap[] THEN { f0 ¬ f }; wrap ¬ wrap + g.wrapDelta; IF ActiveWrap[] THEN { IF f > f0 THEN boxAction[[[s, f0], [s+1, f]]] }; <> IF residual = 0 THEN { r: Edges ~ g.link; IF p = NIL THEN e ¬ r ELSE p.link ¬ r; g.link ¬ area.freeEdges; area.freeEdges ¬ g; g ¬ r; } ELSE { g.f0 ¬ fScaled ¬ ImagerScaled.PLUS[fScaled, g.df]; g.sMin ¬ s + 1; g.sCount ¬ LOOPHOLE[residual]; IF p # NIL THEN { IF fScaledPrev.li > LOOPHOLE[fScaled] THEN needSort ¬ TRUE }; p ¬ g; fScaledPrev ¬ fScaled; g ¬ g.link; }; ENDLOOP; IF g#NIL AND g.sMin = e.sMin THEN needSort ¬ TRUE; IF checking AND wrap # 0 THEN ERROR; ENDLOOP; }; }; ConvertToManhattanPolygon: PUBLIC PROC [devicePath: DevicePath, oddWrap: BOOL, clipBox: SF.Box] RETURNS [LIST OF SF.Box] ~ { boxGenerator: SF.BoxGenerator ~ {BoxesFromArea[devicePath, boxAction, clipBox, oddWrap]}; RETURN [ImagerManhattan.CreateFromBoxes[boxGenerator]] }; SetTolerance: PUBLIC PROC [devicePath: DevicePath, tolerance: REAL ¬ 1.0] = { devicePath.tolerance ¬ tolerance; }; PathToLines: PUBLIC PROC [moveTo: ImagerPath.MoveToProc, lineTo: ImagerPath.LineToProc, path: PathProc, transformation: Transformation ¬ NIL, tolerance: REAL] = { <> cLimit, pLimit: INTEGER ¬ dLimit; LineToLines: PROC [p1: Pair] = INLINE { lineTo[[x: p1.s, y: p1.f]] }; ParToLines: PROC [p0, p1, p2: Pair] = { Flat: PROC RETURNS [BOOL] = INLINE { s01: REAL = p0.s - p1.s; f01: REAL = p0.f - p1.f; s21: REAL = p2.s - p1.s; f21: REAL = p2.f - p1.f; IF s01 * s21 + f01 * f21 > 0.0 THEN RETURN [FALSE] -- dot product indicates angle is acute ELSE { d: REAL = RealInline.AbsMax[p2.s - p0.s, p2.f - p0.f]; IF d < flatHack THEN RETURN [TRUE]; IF RealAbsDif[s01 * f21, s21 * f01] < tolerance * d THEN RETURN[TRUE]; RETURN[FALSE]; }; }; IF Flat[] OR TooDeep[pLimit] THEN { <> w02: REAL = 0.1767766953; w1: REAL = 0.6464466094; s012: REAL = (p0.s + p2.s) * w02 + p1.s * w1; f012: REAL = (p0.f + p2.f) * w02 + p1.f * w1; LineToLines[[s012, f012]]; LineToLines[p2]; } ELSE { p10: Pair = Mid[p0, p1]; p12: Pair = Mid[p1, p2]; p012: Pair = Mid[p10, p12]; pLimit ¬ pLimit - 1; ParToLines[p0, p10, p012]; ParToLines[p012, p12, p2]; pLimit ¬ pLimit + 1; }; }; CurveToLines: PROC [p0, p1, p2, p3: Pair] = { R: PROC [p, q: REAL] RETURNS [REAL] = INLINE {RETURN[q + Half[q - p]]}; Ext: PROC [p, q: Pair] RETURNS [Pair] = INLINE {RETURN[[R[p.s, q.s], R[p.f, q.f]]]}; q1: Pair = Ext[p0, p1]; q2: Pair = Ext[p3, p2]; IF RealAbsDif[q1.s, q2.s] + RealAbsDif[q1.f, q2.f] < 1.0 OR TooDeep[cLimit] THEN { ParToLines[p0, Mid[q1, q2], p3] } ELSE { p01: Pair = Mid[p0, p1]; p12: Pair = Mid[p1, p2]; p23: Pair = Mid[p2, p3]; p012: Pair = Mid[p01, p12]; p123: Pair = Mid[p12, p23]; p0123: Pair = Mid[p012, p123]; cLimit ¬ cLimit - 1; CurveToLines[p0, p01, p012, p0123]; CurveToLines[p0123, p123, p23, p3]; cLimit ¬ cLimit + 1; }; }; ConicToLines: PROC [p0, p1, p2: Pair, r: REAL] = { SELECT r FROM > 0.9999 => {LineToLines[p1]; LineToLines[p2]}; <= 0.0 => {LineToLines[p2]}; ENDCASE => { p02: Pair = Mid[p0, p2]; m: Pair = [p1.s - p02.s, p1.f - p02.f]; IF (RealInline.Abs[m.s] + RealInline.Abs[m.f]) * RealAbsDif[r, 0.5] < flatHack OR TooDeep[cLimit] THEN {ParToLines[p0, p1, p2]} ELSE { q: Pair = [m.s * r + p02.s, m.f * r + p02.f]; rNew: REAL = 1.0 / (1.0 + RealFns.SqRt[2.0 * (1 - r)]); cLimit ¬ cLimit - 1; ConicToLines[ p0, [(p1.s - p0.s) * r + p0.s, (p1.f - p0.f) * r + p0.f], q, rNew]; ConicToLines[ q, [(p1.s - p2.s) * r + p2.s, (p1.f - p2.f) * r + p2.f], p2, rNew]; cLimit ¬ cLimit + 1; }; }; }; T: PROC [p: VEC] RETURNS [Pair] = INLINE {RETURN[[s: p.x, f: p.y]]}; myMoveTo: ImagerPath.MoveToProc = {moveTo[p]; lp ¬ p}; myLineTo: ImagerPath.LineToProc = {lineTo[p1]; lp ¬ p1}; myCurveTo: ImagerPath.CurveToProc = { CurveToLines[T[lp], T[p1], T[p2], T[p3]]; lp ¬ p3; }; myConicTo: ImagerPath.ConicToProc = { ConicToLines[T[lp], T[p1], T[p2], r]; lp ¬ p2; }; lp: VEC ¬ [0, 0]; ImagerPath.Transform[path: path, m: transformation, moveTo: myMoveTo, lineTo: myLineTo, curveTo: myCurveTo, conicTo: myConicTo]; }; END.