using InABox.Core; using netDxf; using netDxf.Tables; using Svg; using Svg.Transforms; using Syncfusion.Pdf; using Syncfusion.Pdf.Graphics; using Syncfusion.Pdf.Parsing; using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; using System.Numerics; using System.Text; using System.Threading.Tasks; using FontStyle = System.Drawing.FontStyle; using Vector2 = System.Numerics.Vector2; using Vector3 = System.Numerics.Vector3; using Vector4 = System.Numerics.Vector4; using SPdfGraphics = Syncfusion.Pdf.Graphics.PdfGraphics; using Syncfusion.Pdf.Tables; using netDxf.Entities; namespace InABox.Dxf; public enum TextAlignment { Left, Center, Right, Fit, Justify } public enum TextLineAlignment { Top, Center, Bottom, Baseline } public class TextFormat { public TextAlignment Alignment { get; set; } = TextAlignment.Left; public TextLineAlignment LineAlignment { get; set; } = TextLineAlignment.Top; } public class TransformData { public DxfData Data { get; set; } private Stack MatrixStack = new(); public Matrix4x4 Transform { get; private set; } = Matrix4x4.Identity; public void PushTransform() { MatrixStack.Push(Transform); } public void TransformBy(Matrix4x4 matrix) { Transform = matrix * Transform; } protected virtual void UpdateTransform() { } protected Matrix ProjectMatrix(Matrix4x4 matrix) { var elMatrix = new Matrix3x2(); elMatrix.M11 = (float)matrix.M11; elMatrix.M12 = (float)matrix.M12; elMatrix.M21 = (float)matrix.M21; elMatrix.M22 = (float)matrix.M22; elMatrix.M31 = (float)matrix.M41; elMatrix.M32 = (float)matrix.M42; var newMatrix = new Matrix(); newMatrix.MatrixElements = elMatrix; return newMatrix; } public void PopTransform() { Transform = MatrixStack.Pop(); UpdateTransform(); } public static Matrix4x4 ArbitraryAxisMatrix(Vector3 zAxis) { if (zAxis.Equals(Vector3.UnitZ)) { return Matrix4x4.Identity; } var unitY = Vector3.UnitY; var unitZ = Vector3.UnitZ; var v = ((!(Math.Abs(zAxis.X) < 1.0 / 64.0) || !(Math.Abs(zAxis.Y) < 1.0 / 64.0)) ? Vector3.Cross(unitZ, zAxis) : Vector3.Cross(unitY, zAxis)); v = Vector3.Normalize(v); var vector = Vector3.Cross(zAxis, v); vector = Vector3.Normalize(vector); return new Matrix4x4(v.X, vector.X, zAxis.X, 0, v.Y, vector.Y, zAxis.Y, 0, v.Z, vector.Z, zAxis.Z, 0, 0, 0, 0, 1); } public void ArbitraryAxis(netDxf.Vector3 zAxis) { Transform = ArbitraryAxisMatrix(new((float)zAxis.X, (float)zAxis.Y, (float)zAxis.Z)) * Transform; UpdateTransform(); } public void ArbitraryAxis(Vector3 zAxis) { Transform = ArbitraryAxisMatrix(zAxis) * Transform; UpdateTransform(); } public void Translate(float x, float y) { Transform = Transform.Translate(x, y, 0); UpdateTransform(); } public void Translate(PointF point) { Transform = Transform.Translate(point.X, point.Y, 0); UpdateTransform(); } public void Rotate(float angle) { Transform = Transform.Rotate(0, 0, 1, angle); UpdateTransform(); } public void Scale(float scale) { Scale(scale, scale); } public void Scale(float scaleX, float scaleY) { Transform = Transform.Scale(scaleX, scaleY, 1); UpdateTransform(); } public PointF TransformPoint(float x, float y) { var nVec = Vector4.Transform(new Vector4(x, y, 0, 1), Transform); return new(nVec.X, nVec.Y); } public PointF TransformPoint(PointF vec) { var nVec = Vector4.Transform(new Vector4(vec.X, vec.Y, 0, 1), Transform); return new(nVec.X, nVec.Y); } public PointF TransformPoint(netDxf.Vector2 vec) { var nVec = Vector4.Transform(new Vector4((float)vec.X, (float)vec.Y, 0, 1), Transform); return new(nVec.X, nVec.Y); } public PointF TransformPoint(netDxf.Vector3 vec) { var nVec = Vector4.Transform(new Vector4((float)vec.X, (float)vec.Y, (float)vec.Z, 1), Transform); return new(nVec.X, nVec.Y); } public PointF TransformVec(PointF vec) { var nVec = Vector4.Transform(new Vector4(vec.X, vec.Y, 0, 0), Transform); return new(nVec.X, nVec.Y); } public Vector2 TransformVec(Vector2 vec) { var nVec = Vector4.Transform(new Vector4(vec.X, vec.Y, 0, 0), Transform); return new(nVec.X, nVec.Y); } public float ScaleFactor() { return (TransformVec(new Vector2(1, 0))).Length(); } public static PointF ConvertPoint(netDxf.Vector2 vec) { return new PointF((float)vec.X, (float)vec.Y); } public static PointF ConvertPoint(netDxf.Vector3 vec) { return new PointF((float)vec.X, (float)vec.Y); } } public class DrawData : TransformData { private IGraphics _graphics; public IGraphics Graphics { get => _graphics; set { _graphics = value; _graphics.DrawData = this; } } private Stack _blockColours = new(); public Color BlockColour { get; set; } = Color.Black; public void PushBlockColour(AciColor colour, EntityObject? obj) { _blockColours.Push(BlockColour); BlockColour = ResolveColour(colour, obj); } public void PushBlockColour(Color colour) { _blockColours.Push(BlockColour); BlockColour = colour; } public void PopBlockColour() { BlockColour = _blockColours.Pop(); } public Color ResolveColour(AciColor colour, EntityObject? obj) { if (colour.IsByBlock) { return _blockColours.Count > 0 ? BlockColour : (obj?.Layer is null ? Color.Black : ResolveColour(obj.Layer.Color, null)); } else if (colour.IsByLayer) { return obj?.Layer is null ? Color.Black : ResolveColour(obj.Layer.Color, null); } if(colour.Index == 7) { return Color.Black; } else { return colour.ToColor(); } } protected override void UpdateTransform() { base.UpdateTransform(); Graphics.SetTransform(ProjectMatrix(Transform)); } } public interface IGraphics { DrawData DrawData { get; set; } void SetTransform(Matrix transform); void DrawLine(Color color, float thickness, params PointF[] points); void DrawPoint(Color color, float thickness, PointF point); void FillPolygon(Color color, params PointF[] points); /// /// Set the current font to be the default font at the given . /// /// Size of the new font. void SetFont(float fontSize, FontStyle fontStyle = FontStyle.Regular); void SetFont(string fontName, float fontSize, FontStyle fontStyle = FontStyle.Regular); void DrawText(string text, Color color, PointF position, float rotation, TextFormat format); void DrawText(string text, Color color, PointF position, float rotation, float width, PointF scale); void DrawText(string text, Color color, PointF position, float rotation, SizeF size, PointF scale); void Clear(Color color); void Finish(); } public class GdiGraphics : IGraphics { public Graphics Graphics { get; set; } private Font Font { get; set; } public DrawData DrawData { get; set; } public GdiGraphics(Graphics graphics) { Graphics = graphics; } // public float ConvertThickness(float thickness) // { // return thickness == 0 ? 1f / ScaleFactor() : thickness; // } public void DrawLine(Color color, float thickness, params PointF[] points) { if(points.Length == 2) { Graphics.DrawLine(new Pen(color, thickness), points[0], points[1]); } else { Graphics.DrawLines(new Pen(color, thickness), points); } } public void DrawPoint(Color color, float thickness, PointF point) { Graphics.FillEllipse(new SolidBrush(color), point.X - thickness / 2, point.Y - thickness / 2, point.X + thickness / 2, point.Y + thickness / 2); } private void TransformTextVertical(string text, Font font, PointF position, float rotation, TextFormat format) { DrawData.Translate(position); DrawData.Rotate(rotation); DrawData.Scale(1, -1); var ascentRatio = (float)font.FontFamily.GetCellAscent(font.Style) / font.FontFamily.GetLineSpacing(font.Style); var size = Graphics.MeasureString(text, font, new PointF(), StringFormat.GenericTypographic); var scaleFactor = ascentRatio * font.Height / size.Height; DrawData.Scale(scaleFactor, scaleFactor); switch (format.LineAlignment) { case TextLineAlignment.Center: DrawData.Translate(new PointF(0, -size.Height / 2)); break; case TextLineAlignment.Bottom: DrawData.Translate(new PointF(0, -size.Height)); break; case TextLineAlignment.Baseline: var baseline = ascentRatio * font.Height; DrawData.Translate(new PointF(0, -baseline)); break; case TextLineAlignment.Top: baseline = ascentRatio * font.Height; var lineSpace = font.FontFamily.GetLineSpacing(font.Style); var ratio = font.GetHeight(Graphics) / lineSpace; DrawData.Translate(new PointF(0, -baseline + font.FontFamily.GetCellAscent(font.Style) * ratio)); break; } } public void DrawText(string text, Color color, PointF position, float rotation, TextFormat format) { if (text.IsNullOrWhiteSpace()) { return; } DrawData.PushTransform(); TransformTextVertical(text, Font, position, rotation, format); var pos = 0; foreach(var line in text.Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) { DrawData.PushTransform(); DrawData.Translate(0, pos * Font.Height); var size = Graphics.MeasureString(line, Font); switch (format.Alignment) { case TextAlignment.Left: break; case TextAlignment.Center: DrawData.Translate(new PointF(-(float)size.Width / 2, 0)); break; case TextAlignment.Right: DrawData.Translate(new PointF(-(float)size.Width, 0)); break; } Graphics.DrawString(line, Font, new SolidBrush(color), new PointF(0, 0), StringFormat.GenericTypographic); DrawData.PopTransform(); ++pos; } DrawData.PopTransform(); } public void DrawText(string text, Color color, PointF position, float rotation, float width, PointF scale) { var size = Graphics.MeasureString(text, Font, new PointF(), StringFormat.GenericTypographic); DrawText(text, color, position, rotation, new SizeF(width, size.Height * width / size.Width), scale); } public void DrawText(string text, Color color, PointF position, float rotation, SizeF size, PointF scale) { DrawData.PushTransform(); DrawData.Translate(position); DrawData.Rotate(rotation); DrawData.Scale(1, -1); var (width, height) = (size.Width, size.Height); DrawData.Translate(width / 2, height / 2); DrawData.Scale(scale.X, scale.Y); DrawData.Translate(-width / 2, -height / 2); DrawData.Scale(width / size.Width, height / size.Height); Graphics.DrawString(text, Font, new SolidBrush(color), new PointF(0, 0), StringFormat.GenericTypographic); DrawData.PopTransform(); } public void FillPolygon(Color color, params PointF[] points) { Graphics.FillPolygon(new SolidBrush(color), points); } public void SetFont(string fontName, float fontSize, FontStyle fontStyle) { var fontFamily = new FontFamily(fontName); var font = new Font(fontFamily, fontSize, fontStyle); Font = font; } public void SetFont(float fontSize, FontStyle fontStyle) { var fontFamily = SystemFonts.DefaultFont.FontFamily; var font = new Font(fontFamily, fontSize, fontStyle); Font = font; } public void SetTransform(Matrix transform) { Graphics.Transform = transform; } public void Clear(Color color) { Graphics.Clear(color); } public void Finish() { } } public class PdfGraphics : IGraphics { public DrawData DrawData { get; set; } private SPdfGraphics Graphics { get; set; } private Matrix _transform = new(); private PdfFont _font = new PdfStandardFont(PdfFontFamily.Helvetica, 12, PdfFontStyle.Regular); private float _ascentRatio = 0; public PdfGraphics(SPdfGraphics graphics) { Graphics = graphics; } public void Clear(Color color) { } public void SetTransform(Matrix transform) { _transform = transform; } public void DrawLine(Color color, float thickness, params PointF[] points) { _transform.TransformPoints(points); var pen = new PdfPen(color, thickness); for(int i = 0; i < points.Length - 1; ++i) { Graphics.DrawLine(pen, points[i], points[i + 1]); } } public void DrawPoint(Color color, float thickness, PointF point) { var transformed = _transform.Transform(point); var brush = new PdfSolidBrush(color); Graphics.DrawEllipse(brush, transformed.X - thickness / 2, transformed.Y - thickness / 2, transformed.X + thickness / 2, transformed.Y + thickness / 2); } public void SetFont(float fontSize, FontStyle fontStyle = FontStyle.Regular) { var font = new Font(SystemFonts.DefaultFont.FontFamily, fontSize); _ascentRatio = (float)font.FontFamily.GetCellAscent(font.Style) / (float)font.FontFamily.GetLineSpacing(font.Style); _font = new PdfTrueTypeFont(font); } public void SetFont(string fontName, float fontSize, FontStyle fontStyle = FontStyle.Regular) { var font = new Font(fontName, fontSize); _ascentRatio = (float)font.FontFamily.GetCellAscent(font.Style) / (float)font.FontFamily.GetLineSpacing(font.Style); _font = new PdfTrueTypeFont(new Font(fontName, fontSize)); } public void DrawText(string text, Color color, PointF position, float rotation, TextFormat format) { var fmt = new PdfStringFormat(); fmt.Alignment = format.Alignment switch { TextAlignment.Center => PdfTextAlignment.Center, TextAlignment.Right => PdfTextAlignment.Right, TextAlignment.Justify => PdfTextAlignment.Justify, _ => PdfTextAlignment.Left, }; fmt.LineAlignment = format.LineAlignment switch { TextLineAlignment.Center => PdfVerticalAlignment.Middle, TextLineAlignment.Bottom => PdfVerticalAlignment.Bottom, TextLineAlignment.Baseline => PdfVerticalAlignment.Bottom, _ => PdfVerticalAlignment.Top }; if(format.LineAlignment == TextLineAlignment.Baseline) { fmt.EnableBaseline = true; } Graphics.Save(); var point = _transform.Transform(position); Graphics.TranslateTransform(point.X, point.Y); Graphics.RotateTransform(_transform.GetRotation() - rotation); var scaled = _transform.GetScale(); Graphics.ScaleTransform(scaled / _ascentRatio, scaled / _ascentRatio); Graphics.DrawString(text, _font, new PdfSolidBrush(color), new PointF(), fmt); Graphics.Restore(); } public void DrawText(string text, Color color, PointF position, float rotation, float width, PointF scale) { Graphics.Save(); var point = _transform.Transform(position); Graphics.TranslateTransform(point.X, point.Y); Graphics.RotateTransform(_transform.GetRotation() - rotation); var scaleFactor = (_font.Height / _font.Size) * _transform.GetScale(); Graphics.ScaleTransform(scaleFactor, scaleFactor * scale.Y); if(scale.X != 1) { Graphics.TranslateTransform(width / 2, 0); Graphics.ScaleTransform(scale.X, 1); Graphics.TranslateTransform(-width / 2, 0); } Graphics.DrawString(text, _font, new PdfSolidBrush(color), new RectangleF(0, 0, width, float.MaxValue), new PdfStringFormat { Alignment = PdfTextAlignment.Justify, LineAlignment = PdfVerticalAlignment.Top, EnableBaseline = true }); Graphics.Restore(); } public void DrawText(string text, Color color, PointF position, float rotation, SizeF size, PointF scale) { Graphics.Save(); var point = _transform.Transform(position); Graphics.TranslateTransform(point.X, point.Y); Graphics.RotateTransform(_transform.GetRotation() - rotation); var scaleFactor = (_font.Height / _font.Size) * _transform.GetScale(); Graphics.ScaleTransform(scaleFactor, scaleFactor * scale.Y); if(scale.X != 1 || scale.Y != 1) { Graphics.TranslateTransform(size.Width / 2, size.Height / 2); Graphics.ScaleTransform(scale.X, 1); Graphics.TranslateTransform(-size.Width / 2, size.Height / 2); } Graphics.DrawString(text, _font, new PdfSolidBrush(color), new RectangleF(0, 0, size.Width, size.Height), new PdfStringFormat { Alignment = PdfTextAlignment.Justify, LineAlignment = PdfVerticalAlignment.Top, EnableBaseline = true }); Graphics.Restore(); } public void FillPolygon(Color color, params PointF[] points) { _transform.TransformPoints(points); var brush = new PdfSolidBrush(color); Graphics.DrawPolygon(brush, points); } public void Finish() { } } public class SvgGraphics : IGraphics { public DrawData DrawData { get; set; } public SvgDocument Document { get; set; } private SvgMatrix _transform = new(new Matrix().Elements.ToList()); private SvgGroup _group; private Matrix _groupTransform = new(); private static string[][] _groupProps = new[] { new[] { "stroke-width", "style" }, new[] { "vector-effect" } }; public SvgGraphics(SvgDocument document) { Document = document; _group = new SvgGroup(); Document.Stroke = new SvgColourServer(Color.Black); } private void PushGroup() { if(_group.Children.Count > 0) { Document.Children.Add(_group); } } private void AddChild(SvgElement item) { var transMat = _transform.Matrix; if(!Equals(transMat, _groupTransform)) { PushGroup(); _group = new(); _group.Transforms = new() { _transform }; _groupTransform = transMat; } _group.Children.Add(item); } public void SetTransform(Matrix transform) { _transform = new(transform.Elements.ToList()); } public void DrawLine(Color color, float thickness, params PointF[] points) { if(points.Length == 2) { var line = new SvgLine { StartX = points[0].X, StartY = points[0].Y, EndX = points[1].X, EndY = points[1].Y }; if(color != Color.Black) { line.Stroke = new SvgColourServer(color); } if(thickness == 0) { line.StrokeWidth = 1; line.CustomAttributes.Add("vector-effect", "non-scaling-stroke"); } else { line.StrokeWidth = thickness; } AddChild(line); } else { var line = new SvgPolyline(); if(color != Color.Black) { line.Stroke = new SvgColourServer(color); } foreach(var point in points) { line.Points.Add(point.X); line.Points.Add(point.Y); } if(thickness == 0) { line.StrokeWidth = 1; line.CustomAttributes.Add("vector-effect", "non-scaling-stroke"); } else { line.StrokeWidth = thickness; } AddChild(line); } } public void DrawPoint(Color color, float thickness, PointF point) { } public void FillPolygon(Color color, params PointF[] points) { } public void SetFont(float fontSize, FontStyle fontStyle = FontStyle.Regular) { } public void SetFont(string fontName, float fontSize, FontStyle fontStyle = FontStyle.Regular) { } public void DrawText(string text, Color color, PointF position, float rotation, TextFormat format) { } public void Clear(Color color) { } public void Finish() { PushGroup(); } public void DrawText(string text, Color color, PointF position, float rotation, float width, PointF scale) { } public void DrawText(string text, Color color, PointF position, float rotation, SizeF size, PointF scale) { } }