Преглед на файлове

basic unfinished Active Oberon port of the Gfx.(used objects and improved records)
Module interfaces partially saved for backward compatibility.
Compiled modules рave the same names, as old Gfx modules and replace them

git-svn-id: https://svn.inf.ethz.ch/svn/lecturers/a2/trunk@7275 8c9fc860-2736-0410-a75d-ab315db34111

eth.metacore преди 8 години
родител
ревизия
62b354bbd5
променени са 9 файла, в които са добавени 8123 реда и са изтрити 0 реда
  1. 1396 0
      source/AGfx.Mod
  2. BIN
      source/AGfx.Tool
  3. 210 0
      source/AGfxBuffer.Mod
  4. 1129 0
      source/AGfxFonts.Mod
  5. 794 0
      source/AGfxImages.Mod
  6. 345 0
      source/AGfxMatrix.Mod
  7. 1413 0
      source/AGfxPaths.Mod
  8. 1427 0
      source/AGfxRaster.Mod
  9. 1409 0
      source/AGfxRegions.Mod

+ 1396 - 0
source/AGfx.Mod

@@ -0,0 +1,1396 @@
+(* ETH Oberon, Copyright 2001 ETH Zuerich Institut fuer Computersysteme, ETH Zentrum, CH-8092 Zuerich.
+Refer to the "General ETH Oberon System Source License" contract available at: http://www.oberon.ethz.ch/ *)
+
+MODULE Gfx; (** portable *)	(* eos  *)
+(** AUTHOR "eos"; PURPOSE "High-level, device independent 2D-graphics"; *)
+
+	(*
+		11.2.98 - changed behaviour of GetOutline if current line width is zero: calculates dashed path instead of outline (eos)
+		11.2.98 - eliminated offset parameter from subpath begin since it can be simulated by modifying the dash phase
+		16.2.98 - DrawPath now accepts current path
+		17.2.98 - ...but only if Record is not included in mode
+		17.2.98 - added RenderPath
+		19.2.98 - simplified cap and join styles to procedures
+		19.2.98 - eliminated clip path (was not correct, anyway), introduced GetClipRect instead
+		6.3.98 - fixed bug in GetDashOutline (started last dash twice even if fully drawn)
+		18.9.98 - several changes: renaming, added stroke pattern and rect/ellipse methods, text is now part of path model,
+			standard colors
+		9.12.98 - adaptation to new GfxMaps
+		10.3.99 - separate dash pattern into on/off arrays
+		13.5.99 - remodeled cap and join styles
+		13.5.99 - eliminated 'erase'
+		2.6.99 - bugfix in GetStrokeOutline and GetDashOutline (forgot to consume GfxPaths.Exit after subpath)
+		8.6.99 - made GetPolyOutline automatically close open subpaths
+		25.8.99 - use GfxImages instead of GfxMaps
+		6.10.99 - save and restore graphics state, MoveTo and Close
+		13.02.2000 - added ClipArea, replaced SaveClip/RestoreClip by GetClip/SetClip, fine-grained SaveState semantics
+		26.02.2000 - allow font change within path
+		27.02.2000 - added DrawArc
+		29.03.2000 - made default flatness 1/2
+		25.05.2000 - no longer imports Texts and Oberon
+		12.06.2001 - made tmpPath and dashPath ctxt fields to allow concurrency
+	*)
+
+	IMPORT
+		Math, GfxMatrix, GfxImages, GfxPaths, GfxFonts;
+
+
+	CONST
+		Version* = "Gfx 2.0/eos 25.05.2000";
+
+		Record* = 0; Fill* = 1; Clip* = 2; Stroke* = 3; EvenOdd* = 4;	(** drawing mode elements **)
+		InPath* = 5; InSubpath* = 6;	(** context state **)
+		MaxDashPatSize* = 8;	(** maximal number of dash entries **)
+		NoJoin* = 0; MiterJoin* = 1; BevelJoin* = 2; RoundJoin* = 3;	(** join styles **)
+		NoCap* = 0; ButtCap* = 1; SquareCap* = 2; RoundCap* = 3;	(** cap styles **)
+
+		(** state elements **)
+		fillColPat* = 0; strokeColPat* = 1; lineWidth* = 2; dashPat* = 3; capStyle* = 4; joinStyle* = 5; styleLimit* = 6;
+		flatness* = 7; font* = 8; ctm* = 9; clip* = 10;
+		strokeAttr* = {strokeColPat..styleLimit};
+		attr* = {fillColPat..font}; all* = attr + {ctm, clip};
+
+
+	TYPE
+
+		(** color type **)
+		Color* = RECORD
+			r*, g*, b*, a*: INTEGER;
+		END;
+
+		(** fill patterns **)
+		Pattern* = OBJECT
+		VAR
+			img*: GfxImages.Image;	(** replicated image map **)
+			px*, py*: REAL;	(** pinpoint coordinates **)
+		END Pattern;
+
+		(** line join and cap styles **)
+		JoinStyle* = SHORTINT;
+		CapStyle* = SHORTINT;
+
+		(** abstract clip areas **)
+		ClipArea* = POINTER TO ClipAreaDesc;
+		ClipAreaDesc* = RECORD END;
+
+		(** graphics context (continued) **)
+		Context* = OBJECT
+		VAR
+			mode*: SET;	(** current drawing mode **)
+			path*: GfxPaths.Path;	(** current path in device coordinates (updated only if mode contains the 'Record' flag) **)
+			cpx*, cpy*: REAL;	(** current point in user coordinates **)
+			ctm*: GfxMatrix.Matrix;	(** current transformation matrix **)
+			cam*: GfxMatrix.Matrix;	(** current attribute matrix (frozen ctm while inside path) **)
+			strokeCol*, fillCol*: Color;	(** current stroke and fill color **)
+			strokePat*, fillPat*: Pattern;	(** current stroke and fill pattern **)
+			lineWidth*: REAL;	(** current line width **)
+			dashPatOn*, dashPatOff*: ARRAY MaxDashPatSize OF REAL;	(** line dash array **)
+			dashPatLen*: LONGINT;	(** number of valid elements in dash arrays **)
+			dashPhase*: REAL;	(** offset for first dash **)
+			dashPeriod*: REAL;	(** sum of dash element lengths **)
+			capStyle*: CapStyle;	(** line cap style **)
+			joinStyle*: JoinStyle;	(** line join style **)
+			styleLimit*: REAL;	(** determines area that may be rendered to by styles **)
+			flatness*: REAL;	(** current flatness tolerance (in device coordinates) **)
+			font*: GfxFonts.Font;	(** current font **)
+			dashPath: GfxPaths.Path;	(* path for temporarily storing dashes *)
+			tmpPath: GfxPaths.Path;
+
+
+			(** initialize context values to defaults **)
+			PROCEDURE Init*();
+			BEGIN
+				SELF.ctm := GfxMatrix.Identity; SELF.cam := SELF.ctm;
+				SELF.strokeCol := Black; SELF.strokePat := NIL;
+				SELF.fillCol := Black; SELF.fillPat := NIL;
+				SELF.lineWidth := 1;
+				SELF.dashPatLen := 0; SELF.dashPhase := 0; SELF.dashPeriod := 0;
+				SELF.capStyle := DefaultCap; SELF.joinStyle := DefaultJoin; SELF.styleLimit := 5;
+				SELF.mode := {};
+				SELF.path := NIL;
+				SELF.cpx := 0; SELF.cpy := 0;
+				SELF.flatness := 0.5;
+				SELF.font := GfxFonts.Default;
+				IF SELF.tmpPath = NIL THEN
+					NEW(SELF.tmpPath);
+				END;
+				IF SELF.dashPath = NIL THEN
+					NEW(SELF.dashPath)
+				END;
+			END Init;
+
+			(** current transformation matrix **)
+			PROCEDURE Reset* ();
+			BEGIN
+				Init();
+				SELF.ResetClip();
+				SELF.ResetCTM()
+			END Reset;
+
+
+	(**--- Coordinate System ---**)
+
+			(** reset current transformation matrix **)
+			PROCEDURE ResetCTM* ();
+			END ResetCTM;
+
+			(** set current transformation matrix **)
+			PROCEDURE SetCTM* (VAR mat: GfxMatrix.Matrix);
+			BEGIN
+				SELF.ctm := mat
+			END SetCTM;
+
+			(** translate coordinate system **)
+			PROCEDURE Translate* (dx, dy: REAL);
+			BEGIN
+				GfxMatrix.Translate(SELF.ctm, dx, dy, SELF.ctm)
+			END Translate;
+
+			(** scale coordinate system at origin **)
+			PROCEDURE Scale* (sx, sy: REAL);
+			BEGIN
+				GfxMatrix.Scale(SELF.ctm, sx, sy, SELF.ctm)
+			END Scale;
+
+			(** scale coordinate system at specified point **)
+			PROCEDURE ScaleAt* (sx, sy, x, y: REAL);
+			BEGIN
+				SELF.Translate(x, y);
+				SELF.Scale(sx, sy);
+				SELF.Translate(-x, -y)
+			END ScaleAt;
+
+			(** rotate coordinate system at origin **)
+			PROCEDURE Rotate* (sin, cos: REAL);
+			BEGIN
+				GfxMatrix.Rotate(SELF.ctm, sin, cos, SELF.ctm)
+			END Rotate;
+
+			(** rotate coordinate system at specified point **)
+			PROCEDURE RotateAt* (sin, cos, x, y: REAL);
+			BEGIN
+				SELF.Translate(x, y);
+				SELF.Rotate(sin, cos);
+				SELF.Translate(-x, -y)
+			END RotateAt;
+
+			(** concat transformation matrix to CTM **)
+			PROCEDURE Concat* (VAR mat: GfxMatrix.Matrix);
+			BEGIN
+				GfxMatrix.Concat(mat, SELF.ctm, SELF.ctm)
+			END Concat;
+
+	(**--- Clipping ---**)
+
+			(** reset clip path **)
+			PROCEDURE ResetClip* ();
+			END ResetClip;
+
+			(** get bounding box of clipping path in user coordinates **)
+			PROCEDURE GetClipRect* (VAR llx, lly, urx, ury: REAL);
+			END GetClipRect;
+
+			(** get current clipping area **)
+			PROCEDURE GetClip* (): ClipArea;
+			END GetClip;
+
+			(** restore saved clipping path **)
+			PROCEDURE SetClip* (clip: ClipArea);
+			END SetClip;
+
+	(** graphics state **)
+			PROCEDURE SetStrokeColor* (color: Color);
+			BEGIN
+				SELF.strokeCol := color
+			END SetStrokeColor;
+
+			PROCEDURE SetStrokePattern* (pat: Pattern);
+			BEGIN
+				SELF.strokePat := pat
+			END SetStrokePattern;
+
+			PROCEDURE SetFillColor* (color: Color);
+			BEGIN
+				SELF.fillCol := color
+			END SetFillColor;
+
+			PROCEDURE SetFillPattern* (pat: Pattern);
+			BEGIN
+				SELF.fillPat := pat
+			END SetFillPattern;
+
+			PROCEDURE SetLineWidth* (width: REAL);
+			BEGIN
+				SELF.lineWidth := width
+			END SetLineWidth;
+
+			PROCEDURE SetDashPattern* (VAR on, off: ARRAY OF REAL; len: LONGINT; phase: REAL);
+			BEGIN
+				SetDashArray(SELF, on, off, len);
+				SELF.dashPhase := phase
+			END SetDashPattern;
+
+			PROCEDURE SetCapStyle* (style: CapStyle);
+			BEGIN
+				SELF.capStyle := style
+			END SetCapStyle;
+
+			PROCEDURE SetJoinStyle* (style: JoinStyle);
+			BEGIN
+				SELF.joinStyle := style
+			END SetJoinStyle;
+
+			PROCEDURE SetStyleLimit* (limit: REAL);
+			BEGIN
+				SELF.styleLimit := limit
+			END SetStyleLimit;
+
+			PROCEDURE SetFlatness* (flatness: REAL);
+			BEGIN
+				SELF.flatness := flatness
+			END SetFlatness;
+
+			PROCEDURE SetFont* (font: GfxFonts.Font);
+			BEGIN
+				SELF.font := font
+			END SetFont;
+
+			PROCEDURE GetStringWidth* (VAR str: ARRAY OF CHAR; VAR dx, dy: REAL); (*GetStringWidth*)
+			BEGIN
+				GfxFonts.GetStringWidth(SELF.font, str, dx, dy)
+			END GetStringWidth;
+
+	(**--- Current Path ---**)
+
+			(** start new path **)
+			PROCEDURE Begin* (mode: SET);
+			END Begin;
+
+			(** exit current subpath (if open) and end current path **)
+			PROCEDURE End* ( );
+			END End;
+
+			(** start subpath at inner point **)
+			PROCEDURE Enter* (x, y, dx, dy: REAL);
+			END Enter;
+
+			(** end subpath at inner point **)
+			PROCEDURE Exit* (dx, dy: REAL);
+			END Exit;
+
+			(** close current subpath **)
+			PROCEDURE Close* ();
+			END Close;
+
+			(** append line to current path **)
+			PROCEDURE Line* (x, y: REAL);
+			END Line;
+
+			(** append arc to current path **)
+			PROCEDURE Arc* (x, y, x0, y0, x1, y1, x2, y2: REAL);
+			END Arc;
+
+			(** append cubic bezier to current path **)
+			PROCEDURE Bezier* (x, y, x1, y1, x2, y2: REAL);
+			END Bezier;
+
+			(** append character outlines to current path at given point; advance current point to position after last character **)
+			PROCEDURE Show* (x, y: REAL; VAR str: ARRAY OF CHAR);
+			END Show;
+
+			PROCEDURE Render* (mode: SET);
+			END Render;
+
+			PROCEDURE Rect* (x0, y0, x1, y1: REAL);
+			END Rect;
+
+			PROCEDURE Ellipse* (x, y, rx, ry: REAL);
+			END Ellipse;
+
+			PROCEDURE Image* (x, y: REAL; img: GfxImages.Image; VAR filter: GfxImages.Filter);
+			END Image;
+
+			PROCEDURE NewPattern* (img: GfxImages.Image; px, py: REAL): Pattern;
+				VAR pat: Pattern;
+			BEGIN
+				NEW(pat); pat.img := img; pat.px := px; pat.py := py;
+				RETURN pat
+			END NewPattern;
+
+			PROCEDURE Flatten* ();
+			BEGIN
+				GetFlattenedPath(SELF, SELF.tmpPath);
+				SELF.tmpPath.CopyTo(SELF.path);
+				SELF.tmpPath.Clear();
+			END Flatten;
+
+			PROCEDURE Outline* ();
+			BEGIN
+				GetOutline(SELF, SELF.tmpPath);
+				SELF.tmpPath.CopyTo(SELF.path);
+				SELF.tmpPath.Clear()
+			END Outline;
+
+
+		END Context;
+
+		(** graphics state **)
+		State* = RECORD
+			saved: SET;
+			strokeCol, fillCol: Color; strokePat, fillPat: Pattern;
+			lineWidth: REAL;
+			dashPatOn, dashPatOff: ARRAY MaxDashPatSize OF REAL;
+			dashPatLen: LONGINT; dashPhase: REAL;
+			capStyle: CapStyle; joinStyle: JoinStyle; styleLimit: REAL;
+			flatness: REAL;
+			font: GfxFonts.Font;
+			ctm: GfxMatrix.Matrix;
+			clip: ClipArea;
+		END;
+
+		PathData = RECORD (GfxPaths.EnumData)
+			path: GfxPaths.Path;
+		END;
+
+
+	VAR
+		Black*, White*, Red*, Green*, Blue*, Cyan*, Magenta*, Yellow*, LGrey*, MGrey*, DGrey*: Color;	(** standard colors **)
+		DefaultCap*: CapStyle;	(** default line cap style (initially butt caps) **)
+		DefaultJoin*: JoinStyle;	(** default line join style (initially miter joins) **)
+	(*	DashPath: GfxPaths.Path;	(* path for temporarily storing dashes *)
+		TmpPath: GfxPaths.Path; *)
+
+	(**--- Contexts ---**)
+	
+	(** reset context to default values **)
+	PROCEDURE Reset* (ctxt: Context);
+	BEGIN
+		ctxt.Reset()
+	END Reset;
+
+	(** initialize context values to defaults **)
+	PROCEDURE Init* (ctxt: Context);
+	BEGIN
+		ctxt.Init( );
+	END Init;
+
+	(** save and restore graphics state **)
+	PROCEDURE Save* (ctxt: Context; elems: SET; VAR state: State);
+		VAR i: LONGINT;
+	BEGIN
+		state.saved := elems;
+		state.strokeCol := ctxt.strokeCol; state.strokePat := ctxt.strokePat;
+		state.fillCol := ctxt.fillCol; state.fillPat := ctxt.fillPat;
+		state.lineWidth := ctxt.lineWidth;
+		IF dashPat IN elems THEN
+			state.dashPatLen := ctxt.dashPatLen; state.dashPhase := ctxt.dashPhase;
+			i := 0;
+			WHILE i < ctxt.dashPatLen DO
+				state.dashPatOn[i] := ctxt.dashPatOn[i]; state.dashPatOff[i] := ctxt.dashPatOff[i]; INC(i)
+			END
+		END;
+		state.capStyle := ctxt.capStyle; state.joinStyle := ctxt.joinStyle; state.styleLimit := ctxt.styleLimit;
+		state.flatness := ctxt.flatness;
+		state.font := ctxt.font;
+		IF ctm IN elems THEN
+			state.ctm := ctxt.ctm
+		END;
+		IF clip IN elems THEN
+			state.clip := ctxt.GetClip()
+		END
+	END Save;
+
+	PROCEDURE Restore* (ctxt: Context; state: State);
+	BEGIN
+		ASSERT(~(InPath IN ctxt.mode), 100);
+		IF strokeColPat IN state.saved THEN
+			ctxt.SetStrokeColor(state.strokeCol);
+			ctxt.SetStrokePattern(state.strokePat)
+		END;
+		IF fillColPat IN state.saved THEN
+			ctxt.SetFillColor(state.fillCol);
+			ctxt.SetFillPattern(state.fillPat)
+		END;
+		IF lineWidth IN state.saved THEN
+			ctxt.SetLineWidth(state.lineWidth)
+		END;
+		IF dashPat IN state.saved THEN
+			ctxt.SetDashPattern(state.dashPatOn, state.dashPatOff, state.dashPatLen, state.dashPhase)
+		END;
+		IF capStyle IN state.saved THEN
+			ctxt.SetCapStyle(state.capStyle)
+		END;
+		IF joinStyle IN state.saved THEN
+			ctxt.SetJoinStyle(state.joinStyle)
+		END;
+		IF styleLimit IN state.saved THEN
+			ctxt.SetStyleLimit(state.styleLimit)
+		END;
+		IF flatness IN state.saved THEN
+			ctxt.SetFlatness(state.flatness)
+		END;
+		IF font IN state.saved THEN
+			ctxt.SetFont(state.font)
+		END;
+		IF ctm IN state.saved THEN
+			ctxt.SetCTM(state.ctm)
+		END;
+		IF clip IN state.saved THEN
+			ctxt.SetClip(state.clip)
+		END
+	END Restore;
+
+
+	(**--- Coordinate System ---**)
+
+	(** reset current transformation matrix **)
+	PROCEDURE ResetCTM* (ctxt: Context);
+	BEGIN
+		ctxt.ResetCTM()
+	END ResetCTM;
+
+	(** set current transformation matrix **)
+	PROCEDURE SetCTM* (ctxt: Context; VAR mat: GfxMatrix.Matrix);
+	BEGIN
+		ctxt.SetCTM(mat)
+	END SetCTM;
+
+	(** translate coordinate system **)
+	PROCEDURE Translate* (ctxt: Context; dx, dy: REAL);
+	BEGIN
+		ctxt.Translate(dx, dy)
+	END Translate;
+
+	(** scale coordinate system at origin **)
+	PROCEDURE Scale* (ctxt: Context; sx, sy: REAL);
+	BEGIN
+		ctxt.Scale(sx, sy)
+	END Scale;
+
+	(** scale coordinate system at specified point **)
+	PROCEDURE ScaleAt* (ctxt: Context; sx, sy, x, y: REAL);
+	BEGIN
+		ctxt.ScaleAt(sx, sy, x, y)
+	END ScaleAt;
+
+	(** rotate coordinate system at origin **)
+	PROCEDURE Rotate* (ctxt: Context; sin, cos: REAL);
+	BEGIN
+		ctxt.Rotate(sin, cos)
+	END Rotate;
+
+	(** rotate coordinate system at specified point **)
+	PROCEDURE RotateAt* (ctxt: Context; sin, cos, x, y: REAL);
+	BEGIN
+		ctxt.Translate(x, y);
+		ctxt.Rotate(sin, cos);
+		ctxt.Translate(-x, -y)
+	END RotateAt;
+
+	(** concat transformation matrix to CTM **)
+	PROCEDURE Concat* (ctxt: Context; VAR mat: GfxMatrix.Matrix);
+	BEGIN
+		ctxt.Concat(mat)
+	END Concat;
+
+
+	(**--- Clipping ---**)
+
+	(** reset clip path **)
+	PROCEDURE ResetClip* (ctxt: Context);
+	BEGIN
+		ctxt.ResetClip()
+	END ResetClip;
+
+	(** get bounding box of clipping path in user coordinates **)
+	PROCEDURE GetClipRect* (ctxt: Context; VAR llx, lly, urx, ury: REAL);
+	BEGIN
+		ctxt.GetClipRect(llx, lly, urx, ury)
+	END GetClipRect;
+
+	(** get current clipping area **)
+	PROCEDURE GetClip* (ctxt: Context): ClipArea;
+	BEGIN
+		RETURN ctxt.GetClip()
+	END GetClip;
+
+	(** restore saved clipping path **)
+	PROCEDURE SetClip* (ctxt: Context; clip: ClipArea);
+	BEGIN
+		ctxt.SetClip(clip)
+	END SetClip;
+
+
+	(**--- Graphics State ---**)
+
+	(** set stroke color **)
+	PROCEDURE SetStrokeColor* (ctxt: Context; color: Color);
+	BEGIN
+		ASSERT(~(InPath IN ctxt.mode), 100);
+		ctxt.SetStrokeColor(color)
+	END SetStrokeColor;
+
+	(** set stroke pattern (NIL = solid) **)
+	PROCEDURE SetStrokePattern* (ctxt: Context; pat: Pattern);
+	BEGIN
+		ASSERT(~(InPath IN ctxt.mode), 100);
+		ctxt.SetStrokePattern(pat)
+	END SetStrokePattern;
+
+	(** set fill color **)
+	PROCEDURE SetFillColor* (ctxt: Context; color: Color);
+	BEGIN
+		ASSERT(~(InPath IN ctxt.mode), 100);
+		ctxt.SetFillColor(color)
+	END SetFillColor;
+
+	(** set fill pattern (NIL = solid) **)
+	PROCEDURE SetFillPattern* (ctxt: Context; pat: Pattern);
+	BEGIN
+		ASSERT(~(InPath IN ctxt.mode), 100);
+		ctxt.SetFillPattern(pat)
+	END SetFillPattern;
+
+	(** set line width **)
+	PROCEDURE SetLineWidth* (ctxt: Context; width: REAL);
+	BEGIN
+		ASSERT(~(InPath IN ctxt.mode), 100);
+		ASSERT(width >= 0.0, 101);
+		ctxt.SetLineWidth(width)
+	END SetLineWidth;
+
+	(** set dash pattern **)
+	PROCEDURE SetDashPattern* (ctxt: Context; VAR on, off: ARRAY OF REAL; len: LONGINT; phase: REAL);
+	BEGIN
+		ASSERT(~(InPath IN ctxt.mode), 100);
+		ASSERT((len <= LEN(on)) & (len <= LEN(off)), 101);
+		ctxt.SetDashPattern(on, off, len, phase)
+	END SetDashPattern;
+
+	(** copy values from parameter, and calculate dash period **)
+	PROCEDURE SetDashArray* (ctxt: Context; VAR on, off: ARRAY OF REAL; len: LONGINT);
+	BEGIN
+		ctxt.dashPatLen := len;
+		ctxt.dashPeriod := 0;
+		IF len > 0 THEN
+			REPEAT
+				DEC(len);
+				ctxt.dashPatOn[len] := on[len]; ctxt.dashPatOff[len] := off[len];
+				ctxt.dashPeriod := ctxt.dashPeriod + on[len] + off[len]
+			UNTIL len = 0
+		END;
+		ASSERT((ctxt.dashPatLen = 0) OR (ctxt.dashPeriod # 0), 120)
+	END SetDashArray;
+
+	(** set line cap style **)
+	PROCEDURE SetCapStyle* (ctxt: Context; style: CapStyle);
+	BEGIN
+		ASSERT(~(InPath IN ctxt.mode), 100);
+		ASSERT((NoCap <= style) & (style <= RoundCap), 101);
+		ctxt.SetCapStyle(style)
+	END SetCapStyle;
+
+	(** set line join style **)
+	PROCEDURE SetJoinStyle* (ctxt: Context; style: JoinStyle);
+	BEGIN
+		ASSERT(~(InPath IN ctxt.mode), 100);
+		ASSERT((NoJoin <= style) & (style <= RoundJoin), 101);
+		ctxt.SetJoinStyle(style)
+	END SetJoinStyle;
+
+	(** set style border factor **)
+	PROCEDURE SetStyleLimit* (ctxt: Context; limit: REAL);
+	BEGIN
+		ASSERT(~(InPath IN ctxt.mode), 100);
+		ctxt.SetStyleLimit(limit)
+	END SetStyleLimit;
+
+	(** set flatness parameter **)
+	PROCEDURE SetFlatness* (ctxt: Context; flatness: REAL);
+	BEGIN
+		ctxt.SetFlatness(flatness)
+	END SetFlatness;
+
+	(** set current font **)
+	PROCEDURE SetFont* (ctxt: Context; font: GfxFonts.Font);
+	BEGIN
+		ASSERT(font # NIL, 100);
+		ctxt.SetFont(font)
+	END SetFont;
+
+	(** set current font using name and size **)
+	PROCEDURE SetFontName* (ctxt: Context; fontname: ARRAY OF CHAR; size: INTEGER);
+		VAR font: GfxFonts.Font;
+	BEGIN
+		font := GfxFonts.OpenSize(fontname, size);
+		IF font = NIL THEN font := GfxFonts.Default END;
+		ctxt.SetFont(font)
+	END SetFontName;
+
+	(** calculate distance that current point would move if given string were rendered **)
+	PROCEDURE GetStringWidth* (ctxt: Context; str: ARRAY OF CHAR; VAR dx, dy: REAL);
+	BEGIN
+		ctxt.GetStringWidth(str, dx, dy)
+	END GetStringWidth;
+
+	(**--- Current Path ---**)
+
+	(** start new path **)
+	PROCEDURE Begin* (ctxt: Context; mode: SET);
+	BEGIN
+		ASSERT(~(InPath IN ctxt.mode), 100);
+		ctxt.Begin(mode);
+		INCL(ctxt.mode, InPath)
+	END Begin;
+
+	(** exit current subpath (if open) and end current path **)
+	PROCEDURE End* (ctxt: Context);
+	BEGIN
+		ASSERT(InPath IN ctxt.mode, 100);
+		IF InSubpath IN ctxt.mode THEN ctxt.Exit(0, 0) END;
+		ctxt.End();
+		EXCL(ctxt.mode, InPath)
+	END End;
+
+	(** end current subpath (if open) and begin new subpath **)
+	PROCEDURE MoveTo* (ctxt: Context; x, y: REAL);
+	BEGIN
+		ASSERT(InPath IN ctxt.mode, 100);
+		IF InSubpath IN ctxt.mode THEN ctxt.Exit(0, 0) END;
+		ctxt.Enter(x, y, 0, 0);
+		INCL(ctxt.mode, InSubpath)
+	END MoveTo;
+
+	(** start subpath at inner point **)
+	PROCEDURE Enter* (ctxt: Context; x, y, dx, dy: REAL);
+	BEGIN
+		ASSERT(InPath IN ctxt.mode, 100);
+		IF InSubpath IN ctxt.mode THEN ctxt.Exit(0, 0) END;
+		ctxt.Enter(x, y, dx, dy);
+		INCL(ctxt.mode, InSubpath)
+	END Enter;
+
+	(** end subpath at inner point **)
+	PROCEDURE Exit* (ctxt: Context; dx, dy: REAL);
+	BEGIN
+		ASSERT(InSubpath IN ctxt.mode, 100);
+		ctxt.Exit(dx, dy);
+		EXCL(ctxt.mode, InSubpath)
+	END Exit;
+
+	(** close current subpath **)
+	PROCEDURE Close* (ctxt: Context);
+	BEGIN
+		ASSERT(InSubpath IN ctxt.mode, 100);
+		ctxt.Close();
+		EXCL(ctxt.mode, InSubpath)
+	END Close;
+
+	(** append line to current path **)
+	PROCEDURE LineTo* (ctxt: Context; x, y: REAL);
+	BEGIN
+		ASSERT(InSubpath IN ctxt.mode, 100);
+		ctxt.Line(x, y)
+	END LineTo;
+
+	(** append arc to current path **)
+	PROCEDURE ArcTo* (ctxt: Context; x, y, x0, y0, x1, y1, x2, y2: REAL);
+	BEGIN
+		ASSERT(InSubpath IN ctxt.mode, 100);
+		ctxt.Arc(x, y, x0, y0, x1, y1, x2, y2)
+	END ArcTo;
+
+	(** append cubic bezier to current path **)
+	PROCEDURE BezierTo* (ctxt: Context; x, y, x1, y1, x2, y2: REAL);
+	BEGIN
+		ASSERT(InSubpath IN ctxt.mode, 100);
+		ctxt.Bezier(x, y, x1, y1, x2, y2)
+	END BezierTo;
+
+	(** append character outlines to current path at given point; advance current point to position after last character **)
+	PROCEDURE ShowAt* (ctxt: Context; x, y: REAL; str: ARRAY OF CHAR);
+	BEGIN
+		ASSERT(InPath IN ctxt.mode, 100);
+		IF InSubpath IN ctxt.mode THEN ctxt.Exit(0, 0) END;
+		ctxt.Show(x, y, str)
+	END ShowAt;
+
+	(** append character outlines to current path at current point; advance current point to position after last character **)
+	PROCEDURE Show* (ctxt: Context; str: ARRAY OF CHAR);
+	BEGIN
+		ASSERT(InPath IN ctxt.mode, 100);
+		IF InSubpath IN ctxt.mode THEN ctxt.Exit(0, 0) END;
+		ctxt.Show(ctxt.cpx, ctxt.cpy, str)
+	END Show;
+
+
+	(**--- Path Flattening ---**)
+
+	(** replace arcs and beziers in current path by approximation using straight lines **)
+	PROCEDURE Flatten* (ctxt: Context);
+	BEGIN
+		ASSERT(~(InPath IN ctxt.mode), 100);
+		ctxt.Flatten()
+	END Flatten;
+
+	PROCEDURE EnumPathElem (VAR data: GfxPaths.EnumData);
+	BEGIN
+		WITH data: PathData DO
+			CASE data.elem OF
+			| GfxPaths.Enter: data.path.AddEnter(data.x, data.y, data.dx, data.dy)
+			| GfxPaths.Line: data.path.AddLine(data.x, data.y)
+			| GfxPaths.Exit: data.path.AddExit(data.dx, data.dy)
+			END
+		END
+	END EnumPathElem;
+
+	(** store flattened current path in given path  **)
+	PROCEDURE GetFlattenedPath* (ctxt: Context; path: GfxPaths.Path);
+		VAR data: PathData;
+	BEGIN
+		ASSERT(ctxt.path # path, 100);
+		path.Clear();
+		data.path := path;
+		ctxt.path.EnumFlattened(ctxt.flatness, EnumPathElem, data)
+	END GetFlattenedPath;
+
+
+	(**--- Cap Styles ---**)
+
+	PROCEDURE EnterCapStyle* (ctxt: Context; x, y, dx, dy: REAL; path: GfxPaths.Path);
+	BEGIN
+		IF ctxt.capStyle = ButtCap THEN
+			path.AddEnter(x, y, dy, -dx);
+			path.AddLine(x + dy, y - dx)
+		ELSIF ctxt.capStyle = SquareCap THEN
+			path.AddEnter(x - dx, y - dy, dy, -dx);
+			path.AddLine(x - dx + dy, y - dy - dx);
+			path.AddLine(x + dy, y - dx)
+		ELSIF ctxt.capStyle = RoundCap THEN
+			path.AddEnter(x - dx, y - dy, dy, -dx);
+			path.AddArc(x + dy, y - dx, x, y, x - dx, y - dy, x + dy, y - dx)
+		ELSE
+			path.AddEnter(x + dy, y - dx, 0, 0)
+		END
+	END EnterCapStyle;
+
+	PROCEDURE AddCapStyle* (ctxt: Context; x, y, dx, dy: REAL; path: GfxPaths.Path);
+	BEGIN
+		IF ctxt.capStyle = ButtCap THEN
+			path.AddLine(x + dy, y - dx)
+		ELSIF ctxt.capStyle = SquareCap THEN
+			path.AddLine(x - dx - dy, y - dy + dx);
+			path.AddLine(x - dx + dy, y - dy - dx);
+			path.AddLine(x + dy, y - dx)
+		ELSIF ctxt.capStyle = RoundCap THEN
+			path.AddArc(x + dy, y - dx, x, y, x - dy, y + dx, x - dx, y - dy)
+		ELSE
+			path.AddExit(0, 0);
+			path.AddEnter(x + dy, y - dx, 0, 0)
+		END
+	END AddCapStyle;
+
+	PROCEDURE ExitCapStyle* (ctxt: Context; x, y, dx, dy: REAL; path: GfxPaths.Path);
+	BEGIN
+		IF ctxt.capStyle = ButtCap THEN
+			path.AddLine(x, y);
+			path.AddExit(dy, -dx)
+		ELSIF ctxt.capStyle = SquareCap THEN
+			path.AddLine(x - dx - dy, y - dy + dx);
+			path.AddLine(x - dx, y - dy);
+			path.AddExit(dy, -dx)
+		ELSIF ctxt.capStyle = RoundCap THEN
+			path.AddArc(x - dx, y - dy, x, y, x - dy, y + dx, x - dx, y - dy);
+			path.AddExit(dy, -dx)
+		ELSE
+			path.AddExit(0, 0)
+		END
+	END ExitCapStyle;
+
+
+	(**--- Join Styles ---**)
+
+	(** return if half axis vector (in device coordinates) exceeds style limit **)
+	PROCEDURE ExceedsLimit* (ctxt: Context; hx, hy: REAL): BOOLEAN;
+		VAR limit: REAL;
+	BEGIN
+		GfxMatrix.ApplyToDist(ctxt.cam, 0.5*ctxt.lineWidth * ctxt.styleLimit, limit);
+		RETURN hx * hx + hy * hy > limit * limit
+	END ExceedsLimit;
+
+	PROCEDURE EnterJoinStyle* (ctxt: Context; x, y, idx, idy, hx, hy, odx, ody: REAL; path: GfxPaths.Path);
+		VAR ix, iy, t: REAL;
+	BEGIN
+		IF (ctxt.joinStyle = BevelJoin) OR (ctxt.joinStyle = MiterJoin) & ExceedsLimit(ctxt, hx, hy) THEN
+			GfxPaths.IntersectLines(x, y, hx, hy, x + ody, y - odx, -hy, hx, ix, iy);
+			path.AddEnter(ix, iy, -hy, hx);
+			path.AddLine(x + ody, y - odx)
+		ELSIF ctxt.joinStyle = MiterJoin THEN
+			path.AddEnter(x + hx, y + hy, idx, idy);
+			path.AddLine(x + ody, y - odx)
+		ELSIF ctxt.joinStyle = RoundJoin THEN
+			t := Math.sqrt((odx * odx + ody * ody)/(hx * hx + hy * hy));
+			path.AddEnter(x + t * hx, y + t * hy, -hy, hx);
+			path.AddArc(x + ody, y - odx, x, y, x - odx, y - ody, x + ody, y - odx)
+		ELSE
+			path.AddEnter(x + ody, y - odx, 0, 0)
+		END
+	END EnterJoinStyle;
+
+	PROCEDURE AddJoinStyle* (ctxt: Context; x, y, idx, idy, hx, hy, odx, ody: REAL; path: GfxPaths.Path);
+	BEGIN
+		IF (ctxt.joinStyle = BevelJoin) OR (ctxt.joinStyle = MiterJoin) & ExceedsLimit(ctxt, hx, hy) THEN
+			path.AddLine(x + ody, y - odx)
+		ELSIF ctxt.joinStyle = MiterJoin THEN
+			path.AddLine(x + hx, y + hy);
+			path.AddLine(x + ody, y - odx)
+		ELSIF ctxt.joinStyle = RoundJoin THEN
+			path.AddArc(x + ody, y - odx, x, y, x - odx, y - ody, x + ody, y - odx)
+		ELSE
+			path.AddExit(0, 0);
+			path.AddEnter(x + ody, y - odx, 0, 0)
+		END
+	END AddJoinStyle;
+
+	PROCEDURE ExitJoinStyle* (ctxt: Context; x, y, idx, idy, hx, hy, odx, ody: REAL; path: GfxPaths.Path);
+		VAR ix, iy, t: REAL;
+	BEGIN
+		IF (ctxt.joinStyle = BevelJoin) OR (ctxt.joinStyle = MiterJoin) & ExceedsLimit(ctxt, hx, hy) THEN
+			GfxPaths.IntersectLines(x, y, hx, hy, x + idy, y - idx, -hy, hx, ix, iy);
+			path.AddLine(ix, iy);
+			path.AddExit(-hy, hx)
+		ELSIF ctxt.joinStyle = MiterJoin THEN
+			path.AddLine(x + hx, y + hy);
+			path.AddExit(odx, ody)
+		ELSIF ctxt.joinStyle = RoundJoin THEN
+			t := Math.sqrt((odx * odx + ody * ody)/(hx * hx + hy * hy));
+			path.AddArc(x + t * hx, y + t * hy, x, y, x - idx, y - idy, x + idy, y - idx);
+			path.AddExit(-hy, hx)
+		ELSE
+			path.AddExit(0, 0)
+		END
+	END ExitJoinStyle;
+
+
+	(**--- Path Outline ---**)
+
+	(** replace current path by outline of area which would be drawn to if the path were stroked **)
+	PROCEDURE Outline* (ctxt: Context);
+	BEGIN
+		ASSERT(~(InPath IN ctxt.mode), 100);
+		ctxt.Outline()
+	END Outline;
+
+	(** return vector scaled to given length **)
+	PROCEDURE GetNormVector* (x, y, len: REAL; VAR nx, ny: REAL);
+		VAR t: REAL;
+	BEGIN
+		t := len/Math.sqrt(x * x + y * y);
+		nx := t * x; ny := t * y
+	END GetNormVector;
+
+	(** return vector to outer corner of two joining vectors whose lengths correspond to line width **)
+	PROCEDURE GetHalfAxisVector* (idx, idy, odx, ody: REAL; VAR hx, hy: REAL);
+		VAR cprod, t: REAL;
+	BEGIN
+		cprod := idx * ody - idy * odx;
+		IF ABS(cprod) < 1.0E-3 THEN
+			hx := 0; hy := 0
+		ELSE	(* intersect outer border lines to find half axis vector *)
+			t := ((idy - ody) * ody + (idx - odx) * odx)/cprod;
+			IF cprod > 0 THEN	(* left turn *)
+				hx := idy - t * idx; hy := -(idx + t * idy)
+			ELSE	(* right turn *)
+				hx := t * idx - idy; hy := idx + t * idy
+			END
+		END
+	END GetHalfAxisVector;
+
+	PROCEDURE AddEnterJoinStyle (ctxt: Context; x, y, hx, hy, odx, ody: REAL; path: GfxPaths.Path);
+		VAR ix, iy, t: REAL;
+	BEGIN
+		IF (ctxt.joinStyle = BevelJoin) OR (ctxt.joinStyle = MiterJoin) & ExceedsLimit(ctxt, hx, hy) THEN
+			GfxPaths.IntersectLines(x, y, hx, hy, x + ody, y - odx, -hy, hx, ix, iy);
+			path.AddLine(ix, iy); path.AddLine(x + ody, y - odx)
+		ELSIF ctxt.joinStyle = MiterJoin THEN
+			path.AddLine(x + hx, y + hy); path.AddLine(x + ody, y - odx)
+		ELSIF ctxt.joinStyle = RoundJoin THEN
+			t := Math.sqrt((odx * odx + ody * ody)/(hx * hx + hy * hy));
+			path.AddLine(x + t * hx, y + t * hy);
+			path.AddArc(x + ody, y - odx, x, y, x - odx, y - ody, x + ody, y - odx)
+		ELSE
+			path.AddLine(x + ody, y - odx)
+		END
+	END AddEnterJoinStyle;
+
+	PROCEDURE AddExitJoinStyle (ctxt: Context; x, y, idx, idy, hx, hy: REAL; path: GfxPaths.Path);
+		VAR ix, iy, t: REAL;
+	BEGIN
+		IF (ctxt.joinStyle = BevelJoin) OR (ctxt.joinStyle = MiterJoin) & ExceedsLimit(ctxt, hx, hy) THEN
+			GfxPaths.IntersectLines(x, y, hx, hy, x + idy, y - idx, -hy, hx, ix, iy);
+			path.AddLine(ix, iy)
+		ELSIF ctxt.joinStyle = MiterJoin THEN
+			path.AddLine(x + hx, y + hy)
+		ELSIF ctxt.joinStyle = RoundJoin THEN
+			t := Math.sqrt((idx * idx + idy * idy)/(hx * hx + hy * hy));
+			path.AddArc(x + t * hx, y + t * hy, x, y, x - idx, y - idy, x + idy, y - idx)
+		END;
+		path.AddLine(x - hx, y - hy)
+	END AddExitJoinStyle;
+
+	PROCEDURE GetPolyOutline (ctxt: Context; VAR x, y: ARRAY OF REAL; n: LONGINT; dxi, dyi, dxo, dyo: REAL; dst: GfxPaths.Path);
+		VAR closed: BOOLEAN; width, odx, ody, idx, idy, hx, hy: REAL; i, j: LONGINT;
+	BEGIN
+		closed := (x[n] = x[0]) & (y[n] = y[0]);
+		GfxMatrix.ApplyToDist(ctxt.cam, 0.5*ctxt.lineWidth, width);
+		GetNormVector(x[1] - x[0], y[1] - y[0], width, odx, ody);
+		IF (dxi = 0) & (dyi = 0) THEN
+			EnterCapStyle(ctxt, x[0], y[0], odx, ody, dst)
+		ELSE
+			GetNormVector(dxi, dyi, width, idx, idy);
+			GetHalfAxisVector(idx, idy, odx, ody, hx, hy);
+			IF (hx = 0) & (hy = 0) THEN	(* collinear vectors *)
+				IF closed THEN
+					dst.AddEnter(x[0] + ody, y[0] - odx, dxi, dyi)
+				ELSE
+					dst.AddEnter(x[0] - ody, y[0] + odx, ody, -odx);
+					dst.AddLine(x[0] + ody, y[0] - odx)
+				END
+			ELSIF idx * ody > idy * odx THEN	(* starts with left turn *)
+				IF closed THEN
+					EnterJoinStyle(ctxt, x[0], y[0], idx, idy, hx, hy, odx, ody, dst)
+				ELSE
+					dst.AddEnter(x[0] - hx, y[0] - hy, ody, -odx);
+					AddEnterJoinStyle(ctxt, x[0], y[0], hx, hy, odx, ody, dst)
+				END
+			ELSE
+				IF closed THEN
+					dst.AddEnter(x[0] - hx, y[0] - hy, dxi, dyi)
+				ELSE
+					dst.AddEnter(x[0] + hx, y[0] + hy, -hx, -hy);
+					dst.AddLine(x[0] - hx, y[0] - hy)
+				END
+			END
+		END;
+
+		i := 1; j := 2;
+		WHILE j <= n DO
+			idx := odx; idy := ody;
+			GetNormVector(x[j] - x[i], y[j] - y[i], width, odx, ody);
+			GetHalfAxisVector(idx, idy, odx, ody, hx, hy);
+			IF (hx = 0) & (hy = 0) THEN	(* collinear vectors *)
+				dst.AddLine(x[i] + idy, y[i] - idx)
+			ELSIF idx * ody > idy * odx THEN	(* left turn => outer join *)
+				dst.AddLine(x[i] + idy, y[i] - idx);
+				AddJoinStyle(ctxt, x[i], y[i], idx, idy, hx, hy, odx, ody, dst)
+			ELSE	(* right turn => inner join *)
+				dst.AddLine(x[i] - hx, y[i] - hy)
+			END;
+			i := j; INC(j)
+		END;
+
+		idx := odx; idy := ody;
+		IF (dxo = 0) & (dyo = 0) THEN
+			dst.AddLine(x[n] + ody, y[n] - odx);
+			AddCapStyle(ctxt, x[n], y[n], -odx, -ody, dst)
+		ELSE
+			dst.AddLine(x[n] + idy, y[n] - idx);
+			GetNormVector(dxo, dyo, width, odx, ody);
+			GetHalfAxisVector(idx, idy, odx, ody, hx, hy);
+			IF (hx = 0) & (hy = 0) THEN	(* collinear vectors *)
+				IF closed THEN
+					dst.AddExit(odx, ody);
+					dst.AddEnter(x[n] - idy, y[n] + idx, -dxo, -dyo)
+				ELSE
+					dst.AddLine(x[n] - idy, y[n] + idx)
+				END
+			ELSIF idx * ody > idy * odx THEN	(* ends in left turn *)
+				IF closed THEN
+					ExitJoinStyle(ctxt, x[n], y[n], idx, idy, hx, hy, odx, ody, dst);
+					dst.AddEnter(x[n] - hx, y[n] - hy, -dxo, -dyo)
+				ELSE
+					AddExitJoinStyle(ctxt, x[n], y[n], idx, idy, hx, hy, dst)
+				END
+			ELSE
+				dst.AddLine(x[n] - hx, y[n] - hy);
+				IF closed THEN
+					dst.AddExit(dxo, dyo);
+					EnterJoinStyle(ctxt, x[n], y[n], -odx, -ody, -hx, -hy, -idx, -idy, dst)
+				ELSE
+					AddEnterJoinStyle(ctxt, x[n], y[n], -hx, -hy, -idx, -idy, dst)
+				END
+			END
+		END;
+
+		odx := -idx; ody := -idy;
+		i := n-1; j := n-2;
+		WHILE j >= 0 DO
+			idx := odx; idy := ody;
+			GetNormVector(x[j] - x[i], y[j] - y[i], width, odx, ody);
+			GetHalfAxisVector(idx, idy, odx, ody, hx, hy);
+			IF (hx = 0) & (hy = 0) THEN	(* collinear vectors *)
+				dst.AddLine(x[i] + idy, y[i] - idx)
+			ELSIF idx * ody > idy * odx THEN	(* left turn => outer join *)
+				dst.AddLine(x[i] + idy, y[i] - idx);
+				AddJoinStyle(ctxt, x[i], y[i], idx, idy, hx, hy, odx, ody, dst)
+			ELSE	(* right turn => inner join *)
+				dst.AddLine(x[i] - hx, y[i] - hy)
+			END;
+			i := j; DEC(j)
+		END;
+
+		IF (dxi = 0) & (dyi = 0) THEN
+			dst.AddLine(x[0] + ody, y[0] - odx);
+			ExitCapStyle(ctxt, x[0], y[0], -odx, -ody, dst)
+		ELSE
+			idx := odx; idy := ody;
+			GetNormVector(-dxi, -dyi, width, odx, ody);
+			GetHalfAxisVector(idx, idy, odx, ody, hx, hy);
+			dst.AddLine(x[0] + idy, y[0] - idx);
+			IF (hx = 0) & (hy = 0) THEN	(* collinear vectors *)
+				IF closed THEN
+					dst.AddExit(-dxi, -dyi)
+				ELSE
+					dst.AddExit(-idy, idx)
+				END
+			ELSIF idx * ody > idy * odx THEN	(* left turn *)
+				IF closed THEN
+					ExitJoinStyle(ctxt, x[0], y[0], idx, idy, hx, hy, odx, ody, dst)
+				ELSE
+					AddExitJoinStyle(ctxt, x[0], y[0], idx, idy, hx, hy, dst);
+					dst.AddExit(-idx, -idy)
+				END
+			ELSE
+				dst.AddLine(x[0] - hx, y[0] - hy);
+				IF closed THEN
+					dst.AddExit(-dxi, -dyi)
+				ELSE
+					dst.AddExit(hx, hy)
+				END
+			END
+		END
+	END GetPolyOutline;
+
+	PROCEDURE GetStrokeOutline (ctxt: Context; VAR scan: GfxPaths.Scanner; dst: GfxPaths.Path);
+		CONST last = 127;
+		VAR x, y: ARRAY last+1 OF REAL; dxi, dyi, dxo, dyo: REAL; n: LONGINT;
+	BEGIN
+		ASSERT(scan.elem = GfxPaths.Enter);
+		x[0] := scan.x; y[0] := scan.y; dxi := scan.dx; dyi := scan.dy;
+		scan.Scan( ); n := 0;
+		WHILE scan.elem = GfxPaths.Line DO
+			IF n < last THEN
+				INC(n); x[n] := scan.x; y[n] := scan.y
+			ELSE
+				dxo := scan.x - x[n]; dyo := scan.y - y[n];
+				GetPolyOutline(ctxt, x, y, n, dxi, dyi, dxo, dyo, dst);
+				dxi := x[n] - x[n-1]; dyi := y[n] - y[n-1];
+				x[0] := x[n]; y[0] := y[n];
+				x[1] := scan.x; y[1] := scan.y;
+				n := 1
+			END;
+			scan.Scan( )
+		END;
+		IF n > 0 THEN
+			GetPolyOutline(ctxt, x, y, n, dxi, dyi, scan.dx, scan.dy, dst)
+		END;
+		scan.Scan( )
+	END GetStrokeOutline;
+
+	(** get offset values and pattern index of visible and invisible dash part at start of subpath (in device space) **)
+	PROCEDURE GetDashOffsets* (ctxt: Context; offset: REAL; VAR beg, end, next: REAL; VAR idx: LONGINT);
+		VAR phase, period, len: REAL;
+	BEGIN
+		idx := 0;
+		GfxMatrix.ApplyToDist(ctxt.cam, ctxt.dashPhase, phase);
+		GfxMatrix.ApplyToDist(ctxt.cam, ctxt.dashPeriod, period);
+		beg := ENTIER((phase + offset)/period) * period - phase;	(* offset - period < beg <= offset *)
+		LOOP
+			GfxMatrix.ApplyToDist(ctxt.cam, ctxt.dashPatOn[idx], len);
+			end := beg + len;
+			GfxMatrix.ApplyToDist(ctxt.cam, ctxt.dashPatOff[idx], len);
+			next := end + len;
+			idx := (idx+1) MOD ctxt.dashPatLen;
+			IF next > offset THEN EXIT END;
+			beg := next
+		END
+	END GetDashOffsets;
+
+	PROCEDURE GetDashOutline (ctxt: Context; VAR scan: GfxPaths.Scanner; dst: GfxPaths.Path);
+		VAR
+			width, cx, cy, dx, dy, beg, end, next, offset, len, cos, sin, wdx, wdy, endOff, dash, nx, ny: REAL;
+			index: LONGINT; dscan: GfxPaths.Scanner;
+	BEGIN
+		GfxMatrix.ApplyToDist(ctxt.cam, 0.5*ctxt.lineWidth, width);
+		ASSERT(scan.elem = GfxPaths.Enter);
+		cx := scan.x; cy := scan.y; dx := scan.dx; dy := scan.dy;
+		scan.Scan( );
+		GetDashOffsets(ctxt, 0, beg, end, next, index);
+		IF 0 < end THEN	(* starts within dash *)
+			IF width = 0 THEN
+				dst.AddEnter(cx, cy, dx, dy)
+			ELSE
+				ctxt.dashPath.Clear();
+				ctxt.dashPath.AddEnter(cx, cy, dx, dy)
+			END
+		END;
+		offset := 0;
+		WHILE scan.elem = GfxPaths.Line DO
+			dx := scan.x - cx; dy := scan.y - cy;
+			len := Math.sqrt(dx * dx + dy * dy);
+			cos := dx/len; sin := dy/len;
+			endOff := offset + len;
+			IF offset < end THEN	(* begin of line is within dash *)
+				IF end <= endOff THEN	(* end of current dash comes before end of line => finish current dash *)
+					len := end - offset;
+					IF width = 0 THEN
+						dst.AddLine(cx + len * cos, cy + len * sin);
+						dst.AddExit(0, 0)
+					ELSE
+						ctxt.dashPath.AddLine(cx + len * cos, cy + len * sin);
+						ctxt.dashPath.AddExit(0, 0);
+						dscan.Open(ctxt.dashPath, 0);
+						GetStrokeOutline(ctxt, dscan, dst)
+					END
+				ELSIF width = 0 THEN	(* continue current dash to end of line *)
+					dst.AddLine(scan.x, scan.y)
+				ELSE
+					ctxt.dashPath.AddLine(scan.x, scan.y)
+				END
+			END;
+			IF next < endOff THEN	(* next dash starts before end of line => draw complete dashes *)
+				wdx := width * cos; wdy := width * sin;
+				beg := offset;
+				REPEAT
+					len := next - beg;
+					cx := cx + len * cos; cy := cy + len * sin;
+					beg := next;
+					GfxMatrix.ApplyToDist(ctxt.cam, ctxt.dashPatOn[index], dash);
+					end := beg + dash;
+					GfxMatrix.ApplyToDist(ctxt.cam, ctxt.dashPatOff[index], dash);
+					next := end + dash;
+					index := (index+1) MOD ctxt.dashPatLen;
+					IF end <= endOff THEN	(* next dash can be fully drawn *)
+						len := end - beg;
+						nx := cx + len * cos; ny := cy + len * sin;
+						IF width = 0 THEN
+							dst.AddEnter(cx, cy, 0, 0);
+							dst.AddLine(nx, ny);
+							dst.AddExit(0, 0)
+						ELSE
+							EnterCapStyle(ctxt, cx, cy, wdx, wdy, dst);
+							dst.AddLine(nx + wdy, ny - wdx);
+							AddCapStyle(ctxt, nx, ny, -wdx, -wdy, dst);
+							dst.AddLine(cx - wdy, cy + wdx);
+							ExitCapStyle(ctxt, cx, cy, wdx, wdy, dst)
+						END
+					END
+				UNTIL next >= endOff;
+				IF endOff < end THEN	(* next dash not complete => hasn't been started yet *)
+					IF width = 0 THEN
+						dst.AddEnter(cx, cy, 0, 0);
+						dst.AddLine(scan.x, scan.y)
+					ELSE
+						ctxt.dashPath.Clear();
+						ctxt.dashPath.AddEnter(cx, cy, 0, 0);
+						ctxt.dashPath.AddLine(scan.x, scan.y)
+					END
+				END
+			END;
+			cx := scan.x; cy := scan.y; offset := endOff;
+			scan.Scan( )
+		END;
+		ASSERT(scan.elem = GfxPaths.Exit);
+		IF offset < end THEN	(* currently within dash => end properly *)
+			IF width = 0 THEN
+				dst.AddExit(scan.dx, scan.dy)
+			ELSE
+				ctxt.dashPath.AddExit(scan.dx, scan.dy);
+				dscan.Open(ctxt.dashPath, 0);
+				GetStrokeOutline(ctxt, dscan, dst)
+			END
+		END;
+		scan.Scan( )
+	END GetDashOutline;
+
+	(** store outline/dashes of current path in specified path **)
+	PROCEDURE GetOutline* (ctxt: Context; dst: GfxPaths.Path);
+		VAR scan: GfxPaths.Scanner;
+	BEGIN
+		ASSERT(dst # ctxt.path, 100);
+		ctxt.Flatten();
+		dst.Clear();
+		scan.Open(ctxt.path, 0);
+		WHILE scan.elem = GfxPaths.Enter DO
+			IF ctxt.dashPatLen > 0 THEN
+				GetDashOutline(ctxt, scan, dst)
+			ELSE
+				GetStrokeOutline(ctxt, scan, dst)
+			END
+		END
+	END GetOutline;
+
+
+	(**--- Drawing Operations ---**)
+
+	(** draw current path in requested mode **)
+	PROCEDURE Render* (ctxt: Context; mode: SET);
+	BEGIN
+		ASSERT(~(InPath IN ctxt.mode), 100);
+		EXCL(mode, Record);
+		IF mode # {} THEN
+			ctxt.Render(mode)
+		END
+	END Render;
+
+	(** draw given path in requested mode **)
+	PROCEDURE DrawPath* (ctxt: Context; path: GfxPaths.Path; mode: SET);
+		VAR scan: GfxPaths.Scanner;
+	BEGIN
+		ASSERT(~(InPath IN ctxt.mode), 100);
+		IF path = ctxt.path THEN
+			Render(ctxt, mode)
+		ELSE
+			ctxt.Begin(mode);
+			scan.Open(path, 0);
+			WHILE scan.elem # GfxPaths.Stop DO
+				CASE scan.elem OF
+				| GfxPaths.Enter: ctxt.Enter(scan.x, scan.y, scan.dx, scan.dy)
+				| GfxPaths.Line: ctxt.Line(scan.x, scan.y);
+				| GfxPaths.Arc: ctxt.Arc(scan.x, scan.y, scan.x0, scan.y0, scan.x1, scan.y1, scan.x2, scan.y2)
+				| GfxPaths.Bezier: ctxt.Bezier(scan.x, scan.y, scan.x1, scan.y1, scan.x2, scan.y2)
+				| GfxPaths.Exit: ctxt.Exit(scan.dx, scan.dy)
+				END;
+				scan.Scan( );
+			END;
+			ctxt.End()
+		END
+	END DrawPath;
+
+	(** draw line in requested mode **)
+	PROCEDURE DrawLine* (ctxt: Context; x0, y0, x1, y1: REAL; mode: SET);
+	BEGIN
+		IF (x0=x1)&(y0=y1) THEN RETURN END; (*optimization PH 2012*)
+		ASSERT(~(InPath IN ctxt.mode), 100);
+		ASSERT(mode * {Fill, Clip, EvenOdd} = {}, 101);
+		ctxt.Begin(mode);
+		ctxt.Enter(x0, y0, 0, 0);
+		ctxt.Line(x1, y1);
+		ctxt.Exit(0, 0);
+		ctxt.End()
+	END DrawLine;
+
+	(** draw arc in requested mode (start and end angle in radians; negative radius for clockwise arc) **)
+	PROCEDURE DrawArc* (ctxt: Context; x, y, r, start, end: REAL; mode: SET);
+		VAR x1, y1, x2, y2: REAL;
+	BEGIN
+		ASSERT(~(InPath IN ctxt.mode), 100);
+		ASSERT(mode * {Fill, Clip, EvenOdd} = {}, 101);
+		IF r > 0 THEN x1 := x + r; y1 := y; x2 := x; y2 := y + r
+		ELSIF r < 0 THEN r := -r; x1 := x; y1 := y + r; x2 := x + r; y2 := y
+		ELSE RETURN
+		END;
+		ctxt.Begin(mode);
+		ctxt.Enter(x + r * Math.cos(start), y + r * Math.sin(start), 0, 0);
+		ctxt.Arc(x + r * Math.cos(end), y + r * Math.sin(end), x, y, x1, y1, x2, y2);
+		ctxt.End()
+	END DrawArc;
+
+	(** draw rectangle in requested mode **)
+	PROCEDURE DrawRect* (ctxt: Context; x0, y0, x1, y1: REAL; mode: SET);
+	BEGIN
+		ASSERT(~(InPath IN ctxt.mode), 100);
+		ctxt.Begin(mode);
+		ctxt.Rect(x0, y0, x1, y1);
+		ctxt.End()
+	END DrawRect;
+
+	(** draw circle in requested mode (clockwise if r > 0, counterclockwise if r < 0) **)
+	PROCEDURE DrawCircle* (ctxt: Context; x, y, r: REAL; mode: SET);
+	BEGIN
+		ASSERT(~(InPath IN ctxt.mode), 100);
+		ctxt.Begin(mode);
+		ctxt.Ellipse(x, y, r, ABS(r));
+		ctxt.End()
+	END DrawCircle;
+
+	(** draw ellipse in requested mode (clockwise if rx*ry > 0, counterclockwise if rx*ry < 0) **)
+	PROCEDURE DrawEllipse* (ctxt: Context; x, y, rx, ry: REAL; mode: SET);
+	BEGIN
+		ASSERT(~(InPath IN ctxt.mode), 100);
+		ctxt.Begin(mode);
+		ctxt.Ellipse(x, y, rx, ry);
+		ctxt.End()
+	END DrawEllipse;
+
+	(** draw string at given coordinates and move current point to string end **)
+	PROCEDURE DrawStringAt* (ctxt: Context; x, y: REAL; str: ARRAY OF CHAR);
+	BEGIN
+		ASSERT(~(InPath IN ctxt.mode), 100);
+		ctxt.Begin({Fill});
+		ctxt.Show(x, y, str);
+		ctxt.End()
+	END DrawStringAt;
+
+	(** draw string at current point and move current point to string end **)
+	PROCEDURE DrawString* (ctxt: Context; str: ARRAY OF CHAR);
+	BEGIN
+		ASSERT(~(InPath IN ctxt.mode), 100);
+		ctxt.Begin({Fill});
+		ctxt.Show(ctxt.cpx, ctxt.cpy, str);
+		ctxt.End()
+	END DrawString;
+
+
+	(**--- Images and Patterns ---**)
+
+	(** draw image at given point **)
+	PROCEDURE DrawImageAt* (ctxt: Context; x, y: REAL; img: GfxImages.Image; VAR filter: GfxImages.Filter);
+	BEGIN
+		ctxt.Image(x, y, img, filter)
+	END DrawImageAt;
+
+	(** return new pattern **)
+	PROCEDURE NewPattern* (ctxt: Context; img: GfxImages.Image; px, py: REAL): Pattern;
+	BEGIN
+		RETURN ctxt.NewPattern(img, px, py)
+	END NewPattern;
+
+
+	(**--- Default Methods ---**)
+
+	PROCEDURE DefRect* (ctxt: Context; x0, y0, x1, y1: REAL);
+	BEGIN
+		ctxt.Enter(x0, y0, 0, y0 - y1);
+		ctxt.Line(x1, y0); ctxt.Line(x1, y1); ctxt.Line(x0, y1); ctxt.Line(x0, y0);
+		ctxt.Exit(x1 - x0, 0)
+	END DefRect;
+
+	PROCEDURE DefEllipse* (ctxt: Context; x, y, rx, ry: REAL);
+		VAR xr: REAL;
+	BEGIN
+		xr := x + rx;
+		IF xr # x THEN
+			ctxt.Enter(xr, y, 0, ry);
+			ctxt.Arc(xr, y, x, y, xr, y, x, y + ry);
+			ctxt.Exit(0, ry)
+		END
+	END DefEllipse;
+
+	(*--- Initialization of Standard Colors ---*)
+
+	PROCEDURE InitColors;
+		PROCEDURE init (VAR col: Color; r, g, b: INTEGER);
+		BEGIN
+			col.r := r; col.g := g; col.b := b; col.a := 255
+		END init;
+	BEGIN
+		init(Black, 0, 0, 0); init(White, 255, 255, 255); init(Red, 255, 0, 0); init(Green, 0, 255, 0); init(Blue, 0, 0, 255);
+		init(Cyan, 0, 255, 255); init(Magenta, 255, 0, 255); init(Yellow, 255, 255, 0);
+		init(LGrey, 192, 192, 192); init(MGrey, 160, 160, 160); init(DGrey, 128, 128, 128)
+	END InitColors;
+
+BEGIN
+	InitColors;
+	DefaultCap := ButtCap; DefaultJoin := MiterJoin
+END Gfx.

BIN
source/AGfx.Tool


+ 210 - 0
source/AGfxBuffer.Mod

@@ -0,0 +1,210 @@
+(* ETH Oberon, Copyright 2001 ETH Zuerich Institut fuer Computersysteme, ETH Zentrum, CH-8092 Zuerich.
+Refer to the "General ETH Oberon System Source License" contract available at: http://www.oberon.ethz.ch/ *)
+
+MODULE GfxBuffer; (** portable *)	(* eos  *)
+(** AUTHOR "eos"; PURPOSE "Raster contexts rendering into background buffers"; *)
+
+	(*
+		10.12.98 - first version; derived from GfxDev
+		25.8.99 - replaced GfxMaps with Images/GfxImages
+		10.8.99 - scratched SetPoint, added Close method
+		13.02.2000 - new get/set clip methods
+		10.05.2000 - Rect now ignores empty rectangles
+	*)
+
+
+	IMPORT
+		Images := Raster, GfxMatrix, GfxImages, GfxRegions, Gfx, GfxRaster;
+
+
+	TYPE
+		Context* = OBJECT(GfxRaster.Context)
+		VAR
+			orgX*, orgY*: REAL;	(** origin of default coordinate system **)
+			scale*: REAL;	(** default scale factor **)
+			bgCol*: Gfx.Color;	(** background color for erasing **)
+			img*: Images.Image;	(** target buffer **)
+			pix: Images.Pixel;
+			compOp:SHORTINT; (* composition operation for raster device *)
+
+			(** initialize buffered context **)
+			PROCEDURE InitBuffer* (img: Images.Image);
+			BEGIN
+				InitRaster();
+				SELF.compOp := 1; (* Copy Op is default *)
+				SELF.img := img;
+				SELF.SetCoordinates(0, 0, 1);
+				SELF.SetBGColor(Gfx.White);
+				SELF.Reset();
+			END InitBuffer;
+
+			(*--- Methods ---*)
+
+			(** current transformation matrix **)
+			PROCEDURE ResetCTM*();
+			BEGIN
+				GfxMatrix.Translate(GfxMatrix.Identity, SELF.orgX, SELF.orgY, SELF.ctm);
+				GfxMatrix.Scale(SELF.ctm, SELF.scale, SELF.scale, SELF.ctm)
+			END ResetCTM;
+
+			(** clipping **)
+			PROCEDURE ResetClip*();
+			BEGIN
+				ResetClip^();
+				SELF.clipReg.SetToRect(0, 0, SHORT(SELF.img.width), SHORT(SELF.img.height))
+			END ResetClip;
+
+			(** images and patterns **)
+			PROCEDURE Image* (x, y: REAL; img: Images.Image; VAR filter: GfxImages.Filter);
+				VAR m: GfxMatrix.Matrix; dx, dy, llx, lly, urx, ury: INTEGER; col: Images.Pixel;
+			BEGIN
+				GfxMatrix.Translate(SELF.ctm, x, y, m);
+				dx := SHORT(ENTIER(m[2, 0] + 0.5));
+				dy := SHORT(ENTIER(m[2, 1] + 0.5));
+				col := filter.col;
+				Images.SetModeColor(filter, SELF.fillCol.r, SELF.fillCol.g, SELF.fillCol.b);
+				IF (filter.hshift # GfxImages.hshift) & (dx + 0.1 < m[2, 0]) & (m[2, 0] < dx + 0.9) OR
+					(filter.vshift # GfxImages.vshift) & (dy + 0.1 < m[2, 1]) & (m[2, 1] < dy + 0.9) OR
+					GfxMatrix.Scaled(m) OR
+					GfxMatrix.Rotated(m)
+				THEN
+					GfxImages.Transform(img, SELF.img, m, filter)
+				ELSE
+					llx := 0; lly := 0; urx := SHORT(img.width); ury := SHORT(img.height);
+					GfxRegions.ClipRect(llx, lly, urx, ury, SELF.clipReg.llx - dx, SELF.clipReg.lly - dy, SELF.clipReg.urx - dx, SELF.clipReg.ury - dy);
+					IF SELF.clipReg.llx >  dx THEN dx := SELF.clipReg.llx END;
+					IF SELF.clipReg.lly > dy THEN dy := SELF.clipReg.lly END;
+					IF dx + urx > SELF.img.width THEN urx := SHORT(SELF.img.width - dx) END;
+					IF dy + ury > SELF.img.height THEN ury := SHORT(SELF.img.height - dy) END;
+					IF dx < 0 THEN llx := -dx; dx := 0 END;
+					IF dy < 0 THEN lly := -dy; dy := 0 END;
+					IF (llx < urx) & (lly < ury) THEN
+						IF ~GfxRegions.RectEmpty(llx, lly, urx, ury) THEN
+							Images.Copy(img, SELF.img, llx, lly, urx, ury, dx, dy, filter)
+						END
+					END
+				END;
+				Images.SetModeColor(filter, ORD(col[Images.r]), ORD(col[Images.g]), ORD(col[Images.b]))
+			END Image;
+
+			PROCEDURE SetColPat* (col: Gfx.Color; pat: Gfx.Pattern);
+			BEGIN
+				SELF.col := col; SELF.pat := pat;
+				Images.SetRGBA(SELF.pix, col.r, col.g, col.b, col.a)
+			END SetColPat;
+
+			PROCEDURE PaintDot* (x, y: LONGINT);
+				VAR px, py: LONGINT; mode: Images.Mode;
+			BEGIN
+				IF (SELF.clipState = GfxRaster.In) OR
+					(SELF.clipState = GfxRaster.InOut) & SELF.clipReg.RectInside(SHORT(x), SHORT(y), SHORT(x+1), SHORT(y+1))
+				THEN
+					IF SELF.pat = NIL THEN
+						Images.InitMode(mode, SELF.compOp);
+						Images.Put(SELF.img, SHORT(x), SHORT(y), SELF.pix, mode)
+					ELSE
+						px := (x - ENTIER(SELF.orgX + SELF.pat.px + 0.5)) MOD SELF.pat.img.width;
+						py := (y - ENTIER(SELF.orgY + SELF.pat.py + 0.5)) MOD SELF.pat.img.height;
+						Images.InitModeColor(mode, Images.srcOverDst, SELF.col.r, SELF.col.g, SELF.col.b);
+						Images.Copy(SELF.pat.img, SELF.img, px, py, px+1, py+1, SHORT(x), SHORT(y), mode)
+					END
+				END
+			END PaintDot;
+
+			PROCEDURE PaintRect* (llx, lly, urx, ury: LONGINT);
+				VAR data: RegData; mode: Images.Mode;
+			BEGIN
+				IF (SELF.clipState # GfxRaster.Out) & (llx < urx) & (lly < ury) THEN
+					IF SELF.pat = NIL THEN
+						IF SELF.clipState = GfxRaster.In THEN
+							Images.InitMode(mode, SELF.compOp);
+							Images.Fill(SELF.img, SHORT(llx), SHORT(lly), SHORT(urx), SHORT(ury), SELF.pix, mode)
+						ELSE
+							data.bc := SELF;
+							SELF.clipReg.Enumerate(SHORT(llx), SHORT(lly), SHORT(urx), SHORT(ury), Color, data)
+						END
+					ELSE
+						data.bc := SELF;
+						data.dx := SHORT(ENTIER(SELF.orgX + SELF.pat.px + 0.5));
+						data.dy := SHORT(ENTIER(SELF.orgY + SELF.pat.py + 0.5));
+						Images.InitModeColor(data.mode, Images.srcOverDst, SELF.col.r, SELF.col.g, SELF.col.b);
+						SELF.clipReg.Enumerate(SHORT(llx), SHORT(lly), SHORT(urx), SHORT(ury), Tile, data)
+					END
+				END
+			END PaintRect;
+
+			(*--- Exported Interface ---*)
+
+			(** set default coordinate origin and scale factor **)
+			PROCEDURE SetCoordinates* (x, y, scale: REAL);
+			BEGIN
+				SELF.orgX := x; SELF.orgY := y; SELF.scale := scale
+			END SetCoordinates;
+
+			(** set background color **)
+			PROCEDURE SetBGColor* (col: Gfx.Color);
+			BEGIN
+				SELF.bgCol := col
+			END SetBGColor;
+
+			(** set composition operation as defined in Raster **)
+			PROCEDURE SetCompOp* (op:SHORTINT);
+			BEGIN
+				SELF.compOp := op
+			END SetCompOp;
+
+		END Context;
+
+		RegData = RECORD (GfxRegions.EnumData)
+			dx, dy: INTEGER;
+			bc: Context;
+			mode: Images.Mode;
+		END;
+
+
+	(*--- Rendering ---*)
+
+	PROCEDURE Color (llx, lly, urx, ury: INTEGER; VAR data: GfxRegions.EnumData);
+		VAR bc: Context; mode: Images.Mode;
+	BEGIN
+		bc := data(RegData).bc;
+		Images.InitMode(mode, bc.compOp);
+		Images.Fill(bc.img, llx, lly, urx, ury, bc.pix, mode)
+	END Color;
+
+	PROCEDURE Tile (llx, lly, urx, ury: INTEGER; VAR data: GfxRegions.EnumData);
+		VAR bc: Context;
+	BEGIN
+		WITH data: RegData DO
+			bc := data.bc;
+			Images.FillPattern(bc.pat.img, bc.img, llx, lly, urx, ury, data.dx, data.dy, data.mode)
+		END
+	END Tile;
+
+	(*--- Exported Interface ---*)
+
+	(** set default coordinate origin and scale factor **)
+	PROCEDURE SetCoordinates* (bc: Context; x, y, scale: REAL);
+	BEGIN
+		bc.SetCoordinates (x, y, scale);
+	END SetCoordinates;
+
+	(** set background color **)
+	PROCEDURE SetBGColor* (bc: Context; col: Gfx.Color);
+	BEGIN
+		bc.SetBGColor (col);
+	END SetBGColor;
+
+	(** set composition operation as defined in Raster **)
+	PROCEDURE SetCompOp* (bc: Context; op:SHORTINT);
+	BEGIN
+		bc.SetCompOp (op);
+	END SetCompOp;
+
+	(** initialize buffered context **)
+	PROCEDURE Init* (bc: Context; img: Images.Image);
+	BEGIN
+		bc.InitBuffer(img);
+	END Init;
+
+END GfxBuffer.

+ 1129 - 0
source/AGfxFonts.Mod

@@ -0,0 +1,1129 @@
+(* ETH Oberon, Copyright 2001 ETH Zuerich Institut fuer Computersysteme, ETH Zentrum, CH-8092 Zuerich.
+Refer to the "General ETH Oberon System Source License" contract available at: http://www.oberon.ethz.ch/ *)
+
+MODULE GfxFonts; (** non-portable *)	(* eos  *)
+(** AUTHOR "eos"; PURPOSE "Gfx font engine"; *)
+
+	(*
+		8.1.98 - GetInstance now transforms an existing bounding box correctly
+		13.1.98 - bug fix in OpenRaster, font.ymax was not computed correctly for scaled raster fonts in OpenRaster
+		13.1.98 - improved GetStringWidth; uses outline metrics now if images would be taken from outline, too
+		9.2.98 - adapted SplineToBezier to new behaviour of GfxPaths.EnumSpline (no Enter/Exit)
+		18.9.98 - major cleanup of both interface and implementation (support for other font formats, switch to GfxMaps,
+			aging of cached chars, use GfxFonts0 to enumerate available raster fonts)
+		9.12.98 - raster files; adaptation to new GfxMaps
+		16.2.99 - bugfix in InitDefault (m[1, 1] wasn't set), tnx to pjm
+		12.3.99 - bugfix in WFGetWidth (dy = dx)
+		20.4.99 - put point size in separate field instead of merging it with font matrix
+		8.6.99 - don't set niceMaps for large raster fonts if outline exists
+		16.7.99 - try to open raster font before calling extensions
+		25.8.99 - use Images and GfxImages instead of GfxMaps, PictImages instead of GfxPictures
+		28.10.99 - WarpMap doesn't try to create empty images anymore
+		21.02.2000 - introduced registered font families to improve opening foreign font file formats
+		14.03.2000 - improved wildcard handling for registered font families (key without value is command)
+		30.03.2000 - accept "M.P=*" as wildcard
+		24.05.2000 - integrated GfxFonts0, no longer imports Fonts, for the moment no registered extensions
+	*)
+
+	IMPORT
+		SYSTEM, KernelLog, Commands, Files, Configuration, Math, Raster, GfxMatrix,
+		GfxImages, GfxPaths, GfxRegions;
+
+
+	CONST
+		FontNameLen* = 64;
+		MaxCachedChars = 512;	(* maximal number of cached characters *)
+		MetaFontTag = 01F7H; OldMetaFontTag = 701H - 1000H;	(* = F701H *)
+		MaxBezierPoints = 3*GfxPaths.MaxSplinePoints + 1;
+		DPI = 91.44;
+		FontId = 0DBX;
+
+
+	TYPE
+		FontName* = ARRAY FontNameLen OF CHAR;
+
+		(* Metafont outlines **)
+		Outline = POINTER TO OutlineDesc;
+		OutlineDesc = RECORD
+			width: ARRAY 256 OF REAL;	(* width including left and right side bearings (0 if character undefined) *)
+			len: ARRAY 256 OF SHORTINT;	(* number of subpaths of each character *)
+			path: GfxPaths.Path;	(* path containing character outlines *)
+			pos: ARRAY 256 OF INTEGER;	(* positions of characters within path *)
+			xmin, ymin, xmax, ymax: REAL;	(* union of character bounding boxes *)
+		END;
+
+		(* cached characters *)
+		Char = POINTER TO CharDesc;
+		CharDesc = RECORD
+			x, y, dx, dy: REAL;	(* metrics *)
+			map: Raster.Image;	(* pixels *)
+			used: INTEGER;	(* number of accesses to this character *)
+		END;
+
+		(* raster file *)
+		RasterChar = POINTER TO RasterCharDesc;
+		RasterCharDesc = RECORD
+			dx, x, y, w, h: INTEGER;
+			adr: ADDRESS;
+		END;
+		RasterFile = POINTER TO RasterFileDesc;
+		RasterFileDesc = RECORD
+			xmin, ymin, xmax, ymax: INTEGER;
+			char: ARRAY 256 OF RasterChar;
+			mem: POINTER TO ARRAY OF CHAR;
+		END;
+
+		(** font structure **)
+		Font* = POINTER TO FontDesc;
+		Methods* = POINTER TO MethodDesc;
+		FontDesc* = RECORD
+			class*: Methods;
+			name*: FontName;	(** font name **)
+			ptsize*: INTEGER;	(** point size **)
+			mat*, wmat: GfxMatrix.Matrix;	(** font matrix **)
+			xmin*, ymin*, xmax*, ymax*: INTEGER;	(** union of character bounding boxes **)
+			niceMaps*: BOOLEAN;	(** true if returned bitmaps look better than the filled outlines **)
+			outline: Outline;	(* outline, if available *)
+			prev, next: Font;	(* previous and next font in font cache *)
+			char: ARRAY 256 OF Char;	(* cached characters *)
+			rfile: RasterFile;	(* link to raster file *)
+			
+			PROCEDURE Derive*(ptsize: INTEGER; VAR mat: GfxMatrix.Matrix): Font; END Derive;
+			PROCEDURE GetWidth*(ch: CHAR; VAR dx, dy: REAL); END GetWidth;
+			PROCEDURE GetMap*(ch: CHAR; VAR x, y, dx, dy: REAL; VAR map: Raster.Image); END GetMap;
+			PROCEDURE GetOutline*(ch: CHAR; x, y: REAL; path: GfxPaths.Path); END GetOutline;
+		END;
+
+		MethodDesc* = RECORD
+			derive*: PROCEDURE (font: Font; ptsize: INTEGER; VAR mat: GfxMatrix.Matrix): Font;
+			getwidth*: PROCEDURE (font: Font; ch: CHAR; VAR dx, dy: REAL);
+			getmap*: PROCEDURE (font: Font; ch: CHAR; VAR x, y, dx, dy: REAL; VAR map: Raster.Image);
+			getoutline*: PROCEDURE (font: Font; ch: CHAR; x, y: REAL; path: GfxPaths.Path);
+		END;
+
+		PathEnumData = RECORD (GfxPaths.EnumData)
+			xc, yc: ARRAY MaxBezierPoints OF REAL;	(* control points for spline-Bezier conversion *)
+			n: INTEGER;	(* number of control points *)
+			lx, ly: REAL;	(* current point for converting path to region *)
+			px, py: INTEGER;	(* current region point coordinates *)
+			region: GfxRegions.Region;
+		END;
+
+		RegEnumData = RECORD (GfxRegions.EnumData)
+			map: Raster.Image;
+			dx, dy: INTEGER;
+		END;
+
+
+	VAR
+		Default*: Font;	(** system default font **)
+		OpenProc*: PROCEDURE (VAR family, style: ARRAY OF CHAR; ptsize: INTEGER; VAR mat: GfxMatrix.Matrix): Font;
+		FClass, OFClass, WFClass, OWFClass, OClass: Methods;	(* builtin font classes *)
+		Cache: Font;	(* sentinel for list of cached fonts *) (* RACE ?*)
+		Chars: LONGINT;	(* current number of cached characters *)
+	(*	TmpPath: GfxPaths.Path;
+		TmpRegion: GfxRegions.Region; *)
+
+	(*--- File Directory ---*)
+
+	PROCEDURE Append(VAR to(** in/out *): ARRAY OF CHAR; this: ARRAY OF CHAR);
+		VAR i, j, l: LONGINT;
+	BEGIN
+		i := 0;
+		WHILE to[i] # 0X DO
+			INC(i)
+		END;
+		l := LEN(to)-1; j := 0;
+		WHILE (i < l) & (this[j] # 0X) DO
+			to[i] := this[j]; INC(i); INC(j)
+		END;
+		to[i] := 0X
+	END Append;
+
+	PROCEDURE AppendCh(VAR to(** in/out *): ARRAY OF CHAR; this: CHAR);
+		VAR i: LONGINT;
+	BEGIN
+		i := 0;
+		WHILE to[i] # 0X DO
+			INC(i)
+		END;
+		IF i < (LEN(to)-1) THEN
+			to[i] := this; to[i+1] := 0X
+		END
+	END AppendCh;
+
+	PROCEDURE IntToStr(val: LONGINT; VAR str: ARRAY OF CHAR);
+		VAR
+			i, j: LONGINT;
+			digits: ARRAY 16 OF LONGINT;
+	BEGIN
+		IF val = MIN(LONGINT) THEN
+			COPY("-2147483648", str);
+			RETURN
+		END;
+		IF val < 0 THEN
+			val := -val; str[0] := "-"; j := 1
+		ELSE
+			j := 0
+		END;
+		i := 0;
+		REPEAT
+			digits[i] := val MOD 10; INC(i); val := val DIV 10
+		UNTIL val = 0;
+		DEC(i);
+		WHILE i >= 0 DO
+			str[j] := CHR(digits[i]+ORD("0")); INC(j); DEC(i)
+		END;
+		str[j] := 0X
+	END IntToStr;
+
+	PROCEDURE Find (VAR family, style: ARRAY OF CHAR; sppm: INTEGER; VAR fname: ARRAY OF CHAR; VAR fppm: INTEGER);
+		VAR
+			enum: Files.Enumerator; i, time, date, size: LONGINT; error, ppm: INTEGER;
+			s: ARRAY 4 OF CHAR; pattern: ARRAY 64 OF CHAR; name: Files.FileName; flags: SET;
+	BEGIN
+		fname[0] := 0X; fppm := 0;
+		error := MAX(INTEGER);
+		COPY(family, pattern); AppendCh(pattern, "*");
+		IF style = "Bold" THEN AppendCh(pattern, "b")
+		ELSIF style = "Italic" THEN AppendCh(pattern, "i")
+		ELSIF style = "Medium" THEN AppendCh(pattern, "m")
+		ELSIF style = "BoldItalic" THEN AppendCh(pattern, "j")
+		END;
+		Append(pattern, ".*.Fnt");
+		NEW(enum); enum.Open(pattern, {});
+		WHILE (error # 0) & enum.GetEntry(name, flags, time, date, size) DO
+			i := 0; ppm := 0;
+			WHILE (name[i] # 0X) & (name[i] # ".") & (name[i] < "0") OR ("9" < name[i]) DO INC(i) END;
+			WHILE ("0" <= name[i]) & (name[i] <= "9") DO
+				ppm := 10*ppm + ORD(name[i]) - ORD("0");
+				INC(i)
+			END;
+			IF ppm = 0 THEN ppm := 10 END;
+			IF (style = "") & (name[i] = ".") OR (CAP(style[0]) = CAP(name[i])) THEN
+				WHILE (name[i] # 0X) & (name[i] # ".") DO INC(i) END;
+				IF name[i] = "." THEN INC(i) END;
+				s[0] := name[i]; s[1] := name[i+1]; s[2] := name[i+2]; s[3] := 0X;
+				IF s = "Scn" THEN
+				ELSIF s = "Pr2" THEN ppm := SHORT(200 * LONG(ppm) DIV 91)
+				ELSIF s = "Pr3" THEN ppm := SHORT(300 * LONG(ppm) DIV 91)
+				ELSIF s = "Pr6" THEN ppm := SHORT(600 * LONG(ppm) DIV 91)
+				ELSE ppm := MIN(INTEGER)
+				END;
+				IF ABS(sppm - ppm) < error THEN
+					error := ABS(sppm - ppm); COPY(name, fname); fppm := ppm
+				END
+			END
+		END;
+		enum.Close
+	END Find;
+
+
+	(*--- Outlines ---*)
+
+	(* append path element to Bezier control points *)
+	PROCEDURE AddSplineElem (VAR data: GfxPaths.EnumData);
+		CONST
+			sqrt3 = 1.7320508; t = 4/3*(sqrt3 - 1);
+		VAR
+			rx, ry, trx, try: REAL;
+	BEGIN
+		WITH data: PathEnumData DO
+			CASE data.elem OF
+			| GfxPaths.Line:	(* spline is line *)
+				data.xc[data.n] := data.x; data.yc[data.n] := data.y; INC(data.n)
+			| GfxPaths.Arc:	(* spline is full circle *)
+				rx := data.x - data.x0; ry := data.y - data.y0;
+				trx := t * rx; try := t * ry;
+				data.xc[data.n] := data.x0 + rx - try; data.yc[data.n] := data.y0 + ry + trx; INC(data.n);
+				data.xc[data.n] := data.x0 - ry + trx; data.yc[data.n] := data.y0 + rx + try; INC(data.n);
+				data.xc[data.n] := data.x0 - ry; data.yc[data.n] := data.y0 + rx; INC(data.n);
+				data.xc[data.n] := data.x0 - ry - trx; data.yc[data.n] := data.y0 + rx + try; INC(data.n);
+				data.xc[data.n] := data.x0 - rx - try; data.yc[data.n] := data.y0 - ry + trx; INC(data.n);
+				data.xc[data.n] := data.x0 - rx; data.yc[data.n] := data.y0 - ry; INC(data.n);
+				data.xc[data.n] := data.x0 - rx + try; data.yc[data.n] := data.y0 - ry - trx; INC(data.n);
+				data.xc[data.n] := data.x0 + ry - trx; data.yc[data.n] := data.y0 - rx - try; INC(data.n);
+				data.xc[data.n] := data.x0 + ry; data.yc[data.n] := data.y0 - rx; INC(data.n);
+				data.xc[data.n] := data.x0 + ry + trx; data.yc[data.n] := data.y0 - rx + try; INC(data.n);
+				data.xc[data.n] := data.x0 + rx + try; data.yc[data.n] := data.y0 + ry - trx; INC(data.n);
+				data.xc[data.n] := data.x0 + rx; data.yc[data.n] := data.y0 + ry; INC(data.n)
+			| GfxPaths.Bezier:
+				data.xc[data.n] := data.x1; data.yc[data.n] := data.y1; INC(data.n);
+				data.xc[data.n] := data.x2; data.yc[data.n] := data.y2; INC(data.n);
+				data.xc[data.n] := data.x; data.yc[data.n] := data.y; INC(data.n)
+			END
+		END
+	END AddSplineElem;
+
+	(* convert natural spline to Bezier control points *)
+	PROCEDURE SplineToBezier (VAR x, y: ARRAY OF REAL; VAR n: LONGINT; closed: BOOLEAN);
+		VAR data: PathEnumData;
+	BEGIN
+		data.n := 1; data.x := x[0]; data.y := y[0];
+		GfxPaths.EnumSpline(x, y, SHORT(n), closed, AddSplineElem, data);
+		n := 1;
+		WHILE n < data.n DO
+			x[n] := data.xc[n]; y[n] := data.yc[n]; INC(n)
+		END
+	END SplineToBezier;
+
+	(* convert Bezier2 to Bezier *)
+	PROCEDURE Bezier2ToBezier (VAR x, y: ARRAY OF REAL; VAR n: LONGINT);
+		VAR nout, m: LONGINT;
+	BEGIN
+		IF ODD(n) THEN
+			nout := (n - 1) DIV 2 * 3 + 1;
+			m := nout
+		ELSE	(* ends with line *)
+			nout := (n - 2) DIV 2 * 3 + 2;
+			m := nout-1;
+			x[m] := x[n-1]; y[m] := y[n-1]
+		END;
+		WHILE m > 0 DO
+			DEC(m); DEC(n);
+			x[m] := x[n]; y[m] := y[n];
+			DEC(m); DEC(n);
+			x[m] := (1/3)*(2*x[n] + x[m+1]); y[m] := (1/3)*(2*y[n] + y[m+1]);
+			DEC(m);
+			x[m] := (1/3)*(2*x[n] + x[n-1]); y[m] := (1/3)*(2*y[n] + y[n-1])
+		END;
+		n := nout
+	END Bezier2ToBezier;
+
+	(* load character outlines *)
+		PROCEDURE LoadOutline (outline: Outline; VAR r: Files.Reader);
+		CONST
+			polygon = 0; bezier = 1; spline = 2; bezier2 = 3;
+			maxNofContours = 128;
+
+		VAR
+			minY, maxY, base, i, y, ntypes, nchars, x, left, ncontours, n, m, cont, k: LONGINT; scale: REAL; ch: CHAR;
+			type, pred, succ, last: ARRAY maxNofContours OF LONGINT; str: ARRAY 32 OF CHAR; kind: ARRAY 5 OF INTEGER;
+			closed: BOOLEAN; px, py: POINTER TO ARRAY maxNofContours, MaxBezierPoints OF REAL;
+			done: ARRAY maxNofContours OF BOOLEAN;
+
+		PROCEDURE coincident (px, py, qx, qy: REAL; dist: LONGINT): BOOLEAN;
+		BEGIN
+			RETURN (ABS(px - qx) <= dist) & (ABS(py - qy) <= dist)
+		END coincident;
+
+	BEGIN
+		minY := MAX(LONGINT); maxY := MIN(LONGINT); base := minY;
+		FOR i := 1 TO 5 DO
+			r.RawNum(y);
+			IF y > maxY THEN maxY := y END;
+			IF y < minY THEN base := minY; minY := y
+			ELSIF y < base THEN base := y
+			END
+		END;
+		scale := 1/(maxY - minY);
+
+		NEW(outline.path);
+		outline.path.Clear();
+		outline.xmin := MAX(REAL); outline.ymin := MAX(REAL);
+		outline.xmax := MIN(REAL); outline.ymax := MIN(REAL);
+		NEW(px); NEW(py);
+		ntypes := 1;
+		r.RawNum(nchars);
+		WHILE nchars > 0 DO
+			DEC(nchars);
+			r.Char(ch); r.RawNum(x); left := x;
+			r.RawNum(x);
+			IF x >= left THEN
+				outline.width[ORD(ch)] := scale * SHORT(x - left)
+			ELSE
+				outline.width[ORD(ch)] := scale * SHORT(left - x);
+				left := x
+			END;
+
+			(* read contour curves *)
+			r.RawNum(ncontours);
+			n := 0;
+			WHILE n < ncontours DO
+				r.RawNum(type[n]);
+				IF type[n] = ntypes THEN
+					r.RawString(str);
+					ASSERT(str = "Graphic");
+					r.RawString(str);
+					IF str = "PolygonDesc" THEN kind[type[n]] := polygon
+					ELSIF str = "BezierDesc" THEN kind[type[n]] := bezier
+					ELSIF str = "SplineDesc" THEN kind[type[n]] := spline
+					ELSIF str = "Bezier2Desc" THEN kind[type[n]] := bezier2
+					ELSE HALT(101)
+					END;
+					INC(ntypes)
+				END;
+				r.RawBool(closed);
+				IF closed THEN pred[n] := n; succ[n] := n
+				ELSE pred[n] := -1; succ[n] := -1
+				END;
+				r.RawNum(m);
+				DEC(m);
+				FOR i := 0 TO m DO
+					r.RawNum(x); r.RawNum(y);
+					px[n, i] := x - left; py[n, i] := y - base
+				END;
+				IF m < 1 THEN
+					DEC(ncontours)
+				ELSE
+					IF closed THEN
+						INC(m); px[n, m] := px[n, 0]; py[n, m] := py[n, 0]
+					END;
+					IF kind[type[n]] = spline THEN
+						INC(m);
+						SplineToBezier(px[n], py[n], m, closed);
+						DEC(m)
+					ELSIF kind[type[n]] = bezier2 THEN
+						INC(m);
+						Bezier2ToBezier(px[n], py[n], m);
+						DEC(m)
+					END;
+					FOR i := 0 TO m DO
+						IF px[n, i] < outline.xmin THEN outline.xmin := px[n, i] END;
+						IF px[n, i] > outline.xmax THEN outline.xmax := px[n, i] END;
+						IF py[n, i] < outline.ymin THEN outline.ymin := py[n, i] END;
+						IF py[n, i] > outline.ymax THEN outline.ymax := py[n, i] END
+					END;
+					last[n] := m;
+					INC(n)
+				END
+			END;
+			outline.len[ORD(ch)] := SHORT(SHORT(ncontours));
+
+			(* find connected curves *)
+			FOR i := 0 TO 3 DO
+				n := 0;
+				WHILE n < outline.len[ORD(ch)] DO
+					m := n + 1;
+					WHILE (pred[n] < 0) & (m < outline.len[ORD(ch)]) DO
+						IF (succ[m] < 0) & coincident(px[n, 0], py[n, 0], px[m, last[m]], py[m, last[m]], i) THEN
+							px[m, last[m]] := px[n, 0]; py[m, last[m]] := py[n, 0];
+							pred[n] := m; succ[m] := n
+						END;
+						INC(m)
+					END;
+					m := n + 1;
+					WHILE (succ[n] < 0) & (m < outline.len[ORD(ch)]) DO
+						IF (pred[m] < 0) & coincident(px[n, last[n]], py[n, last[n]], px[m, 0], py[m, 0], i) THEN
+							px[n, last[n]] := px[m, 0]; py[n, last[n]] := py[m, 0];
+							succ[n] := m; pred[m] := n
+						END;
+						INC(m)
+					END;
+					INC(n)
+				END
+			END;
+			FOR cont := 0 TO outline.len[ORD(ch)] - 1 DO
+				(*done[cont] := (succ[cont] < 0) OR (pred[cont] < 0)*)	(* ignore open curves *)
+				done[cont] := FALSE
+			END;
+
+			(* append contour curves to path *)
+			outline.pos[ORD(ch)] := outline.path.elems;
+			cont := 0; k := 0;
+			WHILE cont < outline.len[ORD(ch)] DO
+				IF ~done[cont] THEN
+					n := cont; m := pred[n];
+					IF m < 0 THEN
+						outline.path.AddEnter(scale * px[n, 0], scale * py[n, 0], 0, 0)
+					ELSE
+						i := last[m];
+						outline.path.AddEnter(scale * px[n, 0], scale * py[n, 0], scale * (px[m, i] - px[m, i - 1]), scale * (py[m, i] - py[m, i - 1]))
+					END;
+					REPEAT
+						i := 1;
+						WHILE i <= last[n] DO
+							IF (kind[type[n]] = polygon) OR (i+2 > last[n]) THEN
+								outline.path.AddLine(scale * px[n, i], scale * py[n, i]);
+								INC(i)
+							ELSE
+								outline.path.AddBezier(scale * px[n, i+2], scale * py[n, i+2], scale * px[n, i], scale * py[n, i],
+								  scale * px[n, i+1], scale * py[n, i+1]);
+								INC(i, 3)
+							END
+						END;
+						done[n] := TRUE;
+						n := succ[n]
+					UNTIL (n < 0) OR (n = cont);
+					IF n < 0 THEN
+						outline.path.AddExit(0, 0)
+					ELSE
+						outline.path.AddExit(scale * (px[n, 1] - px[n, 0]), scale * (py[n, 1] - py[n, 0]))
+					END;
+					INC(k)
+				END;
+				INC(cont)
+			END;
+			outline.len[ORD(ch)] := SHORT(SHORT(k))
+		END;
+		outline.xmin := scale * outline.xmin; outline.ymin := scale * outline.ymin;
+		outline.xmax := scale * outline.xmax; outline.ymax := scale * outline.ymax
+	END LoadOutline;
+
+
+	(*--- Font Cache ---*)
+
+	(* enter font in font cache *)
+	PROCEDURE CacheFont (font: Font);
+	BEGIN {EXCLUSIVE}
+		font.prev := Cache.prev; Cache.prev.next := font;
+		font.next := Cache; Cache.prev := font
+	END CacheFont;
+
+	(* put character into cache *)
+	PROCEDURE CacheChar (font: Font; ch: CHAR; x, y, dx, dy: REAL; map: Raster.Image);
+		VAR char: Char; n, m: LONGINT;
+	BEGIN {EXCLUSIVE}
+		NEW(char); font.char[ORD(ch)] := char;
+		char.x := x; char.y := y; char.dx := dx; char.dy := dy; char.map := map;
+		INC(Chars); char.used := 4;	(* extra bonus for new character in cache *)
+		WHILE Chars = MaxCachedChars DO
+			font := Cache.next;
+			WHILE font # Cache DO
+				n := 0; m := 0;
+				WHILE n < 256 DO
+					char := font.char[n];
+					IF char # NIL THEN
+						char.used := char.used DIV 2;	(* age number of uses *)
+						IF char.used = 0 THEN	(* remove character from cache *)
+							DEC(Chars); font.char[n] := NIL
+						ELSE
+							INC(m)
+						END
+					END;
+					INC(n)
+				END;
+				IF m = 0 THEN	(* no characters cached => remove font from cache *)
+					font.prev.next := font.next; font.next.prev := font.prev
+				END;
+				font := font.next
+			END
+		END
+	END CacheChar;
+
+	(* return cached character *)
+	PROCEDURE CachedChar (font: Font; ch: CHAR): Char;
+		VAR char: Char;
+	BEGIN {EXCLUSIVE}
+		char := font.char[ORD(ch)];
+		IF char # NIL THEN INC(char.used) END;
+		RETURN char
+	END CachedChar;
+
+
+	(**--- Fonts ---**)
+
+	(* extract family and style from font name *)
+	PROCEDURE SplitName (name: ARRAY OF CHAR; VAR fam, style: ARRAY OF CHAR);
+		VAR i, j: LONGINT;
+	BEGIN
+		fam[0] := name[0];
+		i := 1;
+		WHILE (name[i] >= "a") & (name[i] <= "z") DO
+			fam[i] := name[i];
+			INC(i)
+		END;
+		fam[i] := 0X;
+		WHILE (name[i] >= "0") & (name[i] <= "9") DO INC(i) END;
+		IF (name[i] = "-") OR (name[i] = " ") THEN INC(i) END;
+		j := 0;
+		WHILE (name[i] # 0X) & (CAP(name[i]) >= "A") & (CAP(name[i]) <= "Z") DO
+			style[j] := name[i];
+			INC(i); INC(j)
+		END;
+		IF j = 1 THEN
+			CASE CAP(style[0]) OF
+			| "I": COPY("Italic", style)
+			| "B": COPY("Bold", style)
+			| "M": COPY("Medium", style)
+			| "J": COPY("BoldItalic", style)
+			ELSE style[1] := 0X
+			END
+		ELSE
+			style[j] := 0X
+		END
+	END SplitName;
+
+	(* create font name from family and style *)
+	PROCEDURE BuildName (fam, style: ARRAY OF CHAR; VAR name: ARRAY OF CHAR);
+	BEGIN
+		COPY(fam, name);
+		IF style # "" THEN
+			AppendCh(name, "-");
+			Append(name, style)
+		END
+	END BuildName;
+
+	(* open MetaFont *)
+	PROCEDURE OpenOutline (VAR family, style: ARRAY OF CHAR): Outline;
+		VAR fname: FontName; file: Files.File; r: Files.Reader; tag: INTEGER; outline: Outline;
+	BEGIN
+		COPY(family, fname); Append(fname, style); Append(fname, ".MTF");
+		file := Files.Old(fname);
+		IF file # NIL THEN
+			Files.OpenReader(r, file, 0);
+			r.RawInt(tag);
+			IF (tag = OldMetaFontTag) OR (tag = MetaFontTag) THEN
+				NEW(outline); LoadOutline(outline, r);
+				RETURN outline
+			END
+		END;
+		RETURN NIL
+	END OpenOutline;
+
+	PROCEDURE LoadRaster (VAR name: ARRAY OF CHAR): RasterFile;
+		VAR
+			rfile: RasterFile; file: Files.File; r: Files.Reader; id, ch: CHAR; type: SHORTINT; height, runs, i, j: INTEGER;
+			beg, end: ARRAY 32 OF INTEGER; size: LONGINT; adr: ADDRESS;
+	BEGIN
+		rfile := NIL;
+		file := Files.Old(name);
+		IF file = NIL THEN RETURN NIL END;
+		Files.OpenReader(r, file, 0);
+		r.Char(id); r.RawSInt(type);
+		IF (id = FontId) & (type = 0) THEN
+			NEW(rfile);
+			r.Char(ch); r.Char(ch); r.RawInt(height);
+			r.RawInt(rfile.xmin); r.RawInt(rfile.xmax);
+			r.RawInt(rfile.ymin); r.RawInt(rfile.ymax);
+			r.RawInt(runs);
+			i := 0;
+			WHILE i < runs DO
+				r.RawInt(beg[i]); r.RawInt(end[i]); INC(i)
+			END;
+			i := 0; size := 0;
+			WHILE i < runs DO
+				j := beg[i];
+				WHILE j < end[i] DO
+					NEW(rfile.char[j]);
+					r.RawInt(rfile.char[j].dx);
+					r.RawInt(rfile.char[j].x); r.RawInt(rfile.char[j].y);
+					r.RawInt(rfile.char[j].w); r.RawInt(rfile.char[j].h);
+					size := size + (rfile.char[j].w + 7) DIV 8 * rfile.char[j].h;
+					INC(j)
+				END;
+				INC(i)
+			END;
+			NEW(rfile.mem, size);
+			i := 0; adr := ADDRESSOF(rfile.mem[0]);
+			WHILE i < runs DO
+				j := beg[i];
+				WHILE j < end[i] DO
+					rfile.char[j].adr := adr;
+					size := (rfile.char[j].w + 7) DIV 8 * rfile.char[j].h;
+					WHILE size > 0 DO
+						r.Char(ch);
+						SYSTEM.PUT(adr, ch);
+						INC(adr); DEC(size)
+					END;
+					INC(j)
+				END;
+				INC(i)
+			END
+		END;
+		RETURN rfile
+	END LoadRaster;
+
+	(* open raster font *)
+	PROCEDURE OpenRaster (VAR family, style: ARRAY OF CHAR; ptsize: INTEGER; VAR mat: GfxMatrix.Matrix; outline: Outline): Font;
+		VAR
+			rfile: RasterFile; font: Font; scale, xmin, ymin, xmax, ymax: REAL; ppm, fppm: INTEGER;
+			ext, pstr: ARRAY 9 OF CHAR; name: FontName;
+	BEGIN
+		rfile := NIL; font := NIL;
+		scale := Math.sqrt(ABS(GfxMatrix.Det(mat)));
+
+		(* look for exactly matching raster font *)
+		IF scale < 2.5 THEN ppm := SHORT(ENTIER(ptsize * scale + 0.5)); ext := ".Scn.Fnt"
+		ELSIF scale < 4.5 THEN ppm := SHORT(ENTIER(ptsize * scale * DPI/300 + 0.5)); ext := ".Pr3.Fnt"
+		ELSE ppm := SHORT(ENTIER(ptsize * scale * DPI/600 + 0.5)); ext := ".Pr6.Fnt"
+		END;
+		COPY(family, name); fppm := ppm;
+		IntToStr(ppm, pstr); Append(name, pstr);
+		IF style = "BoldItalic" THEN AppendCh(name, "j")
+		ELSIF style # "" THEN AppendCh(name, CHR(ORD(CAP(style[0])) - ORD("A") + ORD("a")))
+		END;
+		Append(name, ext);
+		rfile := LoadRaster(name);
+
+		(* check available raster font files *)
+		IF rfile = NIL THEN
+			ppm := SHORT(ENTIER(ptsize * scale + 0.5));
+			Find(family, style, ppm, name, fppm);
+			IF name # "" THEN
+				rfile := LoadRaster(name)
+			END
+		END;
+
+		IF rfile # NIL THEN	(* have raster font of requested family *)
+			IF (fppm = ppm) & ~GfxMatrix.Rotated(mat) & (mat[0, 0] > 0) & (mat[1, 1] > 0) & (mat[0, 0] = mat[1, 1]) THEN
+				NEW(font); font.outline := outline; font.rfile := rfile; font.niceMaps := (outline = NIL) OR (scale < 5);
+				IF outline = NIL THEN font.class := FClass
+				ELSE font.class := OFClass
+				END;
+				font.xmin := rfile.xmin; font.ymin := rfile.ymin; font.xmax := rfile.xmax; font.ymax := rfile.ymax
+			ELSIF (outline = NIL) OR (scale < 2) THEN	(* use warped raster font *)
+				NEW(font); font.outline := outline; font.rfile := rfile; font.niceMaps := TRUE;
+				IF outline = NIL THEN font.class := WFClass
+				ELSE font.class := OWFClass
+				END;
+				scale := 1/scale * ppm/fppm;
+				GfxMatrix.Scale(mat, scale, scale, font.wmat);
+				GfxMatrix.ApplyToRect(font.wmat, rfile.xmin, rfile.ymin, rfile.xmax, rfile.ymax, xmin, ymin, xmax, ymax);
+				font.xmin := SHORT(ENTIER(xmin)); font.ymin := SHORT(ENTIER(ymin));
+				font.xmax := -SHORT(ENTIER(-xmax)); font.ymax := -SHORT(ENTIER(-ymax))
+			END
+		END;
+
+		IF (font = NIL) & (outline # NIL) THEN	(* use outline only, no raster *)
+			NEW(font); font.class := OClass; font.outline := outline; font.niceMaps := FALSE;
+			scale := ptsize * DPI/72.27;	(* scale to display resolution *)
+			GfxMatrix.Scale(mat, scale, scale, font.wmat);
+			GfxMatrix.ApplyToRect(font.wmat, outline.xmin, outline.ymin, outline.xmax, outline.ymax, xmin, ymin, xmax, ymax);
+			font.xmin := SHORT(ENTIER(xmin)); font.ymin := SHORT(ENTIER(ymin));
+			font.xmax := -SHORT(ENTIER(-xmax)); font.ymax := -SHORT(ENTIER(-ymax))
+		END;
+
+		RETURN font
+	END OpenRaster;
+
+	PROCEDURE OpenExtension (VAR family, style: ARRAY OF CHAR; ptsize: INTEGER; VAR m: GfxMatrix.Matrix): Font;
+		VAR
+			i, j, n, res: LONGINT;
+			enum: Files.Enumerator; time, date, size: LONGINT;  continue: BOOLEAN;
+			name: Files.FileName; msg, cmd: ARRAY 64 OF CHAR; flags: SET;
+	BEGIN
+		cmd := "";
+		NEW(enum); enum.Open(family, {});
+		continue := TRUE;
+		WHILE continue & enum.GetEntry(name, flags, time, date, size) DO
+			i := 0; j := 0;
+			WHILE name[i] # 0X DO
+				IF name[i] = "." THEN j := i END;
+				INC(i)
+			END;
+			IF j > 0 THEN
+				msg := "FontFormats"; i := 11;
+				WHILE name[j] # 0X DO msg[i] := name[j]; INC(i); INC(j) END;
+				Configuration.Get(msg, cmd, res);
+				continue := (res = Configuration.Ok);
+			END
+		END;
+		enum.Close;
+			(* if found, invoke it *)
+		IF cmd # "" THEN
+			OpenProc := NIL;
+			Commands.Call (cmd, {Commands.Wait}, res, msg);
+			IF res = Commands.Ok THEN
+				IF OpenProc # NIL THEN
+					RETURN OpenProc(family, style, ptsize, m)
+				END
+			ELSE
+				KernelLog.Enter; KernelLog.String("GfxFonts: "); KernelLog.String(msg); KernelLog.Exit
+			END
+		END;
+		RETURN NIL
+	END OpenExtension;
+
+	(**
+		open font given point size and transformation matrix. the transformation is applied to the display font at that size.
+		the preferred way to specify a font is "Family-Style" (e.g. "Oberon-Bold"), although others are accepted as well
+		("OberonBold", "Oberon10b")
+	**)
+	PROCEDURE Open* (name: ARRAY OF CHAR; ptsize: INTEGER; mat: GfxMatrix.Matrix): Font;
+		VAR family, style, fname: FontName; font, cand: Font;
+	BEGIN
+		mat[2, 0] := 0; mat[2, 1] := 0;
+		SplitName(name, family, style);
+		BuildName(family, style, fname);
+
+		(* search in cache *)
+		font := Cache.next; cand := NIL;
+		WHILE font # Cache DO
+			IF font.name = fname THEN
+				cand := font;	(* keep for deriving font *)
+				IF (ptsize = font.ptsize) & GfxMatrix.Equal(font.mat, mat) THEN
+					RETURN font
+				END
+			END;
+			font := font.next
+		END;
+
+		(* derive from existing font if possible *)
+		IF cand # NIL THEN
+			font := cand.class.derive(cand, ptsize, mat);
+			IF font # NIL THEN
+				COPY(fname, font.name); font.ptsize := ptsize; font.mat := mat;
+				CacheFont(font);
+				RETURN font
+			END
+		END;
+
+		(* try standard raster/outline fonts *)
+		font := OpenRaster(family, style, ptsize, mat, OpenOutline(family, style));
+		IF font # NIL THEN
+			COPY(fname, font.name); font.ptsize := ptsize; font.mat := mat;
+			CacheFont(font);
+			RETURN font
+		END;
+
+		(* try registered font formats *)
+		font := OpenExtension(family, style, ptsize, mat);
+		IF font # NIL THEN
+			COPY(fname, font.name); font.ptsize := ptsize; font.mat := mat;
+			CacheFont(font);
+			RETURN font
+		END;
+
+		RETURN NIL
+	END Open;
+
+	(** open font of specified point size at display resolution **)
+	PROCEDURE OpenSize* (name: ARRAY OF CHAR; ptsize: INTEGER): Font;
+	BEGIN
+		RETURN Open(name, ptsize, GfxMatrix.Identity)
+	END OpenSize;
+
+	(** return character advance vector **)
+	PROCEDURE GetWidth* (font: Font; ch: CHAR; VAR dx, dy: REAL);
+		VAR char: Char;
+	BEGIN
+		char := CachedChar(font, ch);
+		IF char # NIL THEN
+			dx := char.dx; dy := char.dy
+		ELSE
+			font.class.getwidth(font, ch, dx, dy)
+		END
+	END GetWidth;
+
+	(** return character bitmap **)
+	PROCEDURE GetMap* (font: Font; ch: CHAR; VAR x, y, dx, dy: REAL; VAR map: Raster.Image);
+		VAR char: Char;
+	BEGIN
+		char := CachedChar(font, ch);
+		IF char # NIL THEN
+			x := char.x; y := char.y; dx := char.dx; dy := char.dy; map := char.map
+		ELSE
+			font.class.getmap(font, ch, x, y, dx, dy, map);
+			CacheChar(font, ch, x, y, dx, dy, map)
+		END
+	END GetMap;
+
+	(** store character outline rooted at given position in given path **)
+	PROCEDURE GetOutline* (font: Font; ch: CHAR; x, y: REAL; path: GfxPaths.Path);
+	BEGIN
+		font.class.getoutline(font, ch, x, y, path)
+	END GetOutline;
+
+	(** compute advance vector for complete string **)
+	PROCEDURE GetStringWidth* (font: Font; str: ARRAY OF CHAR; VAR dx, dy: REAL);
+		VAR i: LONGINT; ddx, ddy: REAL;
+	BEGIN
+		i := 0; dx := 0; dy := 0;
+		WHILE str[i] # 0X DO
+			GetWidth(font, str[i], ddx, ddy);
+			dx := dx + ddx; dy := dy + ddy;
+			INC(i)
+		END
+	END GetStringWidth;
+
+
+	(*--- Derive Methods ---*)
+
+	(* cannot derive font if no outline is known *)
+	PROCEDURE FDerive (font: Font; ptsize: INTEGER; VAR mat: GfxMatrix.Matrix): Font;
+	BEGIN
+		RETURN NIL
+	END FDerive;
+
+	(* derive font with same outline *)
+	PROCEDURE ODerive (font: Font; ptsize: INTEGER; VAR mat: GfxMatrix.Matrix): Font;
+		VAR family, style: FontName;
+	BEGIN
+		SplitName(font.name, family, style);
+		RETURN OpenRaster(family, style, ptsize, mat, font.outline)
+	END ODerive;
+
+
+	(*--- GetWidth Methods ---*)
+
+	(* ... from raster font *)
+	PROCEDURE FGetWidth (font: Font; ch: CHAR; VAR dx, dy: REAL);
+		VAR rfile: RasterFile;
+	BEGIN
+		rfile := font.rfile;
+		IF rfile.char[ORD(ch)] # NIL THEN dx := rfile.char[ORD(ch)].dx ELSE dx :=0 END; dy := 0
+	END FGetWidth;
+
+	(* ... from warped raster font *)
+	PROCEDURE WFGetWidth (font: Font; ch: CHAR; VAR dx, dy: REAL);
+	BEGIN
+		FGetWidth(font, ch, dx, dy);
+		dy := dx * font.wmat[0, 1];
+		dx := dx * font.wmat[0, 0]
+	END WFGetWidth;
+
+	(* ... from outline *)
+	PROCEDURE OGetWidth (font: Font; ch: CHAR; VAR dx, dy: REAL);
+		VAR w: REAL;
+	BEGIN
+		w := font.outline.width[ORD(ch)];
+		dx := w * font.wmat[0, 0]; dy := w * font.wmat[0, 1]
+	END OGetWidth;
+
+
+	(*--- GetMap Methods ---*)
+
+	PROCEDURE WarpMap (src: Raster.Image; mat: GfxMatrix.Matrix; VAR x, y: REAL; VAR dst: Raster.Image);
+		VAR x0, y0, x1, y1: REAL; w, h: LONGINT; filter: GfxImages.Filter;
+	BEGIN
+		GfxImages.InitLinearFilter(filter);
+		GfxMatrix.Apply(mat, x, y, x, y);
+		x0 := 0; y0 := 0; x1 := 0; y1 := 0;
+		IF mat[0, 0] > 0 THEN x1 := src.width * mat[0, 0] ELSE x0 := src.width * mat[0, 0] END;
+		IF mat[0, 1] > 0 THEN y1 := src.width * mat[0, 1] ELSE y0 := src.width * mat[0, 1] END;
+		IF mat[1, 0] > 0 THEN x1 := x1 + src.height * mat[1, 0] ELSE x0 := x0 + src.height * mat[1, 0] END;
+		IF mat[1, 1] > 0 THEN y1 := y1 + src.height * mat[1, 1] ELSE y0 := y0 + src.height * mat[1, 1] END;
+		mat[2, 0] := -x0; mat[2, 1] := -y0;
+		x := x + x0; y := y + y0;
+		w := -ENTIER(-x1) - ENTIER(x0); h := -ENTIER(-y1) - ENTIER(y0);
+		IF w * h # 0 THEN
+			NEW(dst); Raster.Create(dst, w, h, Raster.A8);
+			GfxImages.Transform(src, dst, mat, filter)
+		ELSE
+			dst := NIL
+		END
+	END WarpMap;
+
+	(* ... from raster font *)
+	PROCEDURE FGetMap (font: Font; ch: CHAR; VAR x, y, dx, dy: REAL; VAR map: Raster.Image);
+		VAR char: RasterChar; stride: LONGINT;
+	BEGIN
+		char := font.rfile.char[ORD(ch)];
+		IF char = NIL THEN
+			dx := 0; dy := 0; x := 0; y := 0; map := NIL
+		ELSE
+			dx := char.dx; dy := 0;
+			IF char.w * char.h = 0 THEN
+				x := 0; y := 0; map := NIL
+			ELSE
+				x := char.x; y := -char.h-char.y; stride:=(char.w+7) DIV 8;
+				NEW(map); Raster.Init(map, char.w, char.h, Raster.A1, -stride, char.adr+(char.h-1)*stride)
+			END
+		END
+	END FGetMap;
+
+	(* ... by warping raster font *)
+	PROCEDURE WFGetMap (font: Font; ch: CHAR; VAR x, y, dx, dy: REAL; VAR map: Raster.Image);
+	BEGIN
+		FGetMap(font, ch, x, y, dx, dy, map);
+		dy := dx * font.wmat[0, 1];
+		dx := dx * font.wmat[0, 0];
+		IF map # NIL THEN
+			WarpMap(map, font.wmat, x, y, map)
+		END
+	END WFGetMap;
+
+	PROCEDURE AddElem (VAR data: GfxPaths.EnumData);
+		VAR px, py, x, y, xstep, ystep, steps: INTEGER; dx, ex, dy, ey, e: REAL;
+	BEGIN
+		WITH data: PathEnumData DO
+			CASE data.elem OF
+			| GfxPaths.Enter:
+				data.lx := data.x; data.ly := data.y;
+				data.px := SHORT(ENTIER(data.x + 0.5)); data.py := SHORT(ENTIER(data.y + 0.5))
+			| GfxPaths.Line:
+				px := SHORT(ENTIER(data.x + 0.5)); py := SHORT(ENTIER(data.y + 0.5));
+				x := data.px; y := data.py;
+				IF py = y THEN	(* horizontal line => ignore *)
+					data.px := px
+				ELSE
+					dx := data.x - data.lx; dy := data.y - data.ly;
+					IF dx >= 0 THEN xstep := 1; ex := data.lx - x
+					ELSE xstep := -1; dx := -dx; ex := x - data.lx
+					END;
+					IF dy >= 0 THEN ystep := 1; ey := data.ly - y
+					ELSE ystep := -1; dy := -dy; ey := y - data.ly
+					END;
+					e := dx * ey - dy * ex + 0.5 * (dy - dx);
+					steps := ABS(px - x) + ABS(py - y);
+					WHILE steps > 0 DO
+						IF (e >= 0) & ((e > 0) OR (xstep <= 0)) THEN
+							INC(y, ystep); e := e - dx;
+							data.region.AddPoint(x, y, ystep)
+						ELSE
+							INC(x, xstep); e := e + dy
+							(* don't have to insert point here because regions are sliced horizontally *)
+						END;
+						DEC(steps)
+					END;
+					data.px := px; data.py := py
+				END;
+				data.lx := data.x; data.ly := data.y
+			ELSE	(* ignore other elements *)
+			END
+		END
+	END AddElem;
+
+	PROCEDURE FillRect (llx, lly, urx, ury: INTEGER; VAR data: GfxRegions.EnumData);
+		VAR pix: Raster.Pixel; mode: Raster.Mode;
+	BEGIN
+		WITH data: RegEnumData DO
+			pix[Raster.a] := 0FFX;
+			Raster.InitMode(mode, Raster.srcCopy);
+			Raster.Fill(data.map, llx - data.dx, lly - data.dy, urx - data.dx, ury - data.dy, pix, mode)
+		END
+	END FillRect;
+
+	(* ... by filling interior of warped outline *)
+	PROCEDURE OGetMap (font: Font; ch: CHAR; VAR x, y, dx, dy: REAL; VAR map: Raster.Image);
+		VAR w: REAL; pathdata: PathEnumData; llx, lly, urx, ury: INTEGER; regdata: RegEnumData;
+		tmpPath: GfxPaths.Path; tmpRegion: GfxRegions.Region; (* was global --> now local for concurrency ... cost? *)
+	BEGIN
+		NEW(tmpPath);
+		NEW(tmpRegion); tmpRegion.Init(GfxRegions.Winding);
+		w := font.outline.width[ORD(ch)];
+		dx := w * font.wmat[0, 0]; dy := w * font.wmat[0, 1];
+		font.class.getoutline(font, ch, 0, 0, tmpPath);
+		tmpRegion.Clear();
+		pathdata.region := tmpRegion;
+		tmpPath.EnumFlattened(0.5, AddElem, pathdata);
+		IF tmpRegion.Empty( ) THEN
+			x := 0; y := 0; map := NIL
+		ELSE
+			llx := tmpRegion.llx; lly := tmpRegion.lly; urx := tmpRegion.urx; ury := tmpRegion.ury;
+			NEW(map); Raster.Create(map, urx - llx, ury - lly, Raster.A1);
+			regdata.map := map; regdata.dx := llx; regdata.dy := lly;
+			tmpRegion.Enumerate(llx, lly, urx, ury, FillRect, regdata);
+			x := llx; y := lly
+		END
+	END OGetMap;
+
+
+	(*--- GetOutline Methods ---*)
+
+	(* ... undefined outline for raster fonts *)
+	PROCEDURE FGetOutline (font: Font; ch: CHAR; x, y: REAL; path: GfxPaths.Path);
+		VAR rfile: RasterFile; w, h: INTEGER; l: REAL;
+	BEGIN
+		path.Clear();
+		rfile := font.rfile;
+		w := rfile.char[ORD(ch)].w; h := rfile.char[ORD(ch)].h;
+		IF w * h > 0 THEN
+			x := x + rfile.char[ORD(ch)].x; y := y + rfile.char[ORD(ch)].y;
+			l := 0.1*(rfile.ymax - rfile.ymin);
+			path.AddRect(x, y, x + w, y + h);
+			path.AddRect(x + l, y + h - l, x + w - l, y + l)
+		END
+	END FGetOutline;
+
+	(* ... undefined outline for warped raster fonts *)
+	PROCEDURE WFGetOutline (font: Font; ch: CHAR; x, y: REAL; path: GfxPaths.Path);
+		VAR rfile: RasterFile; w, h, bx, by: INTEGER; l: REAL; m: GfxMatrix.Matrix;
+	BEGIN
+		path.Clear();
+		rfile := font.rfile;
+		w := rfile.char[ORD(ch)].w; h := rfile.char[ORD(ch)].h;
+		IF w * h > 0 THEN
+			bx := rfile.char[ORD(ch)].x; by := rfile.char[ORD(ch)].y;
+			l := 0.1*(rfile.ymax - rfile.ymin);
+			path.AddRect(bx, by, bx + w, by + h);
+			path.AddRect(bx + l, by + h - l, bx + w - l, by + l);
+			m := font.wmat; m[2, 0] := m[2, 0] + x; m[2, 1] := m[2, 1] + y;
+			path.Apply(m)
+		END
+	END WFGetOutline;
+
+	(* ... from outline structure *)
+	PROCEDURE OGetOutline (font: Font; ch: CHAR; x, y: REAL; path: GfxPaths.Path);
+		VAR outline: Outline; len: LONGINT; mat: GfxMatrix.Matrix; s: GfxPaths.Scanner; scale, dx, dy, x0, y0, x1, y1, x2, y2: REAL;
+	BEGIN
+		path.Clear();
+		outline := font.outline; len := outline.len[ORD(ch)];
+		IF len > 0 THEN
+			scale := font.ptsize * DPI/72.27;
+			GfxMatrix.Scale(font.mat, scale, scale, mat);
+			mat[2, 0] := mat[2, 0] + x; mat[2, 1] := mat[2, 1] + y;
+			s.Open(outline.path, outline.pos[ORD(ch)]);
+			REPEAT
+				CASE s.elem OF
+				| GfxPaths.Enter:
+					GfxMatrix.Apply(mat, s.x, s.y, x, y); GfxMatrix.ApplyToVector(mat, s.dx, s.dy, dx, dy);
+					path.AddEnter(x, y, dx, dy)
+				| GfxPaths.Line:
+					GfxMatrix.Apply(mat, s.x, s.y, x, y);
+					path.AddLine( x, y)
+				| GfxPaths.Arc:
+					GfxMatrix.Apply(mat, s.x, s.y, x, y); GfxMatrix.Apply(mat, s.x0, s.y0, x0, y0);
+					GfxMatrix.Apply(mat, s.x1, s.y1, x1, y1); GfxMatrix.Apply(mat, s.x2, s.y2, x2, y2);
+					path.AddArc(x, y, x0, y0, x1, y1, x2, y2)
+				| GfxPaths.Bezier:
+					GfxMatrix.Apply(mat, s.x, s.y, x, y);
+					GfxMatrix.Apply(mat, s.x1, s.y1, x1, y1); GfxMatrix.Apply(mat, s.x2, s.y2, x2, y2);
+					path.AddBezier(x, y, x1, y1, x2, y2)
+				| GfxPaths.Exit:
+					GfxMatrix.Apply(mat, s.dx, s.dy, dx, dy);
+					path.AddExit(dx, dy);
+					DEC(len)
+				END;
+				s.Scan( )
+			UNTIL len = 0
+		END
+	END OGetOutline;
+
+	PROCEDURE InitClasses;
+	BEGIN
+		NEW(FClass); FClass.derive := FDerive; FClass.getwidth := FGetWidth;
+		FClass.getmap := FGetMap; FClass.getoutline := FGetOutline;
+		NEW(OFClass); OFClass.derive := ODerive; OFClass.getwidth := FGetWidth;
+		OFClass.getmap := FGetMap; OFClass.getoutline := OGetOutline;
+		NEW(WFClass); WFClass.derive := FDerive; WFClass.getwidth := WFGetWidth;
+		WFClass.getmap := WFGetMap; WFClass.getoutline := WFGetOutline;
+		NEW(OWFClass); OWFClass.derive := ODerive; OWFClass.getwidth := WFGetWidth;
+		OWFClass.getmap := WFGetMap; OWFClass.getoutline := OGetOutline;
+		NEW(OClass); OClass.derive := ODerive; OClass.getwidth := OGetWidth;
+		OClass.getmap := OGetMap; OClass.getoutline := OGetOutline
+	END InitClasses;
+
+	PROCEDURE InitDefault;
+	BEGIN
+		Default := OpenSize("Oberon", 10)
+	END InitDefault;
+
+
+BEGIN
+	InitClasses;
+	NEW(Cache); Cache.next := Cache; Cache.prev := Cache; Chars := 0;
+	(* NEW(TmpPath);
+	NEW(TmpRegion); TmpRegion.Init(GfxRegions.Winding); *)
+	InitDefault
+END GfxFonts.
+
+From:	oswald@inf.ethz.ch
+Subject:	Re: Aos specific packages
+Date:	Thu, 22 Jun 2000 17:08:58 +0200 (MET DST)
+
+> What settings do I need?
+
+The usual suspects
+
+ImageFormats.Pict PictImages.Install
+ImageFormats.bmp BMPImages.Install
+ImageFormats.gif GIFImages.Install
+ImageFormats.jpg JPEGImages.Install
+
+FontFormats.TTF GfxOType.Install
+FontFormats.pk GfxPKFonts.Install
+
+Actually, the FontFormats entries have once again changed in
+their semantics. GfxFonts now searches "Arial*" if "Arial-..."
+is requested. If it finds e.g. "Arial.TTF" it uses the TTF
+extension as a key into FontFormats.
+--
+eos
+
+
+(*
+to do:
+o change OpenExtension to use new style commands to avoid races
+*)

+ 794 - 0
source/AGfxImages.Mod

@@ -0,0 +1,794 @@
+(* ETH Oberon, Copyright 2001 ETH Zuerich Institut fuer Computersysteme, ETH Zentrum, CH-8092 Zuerich.
+Refer to the "General ETH Oberon System Source License" contract available at: http://www.oberon.ethz.ch/ *)
+
+MODULE GfxImages; (** non-portable *)	(* eos  **)
+(** AUTHOR "eos"; PURPOSE "Gfx raster image transformations"; *)
+
+	(*
+		24.05.2000 - adapted to new Raster module
+	*)
+
+	IMPORT
+		SYSTEM, Raster, GfxMatrix;
+
+
+	(**
+		Image transformations are decomposed into a series of one-dimensional shift and scale transforms. These
+		are delegated to a filter object provided by the caller. The caller controls visual quality and execution time
+		by selecting a filter which complies with its demands.
+	**)
+
+	TYPE
+		Image* = Raster.Image;
+
+		ShiftProc* = PROCEDURE (VAR filter: Raster.Mode; src, dst: Image; sadr: ADDRESS; sbit: LONGINT; dadr: ADDRESS; dbit, len: LONGINT; t: REAL);
+		ScaleProc* = PROCEDURE (VAR filter: Raster.Mode; src, dst: Image; sadr: ADDRESS; sbit: LONGINT; dadr: ADDRESS; dbit, len: LONGINT; xy, dxy: REAL);
+
+		(** transformation filter **)
+		Filter* = RECORD (Raster.Mode)
+			hshift*, vshift*: ShiftProc;	(** procedures for shifting rows and columns **)
+			hscale*, vscale*: ScaleProc;	(** procedures for scaling rows and columns **)
+		END;
+
+
+	VAR
+		PreCache, Cache: Image;	(* caches for image transformations *)
+		hshift*, vshift*: ShiftProc;
+
+	(**--- Filters ---**)
+
+	(** predefined filter procedures using box filter (i.e. no filtering): fast and ugly **)
+
+	PROCEDURE HShift* (VAR filter: Raster.Mode; src, dst: Image; sadr: ADDRESS; sbit: LONGINT; dadr: ADDRESS; dbit, len: LONGINT; tx: REAL);
+	BEGIN
+		IF tx >= 0.5 THEN
+			dbit := dbit + dst.fmt.bpp; INC(dadr, dbit DIV 8); dbit := dbit MOD 8;
+			DEC(len)
+		END;
+		Raster.Bind(filter, src.fmt, dst.fmt);
+		filter.transfer(filter, sadr, sbit, dadr, dbit, len)
+	END HShift;
+
+	PROCEDURE VShift* (VAR filter: Raster.Mode; src, dst: Image; sadr: ADDRESS; sbit: LONGINT; dadr: ADDRESS; dbit, len: LONGINT; ty: REAL);
+	BEGIN
+		IF ty >= 0.5 THEN
+			INC(dadr, dst.bpr);
+			DEC(len)
+		END;
+		Raster.Bind(filter, src.fmt, dst.fmt);
+		WHILE len > 0 DO
+			filter.transfer(filter, sadr, sbit, dadr, dbit, 1);
+			INC(sadr, src.bpr); INC(dadr, dst.bpr);
+			DEC(len)
+		END
+	END VShift;
+
+	PROCEDURE HScale* (VAR filter: Raster.Mode; src, dst: Image; sadr: ADDRESS; sbit: LONGINT; dadr: ADDRESS; dbit, dlen: LONGINT; x, dx: REAL);
+		VAR i0, i1: LONGINT;
+	BEGIN
+		Raster.Bind(filter, src.fmt, dst.fmt);
+		i0 := 0;
+		WHILE dlen > 0 DO
+			i1 := ENTIER(x);
+			IF i0 < i1 THEN
+				IF i1 >= src.width THEN i1 := src.width-1 END;
+				sbit := sbit + (i1 - i0) * src.fmt.bpp; INC(sadr, sbit DIV 8); sbit := sbit MOD 8;
+				i0 := i1
+			END;
+			filter.transfer(filter, sadr, sbit, dadr, dbit, 1);
+			dbit := dbit + dst.fmt.bpp; INC(dadr, dbit DIV 8); dbit := dbit MOD 8;
+			x := x + dx; DEC(dlen)
+		END
+	END HScale;
+
+	PROCEDURE VScale* (VAR filter: Raster.Mode; src, dst: Image; sadr: ADDRESS; sbit: LONGINT; dadr: ADDRESS; dbit, dlen: LONGINT; y, dy: REAL);
+		VAR j0, j1: LONGINT;
+	BEGIN
+		Raster.Bind(filter, src.fmt, dst.fmt);
+		j0 := 0;
+		WHILE dlen > 0 DO
+			j1 := ENTIER(y);
+			IF j0 < j1 THEN
+				IF j1 >= src.height THEN j1 := src.height-1 END;
+				INC(sadr, (j1 - j0) * src.bpr);
+				j0 := j1
+			END;
+			filter.transfer(filter, sadr, sbit, dadr, dbit, 1);
+			INC(dadr, dst.bpr);
+			y := y + dy; DEC(dlen)
+		END
+	END VScale;
+
+	(** predefined filter procedures for linearly filtered transformations: slow and less ugly **)
+
+	PROCEDURE LinearHShift* (VAR filter: Raster.Mode; src, dst: Image; sadr: ADDRESS; sbit: LONGINT; dadr: ADDRESS; dbit, len: LONGINT; tx: REAL);
+		CONST r = Raster.r; g = Raster.g; b = Raster.b; a = Raster.a;
+		VAR w0, w1, sinc, dinc, i, red, green, blue, alpha: LONGINT; da: ADDRESS; spix, dpix: Raster.Pixel;
+	BEGIN
+		w0 := ENTIER(1000H*tx + 0.5); w1 := 1000H-w0;
+		IF (w0 < 10H) OR (w1 < 10H) THEN
+			HShift(filter, src, dst, sadr, sbit, dadr, dbit, len, tx)
+		ELSE
+			Raster.Bind(filter, Raster.PixelFormat, dst.fmt);
+			sinc := src.fmt.bpp; dinc := dst.fmt.bpp; da := dadr;
+			src.fmt.unpack(src.fmt, sadr, sbit, spix);
+			FOR i := 0 TO 3 DO dpix[i] := CHR(w1 * ORD(spix[i]) DIV 1000H) END;
+			filter.transfer(filter, ADDRESSOF(dpix[0]), 0, dadr, dbit, 1);
+			INC(dbit, dinc); INC(dadr, dbit DIV 8); dbit := dbit MOD 8;
+			DEC(len);
+			WHILE len > 0 DO
+				red := w0 * ORD(spix[r]); green := w0 * ORD(spix[g]); blue := w0 * ORD(spix[b]); alpha := w0 * ORD(spix[a]);
+				INC(sbit, sinc); INC(sadr, sbit DIV 8); sbit := sbit MOD 8;
+				src.fmt.unpack(src.fmt, sadr, sbit, spix);
+				dpix[r] := CHR((red + w1 * ORD(spix[r])) DIV 1000H);
+				dpix[g] := CHR((green + w1 * ORD(spix[g])) DIV 1000H);
+				dpix[b] := CHR((blue + w1 * ORD(spix[b])) DIV 1000H);
+				dpix[a] := CHR((alpha + w1 * ORD(spix[a])) DIV 1000H);
+				filter.transfer(filter, ADDRESSOF(dpix[0]), 0, dadr, dbit, 1);
+				INC(dbit, dinc); INC(dadr, dbit DIV 8); dbit := dbit MOD 8;
+				DEC(len)
+			END;
+			IF (da - dst.adr) DIV dst.bpr = (dadr - dst.adr) DIV dst.bpr THEN
+				FOR i := 0 TO 3 DO dpix[i] := CHR(w0 * ORD(spix[i]) DIV 1000H) END;
+				filter.transfer(filter, ADDRESSOF(dpix[0]), 0, dadr, dbit, 1)
+			END
+		END
+	END LinearHShift;
+
+	PROCEDURE LinearVShift* (VAR filter: Raster.Mode; src, dst: Image; sadr: ADDRESS; sbit: LONGINT; dadr: ADDRESS; dbit, len: LONGINT; ty: REAL);
+		CONST r = Raster.r; g = Raster.g; b = Raster.b; a = Raster.a;
+		VAR w0, w1, i, red, green, blue, alpha: LONGINT; spix, dpix: Raster.Pixel;
+	BEGIN
+		w0 := ENTIER(1000H*ty + 0.5); w1 := 1000H-w0;
+		IF (w0 < 10H) OR (w1 < 10H) THEN
+			VShift(filter, src, dst, sadr, sbit, dadr, dbit, len, ty)
+		ELSE
+			Raster.Bind(filter, Raster.PixelFormat, dst.fmt);
+			src.fmt.unpack(src.fmt, sadr, sbit, spix);
+			FOR i := 0 TO 3 DO dpix[i] := CHR(w1 * ORD(spix[i]) DIV 1000H) END;
+			filter.transfer(filter, ADDRESSOF(dpix[0]), 0, dadr, dbit, 1);
+			INC(dadr, dst.bpr);
+			DEC(len);
+			WHILE len > 0 DO
+				red := w0 * ORD(spix[r]); green := w0 * ORD(spix[g]); blue := w0 * ORD(spix[b]); alpha := w0 * ORD(spix[a]);
+				INC(sadr, src.bpr);
+				src.fmt.unpack(src.fmt, sadr, sbit, spix);
+				dpix[r] := CHR((red + w1 * ORD(spix[r])) DIV 1000H);
+				dpix[g] := CHR((green + w1 * ORD(spix[g])) DIV 1000H);
+				dpix[b] := CHR((blue + w1 * ORD(spix[b])) DIV 1000H);
+				dpix[a] := CHR((alpha + w1 * ORD(spix[a])) DIV 1000H);
+				filter.transfer(filter, ADDRESSOF(dpix[0]), 0, dadr, dbit, 1);
+				INC(dadr, dst.bpr);
+				DEC(len)
+			END;
+			IF (dst.adr < dadr) & (dadr < dst.adr + dst.height * dst.bpr) THEN
+				FOR i := 0 TO 3 DO dpix[i] := CHR(w0 * ORD(spix[i]) DIV 1000H) END;
+				filter.transfer(filter, ADDRESSOF(dpix[0]), 0, dadr, dbit, 1)
+			END
+		END
+	END LinearVShift;
+
+	PROCEDURE LinearHScale* (VAR filter: Raster.Mode; src, dst: Image; sadr: ADDRESS; sbit: LONGINT; dadr: ADDRESS; dbit, dlen: LONGINT; x, dx: REAL);
+		VAR i0, i1,  w1, w0, j: LONGINT; spix: ARRAY 2 OF Raster.Pixel; dpix: Raster.Pixel;
+	BEGIN
+		Raster.Bind(filter, Raster.PixelFormat, dst.fmt);
+		x := x+0.5;	(* displace sample position to midpoint between candidate pixels *)
+		i0 := 0;
+		src.fmt.unpack(src.fmt, sadr, sbit, spix[0]); spix[1] := spix[0];
+		WHILE dlen > 0 DO
+			i1 := ENTIER(x);
+			IF i1 > i0 THEN
+				INC(i0);
+				IF i0 >= src.width THEN
+					spix[0] := spix[1]
+				ELSIF i1 = i0 THEN
+					spix[0] := spix[1];
+					sbit := sbit + src.fmt.bpp; INC(sadr, sbit DIV 8); sbit := sbit MOD 8;
+					src.fmt.unpack(src.fmt, sadr, sbit, spix[1])
+				ELSIF i1 < src.width THEN
+					sbit := sbit + (i1 - i0) * src.fmt.bpp; INC(sadr, sbit DIV 8); sbit := sbit MOD 8;
+					src.fmt.unpack(src.fmt, sadr, sbit, spix[0]);
+					sbit := sbit + src.fmt.bpp; INC(sadr, sbit DIV 8); sbit := sbit MOD 8;
+					src.fmt.unpack(src.fmt, sadr, sbit, spix[1])
+				ELSE
+					sbit := sbit + (src.width - i0) * src.fmt.bpp; INC(sadr, sbit DIV 8); sbit := sbit MOD 8;
+					src.fmt.unpack(src.fmt, sadr, sbit, spix[0]); spix[1] := spix[0]
+				END;
+				i0 := i1
+			END;
+			w1 := ENTIER(1000H*(x - i1)); w0 := 1000H-w1;
+			FOR j := 0 TO 3 DO
+				dpix[j] := Raster.Clamp[200H + (ORD(spix[0, j]) * w0 + ORD(spix[1, j]) * w1) DIV 1000H]
+			END;
+			filter.transfer(filter, ADDRESSOF(dpix[0]), 0, dadr, dbit, 1);
+			dbit := dbit + dst.fmt.bpp; INC(dadr, dbit DIV 8); dbit := dbit MOD 8;
+			x := x + dx; DEC(dlen)
+		END
+	END LinearHScale;
+
+	PROCEDURE LinearVScale* (VAR filter: Raster.Mode; src, dst: Image; sadr: ADDRESS; sbit: LONGINT; dadr: ADDRESS; dbit, dlen: LONGINT; y, dy: REAL);
+		VAR j0, j1, w1, w0, j: LONGINT; spix: ARRAY 2 OF Raster.Pixel; dpix: Raster.Pixel;
+	BEGIN
+		Raster.Bind(filter, Raster.PixelFormat, dst.fmt);
+		y := y+0.5;	(* displace sample position to midpoint between candidate pixels *)
+		j0 := 0;
+		src.fmt.unpack(src.fmt, sadr, sbit, spix[0]); spix[1] := spix[0];
+		WHILE dlen > 0 DO
+			j1 := ENTIER(y);
+			IF j1 > j0 THEN
+				INC(j0);
+				IF j0 >= src.height THEN
+					spix[0] := spix[1]
+				ELSIF j1 = j0 THEN
+					spix[0] := spix[1];
+					INC(sadr, src.bpr);
+					src.fmt.unpack(src.fmt, sadr, sbit, spix[1])
+				ELSIF j1 < src.height THEN
+					INC(sadr, (j1 - j0) * src.bpr);
+					src.fmt.unpack(src.fmt, sadr, sbit, spix[0]);
+					INC(sadr, src.bpr);
+					src.fmt.unpack(src.fmt, sadr, sbit, spix[1])
+				ELSE
+					INC(sadr, src.bpr);
+					src.fmt.unpack(src.fmt, sadr, sbit, spix[0]); spix[1] := spix[0]
+				END;
+				j0 := j1
+			END;
+			w1 := ENTIER(1000H*(y - j1)); w0 := 1000H-w1;
+			FOR j := 0 TO 3 DO
+				dpix[j] := Raster.Clamp[200H + (ORD(spix[0, j]) * w0 + ORD(spix[1, j]) * w1) DIV 1000H]
+			END;
+			filter.transfer(filter, ADDRESSOF(dpix[0]), 0, dadr, dbit, 1);
+			INC(dadr, dst.bpr);
+			y := y + dy; DEC(dlen)
+		END
+	END LinearVScale;
+
+	(** initialize filter with compositing operation and transformation procedures **)
+	PROCEDURE InitFilter* (VAR filter: Filter; op: SHORTINT; hshift, vshift: ShiftProc; hscale, vscale: ScaleProc);
+	BEGIN
+		Raster.InitMode(filter, op);
+		filter.hshift := hshift; filter.vshift := vshift;
+		filter.hscale := hscale; filter.vscale := vscale
+	END InitFilter;
+
+	(* get temporary pixel format image for storing intermediate images *)
+	PROCEDURE GetTempImage (VAR img, cache: Raster.Image; w, h: LONGINT);
+		VAR size: LONGINT;
+	BEGIN
+		size := w * h;
+		IF (size >= 10000H) OR (cache = NIL) THEN
+			NEW(img)
+		ELSE
+			img := cache; cache := NIL
+		END;
+		Raster.Create(img, w, h, Raster.PixelFormat)
+	END GetTempImage;
+
+	PROCEDURE FreeTempImage (VAR img, cache: Raster.Image);
+	BEGIN
+		IF img.width * img.height < 10000H THEN
+			cache := img
+		END
+	END FreeTempImage;
+
+	(* depending on matrix elements, transpose/mirror image to avoid bottleneck problems *)
+	PROCEDURE Preprocess (VAR src: Raster.Image; VAR m: GfxMatrix.Matrix; VAR filter: Filter);
+		CONST
+			r = Raster.r; g = Raster.g; b = Raster.b;
+		VAR
+			dst: Raster.Image; mode: Raster.Mode; dinc, sinc, h, w, sbit: LONGINT;
+			dadr, sadr, sa, da: ADDRESS;
+			mat: GfxMatrix.Matrix; t: REAL;
+	BEGIN
+		IF ABS(m[0, 0] * m[1, 1]) >= ABS(m[0, 1] * m[1, 0]) THEN	(* no need to swap rows and columns *)
+			IF (m[0, 0] <= 0) OR (m[1, 1] <= 0) THEN
+				GetTempImage(dst, PreCache, src.width, src.height);
+				Raster.InitModeColor(mode, Raster.srcCopy, ORD(filter.col[r]), ORD(filter.col[g]), ORD(filter.col[b]));
+				Raster.Bind(mode, src.fmt, dst.fmt);
+				IF m[0, 0] >= 0 THEN dadr := dst.adr; dinc := 4
+				ELSE dadr := dst.adr + 4*(dst.width-1); dinc := -4
+				END;
+				IF m[1, 1] >= 0 THEN sadr := src.adr; sinc := src.bpr
+				ELSE sadr := src.adr + (src.height-1) * src.bpr; sinc := -src.bpr
+				END;
+				h := 0;
+				WHILE h < src.height DO
+					w := 0; sa := sadr; sbit := 0; da := dadr;
+					WHILE w < src.width DO
+						mode.transfer(mode, sa, sbit, da, 0, 1);
+						sbit := sbit + src.fmt.bpp; INC(sa, sbit DIV 8); sbit := sbit MOD 8;
+						INC(da, dinc); INC(w)
+					END;
+					INC(sadr, sinc); INC(dadr, dst.bpr); INC(h)
+				END;
+				IF m[0, 0] < 0 THEN
+					GfxMatrix.Init(mat, -1, 0, 0, 1, w, 0);
+					GfxMatrix.Concat(mat, m, m)
+				END;
+				IF m[1, 1] < 0 THEN
+					GfxMatrix.Init(mat, 1, 0, 0, -1, 0, h);
+					GfxMatrix.Concat(mat, m, m)
+				END;
+				src := dst;
+				FreeTempImage(dst, PreCache)	(* reuse allocated image in next call *)
+			END
+		ELSE	(* need to transpose *)
+			t := m[0, 0]; m[0, 0] := m[1, 0]; m[1, 0] := t;
+			t := m[0, 1]; m[0, 1] := m[1, 1]; m[1, 1] := t;
+			GetTempImage(dst, PreCache, src.height, src.width);
+			Raster.InitModeColor(mode, Raster.srcCopy, ORD(filter.col[r]), ORD(filter.col[g]), ORD(filter.col[b]));
+			Raster.Bind(mode, src.fmt, dst.fmt);
+			IF m[0, 0] <= 0 THEN dadr := dst.adr; dinc := dst.bpr
+			ELSE dadr := dst.adr + (dst.height-1) * dst.bpr; dinc := -dst.bpr
+			END;
+			IF m[1, 1] <= 0 THEN sadr := src.adr; sinc := src.bpr
+			ELSE sadr := src.adr + (src.height-1) * src.bpr; sinc := -src.bpr
+			END;
+			h := 0;
+			WHILE h < src.height DO
+				w := 0; sa := sadr; sbit := 0; da := dadr;
+				WHILE w < src.width DO
+					mode.transfer(mode, sa, sbit, da, 0, 1);
+					sbit := sbit + src.fmt.bpp; INC(sa, sbit DIV 8); sbit := sbit MOD 8;
+					INC(da, dinc); INC(w)
+				END;
+				INC(sadr, sinc); INC(dadr, 4); INC(h)
+			END;
+			IF m[0, 0] < 0 THEN
+				GfxMatrix.Init(mat, -1, 0, 0, 1, dst.width, 0);
+				GfxMatrix.Concat(mat, m, m)
+			END;
+			IF m[1, 1] < 0 THEN
+				GfxMatrix.Init(mat, 1, 0, 0, -1, 0, dst.height);
+				GfxMatrix.Concat(mat, m, m)
+			END;
+			src := dst;
+			FreeTempImage(dst, PreCache)
+		END
+	END Preprocess;
+
+	(* shift source row by fractional amount *)
+	PROCEDURE SkewRow (src, dst: Image; si, sj, w, di, dj: LONGINT; tx: REAL; VAR filter: Filter);
+		VAR sbit, dbit: LONGINT; sadr, dadr: ADDRESS;
+	BEGIN
+		ASSERT((0.0 <= tx) & (tx <= 1.0), 100);	(* rounding problem if using tx < 1.0 *)
+		IF si < 0 THEN INC(w, si); DEC(di, si); si := 0 END;
+		IF si + w > src.width THEN w := src.width - si END;
+		IF w > 0 THEN
+			sbit := si * src.fmt.bpp; sadr := src.adr + sj * src.bpr + sbit DIV 8; sbit := sbit MOD 8;
+			dbit := di * dst.fmt.bpp; dadr := dst.adr + dj * dst.bpr + dbit DIV 8; dbit := dbit MOD 8;
+			filter.hshift(filter, src, dst, sadr, sbit, dadr, dbit, w, tx)
+		END
+	END SkewRow;
+
+	(* shear rectangle in source image horizontally; clip to destination boundary *)
+	PROCEDURE SkewRows (src, dst: Image; si, sj, w, h, dj: LONGINT; x, dx: REAL; VAR filter: Filter);
+		VAR j, di, n: LONGINT;
+	BEGIN
+		j := 0;
+		IF dj < 0 THEN
+			j := -dj;
+			IF j >= h THEN RETURN END
+		END;
+		IF dj + h > dst.height THEN
+			h := dst.height - dj;
+			IF h <= 0 THEN RETURN END
+		END;
+
+		IF dx > 0 THEN
+			IF x + h * dx >= dst.width THEN
+				h := -ENTIER((x - dst.width)/dx)
+			END;
+			x := x + j * dx;
+			IF x + w < 0 THEN
+				n := -ENTIER((x + w)/dx);
+				INC(j, n); x := x + n * dx
+			END;
+			IF x < 0 THEN
+				n := j - ENTIER(x/dx);
+				IF n > h THEN n := h END;
+				WHILE j < n DO
+					di := ENTIER(x);
+					IF di + w > dst.width THEN w := dst.width END;
+					SkewRow(src, dst, si - di, sj + j, di + w, 0, dj + j, x - di, filter);
+					INC(j); x := x + dx
+				END
+			END;
+			WHILE j < h DO
+				di := ENTIER(x);
+				IF di + w > dst.width THEN w := dst.width - di END;
+				SkewRow(src, dst, si, sj + j, w, di, dj + j, x - di, filter);
+				INC(j); x := x + dx
+			END
+
+		ELSIF dx < 0 THEN
+			IF x + w + h * dx < 0 THEN
+				h := -ENTIER((x + w)/dx)
+			END;
+			x := x + j * dx;
+			IF x >= dst.width THEN
+				n := ENTIER((dst.width - x)/dx) + 1;
+				INC(j, n); x := x + n * dx
+			END;
+			n := j - ENTIER(x/dx);	(* row at which x drops below zero *)
+			IF n > h THEN n := h END;
+			WHILE j < n DO
+				di := ENTIER(x);
+				IF di + w < dst.width THEN
+					SkewRow(src, dst, si, sj + j, w, di, dj + j, x - di, filter)
+				ELSE
+					SkewRow(src, dst, si, sj + j, dst.width - di, di, dj + j, x - di, filter)
+				END;
+				INC(j); x := x + dx
+			END;
+			WHILE j < h DO
+				di := ENTIER(x);
+				IF di + w < dst.width THEN
+					SkewRow(src, dst, si - di, sj + j, di + w, 0, dj + j, x - di, filter)
+				ELSE
+					SkewRow(src, dst, si - di, sj + j, dst.width, 0, dj + j, x - di, filter)
+				END;
+				INC(j); x := x + dx
+			END
+
+		ELSIF x < 0 THEN
+			di := ENTIER(x);
+			IF di + w > dst.width THEN
+				si := si - di; x := x - di;
+				WHILE j < h DO
+					SkewRow(src, dst, si, sj + j, dst.width, 0, dj + j, x, filter);
+					INC(j)
+				END
+			ELSIF di + w >= 0 THEN
+				si := si - di; w := w + di; x := x - di;
+				WHILE j < h DO
+					SkewRow(src, dst, si, sj + j, w, 0, dj + j, x, filter);
+					INC(j)
+				END
+			END
+
+		ELSIF x < dst.width THEN
+			di := ENTIER(x); x := x - di;
+			IF di + w > dst.width THEN w := dst.width - di END;
+			WHILE j < h DO
+				SkewRow(src, dst, si, sj + j, w, di, dj + j, x, filter);
+				INC(j)
+			END
+		END
+	END SkewRows;
+
+	(* shift source column by fractional amount *)
+	PROCEDURE SkewCol (src, dst: Image; si, sj, h, di, dj: LONGINT; ty: REAL; VAR filter: Filter);
+		VAR sbit, dbit: LONGINT; sadr, dadr: ADDRESS;
+	BEGIN
+		ASSERT((0.0 <= ty) & (ty <= 1.0), 100);	(* rounding problem with ty < 1.0 *)
+		IF sj < 0 THEN INC(h, sj); DEC(dj, sj); sj := 0 END;
+		IF sj + h > src.height THEN h := src.height - sj END;
+		IF h > 0 THEN
+			sbit := si * src.fmt.bpp; sadr := src.adr + sj * src.bpr + sbit DIV 8; sbit := sbit MOD 8;
+			dbit := di * dst.fmt.bpp; dadr := dst.adr + dj * dst.bpr + dbit DIV 8; dbit := dbit MOD 8;
+			filter.vshift(filter, src, dst, sadr, sbit, dadr, dbit, h, ty)
+		END
+	END SkewCol;
+
+	(* shear rectangle in source image vertically; clip to destination boundary *)
+	PROCEDURE SkewCols (src, dst: Image; si, sj, w, h, di: LONGINT; y, dy: REAL; VAR filter: Filter);
+		VAR i, dj, n: LONGINT;
+	BEGIN
+		i := 0;
+		IF di < 0 THEN
+			i := -di;
+			IF i >= w THEN RETURN END
+		END;
+		IF di + w > dst.width THEN
+			w := dst.width - di;
+			IF w <= 0 THEN RETURN END
+		END;
+
+		IF dy > 0 THEN
+			IF y + w * dy >= dst.height THEN
+				w := -ENTIER((y - dst.height)/dy)
+			END;
+			y := y + i * dy;
+			IF y + h < 0 THEN
+				n := -ENTIER((y + h)/dy);
+				INC(i, n); y := y + n * dy
+			END;
+			IF y < 0 THEN
+				n := i - ENTIER(y/dy);
+				IF n > w THEN n := w END;
+				WHILE i < n DO
+					dj := ENTIER(y);
+					IF dj + h > dst.height THEN h := dst.height END;
+					SkewCol(src, dst, si + i, sj - dj, dj + h, di + i, 0, y - dj, filter);
+					INC(i); y := y + dy
+				END
+			END;
+			WHILE i < w DO
+				dj := ENTIER(y);
+				IF dj + h > dst.height THEN h := dst.height - dj END;
+				SkewCol(src, dst, si + i, sj, h, di + i, dj, y - dj, filter);
+				INC(i); y := y + dy
+			END
+
+		ELSIF dy < 0 THEN
+			IF y + h + w * dy < 0 THEN
+				w := -ENTIER((y + h)/dy)
+			END;
+			y := y + i * dy;
+			IF y >= dst.height THEN
+				n := ENTIER((dst.height - y)/dy) + 1;
+				INC(i, n); y := y + n * dy
+			END;
+			n := i - ENTIER(y/dy);	(* column at which y drops below zero *)
+			IF n > w THEN n := w END;
+			WHILE i < n DO
+				dj := ENTIER(y);
+				IF dj + h < dst.height THEN
+					SkewCol(src, dst, si + i, sj, h, di + i, dj, y - dj, filter)
+				ELSE
+					SkewCol(src, dst, si + i, sj, dst.height - dj, di + i, dj, y - dj, filter)
+				END;
+				INC(i); y := y + dy
+			END;
+			WHILE i < w DO
+				dj := ENTIER(y);
+				IF dj + h < dst.height THEN
+					SkewCol(src, dst, si + i, sj - dj, h + dj, di + i, 0, y - dj, filter)
+				ELSE
+					SkewCol(src, dst, si + i, sj - dj, dst.height, di + i, 0, y - dj, filter)
+				END;
+				INC(i); y := y + dy
+			END
+
+		ELSIF y < 0 THEN
+			dj := ENTIER(y);
+			IF dj + h > dst.height THEN
+				sj := sj - dj; y := y - dj;
+				WHILE i < w DO
+					SkewCol(src, dst, si + i, sj, dst.height, di + i, 0, y, filter);
+					INC(i)
+				END
+			ELSIF dj + h >= 0 THEN
+				sj := sj - dj; h := h + dj; y := y - dj;
+				WHILE i < w DO
+					SkewCol(src, dst, si + i, sj, h, di + i, 0, y, filter);
+					INC(i)
+				END
+			END
+
+		ELSIF y < dst.height THEN
+			dj := ENTIER(y); y := y - dj;
+			IF dj + h > dst.height THEN h := dst.height - di END;
+			WHILE i < w DO
+				SkewCol(src, dst, si + i, sj, h, di + i, dj, y, filter);
+				INC(i)
+			END
+		END
+	END SkewCols;
+
+	(** render translated image on destination **)
+	PROCEDURE Translate* (src, dst: Image; tx, ty: REAL; VAR filter: Filter);
+		VAR ti, tj, i, j, w, h: LONGINT; tmp: Image;
+	BEGIN
+		ti := ENTIER(tx); tx := tx - ti;
+		tj := ENTIER(ty); ty := ty - tj;
+		IF tx < 0.01 THEN
+			SkewCols(src, dst, 0, 0, src.width, src.height, ti, tj + ty, 0, filter)
+		ELSIF ty < 0.01 THEN
+			SkewRows(src, dst, 0, 0, src.width, src.height, tj, ti + tx, 0, filter)
+		ELSE
+			i := 0; j := 0; w := src.width; h := src.height;
+			IF ti < 0 THEN i := -ti; INC(w, ti) END;
+			IF ti + w >= dst.width THEN w := dst.width - ti - 1 END;
+			IF tj < 0 THEN j := -tj; INC(h, tj) END;
+			IF tj + h >= dst.height THEN h := dst.height - tj - 1 END;
+			GetTempImage(tmp, Cache, w, h+1);
+			SkewCols(src, tmp, i, j, w, h, 0, ty, 0, filter);
+			SkewRows(tmp, dst, 0, 0, tmp.width, tmp.height, tj, ti + tx, 0, filter);
+			FreeTempImage(tmp, Cache)
+		END
+	END Translate;
+
+	(** render scaled image on destination **)
+	PROCEDURE Scale* (src, dst: Image; sx, sy, tx, ty: REAL; VAR filter: Filter);
+		VAR xl, xr, yb, yt, w, h, sbit, dbit, i: LONGINT; sadr, dadr: ADDRESS; dy, y, dx, x: REAL; tmp: Image;
+	BEGIN
+		ASSERT((sx > 0) & (sy > 0), 100);
+		xl := ENTIER(tx); xr := -ENTIER(-(tx + sx * src.width));
+		IF xl < 0 THEN xl := 0 END;
+		IF xr > dst.width THEN
+			xr := dst.width;
+			IF xr <= xl THEN RETURN END;
+		END;
+		yb := ENTIER(ty); yt := -ENTIER(-(ty + sy * src.height));
+		IF yb < 0 THEN yb := 0 END;
+		IF yt > dst.height THEN
+			yt := dst.height;
+			IF yt <= yb THEN RETURN END
+		END;
+		w := xr - xl; h := yt - yb;
+
+		IF ABS(w - src.width) < 1 THEN
+			dy := 1.0/sy; y := (0.5 - (ty - ENTIER(ty))) * dy;
+			sadr := src.adr; sbit := 0;
+			dbit := xl * dst.fmt.bpp; dadr := dst.adr + yb * dst.bpr + dbit DIV 8; dbit := dbit MOD 8;
+			i := 0;
+			WHILE i < src.width DO
+				filter.vscale(filter, src, dst, sadr, sbit, dadr, dbit, h, y, dy);
+				sbit := sbit + src.fmt.bpp; INC(sadr, sbit DIV 8); sbit := sbit MOD 8;
+				dbit := dbit + dst.fmt.bpp; INC(dadr, dbit DIV 8); dbit := dbit MOD 8;
+				INC(i)
+			END
+
+		ELSIF ABS(h - src.height) < 1 THEN
+			dx := 1.0/sx; x := (0.5 - (tx - ENTIER(tx))) * dx;
+			sadr := src.adr; sbit := 0;
+			dbit := xl * dst.fmt.bpp; dadr := dst.adr + yb * dst.bpr + dbit DIV 8; dbit := dbit MOD 8;
+			i := 0;
+			WHILE i < src.height DO
+				filter.hscale(filter, src, dst, sadr, sbit, dadr, dbit, w, x, dx);
+				INC(sadr, src.bpr); INC(dadr, dst.bpr);
+				INC(i)
+			END
+
+		ELSE
+			GetTempImage(tmp, Cache, src.width, h);
+			dy := 1.0/sy; y := (0.5 - (ty - ENTIER(ty))) * dy;
+			sadr := src.adr; sbit := 0; dadr := tmp.adr; dbit := 0;
+			i := 0;
+			WHILE i < src.width DO
+				filter.vscale(filter, src, tmp, sadr, sbit, dadr, dbit, h, y, dy);
+				sbit := sbit + src.fmt.bpp; INC(sadr, sbit DIV 8); sbit := sbit MOD 8;
+				dbit := dbit + tmp.fmt.bpp; INC(dadr, dbit DIV 8); dbit := dbit MOD 8;
+				INC(i)
+			END;
+			dx := 1.0/sx; x := (0.5 - (tx - ENTIER(tx))) * dx;
+			sadr := tmp.adr; sbit := 0;
+			dbit := xl * dst.fmt.bpp; dadr := dst.adr + yb * dst.bpr + dbit DIV 8; dbit := dbit MOD 8;
+			i := 0;
+			WHILE i < h DO
+				filter.hscale(filter, tmp, dst, sadr, sbit, dadr, dbit, w, x, dx);
+				INC(sadr, tmp.bpr); INC(dadr, dst.bpr);
+				INC(i)
+			END;
+			FreeTempImage(tmp, Cache)
+		END
+	END Scale;
+
+	(** render rotated image on destination **)
+	PROCEDURE Rotate* (src, dst: Image; sin, cos, tx, ty: REAL; VAR filter: Filter);
+		VAR m: GfxMatrix.Matrix; tan, htan, wsin, hcos, x, y: REAL; wmax, h, iy, sw, sh: LONGINT; tmp: Image;
+	BEGIN
+		ASSERT(ABS(sin * sin + cos * cos - 1) < 0.0001, 100);
+		m[0, 0] := cos; m[0, 1] := sin; m[1, 0] := -sin; m[1, 1] := cos; m[2, 0] := tx; m[2, 1] := ty;
+		Preprocess(src, m, filter);
+		cos := m[0, 0]; sin := m[0, 1]; tx := m[2, 0]; ty := m[2, 1];
+		tan := sin/(1.0 + cos);	(* identity for tan(phi/2); 1/2 SQRT(3) <= cos <= 1 *)
+		sw := src.width; sh := src.height;
+		htan := ABS(tan) * sh;
+		wsin := ABS(sin) * sw;
+		hcos := cos * sh;
+		wmax := sw + ENTIER(htan) + 1;	(* width of intermediate image *)
+		h := ENTIER(wsin + hcos) + 2;	(* second extra pixel for ty - tj *)
+		GetTempImage(tmp, Cache, wmax, h + sh);	(* stack two intermediate images on top of each other *)
+		IF sin >= 0 THEN
+			x := htan; tx := tx - x; y := hcos - sh
+		ELSE
+			x := 0; y := wsin; tx := tx + wsin * tan; ty := ty - y
+		END;
+		iy := ENTIER(ty); y := y + (ty - iy);
+		SkewRows(src, tmp, 0, 0, sw, sh, h, x, -tan, filter);	(* first pass: skew horizontal scanlines *)
+		SkewCols(tmp, tmp, 0, h, wmax, sh, 0, y, sin, filter);	(* second pass: skew vertical scanlines *)
+		SkewRows(tmp, dst, 0, 0, wmax, h, iy, tx, -tan, filter);	(* third pass: skew horizontal scanlines *)
+		FreeTempImage(tmp, Cache)
+	END Rotate;
+
+	(** render horizontally sheared image on destination **)
+	PROCEDURE ShearRows* (src, dst: Image; sx, tx: REAL; VAR filter: Filter);
+	BEGIN
+		SkewRows(src, dst, 0, 0, src.width, src.height, 0, tx, sx, filter)
+	END ShearRows;
+
+	(** render vertically sheared image on destination **)
+	PROCEDURE ShearCols* (src, dst: Image; sy, ty: REAL; VAR filter: Filter);
+	BEGIN
+		SkewCols(src, dst, 0, 0, src.width, src.height, 0, ty, sy, filter)
+	END ShearCols;
+
+	(** render affinely transformed image on destination **)
+	PROCEDURE Transform* (src, dst: Image; m: GfxMatrix.Matrix; VAR filter: Filter);
+		CONST eps = 0.003;
+		VAR det, s, dx, x: REAL; iy, w, h, ix: LONGINT; tmp: Image;
+	BEGIN
+		Preprocess(src, m, filter);
+		IF (ABS(m[0, 0]) >= eps) & (ABS(m[1, 1]) >= eps) THEN	(* matrix isn't singular *)
+			IF (ABS(m[0, 1]) < eps) & (ABS(m[1, 0]) < eps) THEN	(* no rotation or shear *)
+				IF (ABS(m[0, 0]-1) < eps) & (ABS(m[1, 1]-1) < eps) THEN	(* not even scaled *)
+					Translate(src, dst, m[2, 0], m[2, 1], filter)
+				ELSE
+					Scale(src, dst, m[0, 0], m[1, 1], m[2, 0], m[2, 1], filter)
+				END
+			ELSE
+				det := m[0, 0] * m[1, 1] - m[0, 1] * m[1, 0];
+				IF ABS(det) >= eps THEN
+					IF (ABS(det-1) < eps) & (ABS(m[0, 0] - m[1, 1]) < eps) & (ABS(m[0, 1] + m[1, 0]) < eps) THEN
+						Rotate(src, dst, m[0, 1], m[0, 0], m[2, 0], m[2, 1], filter)
+					ELSIF ABS(m[0, 1]) < eps THEN	(* horizontal shear *)
+						iy := ENTIER(m[2, 1]);
+						IF ABS(det-1) >= eps THEN	(* scaled *)
+							w := ENTIER(m[0, 0] * src.width)+1;
+							h := ENTIER(m[1, 1] * src.height)+1;
+							GetTempImage(tmp, Cache, w, h);
+							Scale(src, tmp, m[0, 0], m[1, 1], 0, m[2, 1] - iy, filter);
+							SkewRows(tmp, dst, 0, 0, tmp.width, tmp.height, iy, m[2, 0], m[1, 0]/m[1, 1], filter);
+							FreeTempImage(tmp, Cache)
+						ELSIF m[2, 1] - iy < eps THEN	(* integer translation *)
+							SkewRows(src, dst, 0, 0, src.width, src.height, iy, m[2, 0], m[1, 0], filter)
+						ELSE
+							GetTempImage(tmp, Cache, src.width, src.height+1);
+							Translate(src, tmp, 0, m[2, 1] - iy, filter);
+							SkewRows(tmp, dst, 0, 0, tmp.width, tmp.height, iy, m[2, 0], m[1, 0], filter);
+							FreeTempImage(tmp, Cache)
+						END
+					ELSIF ABS(m[1, 0]) < eps THEN	(* vertical shear *)
+						ix := ENTIER(m[2, 0]);
+						IF ABS(det-1) >= eps THEN	(* scaled *)
+							w := ENTIER(m[0, 0] * src.width)+1;
+							h := ENTIER(m[1, 1] * src.height)+1;
+							GetTempImage(tmp, Cache, w, h);
+							Scale(src, tmp, m[0, 0], m[1, 1], m[2, 0] - ix, 0, filter);
+							SkewCols(tmp, dst, 0, 0, tmp.width, tmp.height, ix, m[2, 1], m[0, 1]/m[0, 0], filter);
+							FreeTempImage(tmp, Cache)
+						ELSIF m[2, 0] - ix < eps THEN	(* integer translation *)
+							SkewCols(src, dst, 0, 0, src.width, src.height, ix, m[2, 1], m[0, 1], filter)
+						ELSE
+							GetTempImage(tmp, Cache, src.width+1, src.height);
+							Translate(src, tmp, m[2, 0] - ix, 0, filter);
+							SkewRows(tmp, dst, 0, 0, tmp.width, tmp.height, ix, m[2, 1], m[0, 1], filter);
+							FreeTempImage(tmp, Cache)
+						END
+					ELSE
+						(*
+							use the following identity:
+								[ a b ]	[ a         0       ] [        1           0 ] [ 1 b/a ]
+								[ c d ] = [ 0 (ad-bc)/a ] [ ca/(ad-bc) 1 ]  [ 0   1   ]
+						*)
+						s := det/m[0, 0];
+						w := ENTIER(m[0, 0] * src.width)+1;
+						h := ENTIER(s * src.height)+1;
+						dx := m[1, 0]/s; x := (h-1) * ABS(dx) + 2;
+						GetTempImage(tmp, Cache, w - 2*ENTIER(-x) + 1, 2*h);
+						Scale(src, tmp, m[0, 0], s, x, h, filter);
+						ix := ENTIER(m[2, 0]);
+						SkewRows(tmp, tmp, 0, h, tmp.width, h, 0, m[2, 0] - ix, dx, filter);
+						w := ENTIER(x);
+						IF dx >= 0 THEN
+							SkewCols(tmp, dst, w, 0, tmp.width - w, h, ix, m[2, 1], m[0, 1]/m[0, 0], filter)
+						ELSE
+							s := m[0, 1]/m[0, 0];
+							SkewCols(tmp, dst, 0, 0, tmp.width - w, h, ix - w, m[2, 1] - w * s, s, filter)
+						END;
+						FreeTempImage(tmp, Cache)
+					END
+				END
+			END
+		END
+	END Transform;
+
+	(** uses nearest-neighbor resampling (box filter); bad aliasing when downscaling **)
+	PROCEDURE InitNoFilter*(VAR filter: Filter);
+	BEGIN
+		InitFilter(filter, Raster.srcOverDst, HShift, VShift, HScale, VScale)
+	END InitNoFilter;
+
+	(** uses linear interpolation (triangle filter); blurry when upscaling **)
+	PROCEDURE InitLinearFilter*(VAR filter: Filter);
+	BEGIN
+		InitFilter(filter, Raster.srcOverDst, LinearHShift, LinearVShift, LinearHScale, LinearVScale);
+	END InitLinearFilter;
+
+BEGIN
+	hshift := HShift; vshift := VShift
+END GfxImages.

+ 345 - 0
source/AGfxMatrix.Mod

@@ -0,0 +1,345 @@
+(* ETH Oberon, Copyright 2001 ETH Zuerich Institut fuer Computersysteme, ETH Zentrum, CH-8092 Zuerich.
+Refer to the "General ETH Oberon System Source License" contract available at: http://www.oberon.ethz.ch/ *)
+
+MODULE GfxMatrix; (** portable *)	(* eos  *)
+(** AUTHOR "eos"; PURPOSE "Affine transformations in 2D"; *)
+
+	(*
+		21.02.2000 - bugfix in Scaled: didn't recognize downscaling
+		15.04.2000 - added Atan2
+	*)
+
+	IMPORT
+		Streams, Math;
+
+
+	CONST
+		Eps = 1.0E-5;
+
+
+	TYPE
+		(**
+			Transformation matrix
+				3x2_matrices can represent any combination of affine transformations, i.e. of translation, rotation, scaling and
+				shearing.
+
+			Translate by tx, ty:
+				[  1  0 ]
+				[  0  1 ]
+				[ tx ty ]
+
+			Scale by sx, sy:
+				[ sx   0 ]
+				[  0  sy ]
+				[  0   0 ]
+
+			Rotate counter-clockwise by angle phi:
+				[  cos(phi)   sin(phi) ]
+				[ -sin(phi)  cos(phi) ]
+				[       0             0       ]
+
+			Shear along x_axis by factor f:
+				[ 1   0 ]
+				[ f    1 ]
+				[ 0   0 ]
+		**)
+
+		Matrix* = ARRAY 3, 2 OF REAL;
+
+
+	VAR
+		Identity*: Matrix;	(** identity matrix (read_only) **)
+
+
+	(**--- Matrix Computation ---**)
+
+	(** initialize matrix with given values **)
+	PROCEDURE Init* (VAR m: Matrix; m00, m01, m10, m11, m20, m21: REAL);
+	BEGIN
+		m[0, 0] := m00; m[0, 1] := m01;
+		m[1, 0] := m10; m[1, 1] := m11;
+		m[2, 0] := m20; m[2, 1] := m21
+	END Init;
+
+	(**
+		Procedures Get3PointTransform, Get2PointTransform and Invert may not be able to find a solution. In that case,
+		they return a singular matrix with all elements set to zero.
+	**)
+
+	(** calculate matrix that maps p0 to p1, q0 to q1, and r0 to r1 **)
+	PROCEDURE Get3PointTransform* (px0, py0, px1, py1, qx0, qy0, qx1, qy1, rx0, ry0, rx1, ry1: REAL; VAR res: Matrix);
+		VAR m: ARRAY 6, 7 OF REAL; i, j, k: LONGINT; max, t: REAL; v: ARRAY 6 OF REAL;
+	BEGIN
+		(* initialize set of linear equations for matrix coefficients *)
+		m[0, 0] := px0; m[0, 1] := py0; m[0, 2] := 1.0; m[0, 3] := 0.0; m[0, 4] := 0.0; m[0, 5] := 0.0; m[0, 6] := px1;
+		m[1, 0] := qx0; m[1, 1] := qy0; m[1, 2] := 1.0; m[1, 3] := 0.0; m[1, 4] := 0.0; m[1, 5] := 0.0; m[1, 6] := qx1;
+		m[2, 0] := rx0; m[2, 1] := ry0; m[2, 2] := 1.0; m[2, 3] := 0.0; m[2, 4] := 0.0;  m[2, 5] := 0.0; m[2, 6] := rx1;
+		m[3, 0] := 0.0; m[3, 1] := 0.0; m[3, 2] := 0.0; m[3, 3] := px0; m[3, 4] := py0; m[3, 5] := 1.0; m[3, 6] := py1;
+		m[4, 0] := 0.0; m[4, 1] := 0.0; m[4, 2] := 0.0; m[4, 3] := qx0; m[4, 4] := qy0; m[4, 5] := 1.0; m[4, 6] := qy1;
+		m[5, 0] := 0.0; m[5, 1] := 0.0; m[5, 2] := 0.0; m[5, 3] := rx0; m[5, 4] := ry0; m[5, 5] := 1.0; m[5, 6] := ry1;
+
+		(* Gaussian elimination with pivoting *)
+		FOR i := 0 TO 5 DO
+			k := i; max := ABS(m[i, i]);
+			FOR j := i+1 TO 5 DO
+				IF ABS(m[j, i]) > max THEN
+					k := j; max := ABS(m[j, i])
+				END
+			END;
+			IF max < Eps THEN	(* matrix is singular *)
+				Init(res, 0, 0, 0, 0, 0, 0);
+				RETURN
+			END;
+			IF k # i THEN	(* swap rows to bring largest element up *)
+				FOR j := i TO 6 DO
+					t := m[i, j]; m[i, j] := m[k, j]; m[k, j] := t
+				END
+			END;
+			FOR k := i+1 TO 5 DO
+				t := m[k, i]/m[i, i];
+				FOR j := i+1 TO 6 DO
+					m[k, j] := m[k, j] - t * m[i, j]
+				END
+			END
+		END;
+
+		(* solve equations *)
+		FOR i := 5 TO 0 BY -1 DO
+			t := m[i, 6];
+			FOR j := i+1 TO 5 DO
+				t := t - v[j] * m[i, j]
+			END;
+			v[i] := t/m[i, i]
+		END;
+
+		Init(res, v[0], v[3], v[1], v[4], v[2], v[5])
+	END Get3PointTransform;
+
+	(** calculate matrix that maps p0 to p1 and q0 to q1 **)
+	PROCEDURE Get2PointTransform* (px0, py0, px1, py1, qx0, qy0, qx1, qy1: REAL; VAR res: Matrix);
+		VAR rx0, ry0, rx1, ry1: REAL;
+	BEGIN
+		rx0 := px0 + py0 - qy0; ry0 := py0 + qx0 - px0;
+		rx1 := px1 + py1 - qy1; ry1 := py1 + qx1 - px1;
+		Get3PointTransform(px0, py0, px1, py1, qx0, qy0, qx1, qy1, rx0, ry0, rx1, ry1, res)
+	END Get2PointTransform;
+
+	(** calculate inverse of matrix **)
+	PROCEDURE Invert* (m: Matrix; VAR res: Matrix);
+		VAR det, inv: REAL;
+	BEGIN
+		det := m[0, 0] * m[1, 1] - m[0, 1] * m[1, 0];
+		IF ABS(det) >= Eps THEN	(* matrix can be inverted; use Cramer's rule *)
+			inv := 1/det;
+			res[0, 0] := +inv * m[1, 1];
+			res[0, 1] := -inv * m[0, 1];
+			res[1, 0] := -inv * m[1, 0];
+			res[1, 1] := +inv * m[0, 0];
+			res[2, 0] := +inv * (m[1, 0] * m[2, 1] - m[1, 1] * m[2, 0]);
+			res[2, 1] := +inv * (m[0, 1] * m[2, 0] - m[0, 0] * m[2, 1])
+		ELSE
+			Init(res, 0, 0, 0, 0, 0, 0)
+		END
+	END Invert;
+
+
+	(**--- Detection of Special Cases ---**)
+
+	(** return determinant of matrix **)
+	PROCEDURE Det* (VAR m: Matrix): REAL;
+	BEGIN
+		RETURN m[0, 0] * m[1, 1] - m[0, 1] * m[1, 0]
+	END Det;
+
+	(** return whether matrix is singular **)
+	PROCEDURE Singular* (VAR m: Matrix): BOOLEAN;
+	BEGIN
+		RETURN ABS(m[0, 0] * m[1, 1] - m[0, 1] * m[1, 0]) < Eps
+	END Singular;
+
+	(** return whether matrix changes vector lengths **)
+	PROCEDURE Scaled* (VAR m: Matrix): BOOLEAN;
+	BEGIN
+		RETURN ABS(ABS(m[0, 0] * m[1, 1] - m[0, 1] * m[1, 0]) - 1) > Eps
+	END Scaled;
+
+	(** return whether matrix includes rotation, shear, or mirror transformation **)
+	PROCEDURE Rotated* (VAR m: Matrix): BOOLEAN;
+	BEGIN
+		RETURN (m[0, 0] < -Eps) OR (m[1, 1] < -Eps) OR (ABS(m[0, 1]) > Eps) OR (ABS(m[1, 0]) > Eps)
+	END Rotated;
+
+	(** return whether matrices should be considered equal **)
+	PROCEDURE Equal* (VAR m, n: Matrix): BOOLEAN;
+	BEGIN
+		RETURN
+			(ABS(m[0, 0] - n[0, 0]) < Eps) & (ABS(m[0, 1] - n[0, 1]) < Eps) &
+			(ABS(m[1, 0] - n[1, 0]) < Eps) & (ABS(m[1, 1] - n[1, 1]) < Eps) &
+			(ABS(m[2, 0] - n[2, 0]) < Eps) & (ABS(m[2, 1] - n[2, 1]) < Eps)
+	END Equal;
+
+
+	(**--- Matrix Concatenation ---**)
+
+	(**
+		Combinations of single transformations are evaluated from left to right. Executing Translate, Rotate or Scale
+		pre-concatenates a corresponding matrix to the left of the given matrix parameter. This has the effect that
+		the new transformation is applied before all previously accumulated transformations. Every transformation is
+		therefore executed in the context of the coordinate system defined by the concatenation of all transformations
+		to its right.
+	**)
+
+	(** translation by (dx, dy) **)
+	PROCEDURE Translate* (m: Matrix; dx, dy: REAL; VAR res: Matrix);
+	BEGIN
+		res[0, 0] := m[0, 0]; res[0, 1] := m[0, 1];
+		res[1, 0] := m[1, 0]; res[1, 1] := m[1, 1];
+		res[2, 0] := m[2, 0] + dx * m[0, 0] + dy * m[1, 0];
+		res[2, 1] := m[2, 1] + dx * m[0, 1] + dy * m[1, 1]
+	END Translate;
+
+	(** scale by (sx, sy) **)
+	PROCEDURE Scale* (m: Matrix; sx, sy: REAL; VAR res: Matrix);
+	BEGIN
+		res[0, 0] := sx * m[0, 0]; res[0, 1] := sx * m[0, 1];
+		res[1, 0] := sy * m[1, 0]; res[1, 1] := sy * m[1, 1];
+		res[2, 0] := m[2, 0]; res[2, 1] := m[2, 1]
+	END Scale;
+
+	(** scale at (ox, oy) by (sx, sy) **)
+	PROCEDURE ScaleAt* (m: Matrix; ox, oy, sx, sy: REAL; VAR res: Matrix);
+		VAR tx, ty: REAL;
+	BEGIN
+		res[0, 0] := sx * m[0, 0]; res[0, 1] := sx * m[0, 1];
+		res[1, 0] := sy * m[1, 0]; res[1, 1] := sy * m[1, 1];
+		tx := ox * (1-sx); ty := oy * (1-sy);
+		res[2, 0] := tx * m[0, 0] + ty * m[1, 0] + m[2, 0];
+		res[2, 1] := tx * m[0, 1] + ty * m[1, 1] + m[2, 1]
+	END ScaleAt;
+
+	(** rotate counter-clockwise by angle specified by its sine and cosine **)
+	PROCEDURE Rotate* (m: Matrix; sin, cos: REAL; VAR res: Matrix);
+	BEGIN
+		res[0, 0] := cos * m[0, 0] + sin * m[1, 0]; res[0, 1] := cos * m[0, 1] + sin * m[1, 1];
+		res[1, 0] := -sin * m[0, 0] + cos * m[1, 0]; res[1, 1] := -sin * m[0, 1] + cos * m[1, 1];
+		res[2, 0] := m[2, 0]; res[2, 1] := m[2, 1]
+	END Rotate;
+
+	(** rotate counter-clockwise around (ox, oy) by angle specified by its sine and cosine **)
+	PROCEDURE RotateAt* (m: Matrix; ox, oy, sin, cos: REAL; VAR res: Matrix);
+		VAR tx, ty: REAL;
+	BEGIN
+		res[0, 0] := cos * m[0, 0] + sin * m[1, 0]; res[0, 1] := cos * m[0, 1] + sin * m[1, 1];
+		res[1, 0] := -sin * m[0, 0] + cos * m[1, 0]; res[1, 1] := -sin * m[0, 1] + cos * m[1, 1];
+		tx := ox * (1-cos) + oy * sin; ty := oy * (1-cos) - ox * sin;
+		res[2, 0] := tx * m[0, 0] + ty * m[1, 0] + m[2, 0];
+		res[2, 1] := tx * m[0, 1] + ty * m[1, 1] + m[2, 1]
+	END RotateAt;
+
+	(** concatenate matrices **)
+	PROCEDURE Concat* (m, n: Matrix; VAR res: Matrix);
+	BEGIN
+		res[0, 0] := m[0, 0] * n[0, 0] + m[0, 1] * n[1, 0];
+		res[0, 1] := m[0, 0] * n[0, 1] + m[0, 1] * n[1, 1];
+		res[1, 0] := m[1, 0] * n[0, 0] + m[1, 1] * n[1, 0];
+		res[1, 1] := m[1, 0] * n[0, 1] + m[1, 1] * n[1, 1];
+		res[2, 0] := m[2, 0] * n[0, 0] + m[2, 1] * n[1, 0] + n[2, 0];
+		res[2, 1] := m[2, 0] * n[0, 1] + m[2, 1] * n[1, 1] + n[2, 1]
+	END Concat;
+
+
+	(**--- Arctan of Vector ---**)
+
+	PROCEDURE Atan2* (x, y: REAL): REAL;
+		VAR phi: REAL;
+	BEGIN
+		IF (ABS(x) < 1.0) & (ABS(y) >= ABS(x * MAX(REAL))) THEN	(* y/x would overflow *)
+			IF y >= 0.0 THEN phi := Math.pi/2
+			ELSE phi := -Math.pi/2
+			END
+		ELSIF x > 0.0 THEN	(* 1st or 4th quadrant *)
+			phi := Math.arctan(y/x)
+		ELSIF x < 0.0 THEN	(* 2nd or 3rd quadrant *)
+			phi := Math.arctan(y/x) + Math.pi
+		END;
+		RETURN phi
+	END Atan2;
+
+
+	(**--- Matrix Application ---**)
+
+	(** apply transformation matrix to point **)
+	PROCEDURE Apply* (VAR m: Matrix; xin, yin: REAL; VAR xout, yout: REAL);
+	BEGIN
+		xout := xin * m[0, 0] + yin * m[1, 0] + m[2, 0];
+		yout := xin * m[0, 1] + yin * m[1, 1] + m[2, 1]
+	END Apply;
+
+	(** apply transformation matrix to vector (ignoring translation) **)
+	PROCEDURE ApplyToVector* (VAR m: Matrix; xin, yin: REAL; VAR xout, yout: REAL);
+	BEGIN
+		xout := xin * m[0, 0] + yin * m[1, 0];
+		yout := xin * m[0, 1] + yin * m[1, 1]
+	END ApplyToVector;
+
+	(** apply transformation matrix to distance **)
+	PROCEDURE ApplyToDist* (VAR m: Matrix; din: REAL; VAR dout: REAL);
+		VAR x, y: REAL;
+	BEGIN
+		x := din * m[0, 0]; y := din * m[0, 1];
+		IF ABS(y) < 1.0E-3 THEN dout := x
+		ELSE dout := Math.sqrt(x * x + y * y)
+		END
+	END ApplyToDist;
+
+	(** apply transformation matrix to axis-aligned rectangle; result is enclosing axis-aligned rectangle **)
+	PROCEDURE ApplyToRect* (VAR m: Matrix; ilx, ily, irx, iuy: REAL; VAR olx, oly, orx, ouy: REAL);
+		VAR l, h: REAL;
+	BEGIN
+		olx := m[2, 0]; orx := m[2, 0];
+		l := ilx * m[0, 0]; h := irx * m[0, 0];
+		IF l <= h THEN olx := olx + l; orx := orx + h ELSE olx := olx + h; orx := orx + l END;
+		l := ily * m[1, 0]; h := iuy * m[1, 0];
+		IF l <= h THEN olx := olx + l; orx := orx + h ELSE olx := olx + h; orx := orx + l END;
+		oly := m[2, 1]; ouy := m[2, 1];
+		l := ilx * m[0, 1]; h := irx * m[0, 1];
+		IF l <= h THEN oly := oly + l; ouy := ouy + h ELSE oly := oly + h; ouy := ouy + l END;
+		l := ily * m[1, 1]; h := iuy * m[1, 1];
+		IF l <= h THEN oly := oly + l; ouy := ouy + h ELSE oly := oly + h; ouy := ouy + l END
+	END ApplyToRect;
+
+	(** apply inverse of matrix to point **)
+	PROCEDURE Solve* (VAR m: Matrix; u, v: REAL; VAR x, y: REAL);
+		VAR det: REAL;
+	BEGIN
+		det := m[0, 0] * m[1, 1] - m[0, 1] * m[1, 0];
+		IF ABS(det) >= Eps THEN	(* matrix can be inverted *)
+			u := u - m[2, 0]; v := v - m[2, 1];
+			x := (m[1, 1] * u - m[1, 0] * v)/det;
+			y := (m[0, 0] * v - m[0, 1] * u)/det
+		END
+	END Solve;
+
+
+	(**--- Matrix I/O ---**)
+
+	PROCEDURE Write* (VAR w: Streams.Writer; VAR m: Matrix);
+		VAR i: LONGINT;
+	BEGIN
+		FOR i := 0 TO 2 DO
+			w.RawReal(m[i, 0]); w.RawReal(m[i, 1])
+		END
+	END Write;
+
+	PROCEDURE Read* (VAR r: Streams.Reader; VAR m: Matrix);
+		VAR i: LONGINT;
+	BEGIN
+		FOR i := 0 TO 2 DO
+			r.RawReal(m[i, 0]); r.RawReal(m[i, 1])
+		END
+	END Read;
+
+
+BEGIN
+	Init(Identity, 1, 0, 0, 1, 0, 0)
+END GfxMatrix.

+ 1413 - 0
source/AGfxPaths.Mod

@@ -0,0 +1,1413 @@
+(* ETH Oberon, Copyright 2001 ETH Zuerich Institut fuer Computersysteme, ETH Zentrum, CH-8092 Zuerich.
+Refer to the "General ETH Oberon System Source License" contract available at: http://www.oberon.ethz.ch/ *)
+
+MODULE GfxPaths; (** portable *)	(* eos  *)
+(** AUTHOR "eos"; PURPOSE "Two_dimensional paths consisting of lines, arcs and bezier curves"; *)
+
+	(*
+		9.2.98 - made behaviour of EnumSpline similar to that of EnumArc and EnumBezier (produces no Enter/Exit)
+		11.2.98 - eliminated offset parameter in Enter elements, optimized data structure (now always pair in CoordBlock)
+		12.2.98 - added length functions
+		18.3.98 - fixed bug in EnumQuery (kept wrong code for next line)
+		13.5.98 - fixed bug in EnumBezier (wrong calculation; used x instead of y)
+		15.9.98 - minor cleanup: removed position, Save/Restore, GetBBox; simplified scanner interface
+		26.11.98 - added procedure Close
+		26.1.99 - added procedure Split
+		21.5.99 - fixed major bug in Reverse (no update of destination path fields)
+		12.7.99 - fixed another bug in Reverse (wrong direction when reverting Exit element)
+		12.7.99 - approximate arc with line if radius is smaller than flatness
+		18.02.2000 - simpler initial step without sqrt in EnumArc
+		18.02.2000 - more robust bezier code (deals with folded curves and cusps)
+		27.02.2000 - fixed arc code for starting points that are not on the ellipse
+		04.05.2000 - fixed solve; one execution path never set number of solutions (noticed by gf)
+	*)
+
+	IMPORT
+		Math, GfxMatrix;
+
+
+	CONST
+		Stop* = 0; Enter* = 1; Line* = 2; Arc* = 3; Bezier* = 4; Exit* = 5;	(** path element types **)
+
+		ElemBlockSize = 16;	(* base of number of path elements *)
+		CoordBlockSize = 32;	(* base of number of path coordinates *)
+
+		MaxSplinePoints* = 128;	(** maximal number of control points in a spline **)
+
+		Left = 0; Right = 1; Bottom = 2; Top = 3;	(* clip codes *)
+
+
+	TYPE
+		(* internal path structures *)
+		ElemBlock = POINTER TO ElemBlockDesc;
+		ElemBlockDesc = RECORD
+			next: ElemBlock;
+			elem: ARRAY ElemBlockSize OF SHORTINT;
+			coords: INTEGER;
+		END;
+
+		CoordBlock = POINTER TO CoordBlockDesc;
+		CoordBlockDesc = RECORD
+			next: CoordBlock;
+			x, y: ARRAY CoordBlockSize OF REAL;
+		END;
+
+		(** path abstraction **)
+		Path* = OBJECT
+		VAR
+			elems* := 0, coords* := 0: INTEGER;	(** number of elements/coordinate pairs in path **)
+			firstEB := NIL, lastEB := NIL: ElemBlock;	(* path element types *)
+			firstCB := NIL, lastCB := NIL: CoordBlock;	(* path element coordinates *)
+			
+			(** discard previous contents and start new path **)
+			PROCEDURE Clear* ();
+			BEGIN
+				IF SELF.firstEB = NIL THEN NEW(SELF.firstEB) END;
+				SELF.lastEB := SELF.firstEB; SELF.lastEB.next := NIL;
+				IF SELF.firstCB = NIL THEN NEW(SELF.firstCB) END;
+				SELF.lastCB := SELF.firstCB; SELF.lastCB.next := NIL;
+				SELF.elems := 0; SELF.coords := 0;
+				SELF.firstEB.coords := 0
+			END Clear;
+			
+			(** append enter element **)
+			PROCEDURE AddEnter* (x, y, dx, dy: REAL);
+			BEGIN
+				AddElem(SELF, Enter);
+				AddCoord(SELF, dx, dy);
+				AddCoord(SELF, x, y)
+			END AddEnter;
+			
+			(** append line element **)
+			PROCEDURE AddLine* (x, y: REAL);
+			BEGIN
+				AddElem(SELF, Line);
+				AddCoord(SELF, x, y)
+			END AddLine;
+
+			(** append arc element **)
+			PROCEDURE AddArc* (x, y, x0, y0, x1, y1, x2, y2: REAL);
+			BEGIN
+				AddElem(SELF, Arc);
+				AddCoord(SELF, x0, y0);
+				AddCoord(SELF, x1, y1);
+				AddCoord(SELF, x2, y2);
+				AddCoord(SELF, x, y)
+			END AddArc;
+
+			(** append bezier element **)
+			PROCEDURE AddBezier* (x, y, x1, y1, x2, y2: REAL);
+			BEGIN
+				AddElem(SELF, Bezier);
+				AddCoord(SELF, x1, y1);
+				AddCoord(SELF, x2, y2);
+				AddCoord(SELF, x, y)
+			END AddBezier;
+
+			(** append exit element **)
+			PROCEDURE AddExit* (dx, dy: REAL);
+			BEGIN
+				AddElem(SELF, Exit);
+				AddCoord(SELF, dx, dy)
+			END AddExit;
+
+			(** append subpath for axis-aligned rectangle **)
+			PROCEDURE AddRect* (llx, lly, urx, ury: REAL);
+			BEGIN
+				SELF.AddEnter(llx, lly, 0, lly - ury);
+				SELF.AddLine(urx, lly); SELF.AddLine(urx, ury); SELF.AddLine(llx, ury); SELF.AddLine(llx, lly);
+				SELF.AddExit(urx - llx, 0)
+			END AddRect;
+
+			(** append one path path another **)
+			PROCEDURE Append* (from: Path);
+				VAR pos, epos, cpos, n: INTEGER; eb: ElemBlock; cb: CoordBlock; elem: SHORTINT;
+			BEGIN
+				pos := 0; epos := 0; cpos := 0; eb := from.firstEB; cb := from.firstCB;
+				WHILE pos < from.elems DO
+					IF epos = ElemBlockSize THEN
+						eb := eb.next; epos := 0
+					END;
+					elem := eb.elem[epos]; INC(epos);
+					AddElem(SELF, elem);
+					n := Coords[elem];
+					WHILE n > 0 DO
+						IF cpos = CoordBlockSize THEN
+							cb := cb.next; cpos := 0
+						END;
+						AddCoord(SELF, cb.x[cpos], cb.y[cpos]);
+						INC(cpos); DEC(n)
+					END;
+					INC(pos)
+				END
+			END Append;
+
+			(** enumerate path elements **)
+			PROCEDURE Enumerate* (enum: Enumerator; VAR data: EnumData);
+				VAR eb: ElemBlock; cb: CoordBlock; pos, epos, cpos: INTEGER;
+
+				PROCEDURE get (VAR x, y: REAL);
+				BEGIN
+					IF cpos = CoordBlockSize THEN
+						cb := cb.next; cpos := 0;
+					END;
+					x := cb.x[cpos]; y := cb.y[cpos]; INC(cpos);
+				END get;
+
+			BEGIN
+				eb := SELF.firstEB; cb := SELF.firstCB;
+				pos := 0; epos := 0; cpos := 0;
+				WHILE pos < SELF.elems DO
+					IF epos = ElemBlockSize THEN
+						eb := eb.next; epos := 0;
+					END;
+					data.elem := eb.elem[epos];
+					CASE data.elem OF
+					| Enter: get(data.dx, data.dy); get(data.x, data.y);
+					| Line: get(data.x, data.y);
+					| Arc: get(data.x0, data.y0); get(data.x1, data.y1); get(data.x2, data.y2); get(data.x, data.y);
+					| Bezier: get(data.x1, data.y1); get(data.x2, data.y2); get(data.x, data.y);
+					| Exit: get(data.dx, data.dy);
+					END;
+					enum(data);
+					INC(pos); INC(epos);
+				END
+			END Enumerate;
+
+			(** enumerate flattened path, i.e. arcs and bezier curves will be approximated with lines **)
+			PROCEDURE EnumFlattened* (flatness: REAL; enum: Enumerator; VAR data: EnumData);
+				VAR eb: ElemBlock; cb: CoordBlock; pos, epos, cpos: INTEGER; x0, y0, x1, y1, x2, y2, x, y: REAL;
+
+				PROCEDURE get (VAR x, y: REAL);
+				BEGIN
+					IF cpos = CoordBlockSize THEN
+						cb := cb.next; cpos := 0;
+					END;
+					x := cb.x[cpos]; y := cb.y[cpos]; INC(cpos);
+				END get;
+
+			BEGIN
+				eb := SELF.firstEB; cb := SELF.firstCB;
+				pos := 0; epos := 0; cpos := 0;
+				WHILE pos < SELF.elems DO
+					IF epos = ElemBlockSize THEN
+						eb := eb.next; epos := 0
+					END;
+					data.elem := eb.elem[epos];
+					CASE data.elem OF
+					| Enter:
+						get(data.dx, data.dy); get(data.x, data.y);
+						enum(data)
+					| Line:
+						get(data.x, data.y);
+						enum(data)
+					| Arc:
+						get(x0, y0); get(x1, y1); get(x2, y2); get(x, y);
+						EnumArc(x0, y0, x1, y1, x2, y2, x, y, flatness, enum, data)
+					| Bezier:
+						get(x1, y1); get(x2, y2); get(x, y);
+						EnumBezier(x1, y1, x2, y2, x, y, flatness, enum, data);
+						(* why this? data.elem := Line; data.x := x; data.y := y; enum(data) *)
+					| Exit:
+						get(data.dx, data.dy);
+						enum(data);
+					END;
+					INC(pos); INC(epos)
+				END
+			END EnumFlattened;
+
+			(** calculate path length **)
+			PROCEDURE Length* (flatness: REAL): REAL;
+				VAR data: LengthData;
+			BEGIN
+				data.len := 0;
+				SELF.EnumFlattened(flatness, EnumLength, data);
+				RETURN data.len
+			END Length;
+
+			(** return whether path is empty **)
+			PROCEDURE Empty* (): BOOLEAN;
+			BEGIN
+				RETURN SELF.elems = 0
+			END Empty;
+
+			(** calculate bounding box of path **)
+			PROCEDURE GetBox* (VAR llx, lly, urx, ury: REAL);
+				VAR data: QueryData;
+			BEGIN
+				data.llx := MAX(REAL); data.lly := MAX(REAL); data.urx := MIN(REAL); data.ury := MIN(REAL);
+				SELF.EnumFlattened(1, EnumBoxElem, data);
+				llx := data.llx; lly := data.lly; urx := data.urx; ury := data.ury
+			END GetBox;
+			
+			(**--- Path Operations ---**)
+
+			(** put reversed source path into destination path; dst remains unchanged if src is empty **)
+			PROCEDURE ReverseTo* (dst: Path);
+				VAR
+					elems, sepos, scpos, depos, dcpos: INTEGER; dstEB, nextEB, srcEB, eb: ElemBlock;
+					dstCB, nextCB, srcCB: CoordBlock; dx, dy, x, y, x0, y0, x1, y1, x2, y2: REAL;
+
+				PROCEDURE get (VAR x, y: REAL);
+				BEGIN
+					IF scpos = CoordBlockSize THEN
+						srcCB := srcCB.next; scpos := 0
+					END;
+					x := srcCB.x[scpos]; y := srcCB.y[scpos]; INC(scpos)
+				END get;
+
+				PROCEDURE put (x, y: REAL);
+					VAR cb: CoordBlock;
+				BEGIN
+					IF dcpos = 0 THEN
+						IF nextCB # NIL THEN cb := nextCB; nextCB := cb.next
+						ELSE NEW(cb)
+						END;
+						cb.next := dstCB; dstCB := cb;
+						dcpos := CoordBlockSize
+					END;
+					DEC(dcpos); INC(dstEB.coords);
+					dstCB.x[dcpos] := x; dstCB.y[dcpos] := y
+				END put;
+
+			BEGIN
+				ASSERT(SELF # dst, 100);
+				elems := SELF.elems;
+				IF elems > 0 THEN
+					IF dst.firstEB # NIL THEN dstEB := dst.firstEB; dstEB.coords := 0; nextEB := dstEB.next; dstEB.next := NIL
+					ELSE NEW(dstEB); nextEB := NIL
+					END;
+					IF dst.firstCB # NIL THEN dstCB := dst.firstCB; nextCB := dstCB.next; dstCB.next := NIL
+					ELSE NEW(dstCB); nextCB := NIL
+					END;
+					dst.lastEB := dstEB; dst.lastCB := dstCB;
+					srcEB := SELF.firstEB; srcCB := SELF.firstCB;
+					sepos := 0; scpos := 0;
+					depos := (SELF.elems-1) MOD ElemBlockSize + 1; dcpos := (SELF.coords-1) MOD CoordBlockSize + 1;
+					REPEAT
+						(*
+							store reverted path in dst:
+								- segment end points become end points of their inverted successors
+								- order of control points is reversed
+								- directions are inverted
+						*)
+						IF sepos = ElemBlockSize THEN
+							srcEB := srcEB.next; sepos := 0
+						END;
+						IF depos = 0 THEN
+							IF nextEB # NIL THEN eb := nextEB; eb.coords := 0; nextEB := eb.next
+							ELSE NEW(eb)
+							END;
+							eb.next := dstEB; dstEB := eb;
+							depos := ElemBlockSize
+						END;
+						DEC(depos);
+						CASE srcEB.elem[sepos] OF
+						| Enter:
+							dstEB.elem[depos] := Exit;
+							get(dx, dy); get(x, y);
+							put(-dx, -dy); put(x, y)
+						| Line:
+							dstEB.elem[depos] := Line;
+							get(x, y);
+							put(x, y)
+						| Arc:
+							dstEB.elem[depos] := Arc;
+							get(x0, y0); get(x1, y1); get(x2, y2); get(x, y);
+							put(x1, y1); put(x2, y2); put(x0, y0); put(x, y)
+						| Bezier:
+							dstEB.elem[depos] := Bezier;
+							get(x1, y1); get(x2, y2); get(x, y);
+							put(x1, y1); put(x2, y2); put(x, y)
+						| Exit:
+							dstEB.elem[depos] := Enter;
+							get(dx, dy);
+							put(-dx, -dy)
+						END;
+						INC(sepos); DEC(elems)
+					UNTIL elems = 0;
+					dst.firstEB := dstEB; dst.firstCB := dstCB;
+					dst.elems := SELF.elems; dst.coords := SELF.coords
+				END
+			END ReverseTo;
+
+			(** return copy of source path in destination path **)
+			PROCEDURE CopyTo* (dst: Path);
+				VAR srcEB, dstEB: ElemBlock; n: INTEGER; srcCB, dstCB: CoordBlock;
+			BEGIN
+				IF SELF # dst THEN
+					IF dst.firstEB = NIL THEN NEW(dst.firstEB) END;
+					srcEB := SELF.firstEB; dstEB := dst.firstEB;
+					LOOP
+						IF srcEB = SELF.lastEB THEN n := (SELF.elems-1) MOD ElemBlockSize + 1
+						ELSE n := ElemBlockSize
+						END;
+						WHILE n > 0 DO
+							DEC(n); dstEB.elem[n] := srcEB.elem[n]
+						END;
+						dstEB.coords := srcEB.coords;
+						IF srcEB = SELF.lastEB THEN EXIT END;
+						IF dstEB.next = NIL THEN NEW(dstEB.next) END;
+						srcEB := srcEB.next; dstEB := dstEB.next
+					END;
+					dst.lastEB := dstEB; dstEB.next := NIL;
+
+					IF dst.firstCB = NIL THEN NEW(dst.firstCB) END;
+					srcCB := SELF.firstCB; dstCB := dst.firstCB;
+					LOOP
+						IF srcCB = SELF.lastCB THEN n := (SELF.coords-1) MOD CoordBlockSize + 1
+						ELSE n := CoordBlockSize
+						END;
+						WHILE n > 0 DO
+							DEC(n); dstCB.x[n] := srcCB.x[n]; dstCB.y[n] := srcCB.y[n]
+						END;
+						IF srcCB = SELF.lastCB THEN EXIT END;
+						IF dstCB.next = NIL THEN NEW(dstCB.next) END;
+						srcCB := srcCB.next; dstCB := dstCB.next
+					END;
+					dst.lastCB := dstCB; dstCB.next := NIL;
+					dst.elems := SELF.elems; dst.coords := SELF.coords
+				END
+			END CopyTo;
+
+			(** apply transformation to all coordinates in path **)
+			PROCEDURE Apply* (VAR mat: GfxMatrix.Matrix);
+				VAR eb: ElemBlock; cb: CoordBlock; pos, epos, cpos: INTEGER;
+
+				PROCEDURE point (VAR b: CoordBlock; VAR idx: INTEGER);
+				BEGIN
+					IF idx = CoordBlockSize THEN
+						b := b.next; idx := 0
+					END;
+					GfxMatrix.Apply(mat, b.x[idx], b.y[idx], b.x[idx], b.y[idx]);
+					INC(idx)
+				END point;
+
+				PROCEDURE vector (VAR b: CoordBlock; VAR idx: INTEGER);
+				BEGIN
+					IF idx = CoordBlockSize THEN
+						b := b.next; idx := 0
+					END;
+					GfxMatrix.ApplyToVector(mat, b.x[idx], b.y[idx], b.x[idx], b.y[idx]);
+					INC(idx)
+				END vector;
+
+			BEGIN
+				eb := SELF.firstEB; cb := SELF.firstCB;
+				pos := 0; epos := 0; cpos := 0;
+				WHILE pos < SELF.elems DO
+					IF epos = ElemBlockSize THEN
+						eb := eb.next; epos := 0
+					END;
+					CASE eb.elem[epos] OF
+					| Enter: vector(cb, cpos); point(cb, cpos)
+					| Line: point(cb, cpos)
+					| Arc: point(cb, cpos); point(cb, cpos); point(cb, cpos); point(cb, cpos)
+					| Bezier: point(cb, cpos); point(cb, cpos); point(cb, cpos)
+					| Exit: vector(cb, cpos)
+					END;
+					INC(pos); INC(epos)
+				END
+			END Apply;
+
+			(** try to close disconnected enter/exit points by modifying their direction vectors **)
+			PROCEDURE Close* ();
+				CONST
+					eps = 0.001;
+
+				VAR
+					pos, epos, cpos, p, spos: INTEGER; eb: ElemBlock; cb, b, sb: CoordBlock; dx, dy, cx, cy, sx, sy, sdx, sdy, x, y, edx, edy: REAL;
+					data: DirData;
+
+				PROCEDURE get (VAR x, y: REAL);
+				BEGIN
+					IF cpos = CoordBlockSize THEN
+						cb := cb.next; cpos := 0
+					END;
+					x := cb.x[cpos]; y := cb.y[cpos];
+					INC(cpos)
+				END get;
+
+			BEGIN
+				pos := 0; epos := 0; cpos := 0; eb := SELF.firstEB; cb := SELF.firstCB;
+				WHILE pos < SELF.elems DO
+					IF epos = ElemBlockSize THEN
+						eb := eb.next; epos := 0
+					END;
+					CASE eb.elem[epos] OF
+					| Enter:
+						b := cb; p := cpos;
+						get(dx, dy); get(cx, cy);
+						IF (dx = 0) & (dy = 0) THEN
+							sb := b; spos := p; sx := cx; sy := cy; sdx := 0; sdy := 0
+						END
+					| Line:
+						get(x, y); dx := x - cx; dy := y - cy; cx := x; cy := y;
+						IF (sdx = 0) & (sdy = 0) THEN
+							sdx := dx; sdy := dy
+						END
+					| Arc:
+						data.sdx := 0; data.sdy := 0; data.cx := cx; data.cy := cy; data.x := cx; data.y := cy;
+						get(data.x0, data.y0); get(data.x1, data.y1); get(data.x2, data.y2); get(cx, cy);
+						EnumArc(data.x0, data.y0, data.x1, data.y1, data.x2, data.y2, cx, cy, 1.0, GetDir, data);
+						IF (sdx = 0) & (sdy = 0) THEN
+							sdx := data.sdx; sdy := data.sdy
+						END;
+						dx := data.edx; dy := data.edy
+					| Bezier:
+						get(x, y);
+						IF (sdx = 0) & (sdy = 0) THEN
+							sdx := x - cx; sdy := y - cy
+						END;
+						get(x, y); get(cx, cy); dx := cx - x; dy := cy - y
+					| Exit:
+						b := cb; p := cpos;
+						get(edx, edy);
+						IF (edx = 0) & (edy = 0) & (ABS(x - sx) <= eps) & (ABS(y - sy) <= eps) THEN
+							IF spos = CoordBlockSize THEN
+								sb := sb.next; spos := 0
+							END;
+							sb.x[spos] := dx; sb.y[spos] := dy;
+							IF p = CoordBlockSize THEN
+								b := b.next; p := 0
+							END;
+							b.x[p] := sdx; b.y[p] := sdy
+						END
+					END;
+					INC(pos); INC(epos)
+				END
+			END Close;
+			
+			(** split subpath in two at given offset (resulting subpaths may be flattened in the process) **)
+			PROCEDURE Split* (offset: REAL; head, tail: Path);
+				VAR data: SplitData;
+			BEGIN
+				IF offset <= 0 THEN
+					SELF.CopyTo(tail); head.Clear()
+				ELSIF offset >= SELF.Length(1) THEN
+					SELF.CopyTo(head); tail.Clear()
+				ELSE
+					head.Clear(); tail.Clear();
+					data.offset := offset; data.head := head; data.tail := tail;
+					SELF.EnumFlattened(1, EnumSplit, data)
+				END
+			END Split;
+			
+			(** return projection of point onto path **)
+			PROCEDURE ProjectTo* (x, y: REAL; VAR u, v: REAL);
+				VAR data: ProjectData;
+			BEGIN
+				data.px := x; data.py := y; data.dist := MAX(REAL); data.rx := MAX(REAL); data.ry := MAX(REAL);
+				SELF.EnumFlattened(1, EnumProject, data);
+				u := data.rx; v := data.ry
+			END ProjectTo;
+
+			(**--- Path Queries ---**)
+
+			(** return whether rectangle is completely inside (closed) path **)
+			PROCEDURE InPath* (llx, lly, urx, ury: REAL; evenOdd: BOOLEAN): BOOLEAN;
+				VAR data: QueryData;
+			BEGIN
+				data.thorough := TRUE; data.sum := 0; data.hit := FALSE; data.llx := llx; data.lly := lly; data.urx := urx; data.ury := ury;
+				SELF.EnumFlattened(1, EnumQuery, data);
+				RETURN data.hit OR evenOdd & ODD(ABS(data.sum) DIV 2) OR ~evenOdd & (data.sum # 0)
+			END InPath;
+
+			(** return whether rectangle intersects SELF **)
+			PROCEDURE OnPath* (llx, lly, urx, ury: REAL): BOOLEAN;
+				VAR data: QueryData;
+			BEGIN
+				data.thorough := FALSE; data.hit := FALSE; data.llx := llx; data.lly := lly; data.urx := urx; data.ury := ury;
+				SELF.EnumFlattened(1, EnumQuery, data);
+				RETURN data.hit
+			END OnPath;
+
+			
+		END Path;
+
+		(** path scanner **)
+		(**
+			Path scanners can be used to iterate over a path under client control. The scanner's elem field specifies what
+			the current element is, whereas the remaining fields contain the parameters for that element. A Stop element
+			indicates that the end of the path has been reached.
+		**)
+		Scanner* = RECORD
+			path*: Path;	(** visited path **)
+			pos*: INTEGER;	(** element position **)
+			elem*: INTEGER;	(** current path element **)
+			x*, y*: REAL;	(** current end coordinates **)
+			dx*, dy*: REAL;	(** direction vector **)
+			x0*, y0*, x1*, y1*, x2*, y2*: REAL;	(** additional control point coordinates **)
+			curEB: ElemBlock;	(* current element block *)
+			curCB: CoordBlock;	(* current coordinate block *)
+			epos, cpos: INTEGER;	(* next element and coordinate position within current block *)
+			
+			
+			(** open scanner on path and load parameters of element at given position **)
+			PROCEDURE Open* (path: Path; pos: INTEGER);
+			BEGIN
+				SELF.path := path;
+				SELF.curEB := path.firstEB; SELF.curCB := path.firstCB;
+				SELF.pos := pos; SELF.epos := pos; SELF.cpos := 0;
+				WHILE SELF.epos > ElemBlockSize DO
+					DEC(SELF.epos, ElemBlockSize);
+					INC(SELF.cpos, SELF.curEB.coords);
+					SELF.curEB := SELF.curEB.next
+				END;
+				pos := 0;
+				WHILE pos < SELF.epos DO
+					SELF.cpos := SELF.cpos + Coords[SELF.curEB.elem[pos]]; INC(pos)
+				END;
+				WHILE SELF.cpos > CoordBlockSize DO
+					DEC(SELF.cpos, CoordBlockSize);
+					SELF.curCB := SELF.curCB.next
+				END;
+				IF SELF.pos = path.elems THEN
+					SELF.elem := Stop
+				ELSE
+					IF SELF.epos = ElemBlockSize THEN	(* at end of current block *)
+						SELF.curEB := SELF.curEB.next; SELF.epos := 0
+					END;
+					SELF.elem := SELF.curEB.elem[SELF.epos]; INC(SELF.epos);
+					CASE SELF.elem OF
+					| Enter: get(SELF.dx, SELF.dy); get(SELF.x, SELF.y)
+					| Line: get(SELF.x, SELF.y)
+					| Arc: get(SELF.x0, SELF.y0); get(SELF.x1, SELF.y1); get(SELF.x2, SELF.y2); get(SELF.x, SELF.y)
+					| Bezier: get(SELF.x1, SELF.y1); get(SELF.x2, SELF.y2); get(SELF.x, SELF.y)
+					| Exit: get(SELF.dx, SELF.dy)
+					END
+				END
+			END Open;
+			
+			(** advance to next element and load its parameters **)
+			PROCEDURE Scan* ();
+			BEGIN
+				IF SELF.pos < SELF.path.elems THEN
+					INC(SELF.pos);
+					IF SELF.pos = SELF.path.elems THEN
+						SELF.elem := Stop
+					ELSE
+						IF SELF.epos = ElemBlockSize THEN	(* at end of current block *)
+							SELF.curEB := SELF.curEB.next; SELF.epos := 0
+						END;
+						SELF.elem := SELF.curEB.elem[SELF.epos]; INC(SELF.epos);
+						CASE SELF.elem OF
+						| Enter: get(SELF.dx, SELF.dy); get(SELF.x, SELF.y)
+						| Exit: get(SELF.dx, SELF.dy)
+						| Line: get(SELF.x, SELF.y)
+						| Arc: get(SELF.x0, SELF.y0); get(SELF.x1, SELF.y1); get(SELF.x2, SELF.y2); get(SELF.x, SELF.y)
+						| Bezier: get(SELF.x1, SELF.y1); get(SELF.x2, SELF.y2); get(SELF.x, SELF.y)
+						END
+					END
+				END
+			END Scan;
+	
+			PROCEDURE get (VAR x, y: REAL);
+			BEGIN
+				IF SELF.cpos = CoordBlockSize THEN
+					SELF.curCB := SELF.curCB.next; SELF.cpos := 0
+				END;
+				x := SELF.curCB.x[SELF.cpos]; y := SELF.curCB.y[SELF.cpos]; INC(SELF.cpos)
+			END get;			
+	
+		END (* Scanner *);
+
+		(** path enumeration **)
+		EnumData* = RECORD
+			elem*: INTEGER;	(** current path element **)
+			x*, y*, dx*, dy*, x0*, y0*, x1*, y1*, x2*, y2*: REAL;	(** element parameters **)
+		END;
+
+		Enumerator* = PROCEDURE (VAR data: EnumData);
+
+		ProjectData = RECORD (EnumData)
+			px, py: REAL;	(* point coordinates *)
+			rx, ry: REAL;	(* projection coordinates *)
+			sx, sy: REAL;	(* previous coordinates *)
+			dist: REAL;	(* distance of projection to original point *)
+		END;
+
+		QueryData = RECORD (EnumData)
+			llx, lly, urx, ury: REAL;	(* query rectangle *)
+			sx, sy: REAL;	(* previous coordinates *)
+			code: SET;	(* clip code of previous point *)
+			sum: LONGINT;	(* number of ray crossings for inside test *)
+			hit, thorough: BOOLEAN;
+		END;
+
+		LengthData = RECORD (EnumData)
+			sx, sy: REAL;	(* previous coordinates *)
+			len: REAL;
+		END;
+
+		DirData = RECORD (EnumData)
+			cx, cy: REAL;
+			sdx, sdy: REAL;
+			edx, edy: REAL;
+		END;
+
+		SplitData = RECORD (EnumData)
+			head, tail: Path;
+			offset: REAL;
+			sx, sy: REAL;
+			sdx, sdy: REAL
+		END;
+
+
+	(**
+		A paths consists of any number of subpaths, where each subpath starts with a Enter element, followed by
+		any number of curve elements, and terminated by an Exit element.
+
+		Enter
+			(x, y) is the starting point for the following curve element
+			(dx, dy) is the tangent vector at the end of an adjacent subpath or (0, 0) if there is none
+
+		Line
+			(x, y) is the end point of the line and the starting point of any subsequent curve
+
+		Arc
+			(x, y) is the end point of the arc and the starting point of any subsequent curve (may coincide with the
+					current point, resulting in a circle or ellipse)
+			(x0, y0) is the center of the circle/ellipse this arc is part of
+			(x1, y1) is the end point of the first half axis vector
+			(x2, y2) is the end point of the first half axis vector (not necessarily perpendicular to the first HAV)
+
+		Bezier
+			(x, y) is the end point of the cubic bezier curve and the starting point of any subsequent curve
+			(x1, y1) is the first control point of the cubic bezier curve
+			(x1, y1) is the second control point of the cubic bezier curve
+
+		Exit
+			(dx, dy) is the tangent vector at the starting point of an adjacent subpath or (0, 0) if there is none
+	**)
+
+
+	VAR
+		Coords: ARRAY Exit+1 OF SHORTINT;	(* number of coordinate pairs for each element type *)
+
+
+	(**--- Path Construction ---**)
+
+	PROCEDURE AddElem (path: Path; elem: SHORTINT);
+		VAR elems: INTEGER; eb: ElemBlock;
+	BEGIN
+		elems := path.elems MOD ElemBlockSize;
+		IF (elems = 0) & (path.elems > 0) THEN
+			NEW(eb); path.lastEB.next := eb; path.lastEB := eb;
+		END;
+		path.lastEB.elem[elems] := elem;
+		INC(path.elems)
+	END AddElem;
+
+	PROCEDURE AddCoord (path: Path; x, y: REAL);
+		VAR coords: INTEGER; cb: CoordBlock;
+	BEGIN
+		coords := path.coords MOD CoordBlockSize;
+		IF (coords = 0) & (path.coords > 0) THEN
+			NEW(cb); path.lastCB.next := cb; path.lastCB := cb
+		END;
+		path.lastCB.x[coords] := x; path.lastCB.y[coords] := y;
+		INC(path.coords); INC(path.lastEB.coords)
+	END AddCoord;
+
+
+	(**--- Enumerating (Flattened) Paths ---**)
+
+	(**
+		In addition to being scanned, path elements may also be enumerated. The advantage of enumerating path
+		elements is that arcs and bezier curves can be enumerated as a sequence of lines approximating the original
+		curve. Besides, natural splines can enumerated in terms of regular path elements.
+	**)
+
+	(** enumerate arc as a sequence of lines with maximal error 'flatness'; current point must be in (data.x, data.y) **)
+	PROCEDURE EnumArc* (x0, y0, x1, y1, x2, y2, x, y, flatness: REAL; enum: Enumerator; VAR data: EnumData);
+		CONST
+			eps = 1.0E-3;
+		VAR
+			lx, ly, sense, xs, ys, xe, ye, dt, p2, tmp, p1, dx1, dx2, dy1, dy2, sx, sy, tx, ty, limit, dx, dy, tlen, ex, ey: REAL;
+			positive: BOOLEAN;
+	BEGIN
+		(* algorithm: D. Fellner & C. Helmberg, Robust Rendering of General Ellipses and Elliptical Arcs, ACM TOG July 1993 *)
+		data.elem := Line;
+		x1 := x1 - x0; y1 := y1 - y0;
+		x2 := x2 - x0; y2 := y2 - y0;
+		IF ABS(x1 * y2 - y1 * x2) <= eps * ABS(x1 * x2 + y1 * y2) THEN	(* approximate with line *)
+			data.x := x; data.y := y; enum(data);
+			RETURN
+		END;
+
+		lx := ABS(x1) + ABS(x2); ly := ABS(y1) + ABS(y2);
+		IF (lx <= ly) & (lx <= flatness) OR (ly <= lx) & (ly <= flatness) THEN	(* radius smaller than flatness *)
+			data.x := x; data.y := y; enum(data);
+			RETURN
+		END;
+
+		IF flatness < eps THEN flatness := eps END;
+		IF x1 * y2 > y1 * x2 THEN sense := 1 ELSE sense := -1 END;
+		xs := data.x - x0; ys := data.y - y0;
+		xe := x - x0; ye := y - y0;
+		IF lx >= ly THEN dt := flatness/lx
+		ELSE dt := flatness/ly
+		END;
+
+		(* find first point on arc *)
+		p2 := xs * y2 - ys * x2;
+		IF ABS(p2) < eps THEN	(* (x2, y2) on start vector *)
+			tmp := x1; x1 := x2; x2 := -tmp;
+			tmp := y1; y1 := y2; y2 := -tmp;
+			p1 := 0
+		ELSE
+			p1 := xs * y1 - ys * x1
+		END;
+		IF ABS(p1) < eps THEN	(* (x1, y1) on start vector *)
+			IF xs * x1 + ys * y1 < -eps THEN	(* on opposite side of origin *)
+				x1 := -x1; y1 := -y1;
+				x2 := -x2; y2 := -y2
+			END;
+			IF ABS(x1 - xs) + ABS(y1 - ys) > flatness THEN
+				data.x := x0 + x1; data.y := y0 + y1;
+				enum(data)
+			END;
+			dx1 := 0; dx2 := 0; dy1 := 0; dy2 := 0
+		ELSE	(* search start point on ellipse *)
+			IF (p1 > 0) = (p2 > 0) THEN
+				tmp := x1; x1 := x2; x2 := -tmp;
+				tmp := y1; y1 := y2; y2 := -tmp;
+				p1 := p2
+			END;
+			IF p1 * sense > 0 THEN
+				x1 := -x1; y1 := -y1;
+				x2 := -x2; y2 := -y2
+			END;
+			dx1 := 0; dx2 := 0; dy1 := 0; dy2 := 0;
+			REPEAT
+				tmp := dx1;
+				dx1 := (x2 - 0.5 * dx2) * dt; dx2 := (x1 + 0.5 * tmp) * dt;
+				x1 := x1 + dx1; x2 := x2 - dx2;
+				tmp := dy1;
+				dy1 := (y2 - 0.5 * dy2) * dt; dy2 := (y1 + 0.5 * tmp) * dt;
+				y1 := y1 + dy1; y2 := y2 - dy2
+			UNTIL (xs * y1 - ys * x1) * sense >= 0;
+			data.x := x0 + x1; data.y := y0 + y1;
+			enum(data)
+		END;
+
+		sx := x1; sy := y1;	(* start point of current line *)
+		tx := 0; ty := 0;	(* (approximate) tangent vector at start point *)
+		limit := flatness * flatness;
+		positive := ((ye * x1 - xe * y1) * sense > 0);
+		LOOP
+			tmp := dx1;
+			dx1 := (x2 - 0.5 * dx2) * dt; dx2 := (x1 + 0.5 * tmp) * dt;
+			x1 := x1 + dx1; x2 := x2 - dx2;
+			tmp := dy1;
+			dy1 := (y2 - 0.5 * dy2) * dt; dy2 := (y1 + 0.5 * tmp) * dt;
+			y1 := y1 + dy1; y2 := y2 - dy2;
+			p1 := (ye * x1 - xe * y1) * sense;
+			IF p1 > 0 THEN
+				positive := TRUE
+			ELSIF positive THEN
+				EXIT
+			END;
+			dx := x1 - sx; dy := y1 - sy;
+			IF (tx = 0) & (ty = 0) THEN	(* first point *)
+				tx := dx; ty := dy; tlen := tx * tx + ty * ty
+			ELSE
+				tmp := dx * ty - dy * tx;
+				IF (tmp * tmp)/tlen > limit THEN	(* distance from new point to tangent vector is greater than flatness *)
+					sx := ex; sy := ey;
+					data.x := x0 + sx; data.y := y0 + sy;
+					enum(data);
+					tx := dx; ty := dy; tlen := tx * tx + ty * ty
+				END
+			END;
+			ex := x1; ey := y1
+		END;
+		data.x := x; data.y := y;
+		enum(data)
+	END EnumArc;
+
+	(** enumerate bezier curve as a sequence of lines with maximal error 'flatness'; current point must be in (data.x, data.y) **)
+	PROCEDURE EnumBezier* (x1, y1, x2, y2, x, y, flatness: REAL; enum: Enumerator; VAR data: EnumData);
+		CONST eps = 1.0E-8;
+		VAR f2, ax, bx, t, x01, x11, x12, x22, x23, y01, y11, y12, y22, y23: REAL;
+
+		PROCEDURE subdiv (t, x0, x1, x2, x3: REAL; VAR a1, a2, m, b1, b2: REAL);
+			VAR s, x12: REAL;
+		BEGIN
+			s := 1-t;
+			a1 := s * x0 + t * x1; b2 := s * x2 + t * x3; x12 := s * x1 + t * x2;
+			a2 := s * a1 + t * x12; b1 := s * x12 + t * b2;
+			m := s * a2 + t * b1
+		END subdiv;
+
+		PROCEDURE draw (x1, y1, x2, y2, x, y: REAL);
+			VAR x01, x11, x12, x22, x23, y01, y11, y12, y22, y23, dx, dy, ex, ey, cp: REAL;
+		BEGIN
+			subdiv(0.5, data.x, x1, x2, x, x01, x11, x12, x22, x23);
+			subdiv(0.5, data.y, y1, y2, y, y01, y11, y12, y22, y23);
+			dx := x12 - data.x; dy := y12 - data.y;
+			ex := x - data.x; ey := y - data.y;
+			cp := dx*ey - dy*ex;
+			IF cp*cp <= f2 * (ex*ex + ey*ey) THEN	(* flat enough *)
+				data.x := x; data.y := y; enum(data)
+			ELSE
+				draw(x01, y01, x11, y11, x12, y12);
+				draw(x22, y22, x23, y23, x, y)
+			END
+		END draw;
+
+		PROCEDURE solve (a, b, c: REAL; VAR t1, t2: REAL; VAR n: INTEGER);
+			VAR d, e, t: REAL;
+		BEGIN
+			n := 0; d := b * b - a * c;
+			IF d >= 0 THEN
+				d := Math.sqrt(d); e := -b + d;
+				IF (a * e > 0) & (ABS(e) < ABS(a)) THEN
+					t1 := e/a; n := 1;
+					e := -b - d;
+					IF (d > 0) & (a * e > 0) & (ABS(e) < ABS(a)) THEN
+						t2 := e/a; n := 2;
+						IF t2 < t1 THEN t := t1; t1 := t2; t2 := t END
+					END
+				ELSE
+					e := -b - d;
+					IF (a * e > 0) & (ABS(e) < ABS(a)) THEN
+						t1 := e/a; n := 1
+					END
+				END
+			END;
+			ASSERT((n = 0) OR (n = 1) & (0 < t1) & (t1 < 1) OR (n = 2) & (0 < t1) & (t1 < t2) & (t2 < 1))
+		END solve;
+
+		PROCEDURE norm2y (x1, y1, x2, y2, x, y: REAL);
+			VAR t1, t2, x01, x11, x12, x22, x23, y01, y11, y12, y22, y23: REAL; n: INTEGER;
+		BEGIN
+			solve(y - data.y + 3*(y1 - y2), data.y - 2*y1 + y2, y1 - data.y, t1, t2, n);
+			IF n = 0 THEN
+				draw(x1, y1, x2, y2, x, y)
+			ELSE
+				subdiv(t1, data.x, x1, x2, x, x01, x11, x12, x22, x23);
+				subdiv(t1, data.y, y1, y2, y, y01, y11, y12, y22, y23);
+				draw(x01, y01, x11, y11, x12, y12);
+				IF n = 2 THEN
+					t2 := (t2 - t1)/(1-t1);
+					subdiv(t2, data.x, x22, x23, x, x01, x11, x12, x22, x23);
+					subdiv(t2, data.y, y22, y23, y, y01, y11, y12, y22, y23);
+					draw(x01, y01, x11, y11, x12, y12)
+				END;
+				draw(x22, y22, x23, y23, x, y)
+			END
+		END norm2y;
+
+		PROCEDURE norm2x (x1, y1, x2, y2, x, y: REAL);
+			VAR t1, t2, x01, x11, x12, x22, x23, y01, y11, y12, y22, y23: REAL; n: INTEGER;
+		BEGIN
+			solve(x - data.x + 3*(x1 - x2), data.x - 2*x1 + x2, x1 - data.x, t1, t2, n);
+			IF n = 0 THEN
+				norm2y(x1, y1, x2, y2, x, y)
+			ELSE
+				subdiv(t1, data.x, x1, x2, x, x01, x11, x12, x22, x23);
+				subdiv(t1, data.y, y1, y2, y, y01, y11, y12, y22, y23);
+				norm2y(x01, y01, x11, y11, x12, y12);
+				IF n = 2 THEN
+					t2 := (t2 - t1)/(1-t1);
+					subdiv(t2, data.x, x22, x23, x, x01, x11, x12, x22, x23);
+					subdiv(t2, data.y, y22, y23, y, y01, y11, y12, y22, y23);
+					norm2y(x01, y01, x11, y11, x12, y12)
+				END;
+				norm2y(x22, y22, x23, y23, x, y)
+			END
+		END norm2x;
+
+		PROCEDURE norm1y (x1, y1, x2, y2, x, y: REAL);
+			VAR ay, by, t, x01, x11, x12, x22, x23, y01, y11, y12, y22, y23: REAL;
+		BEGIN
+			ay := y - data.y + 3*(y1 - y2); by := data.y - 2*y1 + y2;
+			IF (ay * by < 0) & (ABS(by) < ABS(ay)) THEN
+				t := -by/ay;
+				subdiv(t, data.x, x1, x2, x, x01, x11, x12, x22, x23);
+				subdiv(t, data.y, y1, y2, y, y01, y11, y12, y22, y23);
+				norm2x(x01, y01, x11, y11, x12, y12);
+				norm2x(x22, y22, x23, y23, x, y)
+			ELSE
+				norm2x(x1, y1, x2, y2, x, y)
+			END
+		END norm1y;
+
+	BEGIN
+		data.elem := Line;
+		f2 := flatness * flatness;
+		IF f2 < eps THEN f2 := eps END;
+		ax := x - data.x + 3*(x1 - x2); bx := data.x - 2*x1 + x2;
+		IF (ax * bx < 0) & (ABS(bx) < ABS(ax)) THEN
+			t := -bx/ax;
+			subdiv(t, data.x, x1, x2, x, x01, x11, x12, x22, x23);
+			subdiv(t, data.y, y1, y2, y, y01, y11, y12, y22, y23);
+			norm1y(x01, y01, x11, y11, x12, y12);
+			norm1y(x22, y22, x23, y23, x, y)
+		ELSE
+			norm1y(x1, y1, x2, y2, x, y)
+		END
+	END EnumBezier;
+
+	(*
+	 * The code for the spline evaluation has been adapted from Beat Stamm's Graphic module. It handles natural open
+	 * and closed splines.
+	 *)
+
+	PROCEDURE SolveClosed (n: LONGINT; VAR x, y, d: ARRAY OF REAL);
+		VAR hn, dn, d0, d1, t1, t2: REAL; a, b, c, u: ARRAY MaxSplinePoints OF REAL; i: LONGINT;
+	BEGIN
+		hn := 1/(x[n - 1] - x[n - 2]); dn := 3 * (y[n - 1] - y[n - 2]) * hn * hn;
+		b[0] := 1/(x[1] - x[0]); a[0] := hn + 2*b[0]; c[0] := b[0];
+		d0 := 3 * (y[1] - y[0]) * b[0] * b[0]; d[0] := dn + d0;
+		u[0] := 1;
+		i := 1;
+		WHILE i < n - 2 DO
+			b[i] := 1/(x[i + 1] - x[i]); a[i] := 2 * (c[i - 1] + b[i]); c[i] := b[i];
+			d1 := 3 * (y[i + 1] - y[i]) * b[i] * b[i]; d[i] := d0 + d1; d0 := d1;
+			u[i] := 0;
+			INC(i)
+		END;
+		a[i] := 2 * b[i - 1] + hn; d[i] := dn + d0; u[i] := 1;
+
+		i := 0;
+		WHILE i < n - 2 DO
+			c[i] := c[i]/a[i];
+			a[i + 1] := a[i + 1] - c[i] * b[i];
+			INC(i)
+		END;
+		i := 1;
+		WHILE i < n - 1 DO
+			t1 := c[i - 1];
+			t2 := t1 * d[i - 1];
+			d[i] := d[i] - t2;
+			t2 := t1 * u[i - 1];
+			u[i] := u[i] - t2;
+			INC(i)
+		END;
+		d[n - 2] := d[n - 2]/a[n - 2];
+		u[n - 2] := u[n - 2]/a[n - 2];
+		i := n - 3;
+		WHILE i >= 0 DO
+			t1 := b[i] * d[i + 1];
+			d[i] := (d[i] - t1)/a[i];
+			t1 := b[i] * u[i + 1];
+			u[i] := (u[i] - t1)/a[i];
+			DEC(i)
+		END;
+
+		d0 := (d[0] + d[n - 2])/(u[0] + u[n - 2] + x[n - 1] - x[n - 2]);
+		i := 0;
+		WHILE i < n - 1 DO
+			d[i] := d[i] - d0 * u[i];
+			INC(i)
+		END;
+		d[n - 1] := d[0]
+	END SolveClosed;
+
+	PROCEDURE Solve (n: LONGINT; VAR x, y, d: ARRAY OF REAL);
+		VAR a, b, c: ARRAY MaxSplinePoints OF REAL; d0, d1, t: REAL; i: LONGINT;
+	BEGIN
+		b[0] := 1/(x[1] - x[0]); a[0] := 2*b[0]; c[0] := b[0];
+		d0 := 3 * (y[1] - y[0]) * b[0] * b[0]; d[0] := d0;
+		i := 1;
+		WHILE i < n - 1 DO
+			b[i] := 1/(x[i + 1] - x[i]); a[i] := 2 * (c[i - 1] + b[i]); c[i] := b[i];
+			d1 := 3 * (y[i + 1] - y[i]) * b[i] * b[i]; d[i] := d0 + d1; d0 := d1;
+			INC(i)
+		END;
+		a[i] := 2 * b[i - 1]; d[i] := d0;
+
+		i := 0;
+		WHILE i < n - 1 DO
+			c[i] := c[i]/a[i];
+			a[i + 1] := a[i + 1] - c[i] * b[i];
+			INC(i)
+		END;
+		i := 1;
+		WHILE i < n DO
+			t := c[i - 1] * d[i - 1];
+			d[i] := d[i] - t;
+			INC(i)
+		END;
+		d[n - 1] := d[n - 1]/a[n - 1];
+		i := n - 2;
+		WHILE i >= 0 DO
+			t := b[i] * d[i + 1];
+			d[i] := (d[i] - t)/a[i];
+			DEC(i)
+		END
+	END Solve;
+
+	(** enumerate natural spline as sequence of path elements; current point must be in (data.x, data.y) **)
+	PROCEDURE EnumSpline* (VAR x, y: ARRAY OF REAL; n: LONGINT; closed: BOOLEAN; enum: Enumerator; VAR data: EnumData);
+		VAR s, xp, yp: ARRAY MaxSplinePoints OF REAL; i: LONGINT; dx, dy, ds, ds2, bx, by, t: REAL;
+	BEGIN
+		ASSERT((n >= 2) & (n <= MaxSplinePoints));
+		ASSERT(~closed OR (x[0] = x[n - 1]) & (y[0] = y[n - 1]));
+		IF ~closed & (n = 2) THEN
+			data.elem := Line; data.x := x[1]; data.y := y[1]; enum(data)
+		ELSIF closed & (n = 3) THEN
+			data.elem := Arc; data.x0 := 0.5*(x[0] + x[1]); data.y0 := 0.5*(y[0] + y[1]); data.x1 := x[0]; data.y1 := y[0];
+			data.x2 := data.x0 + (data.y0 - data.y); data.y2 := data.y0 + (data.x - data.x0); enum(data)
+		ELSE
+			(* use arc length for parametrizing the spline *)
+			s[0] := 0.0;
+			i := 1;
+			WHILE i < n DO
+				dx := x[i] - x[i - 1]; dy := y[i] - y[i - 1];
+				s[i] := s[i - 1] + Math.sqrt(dx * dx + dy * dy) + 0.01;	(* make sure s[i] > s[i - 1] *)
+				INC(i)
+			END;
+
+			(* calculate derivatives *)
+			IF closed THEN
+				SolveClosed(n, s, x, xp);
+				SolveClosed(n, s, y, yp)
+			ELSE
+				Solve(n, s, x, xp);
+				Solve(n, s, y, yp)
+			END;
+			data.elem := Bezier;
+			i := 1;
+			WHILE i < n DO
+				ds := 1.0/(s[i] - s[i - 1]); ds2 := ds * ds;
+				dx := ds * (x[i] - x[i - 1]);
+				dy := ds * (y[i] - y[i - 1]);
+				bx := ds * (3*dx - 2*xp[i - 1] - xp[i]);
+				by := ds * (3*dy - 2*yp[i - 1] - yp[i]);
+				t := 1/ds;
+				data.x1 := x[i - 1] + (1/3)*xp[i - 1]*t;
+				data.y1 := y[i - 1] + (1/3)*yp[i - 1]*t;
+				t := 1/ds2;
+				data.x2 := 2*data.x1 - x[i - 1] + (1/3) * bx * t;
+				data.y2 := 2*data.y1 - y[i - 1] + (1/3) * by * t;
+				data.x := x[i]; data.y := y[i];
+				enum(data);
+				INC(i)
+			END
+		END
+	END EnumSpline;
+
+
+	(**--- Path Queries ---**)
+
+	PROCEDURE Code (VAR data: QueryData; x, y: REAL): SET;
+		VAR code: SET;
+	BEGIN
+		code := {};
+		IF x < data.llx THEN INCL(code, Left)
+		ELSIF x > data.urx THEN INCL(code, Right)
+		END;
+		IF y < data.lly THEN INCL(code, Bottom)
+		ELSIF y > data.ury THEN INCL(code, Top)
+		END;
+		RETURN code
+	END Code;
+
+	PROCEDURE EnumQuery (VAR data: EnumData);
+		VAR x, y: REAL; code, cc: SET;
+	BEGIN
+		(*
+			The procedure uses a simplified version of the Cohen-Sutherland clipping algorithm. The endpoint of
+			the current line is consecutively clipped against all sides of the rectangle until both points of the line
+			are outside the rectangle with respect to one single rectangle border or until the clipped endpoint
+			is inside the rectangle.
+		*)
+		WITH data: QueryData DO
+			IF ~data.hit THEN
+				IF data.elem = Enter THEN
+					data.code := Code(data, data.x, data.y);
+					IF data.code = {} THEN	(* point inside rectangle *)
+						data.hit := TRUE
+					ELSE
+						data.sx := data.x; data.sy := data.y
+					END
+				ELSIF (data.elem = Line) & ((data.x # data.sx) OR (data.y # data.sy)) THEN
+					x := data.x; y := data.y;
+					LOOP
+						code := Code(data, x, y);
+						IF code = {} THEN	(* point inside rectangle *)
+							data.hit := TRUE;
+							EXIT
+						END;
+						cc := data.code * code;
+						IF cc # {} THEN	(* no intersection with rectangle *)
+							IF data.thorough THEN
+								(*
+									For every line crossing the rectangle's middle y coordinate, accumulate how often the rectangle's
+									midpoint lies to the left/right of the line
+								*)
+								y := 0.5*(data.lly + data.ury);
+								IF (data.sy <= y) & (y < data.y) OR (data.y <= y) & (y < data.sy) THEN
+									x := 0.5*(data.llx + data.urx);
+									IF (data.x - data.sx) * (y - data.sy) >= (data.y - data.sy) * (x - data.sx) THEN
+										INC(data.sum)
+									ELSE
+										DEC(data.sum)
+									END
+								END
+							END;
+							data.code := Code(data, data.x, data.y); data.sx := data.x; data.sy := data.y;
+							EXIT
+						END;
+						IF Left IN code THEN
+							y := data.sy + (y - data.sy) * (data.llx - data.sx)/(x - data.sx);
+							x := data.llx
+						ELSIF Right IN code THEN
+							y := data.sy + (y - data.sy) * (data.urx - data.sx)/(x - data.sx);
+							x := data.urx
+						ELSIF Bottom IN code THEN
+							x := data.sx + (x - data.sx) * (data.lly - data.sy)/(y - data.sy);
+							y := data.lly
+						ELSE	(* Top IN code *)
+							x := data.sx + (x - data.sx) * (data.ury - data.sy)/(y - data.sy);
+							y := data.ury
+						END
+					END
+				END
+			END
+		END
+	END EnumQuery;
+
+	PROCEDURE EnumBoxElem (VAR data: EnumData);
+	BEGIN
+		WITH data: QueryData DO
+			IF data.elem IN {Enter, Line} THEN
+				IF data.x < data.llx THEN data.llx := data.x END;
+				IF data.x > data.urx THEN data.urx := data.x END;
+				IF data.y < data.lly THEN data.lly := data.y END;
+				IF data.y > data.ury THEN data.ury := data.y END
+			END
+		END
+	END EnumBoxElem;
+
+	(** calculate bounding box of path **)
+	PROCEDURE GetBox* (path: Path; VAR llx, lly, urx, ury: REAL); (**DEPRECATED -- SVGRenderer *)
+	BEGIN
+		path.GetBox(llx, lly, urx, ury);
+	END GetBox;
+
+	(** calculate line length **)
+	PROCEDURE LineLength* (x0, y0, x1, y1: REAL): REAL;
+		VAR dx, dy: REAL;
+	BEGIN
+		dx := x1 - x0; dy := y1 - y0;
+		RETURN Math.sqrt(dx * dx + dy * dy)
+	END LineLength;
+
+	PROCEDURE EnumLength (VAR data: EnumData);
+		VAR dx, dy: REAL;
+	BEGIN
+		WITH data: LengthData DO
+			IF data.elem = Line THEN
+				dx := data.x - data.sx; dy := data.y - data.sy;
+				data.len := data.len + Math.sqrt(dx * dx + dy * dy)
+			END;
+			data.sx := data.x; data.sy := data.y
+		END
+	END EnumLength;
+
+	(** calculate arc length **)
+	PROCEDURE ArcLength* (sx, sy, ex, ey, x0, y0, x1, y1, x2, y2, flatness: REAL): REAL;
+		VAR data: LengthData;
+	BEGIN
+		data.x := sx; data.y := sy; data.sx := sx; data.sy := sy; data.len := 0;
+		EnumArc(x0, y0, x1, y1, x2, y2, ex, ey, flatness, EnumLength, data);
+		RETURN data.len
+	END ArcLength;
+
+	(** calculate bezier length **)
+	PROCEDURE BezierLength* (x0, y0, x1, y1, x2, y2, x3, y3, flatness: REAL): REAL;
+		VAR data: LengthData;
+	BEGIN
+		data.x := x0; data.y := y0; data.sx := x0; data.sy := y0; data.len := 0;
+		EnumBezier(x1, y1, x2, y2, x3, y3, flatness, EnumLength, data);
+		RETURN data.len
+	END BezierLength;
+
+
+	(**--- Path Operations ---**)
+
+	(** apply transformation to all coordinates in path **)
+	PROCEDURE Apply* (path: Path; VAR mat: GfxMatrix.Matrix); (**DEPRECATED -- Used in SVGRenderer *)
+	BEGIN
+		path.Apply(mat);
+	END Apply;
+
+	PROCEDURE GetDir (VAR data: EnumData);
+	BEGIN
+		WITH data: DirData DO
+			IF (data.sdx = 0) & (data.sdy = 0) THEN
+				data.sdx := data.x - data.cx; data.sdy := data.y - data.cy
+			END;
+			data.edx := data.x - data.cx; data.edy := data.y - data.cy;
+			data.cx := data.x; data.cy := data.y
+		END
+	END GetDir;
+
+	PROCEDURE EnumSplit (VAR data: EnumData);
+		VAR dx, dy, d, s, sx, sy: REAL;
+	BEGIN
+		WITH data: SplitData DO
+			CASE data.elem OF
+			| Enter:
+				IF data.offset > 0 THEN data.head.AddEnter(data.x, data.y, data.dx, data.dy)
+				ELSE data.tail.AddEnter(data.x, data.y, data.dx, data.dy)
+				END;
+				data.sx := data.x; data.sy := data.y
+			| Line:
+				IF data.offset > 0 THEN	(* still appending to head *)
+					dx := data.x - data.sx; dy := data.y - data.sy; d := Math.sqrt(dx * dx + dy * dy);
+					IF d > 0 THEN
+						IF d < data.offset THEN	(* doesn't reach split offset *)
+							data.head.AddLine(data.x, data.y);
+							data.offset := data.offset - d; data.sx := data.x; data.sy := data.y
+						ELSIF d > data.offset THEN	(* split within line *)
+							s := data.offset/d;
+							sx := data.sx + s * dx; sy := data.sy + s * dy;
+							data.head.AddLine(sx, sy); data.head.AddExit(dx, dy);	(* leave head... *)
+							data.tail.AddEnter(sx, sy, dx, dy); data.tail.AddLine(data.x, data.y);	(* ...and enter tail *)
+							data.offset := data.offset - d	(* now < 0 *)
+						ELSE	(* d = offset: delay until next line/exit *)
+							data.offset := 0; data.sx := data.x; data.sy := data.y; data.sdx := dx; data.sdy := dy
+						END
+					END
+				ELSIF data.offset < 0 THEN	(* appending to tail *)
+					data.tail.AddLine(data.x, data.y)
+				ELSE	(* split point at previous line end point *)
+					data.head.AddLine(data.sx, data.sy); data.head.AddExit(dx, dy);	(* leave head... *)
+					data.tail.AddEnter(data.sx, data.sy, data.sdx, data.sdy);	(* ...and enter tail *)
+					data.tail.AddLine(data.x, data.y);
+					data.offset := -1
+				END
+			| Exit:
+				IF data.offset > 0 THEN data.head.AddExit(data.dx, data.dy)
+				ELSIF data.offset < 0 THEN data.tail.AddExit(data.dx, data.dy)
+				ELSE data.head.AddLine(data.sx, data.sy); data.head.AddExit(data.dx, data.dy); data.offset := -1
+				END
+			END
+		END
+	END EnumSplit;
+
+	(**--- Geometry Support ---**)
+
+	(** compute intersection of two lines **)
+	PROCEDURE IntersectLines* (x1, y1, dx1, dy1, x2, y2, dx2, dy2: REAL; VAR x, y: REAL);
+		VAR d, t: REAL;
+	BEGIN
+		d := dx1 * dy2 - dy1 * dx2;
+		t := (x2 - x1) * dy2 - (y2 - y1) * dx2;
+		IF (ABS(d) >= 1) OR (ABS(d) * MAX(REAL) >= ABS(t)) THEN
+			t := t/d;
+			x := x1 + t * dx1; y := y1 + t * dy1
+		ELSE
+			x := 0.5*(x2 - x1); y := 0.5*(y2 - y1)
+		END
+	END IntersectLines;
+
+	(** compute intersection(s) of line with circle; returns number of solutions in nsol **)
+	PROCEDURE IntersectLineCircle* (sx, sy, tx, ty, mx, my, r: REAL; VAR x1, y1, x2, y2: REAL; VAR nsol: LONGINT);
+		VAR dx, dy, cx, cy, a2, b, c, d, t: REAL;
+	BEGIN
+		dx := tx - sx; dy := ty - sy;
+		cx := sx - mx; cy := sy - my;
+		a2 := 2 * (dx * dx + dy * dy);
+		b := 2 * (dx * cx + dy * cy);
+		c := cx * cx + cy * cy - r * r;
+		d := b * b - 2 * a2 * c;
+		IF d < 0 THEN
+			nsol := 0
+		ELSE
+			d := Math.sqrt(d);
+			IF (d >= b) & (d - b <= a2) THEN
+				t := (d - b)/a2;
+				x1 := sx + t * dx; y1 := sy + t * dy;
+				IF (b + d <= 0) & (b + d >= -a2) THEN
+					t := (b + d)/a2;
+					x2 := sx - t * dx; y2 := sy - t * dy;
+					nsol := 2
+				ELSE
+					nsol := 1
+				END
+			ELSIF (b + d <= 0) & (b + d >= -a2) THEN
+				t := (b + d)/a2;
+				x2 := sx - t * dx; y2 := sy - t * dy;
+				nsol := 1
+			END
+		END
+	END IntersectLineCircle;
+
+	(** return projection of point onto line **)
+	PROCEDURE ProjectToLine* (px, py, qx, qy, x, y: REAL; VAR u, v: REAL);
+		VAR vx, vy, vv, wx, wy, w, d: REAL;
+	BEGIN
+		vx := qx - px; vy := qy - py;
+		vv := vx * vx + vy * vy;
+		wx := x - px; wy := y - py;
+		w := wx * vx + wy * vy;
+		IF (vv >= 1) OR (vv * MAX(REAL) >= ABS(w)) THEN
+			d := w/vv;
+			u := px + d * vx; v := py + d * vy
+		ELSE
+			u := px; v := py
+		END
+	END ProjectToLine;
+
+	(** return projection of point onto ellipse at origin **)
+	PROCEDURE ProjectToEllipse* (ax, ay, bx, by, x, y: REAL; VAR u, v: REAL);
+		VAR a, sina, cosa, b, shear, l: REAL;
+	BEGIN
+		IF ABS(ax * by - ay * bx) < 1.0E-10 THEN
+			u := 0.0; v := 0.0
+		ELSE	(* find parameters to rotate, shear and scale ellipse to unit circle *)
+			a := Math.sqrt(ax * ax + ay * ay);
+			sina := ay/a; cosa := ax/a;
+			b := cosa * by - sina * bx;
+			shear := (cosa * bx + sina * by)/b;
+			v := cosa * y - sina * x;
+			u := (cosa * x + sina * y - shear * v)/a;
+			v := v/b;
+			l := Math.sqrt(u * u + v * v);
+			u := u/l; v := v/l;
+
+			(* map u, v back to original coordinates *)
+			y := v * b;
+			x := u * a + shear * y;
+			u := cosa * x - sina * y;
+			v := sina * x + cosa * y
+		END
+	END ProjectToEllipse;
+
+	PROCEDURE EnumProject (VAR data: EnumData);
+		VAR x, y, dx, dy, d:REAL;
+	BEGIN
+		WITH data: ProjectData DO
+			IF data.elem = Enter THEN
+				data.sx := data.x; data.sy := data.y
+			ELSIF data.elem = Line THEN
+				ProjectToLine(data.sx, data.sy, data.x, data.y, data.px, data.py, x, y);
+				dx := data.px - x; dy := data.py - y;
+				d := dx * dx + dy * dy;
+				IF d < data.dist THEN
+					dx := data.x - data.sx; dy := data.y - data.sy;
+					IF ((x - data.sx) * dx + (y - data.sy) * dy >= 0) & ((data.x - x) * dx + (data.y - y) * dy >= 0) THEN
+						data.rx := x; data.ry := y; data.dist := d
+					END
+				END;
+				data.sx := data.x; data.sy := data.y
+			END
+		END
+	END EnumProject;
+
+	(** return projection of point onto path **)
+	PROCEDURE ProjectToPath* (path: Path; x, y: REAL; VAR u, v: REAL);
+	BEGIN
+		path.ProjectTo(x, y, u, v);
+	END ProjectToPath;
+
+BEGIN
+	Coords[Enter] := 2; Coords[Line] := 1; Coords[Arc] := 4; Coords[Bezier] := 3; Coords[Exit] := 1
+END GfxPaths.

+ 1427 - 0
source/AGfxRaster.Mod

@@ -0,0 +1,1427 @@
+(* ETH Oberon, Copyright 2001 ETH Zuerich Institut fuer Computersysteme, ETH Zentrum, CH-8092 Zuerich.
+Refer to the "General ETH Oberon System Source License" contract available at: http://www.oberon.ethz.ch/ *)
+
+MODULE GfxRaster; (** portable *)	(* eos  *)
+(** AUTHOR "eos"; PURPOSE "Gfx contexts rendering on abstract raster devices"; *)
+
+	(*
+		(probably only interesting for extensions rendering to concrete devices)
+	*)
+
+	(*
+		12.2.98 - eliminated offset parameter from subpath begin
+		12.2.98 - fixed bug with path not being allocated for mode=Clip in BeginPath
+		12.2.98 - fixed bug with ASSERT being executed before rc was assigned in RestoreClip
+		17.2.98 - implemented RenderPath
+		24.2.98 - adaption to new cap and join styles
+		27.2.98 - fixed bug with filled paths (didn't update (u, v) in context)
+		6.4.98 - fixed bug in RenderPath (didn't alloc and init pathReg for thick lines)
+		6.4.98 - fixed bug with thick lines (didn't treat (hx, hy) = (0, 0) correctly)
+		8.4.98 - made filled regions more robust regarding open boundary curves
+		8.4.98 - fixed bug in BeginSubpath (didn't set rc.u and rc.v for mode = {Record})
+		15.4.98 - fixed clipState (now only updated while stroking, removed OutIn state, added InOut state)
+		15.4.98 - improved autoclose logic for filled paths (now only connects open subpaths)
+		18.9.98 - minor changes to comply with new GfxMaps, GfxFonts and Gfx interfaces
+		21.9.98 - implemented special cases for circles and axis-aligned ellipses
+		10.3.99 - new dash pattern
+		1.4.99 - bugfix in Rect: incorrect use of GfxRegions.AddPoint when mode included Clip
+		13.5.99 - remodeled cap and join styles
+		[26.5.99 - use 300 dpi metrics in Show (taken out again 14.7.99)]
+		25.8.99 - replace GfxMaps by GfxImages
+		8.10.99 - added Close functionality
+		26.10.99 - removed auto-close logic for regions
+		7.12.99 - fixed bug in AddCircle and AddEllipse (must add enter/exit points on different scanlines)
+		13.02.2000 - new get/set clip methods
+		18.02.2000 - fixed typo in HairCircle
+		04.04.2000 - fixed bug in Arc: didn't set (u, v) when recording
+	*)
+
+	IMPORT
+		Math, GfxMatrix, GfxImages, GfxPaths, GfxRegions, GfxFonts, Gfx;
+
+
+	CONST
+		In* = 0; Out* = 1; InOut* = 2;	(** clip states **)
+
+
+	TYPE
+		(** region based clip areas **)
+		ClipArea* = POINTER TO ClipAreaDesc;
+		ClipAreaDesc* = RECORD (Gfx.ClipAreaDesc)
+			reg*: GfxRegions.Region;	(** corresponding region **)
+		END;
+
+		(** abstract raster context **)
+		Context* = OBJECT(Gfx.Context)
+		VAR
+			clipReg*: GfxRegions.Region;	(** region inside clip path **)
+			col*: Gfx.Color;	(** current color **)
+			pat*: Gfx.Pattern;	(** current pattern **)
+			clipState*: SHORTINT;	(* current clip state (In, Out, InOut) *)
+			useRegion, lateStroke: BOOLEAN;	(* flags for path operations *)
+			cp: GfxPaths.Path;	(* current path if lateStroke is set *)
+			pathReg: GfxRegions.Region;	(* region surrounded by current path *)
+			plx, ply, prx, puy: INTEGER;	(* rectangle around last point *)
+			border: INTEGER;	(* safety border in proportion to style limit around points *)
+			devWidth: REAL;	(* half the line width in device space *)
+			u, v: REAL;	(* current position in device space *)
+			su, sv: REAL;	(* position of first point for thick lines *)
+			du, dv: REAL;	(* current direction for thick lines *)
+			tu, tv: REAL;	(* point on thick line perpendicular to current inner join vertex *)
+			px, py: LONGINT;	(* current pixel position *)
+			fu, fv: REAL;	(* current outline position for filled areas *)
+			offset: REAL;	(* current offset *)
+			deferred: BOOLEAN;	(* indicates that no cap/join has been drawn for start of thick line yet *)
+			u0, v0, u1, v1: REAL;	(* endpoints of first thick line of current subpath *)
+
+			(** initialize raster context **)
+			PROCEDURE InitRaster* ();
+			BEGIN
+				Init();
+				IF SELF.clipReg = NIL THEN
+					NEW(SELF.clipReg); SELF.clipReg.Init(GfxRegions.Winding);
+				ELSE
+					SELF.clipReg.Init(GfxRegions.Winding)
+				END;
+			END InitRaster;
+
+			PROCEDURE SetColPat* (col: Gfx.Color; pat: Gfx.Pattern);
+			BEGIN
+				SELF.col := col; SELF.pat := pat
+			END SetColPat;
+
+			PROCEDURE{ABSTRACT} PaintDot* (x, y: LONGINT);	(** current dot procedure **)
+			END PaintDot;
+
+			PROCEDURE{ABSTRACT} PaintRect* (lx, ly, rx, uy: LONGINT);	(** current rect procedure **)
+			END PaintRect;
+
+			(** clipping **)
+			PROCEDURE ResetClip* ();
+			BEGIN
+				SELF.clipReg.Clear()
+			END ResetClip;
+
+			PROCEDURE GetClipRect* (VAR llx, lly, urx, ury: REAL);
+				VAR inv: GfxMatrix.Matrix; reg: GfxRegions.Region;
+			BEGIN
+				GfxMatrix.Invert(SELF.ctm, inv);
+				reg := SELF.clipReg;
+				GfxMatrix.ApplyToRect(inv, reg.llx, reg.lly, reg.urx, reg.ury, llx, lly, urx, ury)
+			END GetClipRect;
+
+			PROCEDURE GetClip* (): Gfx.ClipArea;
+				VAR clip: ClipArea;
+			BEGIN
+				NEW(clip); NEW(clip.reg); SELF.clipReg.Copy(clip.reg);
+				RETURN clip
+			END GetClip;
+
+			PROCEDURE SetClip* (clip: Gfx.ClipArea);
+			BEGIN
+				ASSERT(clip IS ClipArea, 100);
+				clip(ClipArea).reg.Copy(SELF.clipReg)
+			END SetClip;
+
+			(** current path **)
+			PROCEDURE Begin* (mode: SET);
+			BEGIN
+				SELF.mode := mode * {Gfx.Record..Gfx.EvenOdd};
+				SELF.cam := SELF.ctm;	(* preserve current transformation for attributes *)
+				IF Gfx.Record IN SELF.mode THEN
+					IF SELF.path = NIL THEN NEW(SELF.path) END;
+					SELF.path.Clear()
+				END;
+				SELF.useRegion := {Gfx.Clip, Gfx.Fill} * SELF.mode # {};
+				SELF.lateStroke := SELF.useRegion & (Gfx.Stroke IN SELF.mode);
+				SELF.deferred := FALSE;
+				IF SELF.lateStroke THEN
+					IF SELF.cp = NIL THEN NEW(SELF.cp) END;
+					SELF.cp.Clear()
+				END;
+				IF Gfx.Stroke IN SELF.mode THEN
+					SELF.StrokePrepare()
+				END;
+				IF SELF.useRegion OR (Gfx.Stroke IN SELF.mode) & (SELF.devWidth > 0.75) THEN
+					IF SELF.pathReg = NIL THEN NEW(SELF.pathReg) END;
+					IF Gfx.EvenOdd IN SELF.mode THEN
+						SELF.pathReg.Init(GfxRegions.EvenOdd)
+					ELSE
+						SELF.pathReg.Init(GfxRegions.Winding)
+					END
+				END;
+			END Begin;
+
+			PROCEDURE End* ( );
+				VAR data: PathData;
+			BEGIN
+				IF Gfx.Clip IN SELF.mode THEN
+					SELF.clipReg.Intersect(SELF.pathReg)
+				END;
+				IF Gfx.Fill IN SELF.mode THEN
+					SELF.pathReg.Intersect(SELF.clipReg);
+					IF ~SELF.pathReg.Empty() THEN
+						SELF.clipState := In;
+						SELF.SetColPat(SELF.fillCol, SELF.fillPat);
+						SELF.FillRegion()
+					END
+				END;
+				IF SELF.lateStroke THEN
+					SELF.SetColPat(SELF.strokeCol, SELF.strokePat);
+					data.context := SELF;
+					SELF.cp.Enumerate(StrokePathElem, data)
+				END
+			END End;
+
+			PROCEDURE Enter* (x, y, dx, dy: REAL);
+				VAR u, v, du, dv: REAL;
+			BEGIN
+				GfxMatrix.Apply(SELF.ctm, x, y, u, v);
+				GfxMatrix.ApplyToVector(SELF.ctm, dx, dy, du, dv);
+				IF Gfx.Record IN SELF.mode THEN
+					SELF.path.AddEnter(u, v, du, dv)
+				END;
+				IF SELF.lateStroke THEN
+					SELF.cp.AddEnter(u, v, du, dv)
+				END;
+				IF SELF.useRegion THEN
+					SELF.EnterLine(u, v);
+					SELF.u := u; SELF.v := v
+				ELSIF Gfx.Stroke IN SELF.mode THEN
+					SELF.StrokeEnter(u, v, du, dv)
+				ELSE
+					SELF.u := u; SELF.v := v
+				END;
+				IF (dx = 0) & (dy = 0) THEN
+					SELF.deferred := TRUE;
+					SELF.u0 := u; SELF.v0 := v
+				END;
+				SELF.cpx := x; SELF.cpy := y
+			END Enter;
+
+			PROCEDURE Exit* (dx, dy: REAL);
+				VAR du, dv: REAL;
+			BEGIN
+				GfxMatrix.ApplyToVector(SELF.ctm, dx, dy, du, dv);
+				IF Gfx.Record IN SELF.mode THEN
+					SELF.path.AddExit(du, dv)
+				END;
+				IF SELF.lateStroke THEN
+					SELF.cp.AddExit(du, dv)
+				END;
+				IF ~SELF.useRegion & (Gfx.Stroke IN SELF.mode) THEN
+					SELF.StrokeExit(du, dv)
+				END;
+				SELF.deferred := FALSE
+			END Exit;
+
+			PROCEDURE Close* ();
+				CONST eps = 0.001;
+			BEGIN
+				IF ~SELF.deferred THEN
+					Exit(0, 0)
+				ELSE
+					IF (ABS(SELF.u - SELF.u0) > eps) OR (ABS(SELF.v - SELF.v0) > eps) THEN
+						IF Gfx.Record IN SELF.mode THEN
+							SELF.path.AddLine(SELF.u0, SELF.v0)
+						END;
+						IF SELF.lateStroke THEN
+							SELF.cp.AddLine(SELF.u0, SELF.v0)
+						END;
+						IF SELF.useRegion THEN
+							SELF.AddLine(SELF.u0, SELF.v0)
+						ELSIF Gfx.Stroke IN SELF.mode THEN
+							SELF.StrokeLineTo(SELF.u0, SELF.v0)
+						END
+					END;
+					IF Gfx.Record IN SELF.mode THEN
+						SELF.path.AddExit(0, 0);
+						SELF.path.Close()
+					END;
+					IF SELF.lateStroke THEN
+						SELF.cp.AddExit(0, 0);
+						SELF.cp.Close()
+					END;
+					IF ~SELF.useRegion & (Gfx.Stroke IN SELF.mode) THEN
+						SELF.StrokeClose()
+					END;
+					SELF.deferred := FALSE
+				END
+			END Close;
+
+			PROCEDURE Line* (x, y: REAL);
+				VAR u, v: REAL;
+			BEGIN
+				GfxMatrix.Apply(SELF.ctm, x, y, u, v);
+				IF (u # SELF.u) OR (v # SELF.v) THEN
+					IF Gfx.Record IN SELF.mode THEN
+						SELF.path.AddLine(u, v)
+					END;
+					IF SELF.lateStroke THEN
+						SELF.cp.AddLine(u, v)
+					END;
+					IF SELF.useRegion THEN
+						SELF.AddLine(u, v);
+						SELF.u := u; SELF.v := v
+					ELSIF Gfx.Stroke IN SELF.mode THEN
+						SELF.StrokeLineTo(u, v)
+					ELSE
+						SELF.u := u; SELF.v := v
+					END;
+					SELF.cpx := x; SELF.cpy := y
+				END
+			END Line;
+
+			PROCEDURE Arc* (x, y, x0, y0, x1, y1, x2, y2: REAL);
+				VAR u, v, u0, v0, u1, v1, u2, v2, ru, rv: REAL; data: PathData;
+			BEGIN
+				GfxMatrix.Apply(SELF.ctm, x, y, u, v);
+				GfxMatrix.Apply(SELF.ctm, x0, y0, u0, v0);
+				GfxMatrix.Apply(SELF.ctm, x1, y1, u1, v1);
+				GfxMatrix.Apply(SELF.ctm, x2, y2, u2, v2);
+				IF Gfx.Record IN SELF.mode THEN
+					SELF.path.AddArc(u, v, u0, v0, u1, v1, u2, v2)
+				END;
+				IF SELF.lateStroke THEN
+					SELF.cp.AddArc(u, v, u0, v0, u1, v1, u2, v2)
+				END;
+				IF SELF.useRegion THEN
+					IF IsEllipse(u0, v0, u1, v1, u2, v2, SELF.u, SELF.v, u, v, SELF.flatness, ru, rv) THEN
+						IF ABS(ABS(ru) - ABS(rv)) < SELF.flatness THEN
+							IF ru * rv > 0 THEN
+								SELF.AddCircle(ENTIER(u0), ENTIER(v0), ENTIER(ABS(ru) + 0.5))
+							ELSE
+								SELF.AddCircle(ENTIER(u0), ENTIER(v0), -ENTIER(ABS(ru) + 0.5))
+							END
+						ELSE
+							SELF.AddEllipse(ENTIER(u0), ENTIER(v0), ENTIER(ru+0.5), ENTIER(rv+0.5))
+						END
+					ELSE
+						data.context := SELF(Context); data.x := SELF.u; data.y := SELF.v;
+						GfxPaths.EnumArc(u0, v0, u1, v1, u2, v2, u, v, SELF.flatness, AddPathElem, data);
+						SELF.u := u; SELF.v := v
+					END
+				ELSIF Gfx.Stroke IN SELF.mode THEN
+					IF (SELF.dashPatLen = 0) & (SELF.devWidth <= 0.75) &
+						IsEllipse(u0, v0, u1, v1, u2, v2, SELF.u, SELF.v, u, v, SELF.flatness, ru, rv)
+					THEN
+						IF ABS(ABS(ru) - ABS(rv)) < SELF.flatness THEN
+							IF ru * rv > 0 THEN
+								SELF.HairCircle(ENTIER(u0), ENTIER(v0), ENTIER(ABS(ru) + 0.5))
+							ELSE
+								SELF.HairCircle(ENTIER(u0), ENTIER(v0), -ENTIER(ABS(ru) + 0.5))
+							END
+						ELSE
+							SELF.HairEllipse(ENTIER(u0), ENTIER(v0), ENTIER(ABS(ru) + 0.5), ENTIER(ABS(rv) + 0.5))
+						END
+					ELSE
+						data.context := SELF; data.x := SELF.u; data.y := SELF.v;
+						GfxPaths.EnumArc(u0, v0, u1, v1, u2, v2, u, v, SELF.flatness, StrokePathElem, data)
+					END
+				ELSE
+					SELF.u := u; SELF.v := v
+				END;
+				SELF.cpx := x; SELF.cpy := y
+			END Arc;
+
+			PROCEDURE Bezier* (x, y, x1, y1, x2, y2: REAL);
+				VAR u, v, u1, v1, u2, v2: REAL; data: PathData;
+			BEGIN
+				GfxMatrix.Apply(SELF.ctm, x, y, u, v);
+				GfxMatrix.Apply(SELF.ctm, x1, y1, u1, v1);
+				GfxMatrix.Apply(SELF.ctm, x2, y2, u2, v2);
+				IF Gfx.Record IN SELF.mode THEN
+					SELF.path.AddBezier(u, v, u1, v1, u2, v2)
+				END;
+				IF SELF.lateStroke THEN
+					SELF.cp.AddBezier(u, v, u1, v1, u2, v2)
+				END;
+				IF SELF.useRegion THEN
+					data.context := SELF(Context); data.x := SELF.u; data.y := SELF.v;
+					GfxPaths.EnumBezier(u1, v1, u2, v2, u, v, SELF.flatness, AddPathElem, data);
+					SELF.u := u; SELF.v := v
+				ELSIF Gfx.Stroke IN SELF.mode THEN
+					data.context := SELF(Context); data.x := SELF.u; data.y := SELF.v;
+					GfxPaths.EnumBezier(u1, v1, u2, v2, u, v, SELF.flatness, StrokePathElem, data)
+				END;
+				SELF.cpx := x; SELF.cpy := y
+			END Bezier;
+
+			PROCEDURE Show* (x, y: REAL; VAR str: ARRAY OF CHAR);
+				VAR
+					mat: GfxMatrix.Matrix; font: GfxFonts.Font; u, v, bu, bv, du, dv: REAL;
+					i: LONGINT; img: GfxImages.Image; data: PathData;
+					TmpPath: GfxPaths.Path;	(* was global: cost of making it local? *)
+					filter: GfxImages.Filter;
+			BEGIN
+				GfxMatrix.Concat(SELF.font.mat, SELF.ctm, mat);
+				font := GfxFonts.Open(SELF.font.name, SELF.font.ptsize, mat);
+				GfxMatrix.Apply(SELF.ctm, x, y, u, v);
+				IF (SELF.mode * {Gfx.Record..Gfx.EvenOdd} = {Gfx.Fill}) & (SELF.fillPat = NIL) THEN
+					mat := SELF.ctm; SELF.ctm := GfxMatrix.Identity;
+					SELF.SetColPat(SELF.fillCol, SELF.fillPat);
+					i := 0; GfxImages.InitNoFilter(filter);
+					WHILE str[i] # 0X DO
+						GfxFonts.GetMap(font, str[i], bu, bv, du, dv, img);
+						IF img # NIL THEN
+							SELF.Image(u + bu, v + bv, img, filter)
+						END;
+						u := u + du; v := v + dv;
+						INC(i)
+					END;
+					SELF.ctm := mat
+				ELSE
+					NEW(TmpPath);
+					i := 0;
+					WHILE str[i] # 0X DO
+						GfxFonts.GetOutline(font, str[i], u, v, TmpPath);
+						GfxFonts.GetWidth(font, str[i], du, dv);
+						u := u + du; v := v + dv;
+						IF Gfx.Record IN SELF.mode THEN
+							SELF.path.Append(TmpPath)
+						END;
+						IF SELF.lateStroke THEN
+							SELF.cp.Append(TmpPath)
+						END;
+						IF SELF.useRegion THEN
+							data.context := SELF;
+							TmpPath.Enumerate(AddPathElem, data);
+							SELF.u := SELF.fu; SELF.v := SELF.fv
+						ELSIF Gfx.Stroke IN SELF.mode THEN
+							data.context := SELF;
+							TmpPath.Enumerate(StrokePathElem, data)
+						END;
+						INC(i)
+					END
+				END;
+				GfxMatrix.Solve(SELF.ctm, u, v, SELF.cpx, SELF.cpy)
+			END Show;
+
+			PROCEDURE Render* (mode: SET);
+				VAR data: PathData;
+			BEGIN
+				IF mode * {Gfx.Clip, Gfx.Fill} # {} THEN
+					IF SELF.pathReg = NIL THEN NEW(SELF.pathReg) END;
+					IF Gfx.EvenOdd IN mode THEN
+						SELF.pathReg.Init(GfxRegions.EvenOdd)
+					ELSE
+						SELF.pathReg.Init(GfxRegions.Winding)
+					END;
+					data.context := SELF;
+					SELF.path.Enumerate(AddPathElem, data);
+					SELF.pathReg.Intersect(SELF.clipReg);
+					IF Gfx.Clip IN mode THEN
+						SELF.pathReg.Copy(SELF.clipReg)
+					END;
+					IF Gfx.Fill IN mode THEN
+						SELF.clipState := In;
+						SELF.SetColPat(SELF.fillCol, SELF.fillPat);
+						SELF.FillRegion()
+					END
+				END;
+				IF Gfx.Stroke IN mode THEN
+					SELF.cam := SELF.ctm;
+					SELF.StrokePrepare();
+					IF SELF.devWidth > 0.75 THEN
+						IF SELF.pathReg = NIL THEN NEW(SELF.pathReg) END;
+						IF Gfx.EvenOdd IN SELF.mode THEN
+							SELF.pathReg.Init(GfxRegions.EvenOdd)
+						ELSE
+							SELF.pathReg.Init(GfxRegions.Winding)
+						END
+					END;
+					data.context := SELF;
+					SELF.path.Enumerate(StrokePathElem, data)
+				END
+			END Render;
+
+			(** painting operators (potential for optimization) **)
+			PROCEDURE Rect* (x0, y0, x1, y1: REAL);
+				VAR u0, v0, u1, v1, t: REAL; enter, exit, llx, lly, urx, ury: INTEGER;
+			BEGIN
+				IF ~(Gfx.Record IN SELF.mode) & ~GfxMatrix.Rotated(SELF.ctm) THEN
+					GfxMatrix.Apply(SELF.ctm, x0, y0, u0, v0);
+					GfxMatrix.Apply(SELF.ctm, x1, y1, u1, v1);
+					enter := -1; exit := 1;
+					IF u0 > u1 THEN t := u0; u0 := u1; u1 := t END;
+					IF v0 > v1 THEN t := v0; v0 := v1; v1 := t; enter := -enter; exit := -exit END;
+					IF SELF.lateStroke THEN
+						SELF.cp.AddRect(u0, v0, u1, v1)
+					END;
+					IF SELF.useRegion THEN
+						llx := SHORT(ENTIER(u0 + 0.5)); lly := SHORT(ENTIER(v0 + 0.5));
+						urx := SHORT(ENTIER(u1 + 0.5)); ury := SHORT(ENTIER(v1 + 0.5));
+						IF Gfx.Clip IN SELF.mode THEN
+							WHILE lly < ury DO
+								SELF.pathReg.AddPoint(llx, lly, enter);
+								INC(lly);
+								SELF.pathReg.AddPoint(urx, lly, exit)
+							END
+						ELSIF SELF.clipReg.RectOverlaps(llx, lly, urx, ury) THEN
+							IF SELF.clipReg.RectInside(llx, lly, urx, ury) THEN SELF.clipState := In
+							ELSE SELF.clipState := InOut
+							END;
+							SELF.SetColPat(SELF.fillCol, SELF.fillPat);
+							SELF.PaintRect(llx, lly, urx, ury)
+						END
+					ELSIF Gfx.Stroke IN SELF.mode THEN
+						IF (SELF.dashPatLen > 0) OR (SELF.devWidth > 0.75) THEN
+							SELF.StrokeEnter(u0, v0, 0, v0 - v1);
+							SELF.StrokeLineTo(u1, v0); SELF.StrokeLineTo(u1, v1); SELF.StrokeLineTo(u0, v1); SELF.StrokeLineTo(u0, v0);
+							SELF.StrokeExit(u1 - u0, 0)
+						ELSE
+							llx := SHORT(ENTIER(u0)); lly := SHORT(ENTIER(v0));
+							urx := SHORT(ENTIER(u1)); ury := SHORT(ENTIER(v1));
+							IF SELF.clipReg.RectOverlaps(llx, lly, urx, ury) THEN
+								IF SELF.clipReg.RectInside(llx, lly, urx, ury) THEN SELF.clipState := In
+								ELSE SELF.clipState := InOut
+								END;
+								SELF.PaintRect(llx, lly, urx, lly+1); SELF.PaintRect(urx, lly, urx+1, ury+1);
+								SELF.PaintRect(llx, ury, urx, ury+1); SELF.PaintRect(llx, lly+1, llx+1, ury)
+							END
+						END
+					END
+				ELSE
+					Gfx.DefRect(SELF, x0, y0, x1, y1)
+				END
+			END Rect;
+
+			PROCEDURE Ellipse* (x, y, rx, ry: REAL);
+				VAR u, v, ru, rv: REAL; data: PathData;
+			BEGIN
+				IF ~(Gfx.Record IN SELF.mode) & ~GfxMatrix.Rotated(SELF.ctm) THEN
+					GfxMatrix.Apply(SELF.ctm, x, y, u, v);
+					GfxMatrix.ApplyToVector(SELF.ctm, rx, ry, ru, rv);
+					IF SELF.lateStroke THEN
+						SELF.cp.AddEnter(u + ru, v, 0, rv);
+						SELF.cp.AddArc(u + ru, v, u, v, u + ru, v, u, v + rv);
+						SELF.cp.AddExit(0, rv)
+					END;
+					IF SELF.useRegion THEN
+						IF ABS(ABS(ru) - ABS(rv)) < SELF.flatness THEN
+							IF ru * rv > 0 THEN SELF.AddCircle(ENTIER(u), ENTIER(v), ENTIER(ABS(ru) + 0.5))
+							ELSE SELF.AddCircle(ENTIER(u), ENTIER(v), -ENTIER(ABS(rv) + 0.5))
+							END
+						ELSE
+							SELF.AddEllipse(ENTIER(u), ENTIER(v), ENTIER(ru + 0.5), ENTIER(rv + 0.5))
+						END
+					ELSIF Gfx.Stroke IN SELF.mode THEN
+						IF (SELF.dashPatLen = 0) & (SELF.devWidth <= 0.75) THEN
+							IF ABS(ABS(ru) - ABS(rv)) < SELF.flatness THEN
+								IF ru * rv > 0 THEN SELF.HairCircle(ENTIER(u), ENTIER(v), ENTIER(ABS(ru) + 0.5))
+								ELSE SELF.HairCircle(ENTIER(u), ENTIER(v), -ENTIER(ABS(ru) + 0.5))
+								END
+							ELSE
+								SELF.HairEllipse(ENTIER(u), ENTIER(v), ENTIER(ru + 0.5), ENTIER(rv + 0.5))
+							END
+						ELSE
+							SELF.StrokeEnter(u + ru, v, 0, rv);
+							data.context := SELF; data.x := SELF.u; data.y := SELF.v;
+							GfxPaths.EnumArc(u, v, u + ru, v, u, v + rv, u + ru, v, SELF.flatness, StrokePathElem, data);
+							SELF.StrokeExit(0, rv)
+						END
+					END
+				ELSE
+					Gfx.DefEllipse(SELF, x, y, rx, ry)
+				END
+			END Ellipse;
+
+			(**--- Clipping ---**)
+
+			PROCEDURE InitClipState (x, y: INTEGER);
+			BEGIN
+				SELF.plx := x - SELF.border; SELF.ply := y - SELF.border;
+				SELF.prx := x + SELF.border; SELF.puy := y + SELF.border;
+				IF SELF.clipReg.RectInside(SELF.plx, SELF.ply, SELF.prx, SELF.puy) THEN
+					SELF.clipState := In
+				ELSIF SELF.clipReg.RectOverlaps(SELF.plx, SELF.ply, SELF.prx, SELF.puy) THEN
+					SELF.clipState := InOut
+				ELSE
+					SELF.clipState := Out
+				END
+			END InitClipState;
+
+			PROCEDURE UpdateClipState (x, y: INTEGER);
+				VAR plx, ply, prx, puy: INTEGER;
+			BEGIN
+				plx := x - SELF.border; ply := y - SELF.border; prx := x + SELF.border; puy := y + SELF.border;
+				GfxRegions.IncludeRect(SELF.plx, SELF.ply, SELF.prx, SELF.puy, plx, ply, prx, puy);
+				IF SELF.clipReg.RectInside(SELF.plx, SELF.ply, SELF.prx, SELF.puy) THEN
+					SELF.clipState := In
+				ELSIF SELF.clipReg.RectOverlaps(SELF.plx, SELF.ply, SELF.prx, SELF.puy) THEN
+					SELF.clipState := InOut
+				ELSE
+					SELF.clipState := Out
+				END;
+				SELF.plx := plx; SELF.ply := ply; SELF.prx := prx; SELF.puy := puy
+			END UpdateClipState;
+
+
+			(*--- Hairlines ---*)
+
+			PROCEDURE HairLineEnter (u, v: REAL);
+			BEGIN
+				SELF.u := u; SELF.v := v;
+				SELF.px := ENTIER(u); SELF.py := ENTIER(v);
+				SELF.InitClipState(SHORT(SELF.px), SHORT(SELF.py));
+				SELF.PaintDot(SELF.px, SELF.py)
+			END HairLineEnter;
+
+			PROCEDURE HairLineTo (u, v: REAL);
+				VAR px, py, xstep, ystep, steps: LONGINT; du, dv, eu, ev, e: REAL;
+			BEGIN
+				px := ENTIER(u); py := ENTIER(v);
+				SELF.UpdateClipState(SHORT(px), SHORT(py));
+				IF px = SELF.px THEN	(* horizontal line *)
+					IF py > SELF.py THEN SELF.PaintRect(px, SELF.py + 1, px + 1, py + 1)
+					ELSIF py < SELF.py THEN SELF.PaintRect(px, py, px + 1, SELF.py)
+					END;
+					SELF.py := py
+				ELSIF py = SELF.py THEN	(* vertical line *)
+					IF px > SELF.px THEN SELF.PaintRect(SELF.px + 1, py, px + 1, py + 1)
+					ELSE SELF.PaintRect(px, py, SELF.px, py + 1)
+					END;
+					SELF.px := px
+				ELSE
+					du := u - SELF.u; dv := v - SELF.v;
+
+					(* bring parameters into first quadrant *)
+					IF du >= 0 THEN xstep := 1; eu := SELF.u - (SELF.px + 0.5)
+					ELSE xstep := -1; du := -du; eu := SELF.px + 0.5 - SELF.u
+					END;
+					IF dv >= 0 THEN ystep := 1; ev := SELF.v - (SELF.py + 0.5)
+					ELSE ystep := -1; dv := -dv; ev := SELF.py + 0.5 - SELF.v
+					END;
+
+					IF du >= dv THEN	(* x-dominant case *)
+						e := du * ev - dv * eu + dv - 0.5*du;
+						steps := ABS(px - SELF.px);
+						WHILE steps > 0 DO
+							IF (e >= 0) & ((e > 0) OR (ystep <= 0)) THEN
+								INC(SELF.py, ystep); e := e - du
+							END;
+							INC(SELF.px, xstep); e := e + dv;
+							SELF.PaintDot(SELF.px, SELF.py);
+							DEC(steps)
+						END
+					ELSE	(* y-dominant case *)
+						e := dv * eu - du * ev + du - 0.5*dv;
+						steps := ABS(py - SELF.py);
+						WHILE steps > 0 DO
+							IF (e >= 0) & ((e > 0) OR (xstep <= 0)) THEN
+								INC(SELF.px, xstep); e := e - dv
+							END;
+							INC(SELF.py, ystep); e := e + du;
+							SELF.PaintDot(SELF.px, SELF.py);
+							DEC(steps)
+						END
+					END
+				END;
+				SELF.u := u; SELF.v := v
+			END HairLineTo;
+
+			PROCEDURE HairCircle (mx, my, r: LONGINT);
+				VAR llx, lly, urx, ury: INTEGER; x, y, d, de, dse: LONGINT;
+			BEGIN
+				llx := SHORT(mx - r); lly := SHORT(my - r); urx := SHORT(mx + r + 1); ury := SHORT(my + r + 1);
+				IF SELF.clipReg.RectOverlaps(llx, lly, urx, ury) THEN
+					IF SELF.clipReg.RectInside(llx, lly, urx, ury) THEN SELF.clipState := In
+					ELSE SELF.clipState := InOut
+					END;
+					x := 0; y := r; d := 1-r; de := 3; dse := -2*r + 5;
+					SELF.PaintDot(mx, my + y); SELF.PaintDot(mx, my - y); SELF.PaintDot(mx + y, my); SELF.PaintDot(mx - y, my);
+					WHILE y > x DO
+						IF d < 0 THEN
+							INC(d, de); INC(de, 2); INC(dse, 2); INC(x)
+						ELSE
+							INC(d, dse); INC(de, 2); INC(dse, 4); INC(x); DEC(y)
+						END;
+						SELF.PaintDot(mx + x, my + y); SELF.PaintDot(mx + y, my + x);
+						SELF.PaintDot(mx + x, my - y); SELF.PaintDot(mx - y, my + x);
+						SELF.PaintDot(mx - x, my - y); SELF.PaintDot(mx - y, my - x);
+						SELF.PaintDot(mx - x, my + y); SELF.PaintDot(mx + y, my - x)
+					END
+				END
+			END HairCircle;
+
+			PROCEDURE HairEllipse (mx, my, rx, ry: LONGINT);
+				VAR llx, lly, urx, ury: INTEGER; x, y, aa, bb, da, db, dy, dx, d: LONGINT;
+			BEGIN
+				llx := SHORT(mx - rx); lly := SHORT(my - ry); urx := SHORT(mx + rx + 1); ury := SHORT(my + ry + 1);
+				IF SELF.clipReg.RectOverlaps(llx, lly, urx, ury) THEN
+					IF SELF.clipReg.RectInside(llx, lly, urx, ury) THEN SELF.clipState := In
+					ELSE SELF.clipState := InOut
+					END;
+					x := rx; y := 0;
+					aa := rx * rx; bb := ry * ry;
+					da := -8*aa; db := -8*bb;
+					dy := -4*aa; dx := -db * (x-1);
+					d := bb * (4*x - 1);
+					WHILE db * x < da * y DO
+						SELF.PaintDot(mx + x, my + y); SELF.PaintDot(mx + x, my - y);
+						SELF.PaintDot(mx - x, my + y); SELF.PaintDot(mx - x, my - y);
+						INC(d, dy); INC(dy, da); INC(y);
+						IF d < 0 THEN
+							INC(d, dx); INC(dx, db); DEC(x)
+						END
+					END;
+					dx := 4 * bb * (2*x - 1); dy := da * (y+1);
+					d := d - bb * (4*x - 1) - aa * (4*y + 1);
+					WHILE x >= 0 DO
+						SELF.PaintDot(mx + x, my + y); SELF.PaintDot(mx + x, my - y);
+						SELF.PaintDot(mx - x, my + y); SELF.PaintDot(mx - x, my - y);
+						INC(d, dx); INC(dx, db); DEC(x);
+						IF d >= 0 THEN
+							INC(d, dy); INC(dy, da); INC(y)
+						END
+					END
+				END
+			END HairEllipse;
+
+			(*--- Filled Areas ---*)
+
+			PROCEDURE EnterLine (u, v: REAL);
+			BEGIN
+				SELF.fu := u; SELF.fv := v;
+				SELF.px := ENTIER(u + 0.5); SELF.py := ENTIER(v + 0.5);
+			END EnterLine;
+
+			PROCEDURE AddLine (u, v: REAL);
+				VAR px, py, x, y, xstep, ystep, steps: LONGINT; du, dv, eu, ev, e: REAL;
+			BEGIN
+				px := ENTIER(u + 0.5); py := ENTIER(v + 0.5);
+				x := SELF.px; y := SELF.py;
+				IF py = y THEN	(* horizontal line => ignore *)
+					SELF.px := px
+				ELSE
+					du := u - SELF.fu; dv := v - SELF.fv;
+					IF du >= 0 THEN xstep := 1; eu := SELF.fu - x
+					ELSE xstep := -1; du := -du; eu := x - SELF.fu
+					END;
+					IF dv >= 0 THEN ystep := 1; ev := SELF.fv - y
+					ELSE ystep := -1; dv := -dv; ev := y - SELF.fv
+					END;
+					e := du * ev - dv * eu + 0.5 * (dv - du);
+					steps := ABS(px - x) + ABS(py - y);
+					WHILE steps > 0 DO
+						IF (e >= 0) & ((e > 0) OR (xstep <= 0)) THEN
+							INC(y, ystep); e := e - du;
+							SELF.pathReg.AddPoint(SHORT(x), SHORT(y), SHORT(ystep))
+						ELSE
+							INC(x, xstep); e := e + dv
+							(* don't have to insert point here because regions are sliced horizontally *)
+						END;
+						DEC(steps)
+					END;
+					SELF.px := px; SELF.py := py
+				END;
+				SELF.fu := u; SELF.fv := v
+			END AddLine;
+
+			PROCEDURE AddCircle (mx, my, r: LONGINT);
+				VAR x, y, d, de, ds, sgn: LONGINT;
+			BEGIN
+				x := 0; y := r; d := 1-r; de := 3; ds := -2*r + 2;
+				IF r > 0 THEN sgn := 1 ELSE sgn := -1 END;
+				WHILE y > x DO
+					REPEAT
+						INC(d, de); INC(de, 2); INC(x, sgn)
+					UNTIL d >= 0;
+					SELF.pathReg.AddPoint(SHORT(mx - x), SHORT(my + y), 1);
+					SELF.pathReg.AddPoint(SHORT(mx + x), SHORT(my - y), -1);
+					INC(d, ds); INC(ds, 2); DEC(y);
+					SELF.pathReg.AddPoint(SHORT(mx + x), SHORT(my + y), -1);
+					SELF.pathReg.AddPoint(SHORT(mx - x), SHORT(my - y), 1)
+				END;
+				WHILE y > 0 DO
+					IF d <= 0 THEN
+						INC(d, de); INC(de, 2); INC(x, sgn)
+					END;
+					SELF.pathReg.AddPoint(SHORT(mx - x), SHORT(my + y), 1);
+					SELF.pathReg.AddPoint(SHORT(mx + x), SHORT(my - y), -1);
+					INC(d, ds); INC(ds, 2); DEC(y);
+					SELF.pathReg.AddPoint(SHORT(mx + x), SHORT(my + y), -1);
+					SELF.pathReg.AddPoint(SHORT(mx - x), SHORT(my - y), 1)
+				END
+			END AddCircle;
+
+			PROCEDURE AddEllipse (mx, my, rx, ry: LONGINT);
+				VAR x, y, aa, bb, da, db, dy, dx, d, sgn: LONGINT;
+			BEGIN
+				x := ABS(rx); y := 0;
+				aa := rx * rx; bb := ry * ry;
+				da := -8*aa; db := -8*bb;
+				dy := -4*aa; dx := -db * (x-1);
+				d := bb * (4*x - 1);
+				IF rx * ry > 0 THEN sgn := 1
+				ELSE x := -x; sgn := -1
+				END;
+				WHILE db * x * sgn < da * y DO
+					SELF.pathReg.AddPoint(SHORT(mx + x), SHORT(my + y), -1);
+					SELF.pathReg.AddPoint(SHORT(mx - x), SHORT(my - y), 1);
+					INC(y);
+					SELF.pathReg.AddPoint(SHORT(mx - x), SHORT(my + y), 1);
+					SELF.pathReg.AddPoint(SHORT(mx + x), SHORT(my - y), -1);
+					IF d < 0 THEN
+						INC(d, dx); INC(dx, db); DEC(x, sgn)
+					END;
+					INC(d, dy); INC(dy, da)
+				END;
+				dx := 4 * bb * (2*ABS(x) - 1); dy := da * (y+1);
+				d := d - bb * (4*ABS(x) - 1) - aa * (4*y + 1);
+				WHILE x * sgn > 0 DO
+					SELF.pathReg.AddPoint(SHORT(mx + x), SHORT(my + y), -1);
+					SELF.pathReg.AddPoint(SHORT(mx - x), SHORT(my - y), 1);
+					INC(y);
+					SELF.pathReg.AddPoint(SHORT(mx - x), SHORT(my + y), 1);
+					SELF.pathReg.AddPoint(SHORT(mx + x), SHORT(my - y), -1);
+					REPEAT
+						INC(d, dx); INC(dx, db); DEC(x, sgn)
+					UNTIL d >= 0;
+					INC(d, dy); INC(dy, da)
+				END
+			END AddEllipse;
+
+			PROCEDURE FillRegion ();
+				VAR data: RegData; reg: GfxRegions.Region;
+			BEGIN
+				reg := SELF.pathReg;
+				IF ~reg.Empty() THEN
+					data.context := SELF;
+					reg.Enumerate(reg.llx, reg.lly, reg.urx, reg.ury, EnumRegion, data)
+				END
+			END FillRegion;
+
+			(*--- Thick Lines ---*)
+
+			PROCEDURE StrokeHalfJoin (cu, cv, u, v, du, dv, hu, hv: REAL; part: LONGINT);
+				VAR limit, bu, bv, t: REAL; data: PathData;
+			BEGIN
+				IF (hu # 0) OR (hv # 0) THEN
+					IF du * hv > dv * hu THEN
+						du := -du; dv := -dv; part := 1 - part	(* turn right turns into left turns *)
+					END;
+					limit := SELF.devWidth * SELF.styleLimit;
+					IF part = 0 THEN
+						IF (SELF.joinStyle = Gfx.BevelJoin) OR (SELF.joinStyle = Gfx.MiterJoin) & (hu * hu + hv * hv > limit * limit) THEN
+							GfxPaths.IntersectLines(cu, cv, hu, hv, cu + dv, cv - du, -hv, hu, bu, bv);
+							SELF.EnterLine(bu, bv); SELF.AddLine(cu + dv, cv - du)
+						ELSIF SELF.joinStyle = Gfx.MiterJoin THEN
+							bu := cu + hu; bv := cv + hv; SELF.EnterLine(bu, bv); SELF.AddLine(cu + dv, cv - du)
+						ELSIF SELF.joinStyle = Gfx.RoundJoin THEN
+							t := Math.sqrt((du * du + dv * dv)/(hu * hu + hv * hv));
+							bu := cu + t * hu; bv := cv + t * hv; SELF.EnterLine(bu, bv);
+							data.context := SELF; data.x := SELF.fu; data.y := SELF.fv;
+							GfxPaths.EnumArc(cu, cv, cu - du, cv - dv, cu + dv, cv - du, cu + dv, cv - du, SELF.flatness, AddPathElem, data)
+						ELSE
+							bu := cu + dv; bv := cv - du;
+							SELF.EnterLine(bu, bv)
+						END;
+						SELF.AddLine(u + dv, v - du); SELF.AddLine(u - dv, v + du); SELF.AddLine(cu, cv); SELF.AddLine(bu, bv)
+					ELSE (* part = 1 *)
+						SELF.EnterLine(cu, cv); SELF.AddLine(u - dv, v + du); SELF.AddLine(u + dv, v - du); SELF.AddLine(cu + dv, cv - du);
+						IF (SELF.joinStyle = Gfx.BevelJoin) OR (SELF.joinStyle = Gfx.MiterJoin) & (hu * hu + hv * hv > limit * limit) THEN
+							GfxPaths.IntersectLines(cu, cv, hu, hv, cu + dv, cv - du, -hv, hu, bu, bv);
+							SELF.AddLine(bu, bv)
+						ELSIF SELF.joinStyle = Gfx.MiterJoin THEN
+							SELF.AddLine(cu + hu, cv + hv)
+						ELSIF SELF.joinStyle = Gfx.RoundJoin THEN
+							t := Math.sqrt((du * du + dv * dv)/(hu * hu + hv * hv));
+							data.context := SELF; data.x := SELF.fu; data.y := SELF.fv;
+							GfxPaths.EnumArc(cu, cv, cu - du, cv - dv, cu + dv, cv - du, cu + t * hu, cv + t * hv, SELF.flatness, AddPathElem, data)
+						END;
+						SELF.AddLine(cu, cv)
+					END
+				END
+			END StrokeHalfJoin;
+
+			PROCEDURE StrokeFullJoin (su, sv, cu, cv, eu, ev, idu, idv, odu, odv, hu, hv: REAL);
+				VAR t, limit: REAL; data: PathData;
+			BEGIN
+				IF (hu # 0) OR (hv # 0) THEN
+					IF idu * odv < idv * odu THEN	(* turn right turns into left turns *)
+						t := idu; idu := -odu; odu := -t;
+						t := idv; idv := -odv; odv := -t;
+						t := su; su := eu; eu := t;
+						t := sv; sv := ev; ev := t
+					END;
+					limit := SELF.devWidth * SELF.styleLimit;
+					SELF.EnterLine(su - idv, sv + idu); SELF.AddLine(su + idv, sv - idu); SELF.AddLine(cu + idv, cv - idu);
+					IF (SELF.joinStyle = Gfx.BevelJoin) OR (SELF.joinStyle = Gfx.MiterJoin) & (hu * hu + hv * hv > limit * limit) THEN
+						SELF.AddLine(cu + odv, cv - odu)
+					ELSIF SELF.joinStyle = Gfx.MiterJoin THEN
+						SELF.AddLine(cu + hu, cv + hv)
+					ELSIF SELF.joinStyle = Gfx.RoundJoin THEN
+						data.context := SELF; data.x := SELF.fu; data.y := SELF.fv;
+						GfxPaths.EnumArc(cu, cv, cu + idv, cv - idu, cu + idu, cv + idv, cu + odv, cv - odu, SELF.flatness, AddPathElem, data)
+					ELSE
+						SELF.AddLine(cu, cv); SELF.AddLine(cu + odv, cv - odu)
+					END;
+					SELF.AddLine(eu + odv, ev - odu); SELF.AddLine(eu - odv, ev + odu); SELF.AddLine(su - idv, sv + idu)
+				END
+			END StrokeFullJoin;
+
+			PROCEDURE StrokeCap (u, v, du, dv: REAL);
+				VAR data: PathData;
+			BEGIN
+				IF SELF.capStyle = Gfx.RoundCap THEN
+					SELF.EnterLine(u - dv, v + du);
+					data.context := SELF; data.x := SELF.fu; data.y := SELF.fv;
+					GfxPaths.EnumArc(u, v, u - dv, v + du, u - du, v - dv, u + dv, v - du, SELF.flatness, AddPathElem, data);
+					SELF.AddLine(u - dv, v + du)
+				ELSIF SELF.capStyle = Gfx.SquareCap THEN
+					SELF.EnterLine(u - dv, v + du);
+					SELF.AddLine(u - du - dv, v - dv + du); SELF.AddLine(u - du + dv, v - dv - du); SELF.AddLine(u + dv, v - du);
+					SELF.AddLine(u - dv, v + du)
+				END
+			END StrokeCap;
+
+			PROCEDURE ThickVerticalLine (lu, v0, v1: REAL);
+				VAR left, right, bot, top: LONGINT;
+			BEGIN
+				IF v0 < v1 THEN
+					left := ENTIER(lu + 0.5); right := left + ENTIER(2*SELF.devWidth + 0.5);
+					bot := ENTIER(v0 + 0.5); top := ENTIER(v1 + 0.5)
+				ELSE
+					right := ENTIER(lu + 0.5); left := right - ENTIER(2*SELF.devWidth + 0.5);
+					bot := ENTIER(v1 + 0.5); top := ENTIER(v0 + 0.5)
+				END;
+				SELF.PaintRect(SHORT(left), SHORT(bot), SHORT(right), SHORT(top))
+			END ThickVerticalLine;
+
+			PROCEDURE ThickHorizontalLine (rv, u0, u1: REAL);
+				VAR left, right, bot, top: LONGINT;
+			BEGIN
+				IF u0 < u1 THEN
+					left := ENTIER(u0 + 0.5); right := ENTIER(u1 + 0.5);
+					bot := ENTIER(rv + 0.5); top := bot + ENTIER(2*SELF.devWidth + 0.5)
+				ELSE
+					left := ENTIER(u1 + 0.5); right := ENTIER(u0 + 0.5);
+					top := ENTIER(rv + 0.5); bot := top - ENTIER(2*SELF.devWidth + 0.5)
+				END;
+				SELF.PaintRect(SHORT(left), SHORT(bot), SHORT(right), SHORT(top))
+			END ThickHorizontalLine;
+
+			PROCEDURE ThickLine (su, sv, eu, ev, du, dv: REAL);
+			BEGIN
+				IF ABS(eu - su) < 0.5 THEN
+					SELF.ThickVerticalLine(su - dv, sv, ev)
+				ELSIF ABS(ev - sv) < 0.5 THEN
+					SELF.ThickHorizontalLine(sv - du, su, eu)
+				ELSE
+					SELF.EnterLine(su - dv, sv + du);
+					SELF.AddLine(su + dv, sv - du); SELF.AddLine(eu + dv, ev - du);
+					SELF.AddLine(eu - dv, ev + du); SELF.AddLine(su - dv, sv + du)
+				END
+			END ThickLine;
+
+			PROCEDURE ThickEnter (u, v, idu, idv: REAL);
+			BEGIN
+				SELF.su := u; SELF.sv := v;
+				SELF.u := u; SELF.v := v;
+				SELF.du := idu; SELF.dv := idv;
+				SELF.InitClipState(SHORT(ENTIER(u)), SHORT(ENTIER(v)))
+			END ThickEnter;
+
+			PROCEDURE ThickLineTo (u, v: REAL);
+				VAR cu, cv, idu, idv, odu, odv, hu, hv, tu, tv: REAL;
+			BEGIN
+				cu := SELF.u; cv := SELF.v;
+				idu := SELF.du; idv := SELF.dv;
+				Gfx.GetNormVector(u - cu, v - cv, SELF.devWidth, odu, odv);
+				IF (cu = SELF.su) & (cv = SELF.sv) THEN	(* first call *)
+					IF SELF.deferred THEN	(* defer rendering of starting cap in case path is later closed *)
+						SELF.u1 := u; SELF.v1 := v;	(* remember direction of first line *)
+						SELF.tu := cu; SELF.tv := cv
+					ELSE	(* render initial joint *)
+						Gfx.GetNormVector(idu, idv, SELF.devWidth, idu, idv);
+						Gfx.GetHalfAxisVector(idu, idv, odu, odv, hu, hv);
+						IF (hu = 0) & (hv = 0) THEN
+							SELF.tu := cu; SELF.tv := cv
+						ELSE
+							TrimJoinLength(cu, cv, 0.5*(cu + u), 0.5*(cv + v), odu, odv, hu, hv, SELF.tu, SELF.tv);
+							SELF.StrokeHalfJoin(cu, cv, SELF.tu, SELF.tv, odu, odv, hu, hv, 0)
+						END
+					END
+				ELSE
+					SELF.UpdateClipState(SHORT(ENTIER(cu)), SHORT(ENTIER(cv)));
+					Gfx.GetHalfAxisVector(idu, idv, odu, odv, hu, hv);
+					IF (hu = 0) & (hv = 0) THEN
+						tu := cu; tv := cv
+					ELSE
+						TrimJoinLength(cu, cv, SELF.tu, SELF.tv, -idu, -idv, hu, hv, tu, tv)
+					END;
+					IF (tu - SELF.tu) * idu + (tv - SELF.tv) * idv > 0 THEN
+						SELF.ThickLine(SELF.tu, SELF.tv, tu, tv, idu, idv)
+					END;
+					IF (hu = 0) & (hv = 0) THEN
+						SELF.tu := cu; SELF.tv := cv
+					ELSE
+						TrimJoinLength(cu, cv, 0.5*(cu + u), 0.5*(cv + v), odu, odv, hu, hv, SELF.tu, SELF.tv);
+						SELF.StrokeFullJoin(tu, tv, cu, cv, SELF.tu, SELF.tv, idu, idv, odu, odv, hu, hv)
+					END
+				END;
+				SELF.su := cu; SELF.sv := cv;
+				SELF.u := u; SELF.v := v;
+				SELF.du := odu; SELF.dv := odv
+			END ThickLineTo;
+
+			PROCEDURE ThickExit (odu, odv: REAL);
+				VAR cu, cv, idu, idv, hu, hv, tu, tv: REAL;
+			BEGIN
+				cu := SELF.u; cv := SELF.v;
+				IF (cu # SELF.su) OR (cv # SELF.sv) THEN	(* at least one thick line was rendered *)
+					idu := SELF.du; idv := SELF.dv;
+					SELF.UpdateClipState(SHORT(ENTIER(cu)), SHORT(ENTIER(cv)));
+					IF (odu = 0) & (odv = 0) THEN
+						SELF.ThickLine(SELF.tu, SELF.tv, cu, cv, idu, idv);
+						SELF.StrokeCap(cu, cv, -idu, -idv)
+					ELSE
+						Gfx.GetNormVector(odu, odv, SELF.devWidth, odu, odv);
+						Gfx.GetHalfAxisVector(idu, idv, odu, odv, hu, hv);
+						IF (hu = 0) & (hv = 0) THEN
+							tu := cu; tv := cv
+						ELSE
+							TrimJoinLength(cu, cv, SELF.tu, SELF.tv, -idu, -idv, hu, hv, tu, tv)
+						END;
+						IF (tu - SELF.tu) * idu + (tv - SELF.tv) * idv > 0 THEN
+							SELF.ThickLine(SELF.tu, SELF.tv, tu, tv, idu, idv)
+						END;
+						IF (hu # 0) OR (hv # 0) THEN
+							SELF.StrokeHalfJoin(cu, cv, tu, tv, idu, idv, hu, hv, 1)
+						END
+					END;
+					IF SELF.deferred THEN	(* render cap at start pos *)
+						SELF.InitClipState(SHORT(ENTIER(SELF.u0)), SHORT(ENTIER(SELF.v0)));
+						Gfx.GetNormVector(SELF.u1 - SELF.u0, SELF.v1 - SELF.v0, SELF.devWidth, odu, odv);
+						SELF.StrokeCap(SELF.u0, SELF.v0, odu, odv)
+					END
+				END
+			END ThickExit;
+
+			PROCEDURE ThickClose ();
+				VAR cu, cv, idu, idv, odu, odv, hu, hv, tu, tv, eu, ev: REAL;
+			BEGIN
+				cu := SELF.u; cv := SELF.v; idu := SELF.du; idv := SELF.dv;
+				SELF.UpdateClipState(SHORT(ENTIER(cu)), SHORT(ENTIER(cv)));
+				Gfx.GetNormVector(SELF.u1 - SELF.u0, SELF.v1 - SELF.v0, SELF.devWidth, odu, odv);
+				Gfx.GetHalfAxisVector(idu, idv, odu, odv, hu, hv);
+				IF (hu = 0) & (hv = 0) THEN
+					tu := cu; tv := cv
+				ELSE
+					TrimJoinLength(cu, cv, SELF.tu, SELF.tv, -idu, -idv, hu, hv, tu, tv)
+				END;
+				IF (tu - SELF.tu) * idu + (tv - SELF.tv) * idv > 0 THEN
+					SELF.ThickLine(SELF.tu, SELF.tv, tu, tv, idu, idv)
+				END;
+				IF (hu # 0) OR (hv # 0) THEN
+					TrimJoinLength(cu, cv, 0.5*(cu + SELF.u1), 0.5*(cv + SELF.v1), odu, odv, hu, hv, eu, ev);
+					SELF.StrokeFullJoin(tu, tv, cu, cv, eu, ev, idu, idv, odu, odv, hu, hv)
+				END
+			END ThickClose;
+
+
+			(*--- Dashed Lines ---*)
+
+			PROCEDURE DashEnter (su, sv, idu, idv: REAL);
+				VAR beg, end, next: REAL; index: LONGINT;
+			BEGIN
+				SELF.offset := 0;
+				Gfx.GetDashOffsets(SELF, 0, beg, end, next, index);
+				IF end > 0 THEN
+					IF SELF.devWidth <= 0.75 THEN SELF.HairLineEnter(su, sv)
+					ELSE SELF.ThickEnter(su, sv, idu, idv)
+					END
+				ELSE
+					SELF.u := su; SELF.v := sv
+				END
+			END DashEnter;
+
+			PROCEDURE DashLineTo (u, v: REAL);
+				VAR
+					cu, cv, du, dv, len, cos, sin, wdu, wdv, offset, beg, end, next, dash, tu, tv, u1, v1: REAL;
+					index: LONGINT; deferred: BOOLEAN;
+			BEGIN
+				cu := SELF.u; cv := SELF.v;
+				du := u - cu; dv := v - cv;
+				len := Math.sqrt(du * du + dv * dv);
+				cos := du/len; sin := dv/len;
+				wdu := SELF.devWidth * cos; wdv := SELF.devWidth * sin;
+				offset := SELF.offset; SELF.offset := offset + len;
+				Gfx.GetDashOffsets(SELF, offset, beg, end, next, index);
+				IF offset < end THEN	(* inside dash *)
+					IF end <= SELF.offset THEN	(* finish current dash *)
+						len := end - offset;
+						IF SELF.devWidth <= 0.75 THEN
+							SELF.HairLineTo(cu + len * cos, cv + len * sin)
+						ELSE
+							SELF.ThickLineTo(cu + len * cos, cv + len * sin);	(* sets u1/v1 if first line in subpath *)
+							deferred := SELF.deferred; SELF.deferred := FALSE;
+							SELF.ThickExit(0, 0);
+							SELF.deferred := deferred
+						END
+					ELSIF SELF.devWidth <= 0.75 THEN
+						SELF.HairLineTo(u, v)
+					ELSE
+						SELF.ThickLineTo(u, v)
+					END
+				END;
+				beg := offset;
+				LOOP
+					len := next - beg;
+					cu := cu + len * cos; cv := cv + len * sin;
+					beg := next;
+					GfxMatrix.ApplyToDist(SELF.cam, SELF.dashPatOn[index], dash);
+					end := beg + dash;
+					GfxMatrix.ApplyToDist(SELF.cam, SELF.dashPatOff[index], dash);
+					next := end + dash;
+					index := (index+1) MOD SELF.dashPatLen;
+					IF end > SELF.offset THEN EXIT END;
+					len := end - beg;
+					IF SELF.devWidth <= 0.75 THEN
+						SELF.HairLineEnter(cu, cv);
+						SELF.HairLineTo(cu + len * cos, cv + len * sin)
+					ELSE
+						SELF.StrokeCap(cu, cv, wdu, wdv);
+						SELF.InitClipState(SHORT(ENTIER(cu)), SHORT(ENTIER(cv)));
+						tu := cu + len * cos; tv := cv + len * sin;
+						SELF.UpdateClipState(SHORT(ENTIER(tu)), SHORT(ENTIER(tv)));
+						SELF.ThickLine(cu, cv, tu, tv, wdu, wdv);
+						SELF.StrokeCap(tu, tv, -wdu, -wdv)
+					END
+				END;
+				IF beg <= SELF.offset THEN	(* begin next dash *)
+					IF SELF.devWidth <= 0.75 THEN
+						SELF.HairLineEnter(cu, cv);
+						SELF.HairLineTo(u, v)
+					ELSE
+						u1 := SELF.u1; v1 := SELF.v1;
+						SELF.StrokeCap(cu, cv, wdu, wdv);
+						SELF.ThickEnter(cu, cv, 0, 0);
+						SELF.ThickLineTo(u, v);
+						SELF.u1 := u1; SELF.v1 := v1	(* restore original point *)
+					END
+				ELSE
+					SELF.u := u; SELF.v := v
+				END
+			END DashLineTo;
+
+			PROCEDURE DashExit (odu, odv: REAL);
+				VAR beg, end, next: REAL; index: LONGINT;
+			BEGIN
+				IF SELF.devWidth > 0.75 THEN
+					Gfx.GetDashOffsets(SELF, SELF.offset, beg, end, next, index);
+					IF (beg < SELF.offset) & (SELF.offset < end) THEN
+						SELF.ThickExit(odu, odv)
+					END
+				END
+			END DashExit;
+
+			PROCEDURE DashClose ();
+				VAR beg, end, next: REAL; index: LONGINT;
+			BEGIN
+				IF SELF.deferred & (SELF.devWidth > 0.75) THEN
+					Gfx.GetDashOffsets(SELF, SELF.offset, beg, end, next, index);
+					IF (beg < SELF.offset) & (SELF.offset < end) THEN
+						SELF.ThickClose()
+					END
+				END
+			END DashClose;
+
+
+			(*--- Stroking ---*)
+
+			PROCEDURE StrokePrepare ();
+			BEGIN
+				GfxMatrix.ApplyToDist(SELF.cam, 0.5*SELF.lineWidth, SELF.devWidth);
+				IF SELF.devWidth <= 0.75 THEN
+					SELF.border := 1
+				ELSE
+					SELF.border := -SHORT(ENTIER(-SELF.devWidth * SELF.styleLimit));
+				END;
+				SELF.SetColPat(SELF.strokeCol, SELF.strokePat)
+			END StrokePrepare;
+
+			PROCEDURE StrokeEnter (u, v, du, dv: REAL);
+			BEGIN
+				IF SELF.devWidth > 0.75 THEN
+					SELF.pathReg.Clear()
+				END;
+				IF SELF.dashPatLen > 0 THEN
+					SELF.DashEnter(u, v, du, dv)
+				ELSIF SELF.devWidth <= 0.75 THEN
+					SELF.HairLineEnter(u, v)
+				ELSE
+					SELF.ThickEnter(u, v, du, dv)
+				END
+			END StrokeEnter;
+
+			PROCEDURE StrokeLineTo (u, v: REAL);
+			BEGIN
+				IF SELF.dashPatLen > 0 THEN
+					SELF.DashLineTo(u, v)
+				ELSIF SELF.devWidth <= 0.75 THEN
+					SELF.HairLineTo(u, v)
+				ELSE
+					SELF.ThickLineTo(u, v)
+				END
+			END StrokeLineTo;
+
+			PROCEDURE StrokeExit (du, dv: REAL);
+			BEGIN
+				IF SELF.dashPatLen > 0 THEN
+					SELF.DashExit(du, dv)
+				ELSIF SELF.devWidth > 0.75 THEN
+					SELF.ThickExit(du, dv)
+				END;
+				IF SELF.devWidth > 0.75 THEN
+					SELF.pathReg.Intersect(SELF.clipReg);
+					IF ~SELF.pathReg.Empty() THEN
+						SELF.clipState := In;
+						SELF.FillRegion()
+					END
+				END
+			END StrokeExit;
+
+			PROCEDURE StrokeClose ();
+			BEGIN
+				IF SELF.dashPatLen > 0 THEN
+					SELF.DashClose()
+				ELSIF SELF.devWidth > 0.75 THEN
+					SELF.ThickClose()
+				END;
+				IF SELF.devWidth > 0.75 THEN
+					SELF.pathReg.Intersect(SELF.clipReg);
+					IF ~SELF.pathReg.Empty() THEN
+						SELF.clipState := In;
+						SELF.FillRegion()
+					END
+				END
+			END StrokeClose;
+
+		END Context;
+
+		(* path data for filling paths *)
+		PathData = RECORD (GfxPaths.EnumData)
+			context: Context;
+		END;
+
+		RegData = RECORD (GfxRegions.EnumData)
+			context: Context;
+		END;
+
+
+	(**--- Ellipse Test ---**)
+
+	(** return if arc parameter describe axis-aligned ellipse and, if so, the radii in x and y direction **)
+	PROCEDURE IsEllipse* (x0, y0, x1, y1, x2, y2, sx, sy, ex, ey, flatness: REAL; VAR rx, ry: REAL): BOOLEAN;
+		CONST eps = 0.01;
+		VAR x, y: REAL;
+	BEGIN
+		IF (ABS(x1 - x0) < eps) & (ABS(y2 - y0) < eps) THEN
+			rx := x0 - x2; ry := y0 - y1
+		ELSIF (ABS(x2 - x0) < eps) & (ABS(y1 - y0) < eps) THEN
+			rx := x1 - x0; ry := y2 - y0
+		ELSE
+			RETURN FALSE
+		END;
+		flatness := 0.25*flatness;
+		IF (ABS(rx - ENTIER(rx + 0.5)) < flatness) & (ABS(ry - ENTIER(ry + 0.5)) < flatness) &
+			(ABS(x0 - 0.5 - ENTIER(x0)) < flatness) & (ABS(y0 - 0.5 - ENTIER(y0)) < flatness) &
+			(ABS(sx - ex) < eps) & (ABS(sy - ey) < eps)
+		THEN
+			GfxPaths.ProjectToEllipse(rx, 0, 0, ry, sx, sy, x, y);
+			RETURN (ABS(sx - x) < eps) & (ABS(sy - y) < eps)
+		ELSE
+			RETURN FALSE
+		END
+	END IsEllipse;
+
+	(**--- Clipping ---**)
+
+	PROCEDURE ResetClip* (ctxt: Gfx.Context);
+	BEGIN
+		ctxt.ResetClip();
+	END ResetClip;
+
+	PROCEDURE GetClipRect* (ctxt: Gfx.Context; VAR llx, lly, urx, ury: REAL);
+	BEGIN
+		ctxt.GetClipRect(llx, lly, urx, ury);
+	END GetClipRect;
+
+	PROCEDURE GetClip* (ctxt: Gfx.Context): Gfx.ClipArea;
+	BEGIN
+		RETURN ctxt.GetClip()
+	END GetClip;
+
+	PROCEDURE SetClip* (ctxt: Gfx.Context; clip: Gfx.ClipArea);
+	BEGIN
+		ctxt.SetClip(clip);
+	END SetClip;
+
+	PROCEDURE EnumRegion (lx, ly, rx, uy: INTEGER; VAR data: GfxRegions.EnumData);
+		VAR rc: Context;
+	BEGIN
+		rc := data(RegData).context;
+		rc.PaintRect(lx, ly, rx, uy)
+	END EnumRegion;
+
+	PROCEDURE AddPathElem (VAR data: GfxPaths.EnumData);
+		VAR rc: Context; x, y: REAL;
+	BEGIN
+		rc := data(PathData).context;
+		CASE data.elem OF
+		| GfxPaths.Enter:
+			rc.EnterLine(data.x, data.y)
+		| GfxPaths.Line:
+			rc.AddLine(data.x, data.y)
+		| GfxPaths.Arc:
+			IF IsEllipse(data.x0, data.y0, data.x1, data.y1, data.x2, data.y2, data.x, data.y, rc.u, rc.v, rc.flatness, x, y) THEN
+				IF ABS(ABS(x) - ABS(y)) < rc.flatness THEN
+					IF x * y > 0 THEN
+						rc.AddCircle(ENTIER(data.x0), ENTIER(data.y0), ENTIER(ABS(x) + 0.5))
+					ELSE
+						rc.AddCircle(ENTIER(data.x0), ENTIER(data.y0), -ENTIER(ABS(x) + 0.5))
+					END
+				ELSE
+					rc.AddEllipse(ENTIER(data.x0), ENTIER(data.y0), ENTIER(x+0.5), ENTIER(y+0.5))
+				END
+			ELSE
+				x := data.x; y := data.y;
+				data.x := rc.fu; data.y := rc.fv;
+				GfxPaths.EnumArc(data.x0, data.y0, data.x1, data.y1, data.x2, data.y2, x, y, rc.flatness, AddPathElem, data)
+			END
+		| GfxPaths.Bezier:
+			x := data.x; y := data.y;
+			data.x := rc.fu; data.y := rc.fv;
+			GfxPaths.EnumBezier(data.x1, data.y1, data.x2, data.y2, x, y, rc.flatness, AddPathElem, data)
+		| GfxPaths.Exit:
+		END
+	END AddPathElem;
+
+	PROCEDURE TrimJoinLength (cu, cv, u, v, du, dv, hu, hv: REAL; VAR tu, tv: REAL);
+	BEGIN
+		IF (u - cu + hu) * du + (v - cv + hv) * dv < 0 THEN	(* joint is longer than allowed *)
+			tu := u; tv := v
+		ELSIF du * hv > dv * hu THEN	(* right turn *)
+			tu := cu - hu - dv; tv := cv - hv + du
+		ELSE
+			tu := cu - hu + dv; tv := cv - hv - du
+		END
+	END TrimJoinLength;
+
+	PROCEDURE StrokePathElem (VAR data: GfxPaths.EnumData);
+		VAR rc: Context; x, y: REAL;
+	BEGIN
+		rc := data(PathData).context;
+		CASE data.elem OF
+		| GfxPaths.Enter:
+			rc.StrokeEnter(data.x, data.y, data.dx, data.dy)
+		| GfxPaths.Line:
+			IF (data.x # rc.u) OR (data.y # rc.v) THEN
+				rc.StrokeLineTo(data.x, data.y)
+			END
+		| GfxPaths.Arc:
+			IF (rc.dashPatLen = 0) & (rc.devWidth <= 0.75) &
+				IsEllipse(data.x0, data.y0, data.x1, data.y1, data.x2, data.y2, data.x, data.y, rc.u, rc.v, rc.flatness, x, y)
+			THEN
+				IF ABS(ABS(x) - ABS(y)) < rc.flatness THEN
+					IF x * y > 0 THEN
+						rc.HairCircle(ENTIER(data.x0), ENTIER(data.y0), ENTIER(ABS(x) + 0.5))
+					ELSE
+						rc.HairCircle(ENTIER(data.x0), ENTIER(data.y0), -ENTIER(ABS(x) + 0.5))
+					END
+				ELSE
+					rc.HairEllipse(ENTIER(data.x0), ENTIER(data.y0), ENTIER(ABS(x) + 0.5), ENTIER(ABS(y) + 0.5))
+				END
+			ELSE
+				x := data.x; y := data.y;
+				data.x := rc.u; data.y := rc.v;
+				GfxPaths.EnumArc(data.x0, data.y0, data.x1, data.y1, data.x2, data.y2, x, y, rc.flatness, StrokePathElem, data)
+			END
+		| GfxPaths.Bezier:
+			x := data.x; y := data.y;
+			data.x := rc.u; data.y := rc.v;
+			GfxPaths.EnumBezier(data.x1, data.y1, data.x2, data.y2, x, y, rc.flatness, StrokePathElem, data)
+		| GfxPaths.Exit:
+			rc.StrokeExit(data.dx, data.dy)
+		END
+	END StrokePathElem;
+
+	(**--- Path Methods ---**)
+
+	PROCEDURE Begin* (ctxt: Gfx.Context; mode: SET);
+	BEGIN
+		ctxt.Begin(mode);
+	END Begin;
+
+	PROCEDURE End* (ctxt: Gfx.Context);
+	BEGIN
+		ctxt.End();
+	END End;
+
+	PROCEDURE Enter* (ctxt: Gfx.Context; x, y, dx, dy: REAL);
+	BEGIN
+		ctxt.Enter(x, y, dx, dy);
+	END Enter;
+
+	PROCEDURE Exit* (ctxt: Gfx.Context; dx, dy: REAL);
+	BEGIN
+		ctxt.Exit(dx, dy);
+	END Exit;
+
+	PROCEDURE Close* (ctxt: Gfx.Context);
+	BEGIN
+		ctxt.Close();
+	END Close;
+
+	PROCEDURE Line* (ctxt: Gfx.Context; x, y: REAL);
+	BEGIN
+		ctxt.Line(x, y);
+	END Line;
+
+	PROCEDURE Arc* (ctxt: Gfx.Context; x, y, x0, y0, x1, y1, x2, y2: REAL);
+	BEGIN
+		ctxt.Arc(x, y, x0, y0, x1, y1, x2, y2);
+	END Arc;
+
+	PROCEDURE Bezier* (ctxt: Gfx.Context; x, y, x1, y1, x2, y2: REAL);
+	BEGIN
+		ctxt.Bezier(x, y, x1, y1, x2, y2);
+	END Bezier;
+
+	PROCEDURE Show* (ctxt: Gfx.Context; x, y: REAL; VAR str: ARRAY OF CHAR);
+	BEGIN
+		ctxt.Show(x, y, str);
+	END Show;
+
+	PROCEDURE Render* (ctxt: Gfx.Context; mode: SET);
+	BEGIN
+		ctxt.Render(mode);
+	END Render;
+
+	PROCEDURE Rect* (ctxt: Gfx.Context; x0, y0, x1, y1: REAL);
+	BEGIN
+		ctxt.Rect(x0, y0, x1, y1);
+	END Rect;
+
+	PROCEDURE Ellipse* (ctxt: Gfx.Context; x, y, rx, ry: REAL);
+	BEGIN
+		ctxt.Ellipse(x, y, rx, ry);
+	END Ellipse;
+
+
+	(**--- Raster Contexts ---**)
+
+	PROCEDURE SetColPat* (rc: Context; col: Gfx.Color; pat: Gfx.Pattern);
+	BEGIN
+		rc.SetColPat(col, pat);
+	END SetColPat;
+
+	(** initialize raster context **)
+	PROCEDURE InitContext* (rc: Context);
+	BEGIN
+		rc.InitRaster();
+	END InitContext;
+
+END GfxRaster.

+ 1409 - 0
source/AGfxRegions.Mod

@@ -0,0 +1,1409 @@
+(* ETH Oberon, Copyright 2001 ETH Zuerich Institut fuer Computersysteme, ETH Zentrum, CH-8092 Zuerich.
+Refer to the "General ETH Oberon System Source License" contract available at: http://www.oberon.ethz.ch/ *)
+
+MODULE GfxRegions; (** portable *)	(* eos  *)
+(** AUTHOR "eos"; PURPOSE "Arbitrarily shaped two_dimensional regions"; *)
+
+	(*
+		17.2.97 - eliminated rectangle type, added shift offsets, made enumerator extensible
+		2.5.97 - prevent dropouts when validating
+		17.7.97 - fixed bug in Validate (trying to copy filler spans if data was reallocated)
+		17.7.97 - eliminated size field
+		12.3.98 - eliminated shifted operations, fixed Shift to treat boundary cases correctly
+		5.5.98 - fixed bug in Intersect (wrong index into arg region)
+	*)
+
+	CONST
+		(** mode for reducing regions to non_overlapping areas **)
+		Winding* = 0;	(** non_zero winding rule **)
+		EvenOdd* = 1;	(** exclusive_or rule **)
+
+		(** interval of valid region coordinates (UBound - LBound is still representable within INTEGER **)
+		UBound* = MAX(INTEGER) DIV 2;
+		LBound* = MIN(INTEGER) DIV 2;
+
+		BlockSize = 512;	(* size increment for region data blocks *)
+		Enter = 1; Exit = -1;	(* direction of bounding curve at scanline intersection *)
+		FirstSlice = 2;	(* index of first slice *)
+		Bottom = MIN(INTEGER); Top = MAX(INTEGER);	(* sentinel values *)
+
+
+	TYPE
+		RegionData = POINTER TO ARRAY OF LONGINT;
+
+		(** regions of arbitrary shape **)
+		(*
+			Implementation notes:
+
+			Regions are managed by slicing them horizontally. For each scanline y, a set of spans on the scanline defines which
+			parts of the scanline are part of the region. The spans are defined through the x_coordinates of their end points.
+			Every point on a scanline has a direction attribute, which specifies whether the point starts a span (Enter) or ends
+			one (Exit), allowing spans to nest or overlap.
+
+			The x_ and y_coordinates of a point along with its direction are encoded into a LONGINT. The chosen encoding
+			weights the y_coordinate most, followed by the x_coordinate and the direction of an intersection. Visiting all
+			encoded points in ascending order therefore traverses all spans of the region from the bottom left corner to the
+			top right corner.
+
+			In order to save space, identical slices adjacent to each other are stored only once. The bottommost scanline of
+			an identical sequence of scanlines serves as a representant for the whole sequence; all others are eliminated.
+			This means that if no points exist for a certain y_coordinate, the spans of the corresponding scanline are identical
+			to those of the one below it. As a consequence, scanlines that are completely outside the region need an empty
+			filler span to distinguish them from eliminated scanlines. A filler span consists of two points located at UBound,
+			one entering the region and the other leaving it.
+
+			Most operations modifying regions append new points in ascending order to the sequence of existing points and
+			then merge the two sequences again. If points cannot be appended in order, the whole set of points has to be
+			sorted before any other operation can be executed. Doing this immediately after the sequence of points has been
+			invalidated can decrease performance significantly if a lot of invalidating operations are issued in sequence, as is
+			typically the case with AddPoint. This is why regions have a valid flag, indicating whether encoded points are sorted
+			or not. Invalidating operations only have to set valid to FALSE, other operations will eventually validate the region
+			again, at the same time eliminating multiple points and overlapping spans.
+		*)			
+		Region* = OBJECT
+		VAR
+			llx*, lly*, urx*, ury*: INTEGER;	(** bounding box **)
+			mode*: INTEGER;	(** mode for reducing region to non_overlapping areas (Winding/EvenOdd) **)
+			valid: BOOLEAN;	(* flag if points in data array are consistent (i.e. sorted & compacted) *)
+			data: RegionData;	(* points defining region boundary *)
+			points: LONGINT;	(* number of data points actually used *)
+
+			(** initialize region **)
+			PROCEDURE Init* (mode: INTEGER);
+			BEGIN
+				SELF.mode := mode;
+				SELF.Clear()
+			END Init;
+			
+			(**--- Region Queries ---**)
+
+			(** return whether region is empty **)
+			PROCEDURE Empty* (): BOOLEAN;
+			BEGIN
+				RETURN (SELF.llx >= SELF.urx) OR (SELF.lly >= SELF.ury)
+			END Empty;
+
+			(** return whether (non_empty) region is rectangular **)
+			PROCEDURE IsRect* (): BOOLEAN;
+			BEGIN
+				SELF.Validate();
+				RETURN SELF.points = 0
+			END IsRect;
+
+			(** return whether point is inside (non_empty) region **)
+			PROCEDURE PointInside* (x, y: INTEGER): BOOLEAN;
+				VAR data: RegionData; n: LONGINT; u, v, dir: INTEGER;
+			BEGIN
+				IF ~PointInRect(x, y, SELF.llx, SELF.lly, SELF.urx, SELF.ury) THEN	(* point not even within region rectangle *)
+					RETURN FALSE
+				ELSIF SELF.IsRect() THEN	(* region is rectangular *)
+					RETURN TRUE
+				END;
+
+				(* find span containing point *)
+				data := SELF.data;
+				SELF.FindLower(y, n);
+				Decode(data[n], u, v, dir);
+				WHILE u < x DO
+					INC(n);
+					Decode(data[n], u, v, dir)
+				END;
+				RETURN (u = x) & (dir = Enter) OR (u > x) & (dir = Exit)
+			END PointInside;
+
+			(** return whether (non_empty) rectangle is completely inside (non_empty) region **)
+			PROCEDURE RectInside* (llx, lly, urx, ury: INTEGER): BOOLEAN;
+				VAR data: RegionData; n: LONGINT; u, v, dir, y: INTEGER;
+			BEGIN
+				IF ~RectInRect(llx, lly, urx, ury, SELF.llx, SELF.lly, SELF.urx, SELF.ury) THEN	(* not even within bounding rectangle *)
+					RETURN FALSE
+				ELSIF SELF.IsRect() THEN	(* region is rectangular *)
+					RETURN TRUE
+				END;
+
+				data := SELF.data;
+				SELF.FindLower(lly, n);
+				Decode(data[n], u, v, dir);
+				REPEAT
+					y := v;
+					WHILE (v = y) & (u <= llx) DO
+						INC(n);
+						Decode(data[n], u, v, dir)
+					END;
+					IF (v > y) OR (u < urx) OR (dir = Enter) THEN	(* rectangle not covered by span *)
+						RETURN FALSE
+					END;
+
+					(* skip to next line *)
+					WHILE v = y DO
+						INC(n);
+						Decode(data[n], u, v, dir)
+					END
+				UNTIL v >= ury;
+
+				RETURN TRUE	(* rectangle is fully covered by spans *)
+			END RectInside;
+
+			(** return whether (non_empty) rectangle overlaps (non_empty) region **)
+			PROCEDURE RectOverlaps* (llx, lly, urx, ury: INTEGER): BOOLEAN;
+				VAR data: RegionData; n: LONGINT; u, v, dir, y: INTEGER;
+			BEGIN
+				IF ~RectsIntersect(llx, lly, urx, ury, SELF.llx, SELF.lly, SELF.urx, SELF.ury) THEN
+					RETURN FALSE	(* rect does not even intersect region rectangle *)
+				ELSIF SELF.IsRect() THEN	(* region is rectangular *)
+					RETURN TRUE
+				END;
+
+				ClipRect(llx, lly, urx, ury, SELF.llx, SELF.lly, SELF.urx, SELF.ury);
+				data := SELF.data;
+				SELF.FindLower(lly, n);
+				Decode(data[n], u, v, dir);
+				REPEAT
+					y := v;
+					WHILE (v = y) & (u <= llx) DO
+						INC(n);
+						Decode(data[n], u, v, dir)
+					END;
+					IF (v = y) & ((u < urx) OR (dir = Exit)) THEN
+						RETURN TRUE
+					END;
+
+					(* skip to next line *)
+					WHILE v = y DO
+						INC(n);
+						Decode(data[n], u, v, dir)
+					END
+				UNTIL v >= ury;
+
+				RETURN FALSE	(* rectangle does not intersect any span *)
+			END RectOverlaps;
+
+			(** return whether region is completely within another region **)
+			PROCEDURE RegionInside* (outer: Region): BOOLEAN;
+				VAR idata, odata: RegionData; in, on, is, os: LONGINT; iu, iv, idir, ou, ov, odir, iy, oy: INTEGER;
+			BEGIN
+				IF ~RectInRect(SELF.llx, SELF.lly, SELF.urx, SELF.ury, outer.llx, outer.lly, outer.urx, outer.ury) THEN
+					RETURN FALSE	(* SELF rect not even within outer rect *)
+				ELSIF outer.IsRect() THEN
+					RETURN TRUE	(* outer region fully covers SELF region *)
+				ELSIF SELF.IsRect() THEN
+					RETURN outer.RectInside(SELF.llx, SELF.lly, SELF.urx, SELF.ury)
+				END;
+
+				idata := SELF.data; odata := outer.data;
+				in := FirstSlice;
+				outer.FindLower(SELF.lly, on);
+				Decode(idata[in], iu, iv, idir);
+				Decode(odata[on], ou, ov, odir);
+				is := in; os := on;
+				REPEAT
+					iy := iv; oy := ov;
+
+					(* skip empty slices *)
+					WHILE (iv = iy) & (iu = UBound) DO
+						INC(in);
+						Decode(idata[in], iu, iv, idir)
+					END;
+
+					(* compare slices *)
+					WHILE (iv = iy) OR (ov = oy) DO
+						IF (ov > oy) OR (iv = iy) & (idir = Exit) & (odir = Enter) THEN
+							RETURN FALSE
+						END;
+						IF (iv > iy) OR (ou <= iu) THEN
+							INC(on);
+							Decode(odata[on], ou, ov, odir)
+						ELSE
+							INC(in);
+							Decode(idata[in], iu, iv, idir)
+						END
+					END;
+
+					(* reset to begin of slice if not on same line *)
+					IF iv > ov THEN
+						in := is; os := on;
+						Decode(idata[in], iu, iv, idir)
+					ELSIF ov > iv THEN
+						on := os; is := in;
+						Decode(odata[on], ou, ov, odir)
+					ELSE
+						is := in; os := on
+					END
+				UNTIL iv = SELF.ury;
+
+				RETURN TRUE	(* all spans were covered by enclosing region *)
+			END RegionInside;
+
+			(** return whether two regions intersect each other **)
+			PROCEDURE RegionOverlaps* (arg: Region): BOOLEAN;
+				VAR rdata, adata: RegionData; bot, top, ru, rv, rdir, au, av, adir, ry, ay: INTEGER; rn, an, rs, as: LONGINT;
+			BEGIN
+				IF ~RectsIntersect(SELF.llx, SELF.lly, SELF.urx, SELF.ury, arg.llx, arg.lly, arg.urx, arg.ury) THEN
+					RETURN FALSE	(* rect does not even intersect arg's bounding box *)
+				ELSIF SELF.IsRect() THEN
+					RETURN arg.RectOverlaps(SELF.llx, SELF.lly, SELF.urx, SELF.ury)
+				ELSIF arg.IsRect() THEN
+					RETURN SELF.RectOverlaps(arg.llx, arg.lly, arg.urx, arg.ury)
+				END;
+
+				rdata := SELF.data; adata := arg.data;
+				bot := MAX(SELF.lly, arg.lly);
+				top := MIN(SELF.ury, arg.ury);
+				SELF.FindLower(bot, rn);
+				arg.FindLower(bot, an);
+				Decode(rdata[rn], ru, rv, rdir);
+				Decode(adata[an], au, av, adir);
+				rs := rn; as := an;
+				REPEAT
+					ry := rv; ay := av;
+
+					(* compare slices *)
+					WHILE (rv = ry) OR (av = ay) DO
+						IF (rv = ry) & (av = ay) & (rdir = Exit) & (adir = Exit) THEN
+							RETURN TRUE
+						END;
+						IF (av > ay) OR (rv = ry) & (ru <= au) THEN
+							INC(rn);
+							Decode(rdata[rn], ru, rv, rdir)
+						ELSE
+							INC(an);
+							Decode(adata[an], au, av, adir)
+						END
+					END;
+
+					(* reset to begin of line if not on same line *)
+					IF rv > av THEN
+						rn := rs; as := an;
+						Decode(rdata[rn], ru, rv, rdir)
+					ELSIF av > rv THEN
+						an := as; rs := rn;
+						Decode(adata[an], au, av, adir)
+					ELSE
+						rs := rn; as := an
+					END
+				UNTIL (rv = top) OR (av = top);
+
+				RETURN FALSE	(* no pair of spans intersected *)
+			END RegionOverlaps;
+
+			(** enumerate region within rectangle **)
+			PROCEDURE Enumerate* (llx, lly, urx, ury: INTEGER; enum: Enumerator; VAR edata: EnumData);
+			BEGIN
+				IF RectsIntersect(SELF.llx, SELF.lly, SELF.urx, SELF.ury, llx, lly, urx, ury) THEN
+					ClipRect(llx, lly, urx, ury, SELF.llx, SELF.lly, SELF.urx, SELF.ury);
+					IF ~RectEmpty(llx, lly, urx, ury) THEN
+						IF SELF.IsRect() THEN
+							enum(llx, lly, urx, ury, edata)
+						ELSE
+							SELF.Enum(llx, lly, urx, ury, enum, edata, Enter)
+						END
+					END
+				END
+			END Enumerate;
+
+			(** enumerate parts of rectangle not within region **)
+			PROCEDURE EnumerateInv* (llx, lly, urx, ury: INTEGER; enum: Enumerator; VAR edata: EnumData);
+			BEGIN
+				IF RectsIntersect(SELF.llx, SELF.lly, SELF.urx, SELF.ury, llx, lly, urx, ury) THEN
+					IF SELF.IsRect() & RectInRect(SELF.llx, SELF.lly, SELF.urx, SELF.ury, llx, lly, urx, ury) THEN
+						IF lly < SELF.lly THEN enum(llx, lly, urx, SELF.lly, edata) END;
+						IF llx < SELF.llx THEN enum(llx, SELF.lly, SELF.llx, SELF.ury, edata) END;
+						IF urx > SELF.urx THEN enum(SELF.urx, SELF.lly, urx, SELF.ury, edata) END;
+						IF ury > SELF.ury THEN enum(llx, SELF.ury, urx, ury, edata) END
+					ELSE
+						SELF.Enum(llx, lly, urx, ury, enum, edata, Exit)
+					END
+				ELSE
+					enum(llx, lly, urx, ury, edata)
+				END
+			END EnumerateInv;
+
+
+			(**--- Region Construction ---**)
+
+			(** make region empty **)
+			PROCEDURE Clear* ();
+			BEGIN
+				SELF.llx := UBound; SELF.lly := UBound;
+				SELF.urx := LBound; SELF.ury := LBound;
+				SELF.valid := TRUE;
+				SELF.points := 0
+			END Clear;
+
+			(** set region mode **)
+			PROCEDURE SetMode* (mode: INTEGER);
+			BEGIN
+				SELF.mode := mode
+			END SetMode;
+
+			(** make region rectangular **)
+			PROCEDURE SetToRect* (llx, lly, urx, ury: INTEGER);
+			BEGIN
+				IF RectEmpty(llx, lly, urx, ury) THEN
+					SELF.Clear()
+				ELSE
+					ClipRect(llx, lly, urx, ury, LBound, LBound, UBound, UBound);
+					SELF.llx := llx; SELF.lly := lly; SELF.urx := urx; SELF.ury := ury;
+					SELF.valid := TRUE;
+					SELF.points := 0
+				END
+			END SetToRect;
+
+			(** shift region **)
+			PROCEDURE Shift* (dx, dy: INTEGER);
+				VAR rdata: RegionData; rn: LONGINT; ru, rv, rdir: INTEGER;
+			BEGIN
+				IF (dx # 0) OR (dy # 0) THEN
+					INC(SELF.llx, dx); INC(SELF.lly, dy); INC(SELF.urx, dx); INC(SELF.ury, dy);
+					IF SELF.points > 0 THEN
+						rdata := SELF.data; rn := FirstSlice;
+						Decode(rdata[rn], ru, rv, rdir);
+						WHILE rv < Top DO
+							IF (ru <= LBound) OR (ru + dx <= LBound) THEN ru := LBound
+							ELSIF (ru >= UBound) OR (ru + dx >= UBound) THEN ru := UBound
+							ELSE INC(ru, dx)
+							END;
+							IF (dy < 0) & (rv < Bottom - dy) THEN rv := Bottom
+							ELSIF (dy > 0) & (rv > Top - dy) THEN rv := Top
+							ELSE INC(rv, dy)
+							END;
+							Encode(rdata[rn], ru, rv, rdir);
+							INC(rn);
+							Decode(rdata[rn], ru, rv, rdir)
+						END
+					END
+				END
+			END Shift;
+
+			(** copy region **)
+			PROCEDURE Copy* (to: Region);
+			BEGIN
+				to.mode := SELF.mode;
+				SELF.CopyData(to)
+			END Copy;
+
+			(** add second region to first **)
+			PROCEDURE Add* (arg: Region);
+				VAR rdata, adata: RegionData; points, aslice, an, rn, rslice: LONGINT; au, av, adir, ru, rv, rdir, top, ry, ay, y: INTEGER;
+			BEGIN
+				IF ~RectEmpty(arg.llx, arg.lly, arg.urx, arg.ury) THEN
+					IF RectEmpty(SELF.llx, SELF.lly, SELF.urx, SELF.ury) THEN
+						arg.CopyData(SELF)
+					ELSIF arg.IsRect() & SELF.RectInside(arg.llx, arg.lly, arg.urx, arg.ury) THEN
+						(* do nothing *)
+					ELSIF SELF.IsRect() & arg.RectInside(SELF.llx, SELF.lly, SELF.urx, SELF.ury) THEN
+						arg.CopyData(SELF)
+					ELSE
+						SELF.Validate(); arg.Validate();
+						SELF.MakeData(); arg.MakeData();
+						rdata := SELF.data; adata := arg.data;
+						points := SELF.points;
+
+						IF arg.lly < SELF.lly THEN
+							(* copy scanlines below SELF *)
+							arg.FindUpper(SELF.lly, aslice);
+							an := FirstSlice;
+							WHILE an < aslice DO
+								Decode(adata[an], au, av, adir);
+								SELF.Append(au, av, adir);
+								INC(an)
+							END;
+							rn := FirstSlice;
+							arg.FindLower(SELF.lly, an)
+						ELSE
+							SELF.FindLower(arg.lly, rn);
+							an := FirstSlice
+						END;
+
+						Decode(rdata[rn], ru, rv, rdir);
+						Decode(adata[an], au, av, adir);
+						rslice := rn; aslice := an;
+						top := MIN(SELF.ury, arg.ury);
+
+						WHILE (av < top) OR (rv < top) DO
+							(* merge slices *)
+							ry := rv; ay := av; y := MAX(ry, ay);
+							REPEAT
+								IF (av > ay) OR (rv = ry) & (ru <= au) THEN
+									IF rv # y THEN	(* do not duplicate points *)
+										SELF.Append(ru, y, rdir)
+									END;
+									INC(rn);
+									Decode(rdata[rn], ru, rv, rdir)
+								ELSE
+									SELF.Append(au, y, adir);
+									INC(an);
+									Decode(adata[an], au, av, adir)
+								END
+							UNTIL (rv > ry) & (av > ay);
+
+							(* advance to next slice *)
+							IF rv < av THEN
+								an := aslice; rslice := rn;
+								Decode(adata[an], au, av, adir)
+							ELSIF av < rv THEN
+								rn := rslice; aslice := an;
+								Decode(rdata[rn], ru, rv, rdir)
+							ELSE
+								rslice := rn; aslice := an
+							END
+						END;
+
+						(* copy slices above SELF *)
+						IF arg.ury > SELF.ury THEN
+							REPEAT
+								SELF.Append(au, av, adir);
+								INC(an);
+								Decode(adata[an], au, av, adir)
+							UNTIL av = Top
+						END;
+
+						SELF.Merge(points);
+						IncludeRect(SELF.llx, SELF.lly, SELF.urx, SELF.ury, arg.llx, arg.lly, arg.urx, arg.ury)
+					END
+				END
+			END Add;
+
+			(** add rectangle to region **)
+			PROCEDURE AddRect* (llx, lly, urx, ury: INTEGER);
+				VAR rectRegion: Region; (*TODO use rect *)
+			BEGIN
+				rectRegion := NEW Region( );
+				rectRegion.Init( Winding );
+				rectRegion.SetToRect(llx, lly, urx, ury);
+				SELF.Add(rectRegion)
+			END AddRect;
+
+			(** subtract second region from first **)
+			PROCEDURE Subtract* (arg: Region);
+				VAR rdata, adata: RegionData; points, rn, an, rslice, aslice: LONGINT; ru, rv, rdir, au, av, adir, top, ry, ay, y: INTEGER;
+			BEGIN
+				IF ~RectEmpty(arg.llx, arg.lly, arg.urx, arg.ury) THEN
+					IF RectEmpty(SELF.llx, SELF.lly, SELF.urx, SELF.ury) OR SELF.RegionInside(arg) THEN
+						SELF.Clear()
+					ELSIF RectsIntersect(SELF.llx, SELF.lly, SELF.urx, SELF.ury, arg.llx, arg.lly, arg.urx, arg.ury) THEN
+						SELF.Validate(); arg.Validate();
+						SELF.MakeData(); arg.MakeData();
+						rdata := SELF.data; adata := arg.data;
+						points := SELF.points;
+						IF SELF.lly <= arg.lly THEN
+							SELF.FindLower(arg.lly, rn);
+							an := FirstSlice
+						ELSE
+							rn := FirstSlice;
+							arg.FindLower(SELF.lly, an)
+						END;
+						Decode(rdata[rn], ru, rv, rdir);
+						Decode(adata[an], au, av, adir);
+
+						rslice := rn; aslice := an;
+						top := MIN(SELF.ury, arg.ury);
+						WHILE (rv < top) OR (av < top) DO
+							(* merge slices *)
+							ry := rv; ay := av; y := MAX(ry, ay);
+							REPEAT
+								IF (av > ay) OR (rv = ry) & (ru <= au) THEN
+									IF rv # y THEN	(* do not duplicate points *)
+										SELF.Append(ru, y, rdir)
+									END;
+									INC(rn);
+									Decode(rdata[rn], ru, rv, rdir)
+								ELSE
+									SELF.Append(au, y, -adir);
+									INC(an);
+									Decode(adata[an], au, av, adir)
+								END
+							UNTIL (rv > ry) & (av > ay);
+
+							(* advance to next slice *)
+							IF rv < av THEN
+								an := aslice; rslice := rn;
+								Decode(adata[an], au, av, adir)
+							ELSIF av < rv THEN
+								rn := rslice; aslice := an;
+								Decode(rdata[rn], ru, rv, rdir)
+							ELSE
+								rslice := rn; aslice := an
+							END
+						END;
+
+						SELF.Merge(points);
+						SELF.CalcRect()
+					END
+				END
+			END Subtract;
+
+			(** subtract rectangle from region **)
+			PROCEDURE SubtractRect* (llx, lly, urx, ury: INTEGER);
+				VAR rectRegion: Region;
+			BEGIN
+				rectRegion := NEW Region( );
+				rectRegion.Init( Winding );
+				rectRegion.SetToRect(llx, lly, urx, ury);
+				SELF.Subtract(rectRegion)
+			END SubtractRect;
+
+			(** intersect first region with second region **)
+			PROCEDURE Intersect* (arg: Region);
+				VAR rdata, adata: RegionData; points, rn, an, rslice, aslice: LONGINT; ru, rv, rdir, au, av, adir, ry, ay, y: INTEGER;
+			BEGIN
+				IF ~RectsIntersect(SELF.llx, SELF.lly, SELF.urx, SELF.ury, arg.llx, arg.lly, arg.urx, arg.ury) THEN
+					SELF.Clear()
+
+				ELSIF ~arg.RectInside(SELF.llx, SELF.lly, SELF.urx, SELF.ury) THEN
+					SELF.Validate(); arg.Validate();
+					SELF.MakeData(); arg.MakeData();
+					rdata := SELF.data; adata := arg.data;
+					points := SELF.points;
+
+					(* cut off slices above arg *)
+					IF SELF.ury > arg.ury THEN
+						SELF.FindUpper(arg.ury, points);
+						Encode(rdata[points], UBound, arg.ury, Enter); INC(points);
+						Encode(rdata[points], UBound, arg.ury, Exit); INC(points);
+						Encode(rdata[points], UBound, Top, Exit); INC(points);
+						SELF.points := points
+					END;
+
+					(* delete slices below arg *)
+					IF SELF.lly < arg.lly THEN
+						SELF.FindLower(arg.lly, rn);
+						IF rn > FirstSlice THEN
+							points := FirstSlice;
+							WHILE rn < SELF.points DO
+								rdata[points] := rdata[rn];
+								INC(points); INC(rn)
+							END;
+							SELF.points := points
+						END;
+
+						rn := FirstSlice;
+						Decode(rdata[rn], ru, rv, rdir);
+						ry := rv;
+						REPEAT
+							Encode(rdata[rn], ru, arg.lly, rdir);
+							INC(rn);
+							Decode(rdata[rn], ru, rv, rdir)
+						UNTIL rv > ry;
+
+						rn := FirstSlice; an := FirstSlice
+					ELSE
+						rn := FirstSlice;
+						arg.FindLower(SELF.lly, an)
+					END;
+
+					Decode(rdata[rn], ru, rv, rdir);
+					Decode(adata[an], au, av, adir);
+					rslice := rn; aslice := an;
+
+					WHILE rv < SELF.ury DO
+						(* merge intersecting slices *)
+						ry := rv; ay := av; y := MAX(ry, ay);
+						SELF.Append(LBound, y, Exit);
+						REPEAT
+							IF (av > ay) OR (rv = ry) & (ru <= au) THEN
+								IF rv # y THEN	(* do not duplicate existing points *)
+									SELF.Append(ru, y, rdir)
+								END;
+								INC(rn);
+								Decode(rdata[rn], ru, rv, rdir)
+							ELSE
+								SELF.Append(au, y, adir);
+								INC(an);
+								Decode(adata[an], au, av, adir)
+							END
+						UNTIL (rv > ry) & (av > ay);
+						SELF.Append(UBound, y, Enter);
+
+						(* advance to next slice *)
+						IF rv < av THEN
+							an := aslice; rslice := rn;
+							Decode(adata[an], au, av, adir)
+						ELSIF av < rv THEN
+							rn := rslice; aslice := an;
+							Decode(rdata[rn], ru, rv, rdir)
+						ELSE
+							rslice := rn; aslice := an
+						END
+					END;
+
+					SELF.Merge(points);
+					SELF.CalcRect()
+				END
+			END Intersect;
+
+			(** intersect region with rectangle **)
+			PROCEDURE IntersectRect* (llx, lly, urx, ury: INTEGER);
+				VAR rectRegion: Region; (**TODO use rect *)
+			BEGIN
+				rectRegion := NEW Region( ); rectRegion.Init( Winding );
+				rectRegion.SetToRect(llx, lly, urx, ury);
+				SELF.Intersect(rectRegion)
+			END IntersectRect;
+
+			(** invert region **)
+			PROCEDURE Invert* ();
+				VAR data: RegionData; points, n: LONGINT; u, v, dir, y: INTEGER;
+			BEGIN
+				IF RectEmpty(SELF.llx, SELF.lly, SELF.urx, SELF.ury) THEN
+					SELF.SetToRect(LBound, LBound, UBound, UBound)
+				ELSE
+					SELF.Validate();
+					SELF.MakeData();
+					data := SELF.data;
+					points := SELF.points;
+					n := FirstSlice;
+					Decode(data[n], u, v, dir);
+
+					IF SELF.lly > LBound THEN
+						SELF.Append(LBound, LBound, Enter);
+						SELF.Append(UBound, LBound, Exit)
+					END;
+
+					REPEAT
+						y := v;
+						SELF.Append(LBound, y, Enter);
+						REPEAT
+							Encode(data[n], u, y, -dir);
+							INC(n);
+							Decode(data[n], u, y, dir)
+						UNTIL v > y;
+						SELF.Append(UBound, y, Exit)
+					UNTIL v >= UBound;
+
+					IF y < UBound THEN
+						SELF.Append(LBound, y, Enter);
+						SELF.Append(UBound, y, Exit)
+					END;
+
+					SELF.Merge(points);
+					SELF.CalcRect()
+				END
+			END Invert;
+
+			(**
+				In addition to creating rectangular regions and using Boolean operations to combine several regions, a region
+				can also be built by tracing its outline with AddPoint. In order to allow the correct handling of self_intersecting
+				contours, a direction parameter is needed which indicates whether the curve is going up (dy = 1) or down
+				(dy = -1) at the given point.
+
+				When performing Boolean operations upon regions or when building regions from self_intersecting contours,
+				it is possible that some areas in the resulting region get "covered" more than once. Since most query operations
+				reduce regions to non_overlapping areas, a rule which decides whether a point is inside a region or not is needed.
+				Imagine a ray originating at such a point and counting every intersection of the ray with the boundary curve of the
+				region as +1 if the curve crosses the ray from right to left and as -1 otherwise.
+					- for mode Winding (the default), a point is inside the region if the resulting sum is non_zero
+					- for mode EvenOdd, a point is inside the region if the resulting sum is odd
+
+				Behaviour of all region queries and region operations is undefined for contours which are not closed.
+			**)
+
+			(** add a scanline intersection to a region **)
+			PROCEDURE AddPoint* (x, y, dy: INTEGER);
+			BEGIN
+				IF (dy # 0) & (y >= LBound) & (y <= UBound) THEN
+					IF x < LBound THEN x := LBound
+					ELSIF x > UBound THEN x := UBound
+					END;
+					SELF.MakeData();
+					IncludePoint(SELF.llx, SELF.lly, SELF.urx, SELF.ury, x, y);
+					SELF.Append(x, y + (-dy) DIV 2, dy);	(* dy = -1 => y, dy = 1 => y - 1 *)
+					SELF.valid := FALSE
+				END
+			END AddPoint;			
+			
+			(*--- Auxiliary Routines For Managing Regions ---*)			
+
+			
+			(* append point to region data *)
+			PROCEDURE Append (u, v, dir: INTEGER);
+				VAR size: LONGINT; data: RegionData;
+			BEGIN
+				IF SELF.data = NIL THEN
+					NEW(SELF.data, BlockSize)
+				ELSIF SELF.points >= LEN(SELF.data^) THEN	(* grow data array *)
+					size := LEN(SELF.data^) + BlockSize;
+					NEW(data, size);
+					CopyPoints(SELF.data, data, SELF.points);
+					SELF.data := data
+				END;
+				Encode(SELF.data[SELF.points], u, v, dir);
+				INC(SELF.points)
+			END Append;
+
+			(* copy region data *)
+			PROCEDURE CopyData (dst: Region);
+				VAR size: LONGINT;
+			BEGIN
+				IF SELF.points > 0 THEN
+					IF (dst.data = NIL) OR (LEN(dst.data^) < SELF.points) THEN
+						size := SELF.points + (-SELF.points) MOD BlockSize;	(* round up to multiple of BlockSize *)
+						NEW(dst.data, size)
+					END;
+					CopyPoints(SELF.data, dst.data, SELF.points)
+				END;
+				dst.points := SELF.points;
+				dst.llx := SELF.llx; dst.lly := SELF.lly;
+				dst.urx := SELF.urx; dst.ury := SELF.ury;
+				dst.valid := SELF.valid
+			END CopyData;
+
+			(* re_calculate bounding box of (valid!) region *)
+			PROCEDURE CalcRect ();
+				VAR data: RegionData; n: LONGINT; u, v, dir, x: INTEGER;
+			BEGIN
+				ASSERT(SELF.valid);
+				IF SELF.points > 0 THEN
+					data := SELF.data;
+					n := FirstSlice;
+					Decode(data[n], u, v, dir);
+					SELF.llx := u; SELF.urx := u; SELF.lly := v;
+					REPEAT
+						SELF.ury := v; x := u;
+						REPEAT
+							IF (dir = Enter) & (u < SELF.llx) THEN
+								SELF.llx := u; x := u
+							ELSIF (dir = Exit) & (u > SELF.urx) & (u > x) THEN	(* last term excludes filler spans *)
+								SELF.urx := u
+							END;
+							INC(n);
+							Decode(data[n], u, v, dir)
+						UNTIL v > SELF.ury;
+					UNTIL v = Top
+				END
+			END CalcRect;
+
+			(* eliminate duplicate slices *)
+			PROCEDURE Compact (src: RegionData);
+				VAR rslice, dslice, sn, rn, dn: LONGINT; dst: RegionData; su, sv, sdir, ru, rv, rdir, sy, ry: INTEGER;
+			BEGIN
+				rslice := 0;	(* start of current reference slice is the bottom sentinel slice *)
+				dslice := FirstSlice;	(* start of current destination slice *)
+				sn := FirstSlice;	(* current reading position *)
+				dst := SELF.data;
+				Decode(src[sn], su, sv, sdir);
+
+				REPEAT
+					(* compare next source slice to current reference slice *)
+					rn := rslice; dn := dslice;
+					Decode(dst[rn], ru, rv, rdir);
+					sy := sv; ry := rv;
+					WHILE (sv = sy) & (rv = ry) & (su = ru) & (sdir = rdir) DO	(* copy while slices are equal *)
+						dst[dn] := src[sn];
+						INC(dn); INC(sn); INC(rn);
+						Decode(src[sn], su, sv, sdir);
+						Decode(dst[rn], ru, rv, rdir)
+					END;
+
+					IF (sv = sy) OR (rv = ry) THEN	(* slices are different => copy rest of source slice to destination *)
+						WHILE sv = sy DO
+							dst[dn] := src[sn];
+							INC(dn); INC(sn);
+							Decode(src[sn], su, sv, sdir)
+						END;
+
+						(* the slice just written becomes the new reference slice *)
+						rslice := dslice;
+						dslice := dn
+					END
+				UNTIL sv = Top;
+
+				IF dn = 6 THEN	(* region contains only one rectangle *)
+					Decode(dst[FirstSlice], SELF.llx, SELF.lly, rdir);
+					Decode(dst[FirstSlice + 1], SELF.urx, SELF.lly, rdir);
+					Decode(dst[FirstSlice + 2], ru, SELF.ury, rdir);
+					SELF.points := 0
+				ELSE
+					Encode(dst[dn], UBound, Top, Exit);
+					SELF.points := dn + 1
+				END
+			END Compact;
+
+			(* merge two runs of data points *)
+			PROCEDURE Merge (split: LONGINT);
+				VAR data: RegionData; n, N, m, M, p, tmp: LONGINT; nu, nv, ndir, mu, mv, mdir, sum, u, v, inc, nsum: INTEGER;
+			BEGIN
+				data := SELF.data;
+				n := 0; N := split;
+				Decode(data[n], nu, nv, ndir);
+				m := split; M := SELF.points;
+				Decode(data[m], mu, mv, mdir);
+				p := 0;
+				SELF.Append(UBound, Top, Exit);	(* sentinel for upper part *)
+
+				IF DataSize <= M THEN	(* reallocate temporary buffer *)
+					DataSize := M - M MOD BlockSize + BlockSize;
+					NEW(Data, DataSize)
+				END;
+
+				WHILE (n < N) & (m < M) DO
+					tmp := p;
+					v := MIN(nv, mv);
+
+					(* eliminate overlapping spans before copying them *)
+					sum := 0;
+					REPEAT
+						(* get next point *)
+						IF (nv < mv) OR (nv = mv) & (nu <= mu) THEN
+							u := nu; inc := ndir;
+							INC(n);
+							Decode(data[n], nu, nv, ndir)
+						ELSE
+							u := mu; inc := mdir;
+							INC(m);
+							Decode(data[m], mu, mv, mdir)
+						END;
+
+						(* accumulate directions of coincident points *)
+						WHILE (nv = v) & (nu = u) DO
+							INC(inc, ndir); INC(n);
+							Decode(data[n], nu, nv, ndir)
+						END;
+						WHILE (mv = v) & (mu = u) DO
+							INC(inc, mdir); INC(m);
+							Decode(data[m], mu, mv, mdir)
+						END;
+
+						IF inc # 0 THEN	(* append point to merged data *)
+							nsum := sum + inc;
+							IF SELF.mode = Winding THEN
+								IF (sum <= 0) & (nsum > 0) THEN
+									Encode(Data[p], u, v, Enter); INC(p)
+								ELSIF (sum > 0) & (nsum <= 0) THEN
+									Encode(Data[p], u, v, Exit); INC(p)
+								END
+							ELSIF (SELF.mode = EvenOdd) & ((sum > 0) & ODD(sum) # (nsum > 0) & ODD(nsum)) THEN
+								IF ODD(sum) THEN
+									Encode(Data[p], u, v, Exit)
+								ELSE
+									Encode(Data[p], u, v, Enter)
+								END;
+								INC(p)
+							END;
+							sum := nsum
+						END
+					UNTIL (nv > v) & (mv > v);
+
+					IF p = tmp THEN	(* line is empty => append filler slice *)
+						Encode(Data[p], UBound, v, Enter); INC(p);
+						Encode(Data[p], UBound, v, Exit); INC(p)
+					END
+				END;
+
+				(* copy remaining points *)
+				WHILE n < N DO
+					Data[p] := data[n];
+					INC(p); INC(n)
+				END;
+				WHILE m < M DO
+					Data[p] := data[m];
+					INC(p); INC(m)
+				END;
+
+				(* copy merged data back and eliminate duplicate scanlines *)
+				SELF.Compact(Data)
+			END Merge;
+
+			(* bring region data into consistent state *)
+			PROCEDURE Validate ();
+				VAR data: RegionData; points, rn, wn, tmp: LONGINT; u, v, dir, y, sum, x, inc: INTEGER;
+			BEGIN
+				IF ~SELF.valid THEN
+					data := SELF.data;
+					SafeQuickSort(data^,0,SELF.points-1);
+					points := SELF.points;
+					rn := FirstSlice; wn := FirstSlice;	(* read and write position *)
+					Decode(data[rn], u, v, dir);
+
+					REPEAT
+						tmp := wn;
+						y := v;
+						sum := 0;
+						REPEAT
+							(* accumulate directions of coincident points *)
+							x := u; inc := 0;
+							REPEAT
+								INC(inc, dir); INC(rn);
+								Decode(data[rn], u, v, dir)
+							UNTIL (v > y) OR (u > x);
+
+							IF x < UBound THEN
+								IF SELF.mode = Winding THEN
+									IF sum = 0 THEN
+										Encode(data[wn], x, y, Enter); INC(wn);
+										INC(x)	(* prevent dropouts *)
+									END;
+									INC(sum, inc);
+									IF sum = 0 THEN
+										Encode(data[wn], x, y, Exit); INC(wn)
+									END
+								ELSIF SELF.mode = EvenOdd THEN
+									IF ~ODD(sum) THEN
+										Encode(data[wn], x, y, Enter); INC(wn);
+										INC(x)	(* prevent dropouts *)
+									END;
+									INC(sum, inc);
+									IF ~ODD(sum) THEN
+										Encode(data[wn], x, y, Exit); INC(wn)
+									END
+								END
+							END
+						UNTIL v > y;
+
+						IF wn = tmp THEN	(* insert filler span if all slices have been eliminated *)
+							Encode(data[wn], UBound, y, Enter); INC(wn);
+							Encode(data[wn], UBound, y, Exit); INC(wn)
+						ELSIF v > y + 1 THEN	(* add filler slice for disconnected regions *)
+							INC(y);
+							SELF.Append(UBound, y, Enter);
+							SELF.Append(UBound, y, Exit)
+						END
+					UNTIL v = Top;
+
+					Encode(data[wn], UBound, Top, Exit); INC(wn);
+					IF SELF.points > points THEN	(* added filler slices => must merge *)
+						IF wn < points THEN	(* some points have been discarded => move filler slices *)
+							rn := points; points := wn;
+							REPEAT
+								IF (wn < LEN(data)) & (rn < LEN(SELF.data)) THEN
+									data[wn] := SELF.data[rn];	(* data may have been reallocated! *)
+								END;
+								INC(wn); INC(rn)
+							UNTIL rn = SELF.points;
+							SELF.data := data;
+							SELF.points := wn
+						END;
+						SELF.Merge(points)
+					ELSE	(* points are still sorted *)
+						SELF.points := wn;
+						SELF.Compact(SELF.data)
+					END;
+					SELF.valid := TRUE
+				END
+			END Validate;
+
+			(* find first point on line y or higher *)
+			PROCEDURE FindUpper (y: INTEGER; VAR n: LONGINT);
+				VAR item, i, j, m: LONGINT;
+			BEGIN
+				item := ASH(LONG(y), 16);	(* leftmost possible point on line y *)
+				i := 0; j := SELF.points;
+				WHILE i + 1 < j DO
+					m := (i + j) DIV 2;
+					IF SELF.data[m] < item THEN
+						i := m
+					ELSE
+						j := m
+					END
+				END;
+				n := j
+			END FindUpper;
+
+			(* find first point on line y or lower *)
+			PROCEDURE FindLower (y: INTEGER; VAR n: LONGINT);
+				VAR v: INTEGER;
+			BEGIN
+				SELF.FindUpper(y, n);
+				v := INTEGER(ASH(SELF.data[n], -16));
+				IF v > y THEN	(* => find leftmost point on lower slice *)
+					DEC(n);
+					y := INTEGER(ASH(SELF.data[n], -16));
+					REPEAT
+						DEC(n)
+					UNTIL (n < 0) OR (ASH(SELF.data[n], -16) < y);
+					INC(n)
+				END
+			END FindLower;
+
+			(* enumerate (inverted) region within rectangle *)
+			PROCEDURE Enum (llx, lly, urx, ury: INTEGER; enum: Enumerator; VAR edata: EnumData; enter: INTEGER);
+				VAR data: RegionData; n, lo, hi: LONGINT; u, v, dir, y, top, x: INTEGER;
+			BEGIN
+				SELF.Validate();
+				ClipRect(llx, lly, urx, ury, LBound, LBound, UBound, UBound);
+				data := SELF.data;
+				SELF.FindLower(lly, n);
+				Decode(data[n], u, v, dir);
+				y := lly;
+
+				REPEAT
+					(* calculate height of slice *)
+					lo := n;
+					REPEAT
+						INC(n);
+						IF u < llx THEN
+							lo := n
+						END;
+						Decode(data[n], u, v, dir)
+					UNTIL v > y;
+					hi := n;
+					top := MIN(v, ury);
+
+					(* enumerate spans of current slice *)
+					n := lo;
+					Decode(data[n], u, v, dir);
+					x := llx;
+					WHILE (v <= y) & ((u < urx) OR (dir # enter)) DO
+						IF u > x THEN
+							IF dir = enter THEN
+								x := u
+							ELSE
+								enum(x, y, MIN(u, urx), top, edata)
+							END
+						END;
+						INC(n);
+						Decode(data[n], u, v, dir)
+					END;
+
+					IF n < hi THEN
+						n := hi;
+						Decode(data[n], u, v, dir)
+					END;
+					y := v
+				UNTIL v >= ury
+			END Enum;
+
+			(* create data points for rectangular region *)
+			PROCEDURE MakeData ();
+			BEGIN
+				IF SELF.points = 0 THEN
+					SELF.Append(UBound, Bottom, Enter);
+					SELF.Append(UBound, Bottom, Exit);
+					IF (SELF.llx <= SELF.urx) & (SELF.lly <= SELF.ury) THEN
+						SELF.Append(SELF.llx, SELF.lly, Enter);
+						SELF.Append(SELF.urx, SELF.lly, Exit);
+						SELF.Append(UBound, SELF.ury, Enter);
+						SELF.Append(UBound, SELF.ury, Exit)
+					END;
+					SELF.Append(UBound, Top, Enter)
+				END
+			END MakeData;
+
+		END Region;
+
+		(** region enumeration **)
+		EnumData* = RECORD END;
+		Enumerator* = PROCEDURE (llx, lly, urx, ury: INTEGER; VAR edata: EnumData);
+
+
+	VAR
+		Data: RegionData;	(* temporary region data for merging *)
+		DataSize: LONGINT;	(* number of points allocated for Data *)
+
+	(**--- Rectangles ---**)
+
+	(** make rectangle large enough to include a point **)
+	PROCEDURE IncludePoint* (VAR llx, lly, urx, ury: INTEGER; x, y: INTEGER);
+	BEGIN
+		IF x < llx THEN llx := x END;
+		IF x > urx THEN urx := x END;
+		IF y < lly THEN lly := y END;
+		IF y > ury THEN ury := y END
+	END IncludePoint;
+
+	(** make rectangle large enough to include other rectangle **)
+	PROCEDURE IncludeRect* (VAR llx, lly, urx, ury: INTEGER; illx, illy, iurx, iury: INTEGER);
+	BEGIN
+		IF illx < llx THEN llx := illx END;
+		IF iurx > urx THEN urx := iurx END;
+		IF illy < lly THEN lly := illy END;
+		IF iury > ury THEN ury := iury END
+	END IncludeRect;
+
+	(** shrink rectangle to area within other rectangle **)
+	PROCEDURE ClipRect* (VAR llx, lly, urx, ury: INTEGER; cllx, clly, curx, cury: INTEGER);
+	BEGIN
+		IF cllx > llx THEN llx := cllx END;
+		IF curx < urx THEN urx := curx END;
+		IF clly > lly THEN lly := clly END;
+		IF cury < ury THEN ury := cury END
+	END ClipRect;
+
+	(** return whether rectangle is empty **)
+	PROCEDURE RectEmpty* (llx, lly, urx, ury: INTEGER): BOOLEAN;
+	BEGIN
+		RETURN (llx >= urx) OR (lly >= ury)
+	END RectEmpty;
+
+	(** return whether (non_empty) rectangle is completely inside other rectangle **)
+	PROCEDURE RectInRect* (llx, lly, urx, ury, illx, illy, iurx, iury: INTEGER): BOOLEAN;
+	BEGIN
+		RETURN (llx >= illx) & (urx <= iurx) & (lly >= illy) & (ury <= iury)
+	END RectInRect;
+
+	(** return whether (non_empty) rectangle intersects other rectangle **)
+	PROCEDURE RectsIntersect* (llx, lly, urx, ury, illx, illy, iurx, iury: INTEGER): BOOLEAN;
+	BEGIN
+		RETURN (llx < iurx) & (urx > illx) & (lly < iury) & (ury > illy)
+	END RectsIntersect;
+
+	(** return whether rectangle contains point **)
+	PROCEDURE PointInRect* (x, y: INTEGER; llx, lly, urx, ury: INTEGER): BOOLEAN;
+	BEGIN
+		RETURN (x >= llx) & (x < urx) & (y >= lly) & (y < ury)
+	END PointInRect;
+
+
+	(*--- Auxiliary Routines For Managing Regions ---*)
+
+	(*
+		Implementation notes:
+
+		Regions are managed by slicing them horizontally. For each scanline y, a set of spans on the scanline defines which
+		parts of the scanline are part of the region. The spans are defined through the x_coordinates of their end points.
+		Every point on a scanline has a direction attribute, which specifies whether the point starts a span (Enter) or ends
+		one (Exit), allowing spans to nest or overlap.
+
+		The x_ and y_coordinates of a point along with its direction are encoded into a LONGINT. The chosen encoding
+		weights the y_coordinate most, followed by the x_coordinate and the direction of an intersection. Visiting all
+		encoded points in ascending order therefore traverses all spans of the region from the bottom left corner to the
+		top right corner.
+
+		In order to save space, identical slices adjacent to each other are stored only once. The bottommost scanline of
+		an identical sequence of scanlines serves as a representant for the whole sequence; all others are eliminated.
+		This means that if no points exist for a certain y_coordinate, the spans of the corresponding scanline are identical
+		to those of the one below it. As a consequence, scanlines that are completely outside the region need an empty
+		filler span to distinguish them from eliminated scanlines. A filler span consists of two points located at UBound,
+		one entering the region and the other leaving it.
+
+		Most operations modifying regions append new points in ascending order to the sequence of existing points and
+		then merge the two sequences again. If points cannot be appended in order, the whole set of points has to be
+		sorted before any other operation can be executed. Doing this immediately after the sequence of points has been
+		invalidated can decrease performance significantly if a lot of invalidating operations are issued in sequence, as is
+		typically the case with AddPoint. This is why regions have a valid flag, indicating whether encoded points are sorted
+		or not. Invalidating operations only have to set valid to FALSE, other operations will eventually validate the region
+		again, at the same time eliminating multiple points and overlapping spans.
+	*)
+
+	(* encode point coordinates and curve direction into a LONGINT *)
+	PROCEDURE Encode (VAR item: LONGINT; u, v, dir: LONGINT);
+	BEGIN
+		item := ASH(v, 16) + ASH((u + 4000H) MOD 8000H, 1) + ASH(1 + dir, -1)
+	END Encode;
+
+	(* restore point coordinates and curve direction from an encoded LONGINT *)
+	PROCEDURE Decode (item: LONGINT; VAR u, v, dir: INTEGER);
+	BEGIN
+		v := INTEGER(ASH(item, -16));
+		u := INTEGER(ASH(item, -1) MOD 8000H - 4000H);
+		dir := INTEGER(ASH(item MOD 2, 1) - 1)
+	END Decode;
+
+	(* copy points between region data blocks *)
+	PROCEDURE CopyPoints (src, dst: RegionData; points: LONGINT);
+		VAR i: LONGINT;
+	BEGIN
+		i := 0;
+		WHILE (i < points) & (i < LEN(dst)) & (i < LEN(src)) DO
+			dst[i] := src[i];
+			INC(i)
+		END
+	END CopyPoints;
+
+	(* quick sort with limited recursion guarantee *)
+	PROCEDURE SafeQuickSort(VAR data: ARRAY OF LONGINT; lo, hi: LONGINT);
+	CONST limit = 8;
+	VAR i, x, j, t, shortLo, shortHi, longLo, longHi: LONGINT;
+	BEGIN
+		WHILE (hi > lo) DO
+			IF hi - lo < limit THEN	(* use straight insertion for less than limit entries... *)
+				i := lo + 1;
+				WHILE i <= hi DO
+					x := data[i];
+					j := i;
+					WHILE (j > lo) & (x < data[j - 1]) DO
+						data[j] := data[j - 1];
+						DEC(j)
+					END;
+					data[j] := x;
+					INC(i)
+				END;
+				hi := lo; (* termination! *)
+			ELSE
+				i := lo; j := hi;
+				x := data[(lo + hi) DIV 2];
+				REPEAT
+					WHILE data[i] < x DO INC(i) END;
+					WHILE data[j] > x DO DEC(j) END;
+					IF i <= j THEN
+						t := data[i]; data[i] := data[j]; data[j] := t;
+						INC(i); DEC(j)
+					END
+				UNTIL i > j;
+				IF (j - lo) < (hi - i) THEN (* generalized interval [lo,j] is smaller than interval [hi, i] *)
+					shortLo := lo; shortHi := j;
+					longLo := i; longHi := hi;
+				ELSE
+					longLo := lo; longHi := j;
+					shortLo := i; shortHi := hi;
+				END;
+				IF (shortLo < shortHi) THEN 
+					SafeQuickSort(data, shortLo, shortHi);
+				END;
+				(* now: tail recursion, call of: SortRange(data, longLo, longHi) *)
+				lo := longLo; hi := longHi;
+			END
+		END;
+	END SafeQuickSort;
+
+	(**--- Region Queries ---**)
+
+	(** return whether region is empty **)
+	PROCEDURE Empty* (reg: Region): BOOLEAN;
+	BEGIN
+		RETURN reg.Empty();
+	END Empty;
+
+	(** return whether (non_empty) region is rectangular **)
+	PROCEDURE IsRect* (reg: Region): BOOLEAN;
+	BEGIN
+		RETURN reg.IsRect();
+	END IsRect;
+
+	(** return whether point is inside (non_empty) region **)
+	PROCEDURE PointInside* (x, y: INTEGER; reg: Region): BOOLEAN;
+	BEGIN
+		RETURN reg.PointInside(x, y);
+	END PointInside;
+
+	(** return whether (non_empty) rectangle is completely inside (non_empty) region **)
+	PROCEDURE RectInside* (llx, lly, urx, ury: INTEGER; reg: Region): BOOLEAN;
+	BEGIN
+		RETURN reg.RectInside(llx, lly, urx, ury);
+	END RectInside;
+
+	(** return whether (non_empty) rectangle overlaps (non_empty) region **)
+	PROCEDURE RectOverlaps* (llx, lly, urx, ury: INTEGER; reg: Region): BOOLEAN;
+	BEGIN
+		RETURN reg.RectOverlaps(llx, lly, urx, ury);
+	END RectOverlaps;
+
+	(** return whether region is completely within another region **)
+	PROCEDURE RegionInside* (inner, outer: Region): BOOLEAN;
+	BEGIN
+		RETURN inner.RegionInside(outer);
+	END RegionInside;
+
+	(** return whether two regions intersect each other **)
+	PROCEDURE RegionOverlaps* (reg, arg: Region): BOOLEAN;
+	BEGIN
+		RETURN reg.RegionOverlaps(arg);
+	END RegionOverlaps;
+
+	(** enumerate region within rectangle **)
+	PROCEDURE Enumerate* (reg: Region; llx, lly, urx, ury: INTEGER; enum: Enumerator; VAR edata: EnumData);
+	BEGIN
+		reg.Enumerate(llx, lly, urx, ury, enum, edata);
+	END Enumerate;
+
+	(** enumerate parts of rectangle not within region **)
+	PROCEDURE EnumerateInv* (reg: Region; llx, lly, urx, ury: INTEGER; enum: Enumerator; VAR edata: EnumData);
+	BEGIN
+		reg.EnumerateInv(llx, lly, urx, ury, enum, edata);
+	END EnumerateInv;
+
+
+	(**--- Region Construction ---**)
+
+	(** make region empty **)
+	PROCEDURE Clear* (reg: Region);
+	BEGIN
+		reg.Clear();
+	END Clear;
+
+	(** set region mode **)
+	PROCEDURE SetMode* (reg: Region; mode: INTEGER);
+	BEGIN
+		reg.SetMode(mode);
+	END SetMode;
+
+	(** initialize region **)
+	PROCEDURE Init* (reg: Region; mode: INTEGER);
+	BEGIN
+		reg.Init(mode);
+	END Init;
+
+	(** make region rectangular **)
+	PROCEDURE SetToRect* (reg: Region; llx, lly, urx, ury: INTEGER);
+	BEGIN
+		reg.SetToRect(llx, lly, urx, ury);
+	END SetToRect;
+
+	(** shift region **)
+	PROCEDURE Shift* (reg: Region; dx, dy: INTEGER);
+	BEGIN
+		reg.Shift(dx, dy);
+	END Shift;
+
+	(** copy region **)
+	PROCEDURE Copy* (from, to: Region);
+	BEGIN
+		from.Copy(to);
+	END Copy;
+
+	(** add second region to first **)
+	PROCEDURE Add* (reg, arg: Region);
+	BEGIN
+		reg.Add(arg);
+	END Add;
+
+	(** add rectangle to region **)
+	PROCEDURE AddRect* (reg: Region; llx, lly, urx, ury: INTEGER);
+	BEGIN
+		reg.AddRect(llx, lly, urx, ury);
+	END AddRect;
+
+	(** subtract second region from first **)
+	PROCEDURE Subtract* (reg, arg: Region);
+	BEGIN
+		reg.Subtract(arg);
+	END Subtract;
+
+	(** subtract rectangle from region **)
+	PROCEDURE SubtractRect* (reg: Region; llx, lly, urx, ury: INTEGER);
+	BEGIN
+		reg.SubtractRect(llx, lly, urx, ury);
+	END SubtractRect;
+
+	(** intersect first region with second region **)
+	PROCEDURE Intersect* (reg, arg: Region);
+	BEGIN
+		reg.Intersect(arg);
+	END Intersect;
+
+	(** intersect region with rectangle **)
+	PROCEDURE IntersectRect* (reg: Region; llx, lly, urx, ury: INTEGER);
+	BEGIN
+		reg.IntersectRect(llx, lly, urx, ury);
+	END IntersectRect;
+
+	(** invert region **)
+	PROCEDURE Invert* (reg: Region);
+	BEGIN
+		reg.Invert();
+	END Invert;
+
+	(**
+		In addition to creating rectangular regions and using Boolean operations to combine several regions, a region
+		can also be built by tracing its outline with AddPoint. In order to allow the correct handling of self_intersecting
+		contours, a direction parameter is needed which indicates whether the curve is going up (dy = 1) or down
+		(dy = -1) at the given point.
+
+		When performing Boolean operations upon regions or when building regions from self_intersecting contours,
+		it is possible that some areas in the resulting region get "covered" more than once. Since most query operations
+		reduce regions to non_overlapping areas, a rule which decides whether a point is inside a region or not is needed.
+		Imagine a ray originating at such a point and counting every intersection of the ray with the boundary curve of the
+		region as +1 if the curve crosses the ray from right to left and as -1 otherwise.
+			- for mode Winding (the default), a point is inside the region if the resulting sum is non_zero
+			- for mode EvenOdd, a point is inside the region if the resulting sum is odd
+
+		Behaviour of all region queries and region operations is undefined for contours which are not closed.
+	**)
+
+	(** add a scanline intersection to a region **)
+	PROCEDURE AddPoint* (reg: Region; x, y, dy: INTEGER);
+	BEGIN
+		reg.AddPoint(x, y, dy);
+	END AddPoint;
+
+END GfxRegions.