فهرست منبع

dxf: fixed text rendering issues

Kenric Nugteren 2 ماه پیش
والد
کامیت
cb05bd1a8b
3فایلهای تغییر یافته به همراه382 افزوده شده و 12 حذف شده
  1. 130 9
      inabox.dxf/DrawData.cs
  2. 4 0
      inabox.dxf/DxfUtils.cs
  3. 248 3
      inabox.dxf/Objects.cs

+ 130 - 9
inabox.dxf/DrawData.cs

@@ -1,4 +1,5 @@
-using netDxf;
+using InABox.Core;
+using netDxf;
 using netDxf.Tables;
 using Svg;
 using Svg.Transforms;
@@ -28,6 +29,7 @@ public enum TextAlignment
     Left,
     Center,
     Right,
+    Fit,
     Justify
 }
 
@@ -260,6 +262,8 @@ public interface IGraphics
 
     void DrawLine(Color color, float thickness, params PointF[] points);
 
+    void DrawPoint(Color color, float thickness, PointF point);
+
     void FillPolygon(Color color, params PointF[] points);
 
     /// <summary>
@@ -271,6 +275,8 @@ public interface IGraphics
     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);
 
@@ -307,13 +313,26 @@ public class GdiGraphics : IGraphics
         }
     }
 
+    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 TransformText(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);
+
+        DrawLine(Color.Black, 1, new(), new(100, 0));
+
         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:
@@ -323,16 +342,14 @@ public class GdiGraphics : IGraphics
                 DrawData.Translate(new PointF(0, -size.Height));
                 break;
             case TextLineAlignment.Baseline:
-                var ascent = font.FontFamily.GetCellAscent(font.Style);
-                var baseline = ascent * font.Height / font.FontFamily.GetEmHeight(font.Style);
+                var baseline = ascentRatio * font.Height;
                 DrawData.Translate(new PointF(0, -baseline));
                 break;
             case TextLineAlignment.Top:
-                ascent = font.FontFamily.GetCellAscent(font.Style);
-                baseline = ascent * font.Height / font.FontFamily.GetEmHeight(font.Style);
+                baseline = ascentRatio * font.Height;
                 var lineSpace = font.FontFamily.GetLineSpacing(font.Style);
                 var ratio = font.GetHeight(Graphics) / lineSpace;
-                DrawData.Translate(new PointF(0, -baseline + ascent * ratio));
+                DrawData.Translate(new PointF(0, -baseline + font.FontFamily.GetCellAscent(font.Style) * ratio));
                 break;
         }
 
@@ -351,6 +368,10 @@ public class GdiGraphics : IGraphics
 
     public void DrawText(string text, Color color, PointF position, float rotation, TextFormat format)
     {
+        if (text.IsNullOrWhiteSpace())
+        {
+            return;
+        }
         DrawData.PushTransform();
         TransformText(text, Font, position, rotation, format);
 
@@ -358,6 +379,29 @@ public class GdiGraphics : IGraphics
 
         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)
     {
@@ -402,6 +446,7 @@ public class PdfGraphics : IGraphics
     private Matrix _transform = new();
 
     private PdfFont _font = new PdfStandardFont(PdfFontFamily.Helvetica, 12, PdfFontStyle.Regular);
+    private float _ascentRatio = 0;
 
     public PdfGraphics(SPdfGraphics graphics)
     {
@@ -428,13 +473,24 @@ public class PdfGraphics : IGraphics
         }
     }
 
+    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)
     {
-        _font = new PdfTrueTypeFont(new Font(SystemFonts.DefaultFont.FontFamily, fontSize));
+        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));
     }
 
@@ -452,6 +508,7 @@ public class PdfGraphics : IGraphics
         {
             TextLineAlignment.Center => PdfVerticalAlignment.Middle,
             TextLineAlignment.Bottom => PdfVerticalAlignment.Bottom,
+            TextLineAlignment.Baseline => PdfVerticalAlignment.Bottom,
             _ => PdfVerticalAlignment.Top
         };
         if(format.LineAlignment == TextLineAlignment.Baseline)
@@ -464,12 +521,64 @@ public class PdfGraphics : IGraphics
         var point = _transform.Transform(position);
         Graphics.TranslateTransform(point.X, point.Y);
         Graphics.RotateTransform(-(_transform.GetRotation() + rotation));
-        var scale = (_font.Height / _font.Size) * _transform.GetScale();
-        Graphics.ScaleTransform(scale, scale);
+
+        var scaled = _transform.TransformVector(new(1, -1));
+
+        Graphics.ScaleTransform(scaled.X / _ascentRatio, scaled.Y / _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)
     {
@@ -586,6 +695,10 @@ public class SvgGraphics : IGraphics
         }
     }
 
+    public void DrawPoint(Color color, float thickness, PointF point)
+    {
+    }
+
     public void FillPolygon(Color color, params PointF[] points)
     {
     }
@@ -610,4 +723,12 @@ public class SvgGraphics : IGraphics
     {
         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)
+    {
+    }
 }

+ 4 - 0
inabox.dxf/DxfUtils.cs

@@ -157,6 +157,10 @@ public static class DxfUtils
         {
             return new DxfArc(a);
         }
+        else if(el is Text t)
+        {
+            return new DxfText(t);
+        }
         else
         {
             return null;

+ 248 - 3
inabox.dxf/Objects.cs

@@ -2,6 +2,8 @@
 using netDxf;
 using netDxf.Blocks;
 using netDxf.Entities;
+using netDxf.Tables;
+using Syncfusion.XPS;
 using System;
 using System.Collections.Generic;
 using System.Data;
@@ -9,6 +11,9 @@ using System.Drawing;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using FontStyle = System.Drawing.FontStyle;
+using SVec2 = System.Numerics.Vector2;
+using TAlignment = netDxf.Entities.TextAlignment;
 
 namespace InABox.Dxf;
 
@@ -29,9 +34,61 @@ internal class DxfLine : IDxfObject
 
         data.PushTransform();
         //data.ArbitraryAxis(Line.Normal);
-        data.Graphics.DrawLine(
-            data.ResolveColour(Line.Color, Line), (float)Line.Thickness,
-            DrawData.ConvertPoint(Line.StartPoint), DrawData.ConvertPoint(Line.EndPoint));
+
+        var colour = data.ResolveColour(Line.Color, Line);
+        var thickness = (float)Line.Thickness;
+
+        if(Line.Linetype.Segments.Count > 0)
+        {
+            var pos = DrawData.ConvertPoint(Line.StartPoint).ToVector2();
+            var end = DrawData.ConvertPoint(Line.EndPoint).ToVector2();
+
+            var remainingLength = SVec2.Distance(pos, end);
+            if(remainingLength > 0)
+            {
+                var dir = SVec2.Normalize(end - pos);
+
+                while(remainingLength > 0)
+                {
+                    foreach(var segment in Line.Linetype.Segments)
+                    {
+                        if(segment is LinetypeSimpleSegment simple)
+                        {
+                            if(simple.Length > 0)
+                            {
+                                var length = Math.Min(remainingLength, (float)(simple.Length * Line.LinetypeScale));
+                                data.Graphics.DrawLine(
+                                    colour, thickness,
+                                    new PointF(pos), new PointF(pos + dir * length));
+                                remainingLength -= length;
+                                pos += dir * length;
+                            }
+                            else if(simple.Length == 0)
+                            {
+                                data.Graphics.DrawPoint(colour, thickness, new PointF(pos));
+                            }
+                            else
+                            {
+                                var length = (float)(-simple.Length * Line.LinetypeScale);
+                                remainingLength -= length;
+                                pos += dir * length;
+                            }
+                        }
+                        if(remainingLength <= 0)
+                        {
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+        else
+        {
+            data.Graphics.DrawLine(
+                colour, thickness,
+                DrawData.ConvertPoint(Line.StartPoint), DrawData.ConvertPoint(Line.EndPoint));
+        }
+
         data.PopTransform();
     }
 
@@ -270,6 +327,194 @@ internal class DxfPolyline2D : IDxfObject
     }
 }
 
+internal class DxfText : IDxfObject
+{
+    public Text Text { get; set; }
+
+    public DxfText(Text text)
+    {
+        Text = text;
+    }
+
+    private Font GetFont()
+    {
+        FontFamily fontFamily;
+        if (Text.Style.FontFamilyName.IsNullOrWhiteSpace())
+        {
+            fontFamily = SystemFonts.DefaultFont.FontFamily;
+        }
+        else
+        {
+            fontFamily = new FontFamily(Text.Style.FontFamilyName);
+        }
+        return new Font(fontFamily, (float)Text.Height, Text.Style.FontStyle switch
+        {
+            netDxf.Tables.FontStyle.Bold => FontStyle.Bold,
+            netDxf.Tables.FontStyle.Italic => FontStyle.Italic,
+            netDxf.Tables.FontStyle.Regular or _ => FontStyle.Regular,
+        });
+    }
+
+    private string GetText()
+    {
+        return Text.Value;
+    }
+
+    private static Bitmap _placeholderBitmap = new Bitmap(1, 1);
+
+    private void Transform(TransformData data, Font font, string text, Graphics g)
+    {
+        data.Translate(new PointF((float)Text.Position.X, (float)Text.Position.Y));
+        data.Rotate((float)Text.Rotation);
+        data.Scale(1, -1);
+
+        var size = g.MeasureString(text, font, new PointF(), StringFormat.GenericTypographic);
+        if(Text.Alignment == TAlignment.Aligned)
+        {
+            data.Scale((float)(Text.Width / size.Width));
+        }
+        else if(Text.Alignment == TAlignment.Fit)
+        {
+            data.Scale((float)(Text.Width / size.Width), 1);
+        }
+        else
+        {
+            switch (Text.Alignment)
+            {
+                case TAlignment.MiddleLeft:
+                case TAlignment.MiddleCenter:
+                case TAlignment.Middle:
+                case TAlignment.MiddleRight:
+                    data.Translate(new PointF(0, -size.Height / 2));
+                    break;
+                case TAlignment.BottomLeft:
+                case TAlignment.BottomCenter:
+                case TAlignment.BottomRight:
+                    data.Translate(new PointF(0, -size.Height));
+                    break;
+                default:
+                    var ascent = font.FontFamily.GetCellAscent(font.Style);
+                    var lineSpace = font.FontFamily.GetLineSpacing(font.Style);
+                    var baseline = ascent * font.Height / font.FontFamily.GetEmHeight(font.Style);
+                    var ratio = font.GetHeight(g) / lineSpace;
+                    data.Translate(new PointF(0, -baseline + ascent * ratio));
+                    break;
+            }
+
+            switch (Text.Alignment)
+            {
+                case TAlignment.TopLeft:
+                case TAlignment.MiddleLeft:
+                case TAlignment.BottomLeft:
+                    break;
+                case TAlignment.TopCenter:
+                case TAlignment.MiddleCenter:
+                case TAlignment.Middle:
+                case TAlignment.BottomCenter:
+                    data.Translate(new PointF(-(float)size.Width / 2, 0));
+                    break;
+                case TAlignment.TopRight:
+                case TAlignment.MiddleRight:
+                case TAlignment.BottomRight:
+                    data.Translate(new PointF(-(float)size.Width, 0));
+                    break;
+            }
+        }
+    }
+
+    public void Draw(DrawData data)
+    {
+        if (!data.Data.ShouldDraw(Text)) return;
+
+        var text = GetText();
+        if (Text.Style.FontFamilyName.IsNullOrWhiteSpace())
+        {
+            data.Graphics.SetFont((float)Text.Height, Text.Style.FontStyle switch
+            {
+                netDxf.Tables.FontStyle.Bold => FontStyle.Bold,
+                netDxf.Tables.FontStyle.Italic => FontStyle.Italic,
+                netDxf.Tables.FontStyle.Regular or _ => FontStyle.Regular,
+            });
+        }
+        else
+        {
+            data.Graphics.SetFont(Text.Style.FontFamilyName, (float)Text.Height, Text.Style.FontStyle switch
+            {
+                netDxf.Tables.FontStyle.Bold => FontStyle.Bold,
+                netDxf.Tables.FontStyle.Italic => FontStyle.Italic,
+                netDxf.Tables.FontStyle.Regular or _ => FontStyle.Regular,
+            });
+        }
+
+        if(Text.Alignment == TAlignment.Aligned || Text.Alignment == TAlignment.Fit)
+        {
+            // Need to calculate a rectangle to draw the text in.
+            var scale = new PointF(Text.IsBackward ? -1 : 1, Text.IsUpsideDown ? -1 : 1);
+            if(Text.Alignment == TAlignment.Aligned)
+            {
+                data.Graphics.DrawText(text, data.ResolveColour(Text.Color, Text), DrawData.ConvertPoint(Text.Position), (float)Text.Rotation,
+                    new SizeF((float)(Text.Width * Text.WidthFactor), (float)Text.Height), scale);
+            }
+            else
+            {
+                data.Graphics.DrawText(text, data.ResolveColour(Text.Color, Text), DrawData.ConvertPoint(Text.Position), (float)Text.Rotation,
+                    (float)(Text.Width * Text.WidthFactor), scale);
+            }
+        }
+        else
+        {
+            var format = new TextFormat
+            {
+                LineAlignment = Text.Alignment switch
+                {
+                    TAlignment.Aligned => TextLineAlignment.Baseline,
+                    TAlignment.Fit => TextLineAlignment.Baseline,
+                    TAlignment.MiddleLeft or TAlignment.MiddleCenter or TAlignment.MiddleRight or TAlignment.Middle => TextLineAlignment.Center,
+                    TAlignment.BottomLeft or TAlignment.BottomCenter or TAlignment.BottomRight => TextLineAlignment.Bottom,
+                    TAlignment.BaselineLeft or TAlignment.BaselineRight or TAlignment.BaselineCenter => TextLineAlignment.Baseline,
+                    _ => TextLineAlignment.Top,
+                },
+                Alignment = Text.Alignment switch
+                {
+                    TAlignment.Aligned => TextAlignment.Justify,
+                    TAlignment.Fit => TextAlignment.Fit,
+                    TAlignment.TopCenter or TAlignment.MiddleCenter or TAlignment.BottomCenter or TAlignment.Middle or TAlignment.BaselineCenter => TextAlignment.Center,
+                    TAlignment.TopRight or TAlignment.MiddleRight or TAlignment.BottomRight or TAlignment.BaselineRight => TextAlignment.Right,
+                    _ => TextAlignment.Left,
+                }
+            };
+
+            data.PushTransform();
+            data.Translate(DrawData.ConvertPoint(Text.Position));
+            data.Rotate((float)Text.Rotation);
+            data.Scale((float)Text.WidthFactor, 1);
+            data.Scale(Text.IsBackward ? -1 : 1, Text.IsUpsideDown ? -1 : 1);
+            data.Graphics.DrawText(text, data.ResolveColour(Text.Color, Text), new(), 0, format);
+            data.PopTransform();
+        }
+    }
+
+    public RectangleF? GetBounds(TransformData data)
+    {
+        if (!data.Data.ShouldDraw(Text)) return null;
+
+        var font = GetFont();
+        var text = GetText();
+
+        data.PushTransform();
+        using var g = Graphics.FromImage(_placeholderBitmap);
+        Transform(data, font, text, g);
+
+        var size = g.MeasureString(text, font, new PointF(), StringFormat.GenericTypographic);
+        var bounds = Utils.RectangleFromPoints(
+            data.TransformPoint(0, 0),
+            data.TransformPoint(size.Width, size.Height));
+
+        data.PopTransform();
+        return bounds;
+    }
+}
+
 internal class DxfMText : IDxfObject
 {
     public MText MText { get; set; }