123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536 |
- MODULE SVGRenderer;
- IMPORT SVG, SVGColors, SVGGradients, SVGFilters, SVGUtilities, XMLObjects,
- Gfx, GfxBuffer, GfxPaths, GfxImages, GfxMatrix, Math, Raster;
- TYPE
- RenderTargetPool=OBJECT
- VAR list: XMLObjects.ArrayCollection; (* container for pooled render targets *)
- PROCEDURE &New*;
- BEGIN
- NEW(list)
- END New;
- (* Allocate an unused or new document *)
- 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;
- (* Add doc to pool *)
- PROCEDURE Free*(doc: SVG.Document);
- BEGIN
- list.Add(doc)
- END Free;
- END RenderTargetPool;
- Renderer*=OBJECT
- VAR
- gradients*: SVGGradients.GradientDict; (* container of all defined gradients *)
- filters*: SVGFilters.FilterDict; (* container of all defined filters *)
- ctxt: GfxBuffer.Context; (* context of the graphics library *)
- mode: SET; (* drawing mode for the graphics library (filled, stroked, ...) *)
- rasterMode: Raster.Mode; (* mode for the raster library (srcOverDst) *)
- filterStack: SVGFilters.FilterStack; (* stack of active filters *)
- renderTarget: SVG.Document; (* current render target *)
- renderTargetPool: RenderTargetPool; (* pool of unused render targets *)
- PROCEDURE &New*;
- BEGIN
- NEW(gradients);
- NEW(filters);
- NEW(filterStack);
- NEW(renderTargetPool);
- Raster.InitMode(rasterMode, Raster.srcOverDst);
- END New;
- (* Fill target white *)
- 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;
- (* Prepare for rendering *)
- 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;
- (* Finalize the rendering *)
- 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:={};
- (* Calculate bounding box of path *)
- GetBBoxes(ctxt.path, state.userToWorldSpace, worldBBox, objectBBox);
- (* Calculate and set stroke width *)
- strokeWidth := state.userToWorldSpace.TransformLength(state.style.strokeWidth);
- Gfx.SetLineWidth(ctxt, SHORT(strokeWidth));
- (* Enlarge bounding box for thick strokes *)
- worldBBox.x := worldBBox.x - strokeWidth;
- worldBBox.y := worldBBox.y - strokeWidth;
- worldBBox.width := worldBBox.width + 2*strokeWidth;
- worldBBox.height := worldBBox.height + 2*strokeWidth;
- (* Set the stroke style *)
- 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;
- (* Set the fill style *)
- 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;
- (* Render the recorded path *)
- Gfx.Render(ctxt, mode)
- ELSE
- (* Can not calculate bounding box of unrecorded path -> assume it is as large as the target *)
- 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;
- (* Prepare use of a filter *)
- 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;
- (* Finalize use of a filter *)
- 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;
- (* Draw a cubic bezier curve *)
- 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;
- (* Draw a quadrativ bezier curve *)
- 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;
- (* Draw an arc *)
- 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
- (* Interpret out-of-range parameters *)
- 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;
- (* Calculate center *)
- 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;
- (* Calculate conjugate diameter pair *)
- 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;
- (* Draw arc *)
- 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;
- (* Render an image *)
- 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);
- (* Gfx cannot record calls to DrawImageAt *)
- RenderBegin(state, FALSE);
- Gfx.DrawImageAt(ctxt, SHORT(x), SHORT(y), image, filter);
- RenderEnd(state, FALSE);
- END RenderImage;
- (* Render a rectangle*)
- 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;
- (* Render a rounded rectangle *)
- 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;
- (* Render a circle *)
- 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;
- (* Render an ellipse *)
- 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;
- (* Render a line *)
- 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;
- (* Render an open polyline or a closed polygon *)
- PROCEDURE RenderPoly*(points: SVG.String; closed: BOOLEAN; state: SVG.State);
- VAR i: SIZE;
- 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;
- (* Render a general path element *)
- PROCEDURE RenderPath*(d: SVG.String; state: SVG.State);
- VAR i: SIZE;
- 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;
- (* Convert SVG.Color to Gfx.Color *)
- 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;
- (* Calculate the bounding box of a path in worldspace and in object space. Also transform the path to worldspace *)
- 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;
- (* Set the matrix to transform to world space. *)
- 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.
|