MODULE SVGRenderer;
IMPORT SVG, SVGColors, SVGGradients, SVGFilters, SVGUtilities, XMLObjects,
Gfx, GfxBuffer, GfxPaths, GfxImages, GfxMatrix, Math, Raster;
TYPE
RenderTargetPool=OBJECT
VAR list: XMLObjects.ArrayCollection;
PROCEDURE &New*;
BEGIN
NEW(list)
END New;
PROCEDURE Alloc*(VAR doc: SVG.Document; width, height: LONGINT);
VAR p: ANY;
BEGIN
IF list.GetNumberOfElements()=0 THEN
doc := SVG.NewDocument(width, height);
ELSE
p := list.GetElement(list.GetNumberOfElements()-1);
doc := p(SVG.Document);
list.Remove(doc);
Raster.Clear(doc);
END
END Alloc;
PROCEDURE Free*(doc: SVG.Document);
BEGIN
list.Add(doc)
END Free;
END RenderTargetPool;
Renderer*=OBJECT
VAR
gradients*: SVGGradients.GradientDict;
filters*: SVGFilters.FilterDict;
ctxt: GfxBuffer.Context;
mode: SET;
rasterMode: Raster.Mode;
filterStack: SVGFilters.FilterStack;
renderTarget: SVG.Document;
renderTargetPool: RenderTargetPool;
PROCEDURE &New*;
BEGIN
NEW(gradients);
NEW(filters);
NEW(filterStack);
NEW(renderTargetPool);
Raster.InitMode(rasterMode, Raster.srcOverDst);
END New;
PROCEDURE FillWhite*(state: SVG.State);
VAR
white: Raster.Pixel;
copyMode: Raster.Mode;
BEGIN
Raster.SetRGB(white,0FFH,0FFH,0FFH);
Raster.InitMode(copyMode, Raster.srcCopy);
Raster.Fill(state.target, 0, 0, state.target.width, state.target.height, white, copyMode)
END FillWhite;
PROCEDURE RenderBegin(state: SVG.State; recordPathMode: BOOLEAN);
BEGIN
NEW(ctxt);
IF state.style.stroke.type = SVG.PaintURI THEN state.transparencyUsed := TRUE END;
IF state.style.fill.type = SVG.PaintURI THEN state.transparencyUsed := TRUE END;
IF state.transparencyUsed THEN
renderTargetPool.Alloc(renderTarget, state.target.width, state.target.height);
GfxBuffer.Init(ctxt, renderTarget);
ELSE
GfxBuffer.Init(ctxt, state.target);
END;
IF recordPathMode THEN
mode := {Gfx.Record};
ELSE
mode := {};
SetMatrix(state.userToWorldSpace,ctxt);
END;
END RenderBegin;
PROCEDURE RenderEnd(state: SVG.State; recordPathMode: BOOLEAN);
VAR pattern: Gfx.Pattern;
worldBBox, objectBBox: SVG.Box;
minx, miny, maxx, maxy: LONGINT;
strokeWidth: SVG.Length;
BEGIN
IF recordPathMode THEN
mode:={};
GetBBoxes(ctxt.path, state.userToWorldSpace, worldBBox, objectBBox);
strokeWidth := state.userToWorldSpace.TransformLength(state.style.strokeWidth);
Gfx.SetLineWidth(ctxt, SHORT(strokeWidth));
worldBBox.x := worldBBox.x - strokeWidth;
worldBBox.y := worldBBox.y - strokeWidth;
worldBBox.width := worldBBox.width + 2*strokeWidth;
worldBBox.height := worldBBox.height + 2*strokeWidth;
IF state.style.stroke.type = SVG.PaintColor THEN
Gfx.SetStrokeColor(ctxt, GetColor(state.style.stroke.color));
mode := mode + {Gfx.Stroke}
ELSIF state.style.stroke.type = SVG.PaintURI THEN
pattern := gradients.GetGradientAsPattern(ctxt, state.style.stroke.uri, worldBBox, objectBBox, state.userToWorldSpace, state.viewport);
IF pattern#NIL THEN
Gfx.SetFillPattern(ctxt, pattern);
mode := mode + {Gfx.Stroke}
END
END;
IF state.style.fill.type = SVG.PaintColor THEN
Gfx.SetFillColor(ctxt, GetColor(state.style.fill.color));
mode := mode + {Gfx.Fill}
ELSIF state.style.fill.type = SVG.PaintURI THEN
pattern := gradients.GetGradientAsPattern(ctxt, state.style.fill.uri, worldBBox, objectBBox, state.userToWorldSpace, state.viewport);
IF pattern#NIL THEN
Gfx.SetFillPattern(ctxt, pattern);
mode := mode + {Gfx.Fill}
END
END;
Gfx.Render(ctxt, mode)
ELSE
worldBBox.x := 0;
worldBBox.y := 0;
worldBBox.width := state.target.width-1;
worldBBox.height := state.target.height-1;
END;
IF state.transparencyUsed THEN
minx := ENTIER(worldBBox.x);
miny := ENTIER(worldBBox.y);
maxx := ENTIER(worldBBox.x+worldBBox.width)+1;
maxy := ENTIER(worldBBox.y+worldBBox.height)+1;
IF minx<ENTIER(state.viewport.x) THEN minx := ENTIER(state.viewport.x) END;
IF miny<ENTIER(state.viewport.y) THEN miny := ENTIER(state.viewport.y) END;
IF maxx>ENTIER(state.viewport.x+state.viewport.width) THEN maxx := ENTIER(state.viewport.x+state.viewport.width) END;
IF maxy>ENTIER(state.viewport.y+state.viewport.height) THEN maxy :=ENTIER(state. viewport.y+state.viewport.height) END;
IF (minx<=maxx) & (miny<=maxy) THEN
Raster.Copy(renderTarget, state.target, minx, miny, maxx, maxy, minx, miny, rasterMode)
END;
renderTargetPool.Free(renderTarget)
END
END RenderEnd;
PROCEDURE BeginFilter*(filter: SVGFilters.Filter; state: SVG.State);
BEGIN
filterStack.Push(filter);
IF filter#NIL THEN
state.Push();
state.target := SVG.NewDocument(state.target.width, state.target.height);
END
END BeginFilter;
PROCEDURE EndFilter*(state: SVG.State);
VAR filterTarget: SVG.Document;
filter: SVGFilters.Filter;
BEGIN
filterStack.Pop(filter);
IF filter#NIL THEN
filterTarget := state.target;
state.Pop();
filter.Apply(filterTarget, state.target);
END
END EndFilter;
PROCEDURE Bezier3To(current, bezier1, bezier2: SVG.Coordinate);
BEGIN
Gfx.BezierTo (ctxt,
SHORT(current.x), SHORT(current.y),
SHORT(bezier1.x), SHORT(bezier1.y),
SHORT(bezier2.x), SHORT(bezier2.y));
END Bezier3To;
PROCEDURE Bezier2To(start, bezier, end: SVG.Coordinate);
VAR bezier1, bezier2: SVG.Coordinate;
BEGIN
bezier1.x := (bezier.x-start.x)*2.0/3.0+start.x;
bezier1.y := (bezier.y-start.y)*2.0/3.0+start.y;
bezier2.x := end.x-(end.x-bezier.x)*2.0/3.0;
bezier2.y := end.y-(end.y-bezier.y)*2.0/3.0;
Bezier3To(end, bezier1, bezier2);
END Bezier2To;
PROCEDURE ArcTo(radius, flags, start, end: SVG.Coordinate; xrot: SVG.Length);
VAR cos, sin, rx2, ry2, tmp: SVG.Length;
diff0, diff, center0, center, d1, d2: SVG.Coordinate;
BEGIN
IF (start.x=end.x) & (start.y=end.y) THEN RETURN END;
IF (radius.x=0) OR (radius.y=0) THEN
Gfx.LineTo(ctxt, SHORT(end.x), SHORT(end.y));
RETURN
END;
radius.x := ABS(radius.x);
radius.y := ABS(radius.y);
IF flags.x#0 THEN flags.x := 1.0 END;
IF flags.y#0 THEN flags.y := 1.0 END;
cos := Math.cos(SHORT(xrot/180.0*Math.pi));
sin := Math.sin(SHORT(xrot/180.0*Math.pi));
diff.x := (start.x-end.x)/2;
diff.y := (start.y-end.y)/2;
diff0.x := cos*diff.x+sin*diff.y;
diff0.y := -sin*diff.x+cos*diff.y;
tmp := diff0.x*diff0.x/(radius.x*radius.x)+diff0.y*diff0.y/(radius.y*radius.y);
IF tmp > 1 THEN
tmp := Math.sqrt(SHORT(tmp));
radius.x := tmp*radius.x;
radius.y := tmp*radius.y;
tmp := 0;
ELSE
rx2 := radius.x*radius.x;
ry2 := radius.y*radius.y;
tmp := rx2*diff0.y*diff0.y+ry2*diff0.x*diff0.x;
tmp := (rx2*ry2-tmp)/tmp;
IF tmp <= 0 THEN tmp := 0 END;
tmp := Math.sqrt(SHORT(tmp));
IF flags.x=flags.y THEN tmp := -tmp END;
END;
center0.x := tmp*radius.x*diff0.y/radius.y;
center0.y := -tmp*radius.y*diff0.x/radius.x;
center.x := cos*center0.x-sin*center0.y+(start.x+end.x)/2;
center.y := sin*center0.x+cos*center0.y+(start.y+end.y)/2;
d1.x := center.x+radius.x*cos;
d1.y := center.y+radius.x*sin;
d2.x := center.x-radius.y*sin;
d2.y := center.y+radius.y*cos;
IF (flags.y = 0.0) = (d1.x*d2.y-d1.y*d2.x > 0.0) THEN
tmp := d1.x;
d1.x := d2.x;
d2.x := tmp;
tmp := d1.y;
d1.y := d2.y;
d2.y := tmp;
END;
Gfx.ArcTo(ctxt,
SHORT(end.x), SHORT(end.y),
SHORT(center.x), SHORT(center.y),
SHORT(d1.x), SHORT(d1.y),
SHORT(d2.x), SHORT(d2.y))
END ArcTo;
PROCEDURE RenderImage*(x, y, width, height: SVG.Length; image: SVG.Document; state: SVG.State);
VAR filter: GfxImages.Filter;
BEGIN
GfxImages.InitLinearFilter(filter);
state.userToWorldSpace := state.userToWorldSpace.Translate(-x, -y);
state.userToWorldSpace := state.userToWorldSpace.Scale(width / image.width, height / image.height);
x := x*image.width / width;
y := y*image.height / height;
state.userToWorldSpace := state.userToWorldSpace.Translate(x, y);
RenderBegin(state, FALSE);
Gfx.DrawImageAt(ctxt, SHORT(x), SHORT(y), image, filter);
RenderEnd(state, FALSE);
END RenderImage;
PROCEDURE RenderRect*(x, y, width, height: SVG.Length; state: SVG.State);
BEGIN
RenderBegin(state,TRUE);
Gfx.DrawRect(ctxt, SHORT(x), SHORT(y), SHORT(x+width), SHORT(y+height), mode);
RenderEnd(state, TRUE);
END RenderRect;
PROCEDURE RenderRoundedRect*(x, y, width, height, rx, ry: SVG.Length; state: SVG.State);
BEGIN
RenderBegin(state, TRUE);
IF rx>width/2 THEN rx := width/2 END;
IF ry>height/2 THEN ry := height/2 END;
Gfx.Begin(ctxt, mode);
Gfx.MoveTo(ctxt, SHORT(x+width/2), SHORT(y));
Gfx.LineTo(ctxt, SHORT(x+width-rx), SHORT(y));
Gfx.ArcTo(ctxt,SHORT(x+width), SHORT(y+ry),
SHORT(x+width-rx), SHORT(y+ry),
SHORT(x+width-rx), SHORT(y),
SHORT(x+width), SHORT(y+ry));
Gfx.LineTo(ctxt, SHORT(x+width), SHORT(y+height-ry));
Gfx.ArcTo(ctxt,SHORT(x+width-rx), SHORT(y+height),
SHORT(x+width-rx), SHORT(y+height-ry),
SHORT(x+width), SHORT(y+height-ry),
SHORT(x+width-rx), SHORT(y+height));
Gfx.LineTo(ctxt, SHORT(x+rx), SHORT(y+height));
Gfx.ArcTo(ctxt,SHORT(x), SHORT(y+height-ry),
SHORT(x+rx), SHORT(y+height-ry),
SHORT(x+rx), SHORT(y+height),
SHORT(x), SHORT(y+height-ry));
Gfx.LineTo(ctxt, SHORT(x), SHORT(y+ry));
Gfx.ArcTo(ctxt,SHORT(x+rx), SHORT(y),
SHORT(x+rx), SHORT(y+ry),
SHORT(x), SHORT(y+ry),
SHORT(x+rx), SHORT(y));
Gfx.Close(ctxt);
Gfx.End(ctxt);
RenderEnd(state, TRUE);
END RenderRoundedRect;
PROCEDURE RenderCircle*(x, y, r: SVG.Length; state: SVG.State);
BEGIN
RenderBegin(state, TRUE);
Gfx.DrawCircle(ctxt, SHORT(x), SHORT(y), SHORT(r), mode);
RenderEnd(state, TRUE);
END RenderCircle;
PROCEDURE RenderEllipse*(x, y, rx, ry: SVG.Length; state: SVG.State);
BEGIN
RenderBegin(state, TRUE);
Gfx.DrawEllipse(ctxt, SHORT(x), SHORT(y), SHORT(rx), SHORT(ry), mode);
RenderEnd(state, TRUE);
END RenderEllipse;
PROCEDURE RenderLine*(x1, y1, x2, y2: SVG.Length; state: SVG.State);
BEGIN
RenderBegin(state, TRUE);
Gfx.DrawLine(ctxt, SHORT(x1), SHORT(y1), SHORT(x2), SHORT(y2), mode-{Gfx.Fill, Gfx.Clip, Gfx.EvenOdd});
RenderEnd(state, TRUE);
END RenderLine;
PROCEDURE RenderPoly*(points: SVG.String; closed: BOOLEAN; state: SVG.State);
VAR i: LONGINT;
current: SVG.Coordinate;
BEGIN
RenderBegin(state, TRUE);
Gfx.Begin(ctxt, mode);
i := 0;
SVGUtilities.SkipWhiteSpace(i, points);
SVG.ParseCoordinate(points, i, current, FALSE);
SVGUtilities.SkipWhiteSpace(i, points);
Gfx.MoveTo(ctxt, SHORT(current.x), SHORT(current.y));
WHILE points[i] # 0X DO
SVG.ParseCoordinate(points, i, current, FALSE);
SVGUtilities.SkipWhiteSpace(i, points);
Gfx.LineTo(ctxt, SHORT(current.x), SHORT(current.y));
END;
IF closed THEN
Gfx.Close(ctxt);
END;
Gfx.End(ctxt);
RenderEnd(state, TRUE);
END RenderPoly;
PROCEDURE RenderPath*(d: SVG.String; state: SVG.State);
VAR i: LONGINT;
subPathStart, current, last: SVG.Coordinate;
relative: BOOLEAN;
command, prevCommand: CHAR;
arcR, arcFlags: SVG.Coordinate;
arcAngle: SVG.Length;
bezier1, bezier2: SVG.Coordinate;
BEGIN
RenderBegin(state, TRUE);
Gfx.Begin(ctxt, mode);
current.x := 0;
current.y := 0;
command := 0X;
i := 0;
SVGUtilities.SkipWhiteSpace(i, d);
IF (d[i] # 'm') & (d[i] # 'M') THEN
SVG.Error("PathData error: missing moveto")
END;
WHILE d[i] # 0X DO
SVGUtilities.SkipWhiteSpace(i, d);
prevCommand := command;
last := current;
IF SVGUtilities.IsAlpha(d[i]) THEN
relative := SVGUtilities.IsLowercase(d[i]);
command := d[i];
INC(i);
SVGUtilities.SkipWhiteSpace(i, d);
END;
CASE command OF
'm', 'M':
SVG.ParseCoordinate(d, i, current, relative);
subPathStart := current;
Gfx.MoveTo(ctxt, SHORT(current.x), SHORT(current.y));
| 'z', 'Z':
current := subPathStart;
Gfx.Close(ctxt);
| 'l', 'L':
SVG.ParseCoordinate(d, i, current, relative);
Gfx.LineTo(ctxt, SHORT(current.x), SHORT(current.y));
| 'h', 'H':
SVG.ParseCoordinate1(d, i, current.x, relative);
Gfx.LineTo(ctxt, SHORT(current.x), SHORT(current.y));
| 'v', 'V':
SVG.ParseCoordinate1(d, i, current.y, relative);
Gfx.LineTo(ctxt, SHORT(current.x), SHORT(current.y));
| 'c', 'C':
bezier1:=current;
bezier2:=current;
SVG.ParseCoordinate(d, i, bezier1, relative);
SVG.ParseCoordinate(d, i, bezier2, relative);
SVG.ParseCoordinate(d, i, current, relative);
Bezier3To(current, bezier1, bezier2);
| 's', 'S':
CASE prevCommand OF
's','S','c','C':
bezier1.x:=current.x-(bezier2.x-current.x);
bezier1.y:=current.y-(bezier2.y-current.y);
ELSE
bezier1:=current;
END;
bezier2:=current;
SVG.ParseCoordinate(d, i, bezier2, relative);
SVG.ParseCoordinate(d, i, current, relative);
Bezier3To(current, bezier1, bezier2);
| 'q', 'Q':
bezier1:=current;
SVG.ParseCoordinate(d, i, bezier1, relative);
SVG.ParseCoordinate(d, i, current, relative);
Bezier2To(last, bezier1, current);
| 't', 'T':
CASE prevCommand OF
't','T','q','Q':
bezier1.x:=current.x-(bezier1.x-current.x);
bezier1.y:=current.y-(bezier1.y-current.y);
ELSE
bezier1:=current;
END;
SVG.ParseCoordinate(d, i, current, relative);
Bezier2To(last, bezier1, current);
| 'a', 'A':
SVG.ParseCoordinate(d, i, arcR, FALSE);
SVG.ParseCoordinate1(d, i, arcAngle, FALSE);
SVG.ParseCoordinate(d, i, arcFlags, FALSE);
SVG.ParseCoordinate(d, i, current, relative);
ArcTo(arcR,arcFlags,last,current,arcAngle);
ELSE
SVG.Error("PathData error: unknown command");
d[i] := 0X;
END;
SVGUtilities.SkipWhiteSpace(i, d)
END;
Gfx.End(ctxt);
RenderEnd(state, TRUE);
END RenderPath;
END Renderer;
PROCEDURE GetColor(color: SVG.Color): Gfx.Color;
VAR c: Gfx.Color;
BEGIN
SVGColors.Split(color, c.r, c.g, c.b, c.a);
RETURN c
END GetColor;
PROCEDURE GetBBoxes(path: GfxPaths.Path; objectToWorldSpace: SVG.Transform; VAR worldBBox, objectBBox: SVG.Box);
VAR
x1World, y1World, x2World, y2World: REAL;
x1Object, y1Object, x2Object, y2Object: REAL;
mat: GfxMatrix.Matrix;
BEGIN
GfxMatrix.Init(mat, SHORT(objectToWorldSpace.a), SHORT(objectToWorldSpace.b),
SHORT(objectToWorldSpace.c), SHORT(objectToWorldSpace.d),
SHORT(objectToWorldSpace.e), SHORT(objectToWorldSpace.f));
GfxPaths.GetBox(path, x1Object, y1Object, x2Object, y2Object);
objectBBox.x:=x1Object;
objectBBox.y:=y1Object;
objectBBox.width :=x2Object-x1Object;
objectBBox.height :=y2Object-y1Object;
GfxMatrix.ApplyToRect(mat, x1Object, y1Object, x2Object, y2Object, x1World, y1World, x2World, y2World);
worldBBox.x := x1World;
worldBBox.y := y1World;
worldBBox.width := x2World-x1World;
worldBBox.height := y2World-y1World;
GfxPaths.Apply(path, mat);
END GetBBoxes;
PROCEDURE SetMatrix(objectToWorldSpace: SVG.Transform; ctxt: GfxBuffer.Context);
VAR mat: GfxMatrix.Matrix;
BEGIN
GfxMatrix.Init(mat, SHORT(objectToWorldSpace.a), SHORT(objectToWorldSpace.b),
SHORT(objectToWorldSpace.c), SHORT(objectToWorldSpace.d),
SHORT(objectToWorldSpace.e), SHORT(objectToWorldSpace.f));
Gfx.SetCTM(ctxt, mat);
END SetMatrix;
END SVGRenderer.