CPF/CPF/Svg/SVG.cs
2024-01-21 16:51:47 +08:00

568 lines
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using CPF.Drawing;
using System;
using System.Collections.Generic;
using System.Text;
using CPF.Styling;
using System.Xml;
using CPF.Controls;
using System.ComponentModel;
using System.Linq;
namespace CPF.Svg
{
/// <summary>
/// 支持显示SVG图形暂时不支持里面的滤镜动画图片引用文字等只能显示简单的图形
/// </summary>
[DefaultProperty(nameof(Source))]
public class SVG : UIElement
{
/// <summary>
/// 支持显示SVG图形暂时不支持里面的滤镜动画图片引用文字等只能显示简单的图形
/// </summary>
public SVG()
{
}
/// <summary>
/// 可以是svg文件路径或者直接是svg文档
/// </summary>
/// <param name="svgSource"></param>
public SVG(string svgSource)
{
Source = svgSource;
}
/// <summary>
/// svg文档
/// </summary>
[UIPropertyMetadata("", UIPropertyOptions.AffectsMeasure | UIPropertyOptions.AffectsRender)]
public string Data
{
get { return GetValue<string>(); }
private set { SetValue(value); }
}
/// <summary>
/// SVG源可以是路径、Url、或者svg文档字符串
/// </summary>
[Description("SVG源可以是路径、Url、或者svg文档字符串")]
[PropertyMetadata(null), CPF.Design.FileBrowser(".svg")]
public string Source
{
get { return GetValue<string>(); }
set { SetValue(value); }
}
[PropertyChanged(nameof(Source))]
void OnSourceChanged(object newValue, object oldValue, PropertyMetadataAttribute attribute)
{
var svg = newValue as string;
if (!string.IsNullOrWhiteSpace(svg))
{
svg = svg.Trim();
if (svg.StartsWith("<") && svg.EndsWith(">"))
{
Data = svg;
}
else
{
ResourceManager.GetText(svg, a =>
{
Invoke(() =>
{
Data = a;
});
});
}
}
}
[PropertyChanged(nameof(Data))]
void OnDataChanged(object newValue, object oldValue, PropertyMetadataAttribute attribute)
{
m_elements.Clear();
if (newValue is string str)
{
try
{
XmlDocument doc = new XmlDocument();
doc.XmlResolver = null;
doc.LoadXml(str);
XmlNode n = doc.GetElementsByTagName("svg")[0];
this.Parse(n);
//bool success = false;
//canvasSize = GetCanvas(true, false, out success);
//if (!success)
// canvasSize = GetCanvas(true, true, out success);
//if (!success)
// canvasSize = GetCanvas(false, false, out success);
//if (!success)
// canvasSize = GetCanvas(false, true, out success);
bool realSuccess = false;
realCanvasSize = GetCanvas(false, false, out realSuccess);
if (!realSuccess)
realCanvasSize = GetCanvas(false, true, out realSuccess);
bool viewBoxSuccess = false;
viewBoxCanvasSize = GetCanvas(true, false, out viewBoxSuccess);
if (!viewBoxSuccess)
viewBoxCanvasSize = GetCanvas(true, true, out viewBoxSuccess);
canvasSize = realSuccess ? viewBoxCanvasSize : realCanvasSize;
}
catch (Exception e)
{
throw new Exception("svg格式不对", e);
}
}
}
SizeField viewBoxCanvasSize;
SizeField realCanvasSize;
SizeField canvasSize;
SizeField GetCanvas(bool viewBox, bool px, out bool success)
{
try
{
string width = "0"; //宽
string height = "0"; //高
string pattern = viewBox ? (px ? "viewBox\\s?=\\s?\"(\\d+px)\\s(\\d+px)\\s(\\d+px)\\s(\\d+px)\"" : "viewBox\\s?=\\s?\"(\\d+)\\s(\\d+)\\s(\\d+)\\s(\\d+)\"") :
(px ? "width\\s?=\\s?\"(\\d+px)\"\\s+height\\s?=\\s?\"(\\d+px)" : "width\\s?=\\s?\"(\\d+)\"\\s+height\\s?=\\s?\"(\\d+)");
string str = Data;
System.Text.RegularExpressions.Regex regex = new System.Text.RegularExpressions.Regex(pattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
System.Text.RegularExpressions.Match m = regex.Match(str);
if (m.Success)
{
if (viewBox)
{
width = m.Groups[3].Value.Replace("px", ""); //宽
height = m.Groups[4].Value.Replace("px", ""); //高
}
else
{
width = m.Groups[1].Value.Replace("px", ""); //宽
height = m.Groups[2].Value.Replace("px", ""); //高
}
success = true;
}
else
success = false;
return new SizeField()
{
Width = float.Parse(width),
Height = float.Parse(height)
};
}
catch
{
success = false;
return new SizeField();
}
}
static PaintServerManager m_paintServers = new PaintServerManager();
// for "use" shape only.
Dictionary<string, SvgShape> m_shapes = new Dictionary<string, SvgShape>();
//internal void AddShape(string id, SvgShape shape)
//{
// //System.Diagnostics.Debug.Assert(id.Length > 0 && m_shapes.ContainsKey(id) == false);
// m_shapes[id] = shape;
//}
/// <summary>
/// 通过ID获取Shape
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public ISvgShape GetShape(string id)
{
//SvgShape shape = null;
m_shapes.TryGetValue(id, out var shape);
return shape;
}
internal static PaintServerManager PaintServers
{
get { return m_paintServers; }
}
List<SvgShape> m_elements = new List<SvgShape>();
/// <summary>
/// 解析到的元素
/// </summary>
[NotCpfProperty, Browsable(false)]
public IEnumerable<ISvgShape> Elements
{
get { return m_elements.Select(a => a as ISvgShape); }
}
void Parse(XmlNode node)
{
if (node == null || node.Name != "svg")
throw new FormatException("Not a valide SVG node");
foreach (XmlNode childnode in node.ChildNodes)
Group.AddToList(/*this, */m_elements, childnode, null);
var fill = Fill;
foreach (var item in m_elements)
{
if (item.GetFill().Color == null)
{
item.GetFill().FillBrush = fill;
}
}
}
/// <summary>
/// 图片缩放模式
/// </summary>
[Description("图片缩放模式")]
[UIPropertyMetadata(Stretch.None, UIPropertyOptions.AffectsMeasure)]
public Stretch Stretch
{
get { return GetValue<Stretch>(); }
set { SetValue(value); }
}
/// <summary>
/// 描述如何对内容应用缩放,并限制对已命名像素类型的缩放。
/// </summary>
[Description("描述如何对内容应用缩放,并限制对已命名像素类型的缩放。")]
[UIPropertyMetadata(StretchDirection.Both, UIPropertyOptions.AffectsMeasure)]
public StretchDirection StretchDirection
{
get { return GetValue<StretchDirection>(); }
set { SetValue(value); }
}
/// <summary>
/// 图形的默认填充
/// </summary>
[UIPropertyMetadata(typeof(ViewFill), "#000", UIPropertyOptions.AffectsRender)]
public ViewFill Fill
{
get { return GetValue<ViewFill>(); }
set { SetValue(value); }
}
[PropertyChanged(nameof(Fill))]
void OnFillChanged(object newValue, object oldValue, PropertyMetadataAttribute attribute)
{
var fill = newValue as ViewFill;
foreach (var item in m_elements)
{
if (item.GetFill().Color == null)
{
item.GetFill().FillBrush = fill;
}
}
}
Size naturalSize;
protected override Size MeasureOverride(in Size availableSize)
{
var b = base.MeasureOverride(availableSize);
var list = m_elements;
if (list.Count > 0)
{
if (canvasSize.Width.Value > 0 && canvasSize.Height.Value > 0)
{
naturalSize = new Size(canvasSize.Width.Value, canvasSize.Height.Value);
}
else
{
foreach (var item in list)
{
var s = Measure(item);
b = new Size(Math.Max(s.Width, b.Width), Math.Max(s.Height, b.Height));
}
naturalSize = new Size(b.Width, b.Height);
}
var size = availableSize;
if (realCanvasSize.Width.Value > 0 && realCanvasSize.Width.Value < viewBoxCanvasSize.Width.Value &&
realCanvasSize.Height.Value > 0 && realCanvasSize.Height.Value < viewBoxCanvasSize.Height.Value)
{
size = new Size(realCanvasSize.Width.Value, realCanvasSize.Height.Value);
}
var maxW = MaxWidth;
if (!maxW.IsAuto && maxW.Unit == Unit.Default)
{
size.Width = maxW.Value;
}
var maxH = MaxHeight;
if (!maxH.IsAuto && maxH.Unit == Unit.Default)
{
size.Height = maxH.Value;
}
Size scaleFactor = ComputeScaleFactor(size,
naturalSize,
this.Stretch, StretchDirection);
Size sizeField = new Size(naturalSize.Width * scaleFactor.Width, naturalSize.Height * scaleFactor.Height);
//Console.WriteLine("newSize:" + naturalSize.Width + "," + naturalSize.Height);
// Returns our minimum size & sets DesiredSize.
return sizeField;
}
return b;
}
internal static Size ComputeScaleFactor(Size availableSize,
Size contentSize,
Stretch stretch, StretchDirection stretchDirection)
{
// Compute scaling factors to use for axes
float scaleX = 1.0f;
float scaleY = 1.0f;
bool isConstrainedWidth = !float.IsPositiveInfinity(availableSize.Width);
bool isConstrainedHeight = !float.IsPositiveInfinity(availableSize.Height);
if ((stretch == Stretch.Uniform || stretch == Stretch.UniformToFill || stretch == Stretch.Fill)
&& (isConstrainedWidth || isConstrainedHeight))
{
// Compute scaling factors for both axes
scaleX = (FloatUtil.IsZero(contentSize.Width)) ? 0f : availableSize.Width / contentSize.Width;
scaleY = (FloatUtil.IsZero(contentSize.Height)) ? 0f : availableSize.Height / contentSize.Height;
if (!isConstrainedWidth) scaleX = scaleY;
else if (!isConstrainedHeight) scaleY = scaleX;
else
{
// If not preserving aspect ratio, then just apply transform to fit
switch (stretch)
{
case Stretch.Uniform: //Find minimum scale that we use for both axes
float minscale = scaleX < scaleY ? scaleX : scaleY;
scaleX = scaleY = minscale;
break;
case Stretch.UniformToFill: //Find maximum scale that we use for both axes
float maxscale = scaleX > scaleY ? scaleX : scaleY;
scaleX = scaleY = maxscale;
break;
case Stretch.Fill: //We already computed the fill scale factors above, so just use them
break;
}
}
//Apply stretch direction by bounding scales.
//In the uniform case, scaleX=scaleY, so this sort of clamping will maintain aspect ratio
//In the uniform fill case, we have the same result too.
//In the fill case, note that we change aspect ratio, but that is okay
switch (stretchDirection)
{
case StretchDirection.UpOnly:
if (scaleX < 1.0) scaleX = 1f;
if (scaleY < 1.0) scaleY = 1f;
break;
case StretchDirection.DownOnly:
if (scaleX > 1.0) scaleX = 1f;
if (scaleY > 1.0) scaleY = 1f;
break;
case StretchDirection.Both:
break;
default:
break;
}
}
//Return this as a size now
return new Size(scaleX, scaleY);
}
protected override void OnRender(DrawingContext dc)
{
if (m_elements.Count > 0)
{
var size = ActualSize;
var w = naturalSize.Width;
var h = naturalSize.Height;
var x = 0f;
var y = 0f;
var sw = 1f;
var sh = 1f;
switch (this.Stretch)
{
case Stretch.None:
//dc.DrawImage(img, new Rect((size.Width - w) / 2, (size.Height - h) / 2, w, h), new Rect(0, 0, w, h));
x = (size.Width - w) / 2;
y = (size.Height - h) / 2;
break;
case Stretch.Fill:
//dc.DrawImage(img, new Rect(0, 0, size.Width, size.Height), new Rect(0, 0, w, h));
sw = size.Width / w;
sh = size.Height / h;
break;
case Stretch.Uniform:
var ww = size.Width;
var hh = size.Height;
if (w / size.Width > h / size.Height)
{
hh = size.Width * h / w;
}
else
{
ww = size.Height * w / h;
}
x = (size.Width - ww) / 2;
y = (size.Height - hh) / 2;
sw = ww / w;
sh = hh / h;
//dc.DrawImage(img, new Rect((size.Width - ww) / 2, (size.Height - hh) / 2, ww, hh), new Rect(0, 0, w, h));
break;
case Stretch.UniformToFill:
var www = size.Width;
var hhh = size.Height;
if (w / size.Width < h / size.Height)
{
hhh = size.Width * h / w;
}
else
{
www = size.Height * w / h;
}
x = (size.Width - www) / 2;
y = (size.Height - hhh) / 2;
sw = www / w;
sh = hhh / h;
//dc.DrawImage(img, new Rect((size.Width - www) / 2, (size.Height - hhh) / 2, www, hhh), new Rect(0, 0, w, h));
break;
}
var old = dc.Transform;
var tran = old;
tran.TranslatePrepend(x, y);
tran.ScalePrepend(sw, sh);
dc.Transform = tran;
foreach (var item in m_elements)
{
DrawShape(dc, item);
}
dc.Transform = old;
}
}
private void DrawShape(DrawingContext dc, SvgShape item)
{
var old = dc.Transform;
if (item.Transform != null)
{
var tran = old;
tran.Prepend(item.Transform.Value);
dc.Transform = tran;
}
if (item is Group group)
{
foreach (SvgShape shape in group.Elements)
{
DrawShape(dc, shape);
}
}
else if (item is UseShape use)
{
var shape = GetShape(use.hRef);
if (shape != null)
{
DrawShape(dc, (SvgShape)shape);
}
}
else
{
var stroke = item.Stroke;
if (stroke != null && stroke.Width > 0 && stroke.StrokeBrush != null)
{
using (var brush = stroke.StrokeBrush.CreateBrush(item.Geometry.GetBounds(), Root.RenderScaling))
{
var stro = new Drawing.Stroke((float)stroke.Width, DashStyles.Solid);
if (stroke.StrokeArray != null)
{
stro.DashStyle = DashStyles.Custom;
stro.DashPattern = stroke.StrokeArray;
switch (stroke.LineJoin)
{
case Stroke.eLineJoin.miter:
stro.LineJoin = LineJoins.Miter;
break;
case Stroke.eLineJoin.round:
stro.LineJoin = LineJoins.Round;
break;
case Stroke.eLineJoin.bevel:
stro.LineJoin = LineJoins.Bevel;
break;
}
switch (stroke.LineCap)
{
case Stroke.eLineCap.butt:
stro.StrokeCap = CapStyles.Flat;
break;
case Stroke.eLineCap.round:
stro.StrokeCap = CapStyles.Round;
break;
case Stroke.eLineCap.square:
stro.StrokeCap = CapStyles.Square;
break;
}
}
dc.DrawPath(brush, stro, item.Geometry);
}
}
if (item.Fill != null && item.Fill.FillBrush != null)
{
if (item.Fill.Color is GradientColor svgFill)
{
using (var brush = item.Fill.FillBrush.CreateBrush(svgFill.GradientUnits == SVGTags.sGradientUserSpace ? new Rect(0, 0, naturalSize.Width, naturalSize.Height) : item.Geometry.GetBounds(), Root.RenderScaling))
{
dc.FillPath(brush, item.Geometry);
}
}
else
{
//if (item.Fill.Color != null)
{
using (var brush = item.Fill.FillBrush.CreateBrush(item.Geometry.GetBounds(), Root.RenderScaling))
{
dc.FillPath(brush, item.Geometry);
}
}
}
}
}
if (item.Transform != null)
{
dc.Transform = old;
}
}
private Size Measure(SvgShape item)
{
if (item is Group group)
{
Size b = new Size();
foreach (SvgShape shape in group.Elements)
{
var s = Measure(shape);
b = new Size(Math.Max(s.Width, b.Width), Math.Max(s.Height, b.Height));
}
return b;
}
else if (item is UseShape useShape)
{
var shape = GetShape(useShape.hRef);
if (shape != null)
{
return Measure((SvgShape)shape);
}
return new Size();
}
else
{
var bound = item.Geometry.GetBounds();
return new Size(bound.Right, bound.Bottom);
}
}
}
}