/// <summary> /// Common code for rendering a marker once the orientation angle has been calculated /// </summary> /// <param name="fAngle"></param> /// <param name="pRenderer"></param> /// <param name="pOwner"></param> /// <param name="pMarkerPoint"></param> private void RenderPart2(float fAngle, ISvgRenderer pRenderer, SvgVisualElement pOwner, PointF pMarkerPoint) { using (var pRenderPen = CreatePen(pOwner, pRenderer)) { using (var markerPath = GetClone(pOwner)) { using (var transMatrix = new Matrix()) { transMatrix.Translate(pMarkerPoint.X, pMarkerPoint.Y); if (Orient.IsAuto) { transMatrix.Rotate(fAngle); } else { transMatrix.Rotate(Orient.Angle); } switch (MarkerUnits) { case SvgMarkerUnits.strokeWidth: transMatrix.Translate(AdjustForViewBoxWidth(-RefX.ToDeviceValue(pRenderer, UnitRenderingType.Horizontal, this) * pOwner.StrokeWidth.ToDeviceValue(pRenderer, UnitRenderingType.Other, this)), AdjustForViewBoxHeight(-RefY.ToDeviceValue(pRenderer, UnitRenderingType.Vertical, this) * pOwner.StrokeWidth.ToDeviceValue(pRenderer, UnitRenderingType.Other, this))); break; case SvgMarkerUnits.userSpaceOnUse: transMatrix.Translate(-RefX.ToDeviceValue(pRenderer, UnitRenderingType.Horizontal, this), -RefY.ToDeviceValue(pRenderer, UnitRenderingType.Vertical, this)); break; } markerPath.Transform(transMatrix); if (pRenderPen != null) { pRenderer.DrawPath(pRenderPen, markerPath); } SvgPaintServer pFill = this.Children.First().Fill; SvgFillRule pFillRule = FillRule; // TODO: What do we use the fill rule for? float fOpacity = FillOpacity; if (pFill != null) { using (var pBrush = pFill.GetBrush(this, pRenderer, fOpacity)) { pRenderer.FillPath(pBrush, markerPath); } } } } } }
/// <summary> /// Renders the stroke of the <see cref="SvgVisualElement"/> to the specified <see cref="ISvgRenderer"/> /// </summary> /// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param> /// <param name="stroke"></param> protected internal virtual bool RenderStroke(ISvgRenderer renderer, SvgPaintServer stroke = null) { stroke = stroke ?? Stroke; if (stroke != null && stroke != SvgPaintServer.None) { var strokeWidth = StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this); renderer.DrawPath(Path(renderer), stroke.GetColor(this, renderer, FixOpacityValue(StrokeOpacity), true), strokeWidth); return(true); } return(false); }
/// <summary> /// Renders the stroke of the <see cref="SvgVisualElement"/> to the specified <see cref="ISvgRenderer"/> /// </summary> /// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param> protected internal virtual bool RenderStroke(ISvgRenderer renderer) { if (Stroke != null && Stroke != SvgPaintServer.None && StrokeWidth > 0f) { var strokeWidth = StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this); using (var brush = Stroke.GetBrush(this, renderer, Math.Min(Math.Max(StrokeOpacity, 0f), 1f), true)) { if (brush != null) { var path = Path(renderer); var bounds = path.GetBounds(); if (path.PointCount < 1) { return(false); } if (bounds.Width <= 0f && bounds.Height <= 0f) { switch (StrokeLineCap) { case SvgStrokeLineCap.Round: using (var capPath = new GraphicsPath()) { capPath.AddEllipse(path.PathPoints[0].X - strokeWidth / 2f, path.PathPoints[0].Y - strokeWidth / 2f, strokeWidth, strokeWidth); renderer.FillPath(brush, capPath); } break; case SvgStrokeLineCap.Square: using (var capPath = new GraphicsPath()) { capPath.AddRectangle(new RectangleF(path.PathPoints[0].X - strokeWidth / 2f, path.PathPoints[0].Y - strokeWidth / 2f, strokeWidth, strokeWidth)); renderer.FillPath(brush, capPath); } break; } } else { using (var pen = new Pen(brush, strokeWidth)) { if (StrokeDashArray != null && StrokeDashArray.Count > 0) { strokeWidth = strokeWidth <= 0 ? 1f : strokeWidth; if (StrokeDashArray.Count % 2 != 0) { // handle odd dash arrays by repeating them once StrokeDashArray.AddRange(StrokeDashArray); } var dashOffset = StrokeDashOffset; /* divide by stroke width - GDI uses stroke width as unit.*/ var dashPattern = StrokeDashArray.Select(u => ((u.ToDeviceValue(renderer, UnitRenderingType.Other, this) <= 0f) ? 1f : u.ToDeviceValue(renderer, UnitRenderingType.Other, this)) / strokeWidth).ToArray(); var length = dashPattern.Length; if (StrokeLineCap == SvgStrokeLineCap.Round) { // to handle round caps, we have to adapt the dash pattern // by increasing the dash length by the stroke width - GDI draws the rounded // edge inside the dash line, SVG draws it outside the line var pattern = new float[length]; var offset = 1; // the values are already normalized to dash width for (var i = 0; i < length; i++) { pattern[i] = dashPattern[i] + offset; if (pattern[i] <= 0f) { // overlapping caps - remove the gap for simplicity, see #508 if (i < length - 1) { // add the next dash segment to the current one dashPattern[i - 1] += dashPattern[i] + dashPattern[i + 1]; length -= 2; for (var k = i; k < length; k++) { dashPattern[k] = dashPattern[k + 2]; } // and handle the combined segment again i -= 2; } else if (i > 2) { // add the last dash segment to the first one // this will change the start point, so adapt the offset var dashLength = dashPattern[i - 1] + dashPattern[i]; pattern[0] += dashLength; length -= 2; dashOffset += dashLength * strokeWidth; } else { // we have only one dash with the gap too small - // do not use dash at all length = 0; break; } } offset *= -1; // increase dash length, decrease spaces } if (length > 0) { if (length < dashPattern.Length) { Array.Resize(ref pattern, length); } dashPattern = pattern; pen.DashCap = DashCap.Round; } } if (length > 0) { pen.DashPattern = dashPattern; if (dashOffset != 0f) { pen.DashOffset = ((dashOffset.ToDeviceValue(renderer, UnitRenderingType.Other, this) <= 0f) ? 1f : dashOffset.ToDeviceValue(renderer, UnitRenderingType.Other, this)) / strokeWidth; } } } switch (StrokeLineJoin) { case SvgStrokeLineJoin.Bevel: pen.LineJoin = LineJoin.Bevel; break; case SvgStrokeLineJoin.Round: pen.LineJoin = LineJoin.Round; break; default: pen.LineJoin = LineJoin.Miter; break; } pen.MiterLimit = StrokeMiterLimit; switch (StrokeLineCap) { case SvgStrokeLineCap.Round: pen.StartCap = LineCap.Round; pen.EndCap = LineCap.Round; break; case SvgStrokeLineCap.Square: pen.StartCap = LineCap.Square; pen.EndCap = LineCap.Square; break; } renderer.DrawPath(pen, path); return(true); } } } } } return(false); }
/// <summary> /// Renders the stroke of the <see cref="SvgVisualElement"/> to the specified <see cref="ISvgRenderer"/> /// </summary> /// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param> protected internal virtual bool RenderStroke(ISvgRenderer renderer) { if (this.Stroke != null && this.Stroke != SvgColourServer.None && this.StrokeWidth > 0) { float strokeWidth = this.StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this); using (var brush = this.Stroke.GetBrush(this, renderer, Math.Min(Math.Max(this.StrokeOpacity * this.Opacity, 0), 1), true)) { if (brush != null) { var path = this.Path(renderer); var bounds = path.GetBounds(); if (path.PointCount < 1) { return(false); } if (bounds.Width <= 0 && bounds.Height <= 0) { switch (this.StrokeLineCap) { case SvgStrokeLineCap.Round: using (var capPath = new GraphicsPath()) { capPath.AddEllipse(path.PathPoints[0].X - strokeWidth / 2, path.PathPoints[0].Y - strokeWidth / 2, strokeWidth, strokeWidth); renderer.FillPath(brush, capPath); } break; case SvgStrokeLineCap.Square: using (var capPath = new GraphicsPath()) { capPath.AddRectangle(new RectangleF(path.PathPoints[0].X - strokeWidth / 2, path.PathPoints[0].Y - strokeWidth / 2, strokeWidth, strokeWidth)); renderer.FillPath(brush, capPath); } break; } } else { using (var pen = new Pen(brush, strokeWidth)) { if (this.StrokeDashArray != null && this.StrokeDashArray.Count > 0) { /* divide by stroke width - GDI behaviour that I don't quite understand yet.*/ pen.DashPattern = this.StrokeDashArray.ConvertAll(u => ((u.ToDeviceValue(renderer, UnitRenderingType.Other, this) <= 0) ? 1 : u.ToDeviceValue(renderer, UnitRenderingType.Other, this)) / ((strokeWidth <= 0) ? 1 : strokeWidth)).ToArray(); } switch (this.StrokeLineJoin) { case SvgStrokeLineJoin.Bevel: pen.LineJoin = LineJoin.Bevel; break; case SvgStrokeLineJoin.Round: pen.LineJoin = LineJoin.Round; break; default: pen.LineJoin = LineJoin.Miter; break; } pen.MiterLimit = this.StrokeMiterLimit; switch (this.StrokeLineCap) { case SvgStrokeLineCap.Round: pen.StartCap = LineCap.Round; pen.EndCap = LineCap.Round; break; case SvgStrokeLineCap.Square: pen.StartCap = LineCap.Square; pen.EndCap = LineCap.Square; break; } renderer.DrawPath(pen, path); return(true); } } } } } return(false); }
protected override Brush CreateBrush(SvgVisualElement renderingElement, ISvgRenderer renderer, float opacity, bool forStroke) { // TODO: figure out how to do the brush transform in the presence of FocalRadius try { if (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) { renderer.SetBoundable(renderingElement); } // Calculate the path and transform it appropriately var center = new PointF(NormalizeUnit(CenterX).ToDeviceValue(renderer, UnitRenderingType.Horizontal, this), NormalizeUnit(CenterY).ToDeviceValue(renderer, UnitRenderingType.Vertical, this)); var focals = new PointF[] { new PointF(NormalizeUnit(FocalX).ToDeviceValue(renderer, UnitRenderingType.Horizontal, this), NormalizeUnit(FocalY).ToDeviceValue(renderer, UnitRenderingType.Vertical, this)) }; var specifiedRadius = NormalizeUnit(Radius).ToDeviceValue(renderer, UnitRenderingType.Other, this); var path = new GraphicsPath(); path.AddEllipse( center.X - specifiedRadius, center.Y - specifiedRadius, specifiedRadius * 2, specifiedRadius * 2 ); using (var transform = EffectiveGradientTransform) { var bounds = renderer.GetBoundable().Bounds; transform.Translate(bounds.X, bounds.Y, MatrixOrder.Prepend); if (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) { transform.Scale(bounds.Width, bounds.Height, MatrixOrder.Prepend); } path.Transform(transform); transform.TransformPoints(focals); } // Calculate any required scaling var scaleBounds = RectangleF.Inflate(renderingElement.Bounds, renderingElement.StrokeWidth, renderingElement.StrokeWidth); var scale = CalcScale(scaleBounds, path); // Not ideal, but this makes sure that the rest of the shape gets properly filled or drawn if (scale > 1.0f && SpreadMethod == SvgGradientSpreadMethod.Pad) { var stop = Stops.Last(); var origColor = stop.GetColor(renderingElement); var renderColor = System.Drawing.Color.FromArgb((int)Math.Round(opacity * stop.StopOpacity * 255), origColor); var origClip = renderer.GetClip(); try { using (var solidBrush = new SolidBrush(renderColor)) { var newClip = origClip.Clone(); newClip.Exclude(path); renderer.SetClip(newClip); var renderPath = (GraphicsPath)renderingElement.Path(renderer); if (forStroke) { using (var pen = new Pen(solidBrush, renderingElement.StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, renderingElement))) { renderer.DrawPath(pen, renderPath); } } else { renderer.FillPath(solidBrush, renderPath); } } } finally { renderer.SetClip(origClip); } } // Get the color blend and any tweak to the scaling var blend = CalculateColorBlend(renderer, opacity, scale, out scale); // Transform the path based on the scaling var gradBounds = path.GetBounds(); var transCenter = new PointF(gradBounds.Left + gradBounds.Width / 2, gradBounds.Top + gradBounds.Height / 2); using (var scaleMat = new Matrix()) { scaleMat.Translate(-1 * transCenter.X, -1 * transCenter.Y, MatrixOrder.Append); scaleMat.Scale(scale, scale, MatrixOrder.Append); scaleMat.Translate(transCenter.X, transCenter.Y, MatrixOrder.Append); path.Transform(scaleMat); } // calculate the brush var brush = new PathGradientBrush(path); brush.CenterPoint = focals[0]; brush.InterpolationColors = blend; return(brush); } finally { if (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) { renderer.PopBoundable(); } } }
/// <summary> /// Common code for rendering a marker once the orientation angle has been calculated /// </summary> /// <param name="fAngle"></param> /// <param name="pRenderer"></param> /// <param name="pOwner"></param> /// <param name="pMarkerPoint"></param> private void RenderPart2(float fAngle, ISvgRenderer pRenderer, SvgVisualElement pOwner, PointF pMarkerPoint) { using (var pRenderPen = CreatePen(pOwner, pRenderer)) { using (var markerPath = GetClone(pOwner, pRenderer)) { using (var transMatrix = new Matrix()) { transMatrix.Translate(pMarkerPoint.X, pMarkerPoint.Y); if (Orient.IsAuto) { transMatrix.Rotate(fAngle); } else { transMatrix.Rotate(Orient.Angle); } switch (MarkerUnits) { case SvgMarkerUnits.StrokeWidth: if (ViewBox.Width > 0 && ViewBox.Height > 0) { transMatrix.Scale(MarkerWidth, MarkerHeight); var strokeWidth = pOwner.StrokeWidth.ToDeviceValue(pRenderer, UnitRenderingType.Other, this); transMatrix.Translate(AdjustForViewBoxWidth(-RefX.ToDeviceValue(pRenderer, UnitRenderingType.Horizontal, this) * strokeWidth), AdjustForViewBoxHeight(-RefY.ToDeviceValue(pRenderer, UnitRenderingType.Vertical, this) * strokeWidth)); } else { // SvgMarkerUnits.UserSpaceOnUse // TODO: We know this isn't correct. // But use this until the TODOs from AdjustForViewBoxWidth and AdjustForViewBoxHeight are done. // MORE see Unit Test "MakerEndTest.TestArrowCodeCreation()" transMatrix.Translate(-RefX.ToDeviceValue(pRenderer, UnitRenderingType.Horizontal, this), -RefY.ToDeviceValue(pRenderer, UnitRenderingType.Vertical, this)); } break; case SvgMarkerUnits.UserSpaceOnUse: transMatrix.Translate(-RefX.ToDeviceValue(pRenderer, UnitRenderingType.Horizontal, this), -RefY.ToDeviceValue(pRenderer, UnitRenderingType.Vertical, this)); break; } if (MarkerElement != null && MarkerElement.Transforms != null) { using (var matrix = MarkerElement.Transforms.GetMatrix()) transMatrix.Multiply(matrix); } markerPath.Transform(transMatrix); if (pRenderPen != null) { pRenderer.DrawPath(pRenderPen, markerPath); } SvgPaintServer pFill = this.Children.First().Fill; SvgFillRule pFillRule = FillRule; // TODO: What do we use the fill rule for? if (pFill != null) { using (var pBrush = pFill.GetBrush(this, pRenderer, FixOpacityValue(FillOpacity))) { pRenderer.FillPath(pBrush, markerPath); } } } } } }
/// <summary> /// Renders the stroke of the <see cref="SvgVisualElement"/> to the specified <see cref="ISvgRenderer"/> /// </summary> /// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param> protected internal virtual bool RenderStroke(ISvgRenderer renderer) { if (this.Stroke != null && this.Stroke != SvgColourServer.None && this.StrokeWidth > 0) { var strokeWidth = this.StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this); using (var brush = this.Stroke.GetBrush(this, renderer, Math.Min(Math.Max(this.StrokeOpacity, 0), 1), true)) { if (brush != null) { var path = this.Path(renderer); var bounds = path.GetBounds(); if (path.PointCount < 1) { return(false); } if (bounds.Width <= 0 && bounds.Height <= 0) { switch (this.StrokeLineCap) { case SvgStrokeLineCap.Round: using (var capPath = new GraphicsPath()) { capPath.AddEllipse(path.PathPoints[0].X - strokeWidth / 2, path.PathPoints[0].Y - strokeWidth / 2, strokeWidth, strokeWidth); renderer.FillPath(brush, capPath); } break; case SvgStrokeLineCap.Square: using (var capPath = new GraphicsPath()) { capPath.AddRectangle(new RectangleF(path.PathPoints[0].X - strokeWidth / 2, path.PathPoints[0].Y - strokeWidth / 2, strokeWidth, strokeWidth)); renderer.FillPath(brush, capPath); } break; } } else { using (var pen = new Pen(brush, strokeWidth)) { if (this.StrokeDashArray != null && this.StrokeDashArray.Count > 0) { if (this.StrokeDashArray.Count % 2 != 0) { // handle odd dash arrays by repeating them once this.StrokeDashArray.AddRange(this.StrokeDashArray); } /* divide by stroke width - GDI behaviour that I don't quite understand yet.*/ pen.DashPattern = this.StrokeDashArray.Select(u => ((u.ToDeviceValue(renderer, UnitRenderingType.Other, this) <= 0) ? 1 : u.ToDeviceValue(renderer, UnitRenderingType.Other, this)) / ((strokeWidth <= 0) ? 1 : strokeWidth)).ToArray(); if (this.StrokeLineCap == SvgStrokeLineCap.Round) { // to handle round caps, we have to adapt the dash pattern // by increasing the dash length by the stroke width - GDI draws the rounded // edge inside the dash line, SVG draws it outside the line var pattern = new float[pen.DashPattern.Length]; int offset = 1; // the values are already normalized to dash width for (int i = 0; i < pen.DashPattern.Length; i++) { pattern[i] = pen.DashPattern[i] + offset; offset *= -1; // increase dash length, decrease spaces } pen.DashPattern = pattern; pen.DashCap = DashCap.Round; } if (this.StrokeDashOffset != null && this.StrokeDashOffset.Value != 0) { pen.DashOffset = ((this.StrokeDashOffset.ToDeviceValue(renderer, UnitRenderingType.Other, this) <= 0) ? 1 : this.StrokeDashOffset.ToDeviceValue(renderer, UnitRenderingType.Other, this)) / ((strokeWidth <= 0) ? 1 : strokeWidth); } } switch (this.StrokeLineJoin) { case SvgStrokeLineJoin.Bevel: pen.LineJoin = LineJoin.Bevel; break; case SvgStrokeLineJoin.Round: pen.LineJoin = LineJoin.Round; break; default: pen.LineJoin = LineJoin.Miter; break; } pen.MiterLimit = this.StrokeMiterLimit; switch (this.StrokeLineCap) { case SvgStrokeLineCap.Round: pen.StartCap = LineCap.Round; pen.EndCap = LineCap.Round; break; case SvgStrokeLineCap.Square: pen.StartCap = LineCap.Square; pen.EndCap = LineCap.Square; break; } renderer.DrawPath(pen, path); return(true); } } } } } return(false); }
public void DrawPath(Pen pen, GraphicsPath path) { _svgRendererImplementation.DrawPath(pen, path); }