/// <summary> /// Sets the path on this element and all child elements. Uses the state /// object to track the state of the drawing /// </summary> /// <param name="state">State of the drawing operation</param> /// <param name="doMeasurements">If true, calculate and apply text length adjustments.</param> private void SetPath(TextDrawingState state, bool doMeasurements) { TextDrawingState origState = null; bool alignOnBaseline = state.BaselinePath != null && (this.TextAnchor == SvgTextAnchor.Middle || this.TextAnchor == SvgTextAnchor.End); if (doMeasurements) { if (this.TextLength != SvgUnit.None) { origState = state.Clone(); } else if (alignOnBaseline) { origState = state.Clone(); state.BaselinePath = null; } } foreach (var node in GetContentNodes()) { SvgTextBase textNode = node as SvgTextBase; if (textNode == null) { if (!string.IsNullOrEmpty(node.Content)) { state.DrawString(PrepareText(node.Content)); } } else { TextDrawingState newState = new TextDrawingState(state, textNode); textNode.SetPath(newState); state.NumChars += newState.NumChars; state.Current = newState.Current; } } var path = state.GetPath() ?? new GraphicsPath(); // Apply any text length adjustments if (doMeasurements) { if (this.TextLength != SvgUnit.None) { var specLength = this.TextLength.ToDeviceValue(state.Renderer, UnitRenderingType.Horizontal, this); var actLength = state.TextBounds.Width; var diff = (actLength - specLength); if (Math.Abs(diff) > 1.5) { if (this.LengthAdjust == SvgTextLengthAdjust.Spacing) { if (this.X.Count < 2) { var numCharDiff = state.NumChars - origState.NumChars - 1; if (numCharDiff != 0) { origState.LetterSpacingAdjust = -1 * diff / numCharDiff; SetPath(origState, false); return; } } } else { using (var matrix = new Matrix()) { matrix.Translate(-1 * state.TextBounds.X, 0, MatrixOrder.Append); matrix.Scale(specLength / actLength, 1, MatrixOrder.Append); matrix.Translate(state.TextBounds.X, 0, MatrixOrder.Append); path.Transform(matrix); } } } } else if (alignOnBaseline) { var bounds = path.GetBounds(); if (this.TextAnchor == SvgTextAnchor.Middle) { origState.StartOffsetAdjust = -1 * bounds.Width / 2; } else { origState.StartOffsetAdjust = -1 * bounds.Width; } SetPath(origState, false); return; } } _path = path; this.IsPathDirty = false; }
/// <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); } } } } } }
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(); } } }
private GraphicsPath GetPath(ISvgRenderer renderer, string text, IList <RectangleF> ranges, bool measureSpaces) { EnsureDictionaries(); RectangleF bounds; SvgGlyph glyph; SvgKern kern; GraphicsPath path; SvgGlyph prevGlyph = null; Matrix scaleMatrix; float xPos = 0; var ascent = Ascent(renderer); var result = new GraphicsPath(); if (string.IsNullOrEmpty(text)) { return(result); } for (int i = 0; i < text.Length; i++) { if (!_glyphs.TryGetValue(text.Substring(i, 1), out glyph)) { glyph = _font.Descendants().OfType <SvgMissingGlyph>().First(); } if (prevGlyph != null && _kerning.TryGetValue(prevGlyph.GlyphName + "|" + glyph.GlyphName, out kern)) { xPos -= kern.Kerning * _emScale; } path = (GraphicsPath)glyph.Path(renderer).Clone(); scaleMatrix = new Matrix(); scaleMatrix.Scale(_emScale, -1 * _emScale, MatrixOrder.Append); scaleMatrix.Translate(xPos, ascent, MatrixOrder.Append); path.Transform(scaleMatrix); scaleMatrix.Dispose(); bounds = path.GetBounds(); if (ranges != null) { if (measureSpaces && bounds == RectangleF.Empty) { ranges.Add(new RectangleF(xPos, 0, glyph.HorizAdvX * _emScale, ascent)); } else { ranges.Add(bounds); } } if (path.PointCount > 0) { result.AddPath(path, false); } xPos += glyph.HorizAdvX * _emScale; prevGlyph = glyph; } return(result); }
/// <summary> /// Gets a <see cref="Brush"/> representing the current paint server. /// </summary> /// <param name="renderingElement">The owner <see cref="SvgVisualElement"/>.</param> /// <param name="opacity">The opacity of the brush.</param> public override Brush GetBrush(SvgVisualElement renderingElement, ISvgRenderer renderer, float opacity, bool forStroke = false) { var chain = new List <SvgPatternServer>(); var curr = this; while (curr != null) { chain.Add(curr); curr = SvgDeferredPaintServer.TryGet <SvgPatternServer>(curr._inheritGradient, renderingElement); } var childElem = chain.Where((p) => p.Children != null && p.Children.Count > 0).FirstOrDefault(); if (childElem == null) { return(null); } var widthElem = chain.Where((p) => p.Width != null && p.Width != SvgUnit.None).FirstOrDefault(); var heightElem = chain.Where((p) => p.Height != null && p.Height != SvgUnit.None).FirstOrDefault(); if (widthElem == null && heightElem == null) { return(null); } var viewBoxElem = chain.Where((p) => p.ViewBox != null && p.ViewBox != SvgViewBox.Empty).FirstOrDefault(); var viewBox = viewBoxElem == null ? SvgViewBox.Empty : viewBoxElem.ViewBox; var xElem = chain.Where((p) => p.X != null && p.X != SvgUnit.None).FirstOrDefault(); var yElem = chain.Where((p) => p.Y != null && p.Y != SvgUnit.None).FirstOrDefault(); var xUnit = xElem == null ? SvgUnit.Empty : xElem.X; var yUnit = yElem == null ? SvgUnit.Empty : yElem.Y; var patternUnitElem = chain.Where((p) => p.PatternUnits != SvgCoordinateUnits.Inherit).FirstOrDefault(); var patternUnits = (patternUnitElem == null ? SvgCoordinateUnits.ObjectBoundingBox : patternUnitElem.PatternUnits); var patternContentUnitElem = chain.Where((p) => p.PatternContentUnits != SvgCoordinateUnits.Inherit).FirstOrDefault(); var patternContentUnits = (patternContentUnitElem == null ? SvgCoordinateUnits.UserSpaceOnUse : patternContentUnitElem.PatternContentUnits); try { if (patternUnits == SvgCoordinateUnits.ObjectBoundingBox) { renderer.SetBoundable(renderingElement); } using (var patternMatrix = new Matrix()) { var bounds = renderer.GetBoundable().Bounds; var xScale = (patternUnits == SvgCoordinateUnits.ObjectBoundingBox ? bounds.Width : 1); var yScale = (patternUnits == SvgCoordinateUnits.ObjectBoundingBox ? bounds.Height : 1); float x = xScale * NormalizeUnit(xUnit).ToDeviceValue(renderer, UnitRenderingType.Horizontal, this); float y = yScale * NormalizeUnit(yUnit).ToDeviceValue(renderer, UnitRenderingType.Vertical, this); float width = xScale * NormalizeUnit(widthElem.Width).ToDeviceValue(renderer, UnitRenderingType.Horizontal, this); float height = yScale * NormalizeUnit(heightElem.Height).ToDeviceValue(renderer, UnitRenderingType.Vertical, this); // Apply a scale if needed patternMatrix.Scale((patternContentUnits == SvgCoordinateUnits.ObjectBoundingBox ? bounds.Width : 1) * (viewBox.Width > 0 ? width / viewBox.Width : 1), (patternContentUnits == SvgCoordinateUnits.ObjectBoundingBox ? bounds.Height : 1) * (viewBox.Height > 0 ? height / viewBox.Height : 1), MatrixOrder.Prepend); Bitmap image = new Bitmap((int)width, (int)height); using (var iRenderer = SvgRenderer.FromImage(image)) { iRenderer.SetBoundable((_patternContentUnits == SvgCoordinateUnits.ObjectBoundingBox) ? new GenericBoundable(0, 0, width, height) : renderer.GetBoundable()); iRenderer.Transform = patternMatrix; iRenderer.SmoothingMode = SmoothingMode.AntiAlias; iRenderer.SetClip(new Region(new RectangleF(0, 0, viewBox.Width > 0 ? viewBox.Width : width, viewBox.Height > 0 ? viewBox.Height : height))); foreach (SvgElement child in childElem.Children) { child.RenderElement(iRenderer); } } TextureBrush textureBrush = new TextureBrush(image); var brushTransform = EffectivePatternTransform.Clone(); brushTransform.Translate(x, y, MatrixOrder.Append); textureBrush.Transform = brushTransform; return(textureBrush); } } finally { if (this.PatternUnits == SvgCoordinateUnits.ObjectBoundingBox) { renderer.PopBoundable(); } } }
public void ScaleTransform(float sx, float sy, MatrixOrder order = MatrixOrder.Append) { _innerMatrix.Scale(sx, sy, order); }
/// <summary> /// Gets a <see cref="Brush"/> representing the current paint server. /// </summary> /// <param name="renderingElement">The owner <see cref="SvgVisualElement"/>.</param> /// <param name="opacity">The opacity of the brush.</param> public override Brush GetBrush(SvgVisualElement renderingElement, SvgRenderer renderer, float opacity) { // If there aren't any children, return null if (this.Children.Count == 0) { return(null); } // Can't render if there are no dimensions if (this._width.Value == 0.0f || this._height.Value == 0.0f) { return(null); } try { if (this.PatternUnits == SvgCoordinateUnits.ObjectBoundingBox) { renderer.Boundable(renderingElement); } float width = this._width.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this); float height = this._height.ToDeviceValue(renderer, UnitRenderingType.Vertical, this); Matrix patternMatrix = new Matrix(); // Apply a translate if needed if (this._x.Value > 0.0f || this._y.Value > 0.0f) { float x = this._x.ToDeviceValue(renderer, UnitRenderingType.HorizontalOffset, this); float y = this._y.ToDeviceValue(renderer, UnitRenderingType.VerticalOffset, this); patternMatrix.Translate(x + -1.0f, y + -1.0f); } else { patternMatrix.Translate(-1, -1); } if (this.ViewBox.Height > 0 || this.ViewBox.Width > 0) { patternMatrix.Scale(this.Width.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this) / this.ViewBox.Width, this.Height.ToDeviceValue(renderer, UnitRenderingType.Vertical, this) / this.ViewBox.Height); } Bitmap image = new Bitmap((int)width, (int)height); using (SvgRenderer iRenderer = SvgRenderer.FromImage(image)) { iRenderer.Boundable((_patternContentUnits == SvgCoordinateUnits.ObjectBoundingBox) ? new GenericBoundable(0, 0, width, height) : renderer.Boundable()); iRenderer.Transform = patternMatrix; iRenderer.CompositingQuality = CompositingQuality.HighQuality; iRenderer.SmoothingMode = SmoothingMode.AntiAlias; iRenderer.PixelOffsetMode = PixelOffsetMode.Half; foreach (SvgElement child in this.Children) { child.RenderElement(iRenderer); } iRenderer.Save(); } image.Save(string.Format(@"C:\test{0:D3}.png", imgNumber++)); TextureBrush textureBrush = new TextureBrush(image); return(textureBrush); } finally { if (this.PatternUnits == SvgCoordinateUnits.ObjectBoundingBox) { renderer.PopBoundable(); } } }
/// <summary> /// Sets the path on this element and all child elements. Uses the state /// object to track the state of the drawing /// </summary> /// <param name="state">State of the drawing operation</param> private void SetPath(TextDrawingState state, bool doMeasurements) { TextDrawingState origState = null; bool alignOnBaseline = state.BaselinePath != null && (this.TextAnchor == SvgTextAnchor.Middle || this.TextAnchor == SvgTextAnchor.End); if (doMeasurements) { if (this.TextLength != SvgUnit.None) { origState = state.Clone(); } else if (alignOnBaseline) { origState = state.Clone(); state.BaselinePath = null; } } foreach (var node in GetContentNodes()) { SvgTextBase textNode = node as SvgTextBase; if (textNode == null) { if (!string.IsNullOrEmpty(node.Content)) { state.DrawString(PrepareText(node.Content)); } } else { TextDrawingState newState = new TextDrawingState(state, textNode); textNode.SetPath(newState); state.NumChars += newState.NumChars; state.Current = newState.Current; } } var path = state.GetPath() ?? new GraphicsPath(); // Apply any text length adjustments if (doMeasurements) { if (this.TextLength != SvgUnit.None) { var bounds = path.GetBounds(); var specLength = this.TextLength.ToDeviceValue(state.Renderer, UnitRenderingType.Horizontal, this); var actLength = bounds.Width; var diff = (actLength - specLength); if (Math.Abs(diff) > 1.5) { if (this.LengthAdjust == SvgTextLengthAdjust.spacing) { origState.LetterSpacingAdjust = -1 * diff / (state.NumChars - origState.NumChars - 1); SetPath(origState, false); return; } else { using (var matrix = new Matrix()) { matrix.Translate(-1 * bounds.X, 0, MatrixOrder.Append); matrix.Scale(specLength / actLength, 1, MatrixOrder.Append); matrix.Translate(bounds.X, 0, MatrixOrder.Append); path.Transform(matrix); } } } } else if (alignOnBaseline) { var bounds = path.GetBounds(); if (this.TextAnchor == SvgTextAnchor.Middle) { origState.StartOffsetAdjust = -1 * bounds.Width / 2; } else { origState.StartOffsetAdjust = -1 * bounds.Width; } SetPath(origState, false); return; } } _path = path; this.IsPathDirty = false; // If we have child tspans we need to correct the position to account for text-anchor var childTSpans = GetContentNodes().OfType <SvgTextSpan>().ToList(); if (childTSpans.Count > 1) // If there's only one tspan we'll allow it to adjust itself in FlushPath() { if (!(TextAnchor == SvgTextAnchor.Middle || TextAnchor == SvgTextAnchor.End)) { return; } // BUG: If we have rotations things break // Skip them for now if (this.Transforms.OfType <SvgRotate>().Any()) { return; } // Need to split tspans by line // An element with an x attribute set seems to start a new render line var lines = new List <List <SvgTextSpan> >(); foreach (var tspan in childTSpans) { if (lines.Count == 0 || tspan.X.Any()) { lines.Add(new List <SvgTextSpan>()); } lines.Last().Add(tspan); } foreach (var line in lines) { // Find total width of line var totalLineWidth = line.Sum(o => o.Bounds.Width); // BUG: If we have tspans with spaces in the source between them (like '<a></a> <b></b>'), they should be // rendered with that space, but there's a bug elsewhere which does this wrong and ignores it. // I'm also ignoring it here, because I don't know how to fix this correctly. float xOffset = 0; switch (TextAnchor) { case SvgTextAnchor.Middle: xOffset = -totalLineWidth / 2; break; case SvgTextAnchor.End: xOffset = -totalLineWidth; break; default: throw new InvalidOperationException("Shouldn't have got here with TextAnchor " + TextAnchor); } using (var matrix = new Matrix()) { matrix.Translate(xOffset, 0); foreach (var node in line) { node._path.Transform(matrix); } } } // The path for the parent text node renders a black copy of the text for some reason _path.Reset(); } }