znlgis 博客

GIS开发与技术分享

第十八章:标注与文本系统

18.1 标注系统概述

18.1.1 标注的重要性

标注是工程图纸中不可或缺的组成部分,用于标明尺寸、角度、半径等几何信息。LightCAD提供了完整的二维和三维标注系统。

18.1.2 标注类型

二维标注(LightCAD.Core/Elements/Dim/)

标注类型 说明
LcDimLinear 线性标注(水平、垂直、对齐)
LcDimAngular 角度标注
LcDimRadial 半径标注
LcDimDiameter 直径标注
LcDimOrdinate 坐标标注
LcDimArcLength 弧长标注

三维标注(LightCAD.Core/Elements/Dim3/)

标注类型 说明
LcDim3dLinear 三维线性标注
LcDim3dAngular 三维角度标注
LcDim3dRadial 三维半径标注
LcDim3dElevation 标高标注

18.2 标注基类

18.2.1 标注数据结构

public abstract class LcDimension : LcEntity, IUpdateObject
{
    /// <summary>
    /// 标注样式
    /// </summary>
    public DimStyle Style { get; set; }

    /// <summary>
    /// 标注文本(null表示使用测量值)
    /// </summary>
    public string OverrideText { get; set; }

    /// <summary>
    /// 文本位置
    /// </summary>
    public Point2d TextPosition { get; set; }

    /// <summary>
    /// 文本旋转角度
    /// </summary>
    public double TextRotation { get; set; }

    /// <summary>
    /// 测量值
    /// </summary>
    public abstract double MeasuredValue { get; }

    /// <summary>
    /// 获取显示文本
    /// </summary>
    public string DisplayText
    {
        get
        {
            if (!string.IsNullOrEmpty(OverrideText))
                return OverrideText;

            return FormatMeasuredValue();
        }
    }

    /// <summary>
    /// 格式化测量值
    /// </summary>
    protected string FormatMeasuredValue()
    {
        var value = MeasuredValue;
        var precision = Style?.Precision ?? 2;
        var prefix = Style?.Prefix ?? "";
        var suffix = Style?.Suffix ?? "";

        return $"{prefix}{value.ToString($"F{precision}")}{suffix}";
    }

    public override string TypeName => "Dimension";
}

18.2.2 标注样式

public class DimStyle
{
    /// <summary>
    /// 样式名称
    /// </summary>
    public string Name { get; set; }

    // 尺寸线设置
    public LcColor DimensionLineColor { get; set; } = LcColor.ByBlock;
    public double DimensionLineWeight { get; set; } = 0.25;
    public double DimensionLineExtension { get; set; } = 2;

    // 延伸线设置
    public LcColor ExtensionLineColor { get; set; } = LcColor.ByBlock;
    public double ExtensionLineOffset { get; set; } = 2;
    public double ExtensionLineExtension { get; set; } = 2;
    public bool SuppressExtLine1 { get; set; } = false;
    public bool SuppressExtLine2 { get; set; } = false;

    // 箭头设置
    public ArrowType ArrowType { get; set; } = ArrowType.ClosedFilled;
    public double ArrowSize { get; set; } = 3;

    // 文本设置
    public string TextFont { get; set; } = "SimSun";
    public double TextHeight { get; set; } = 3.5;
    public LcColor TextColor { get; set; } = LcColor.ByBlock;
    public TextAlignment TextAlignment { get; set; } = TextAlignment.Center;
    public double TextGap { get; set; } = 1;
    public int Precision { get; set; } = 2;
    public string Prefix { get; set; } = "";
    public string Suffix { get; set; } = "";

    // 比例设置
    public double ScaleFactor { get; set; } = 1.0;

    // 公差设置
    public bool ShowTolerance { get; set; } = false;
    public double ToleranceUpper { get; set; } = 0;
    public double ToleranceLower { get; set; } = 0;
}

public enum ArrowType
{
    ClosedFilled,    // 实心箭头
    ClosedEmpty,     // 空心箭头
    Open,            // 开放箭头
    Dot,             // 圆点
    Slash,           // 斜线
    None             // 无
}

public enum TextAlignment
{
    Center,          // 居中
    Above,           // 上方
    Outside          // 外侧
}

18.3 线性标注(LcDimLinear)

18.3.1 数据结构

public class LcDimLinear : LcDimension
{
    /// <summary>
    /// 第一个定义点(被标注对象的端点1)
    /// </summary>
    public Point2d DefPoint1 { get; set; }

    /// <summary>
    /// 第二个定义点(被标注对象的端点2)
    /// </summary>
    public Point2d DefPoint2 { get; set; }

    /// <summary>
    /// 尺寸线位置
    /// </summary>
    public Point2d DimLinePoint { get; set; }

    /// <summary>
    /// 标注方向
    /// </summary>
    public DimLinearType LinearType { get; set; } = DimLinearType.Aligned;

    /// <summary>
    /// 强制角度(用于旋转标注)
    /// </summary>
    public double? ForceAngle { get; set; }

    /// <summary>
    /// 测量值
    /// </summary>
    public override double MeasuredValue
    {
        get
        {
            switch (LinearType)
            {
                case DimLinearType.Horizontal:
                    return Math.Abs(DefPoint2.X - DefPoint1.X);
                case DimLinearType.Vertical:
                    return Math.Abs(DefPoint2.Y - DefPoint1.Y);
                case DimLinearType.Aligned:
                    return DefPoint1.DistanceTo(DefPoint2);
                case DimLinearType.Rotated:
                    if (ForceAngle.HasValue)
                    {
                        var dir = new Vector2d(
                            Math.Cos(ForceAngle.Value),
                            Math.Sin(ForceAngle.Value));
                        var diff = DefPoint2 - DefPoint1;
                        return Math.Abs(diff.Dot(dir));
                    }
                    return DefPoint1.DistanceTo(DefPoint2);
                default:
                    return DefPoint1.DistanceTo(DefPoint2);
            }
        }
    }

    /// <summary>
    /// 获取标注的几何图形(用于渲染)
    /// </summary>
    public DimGeometry GetGeometry()
    {
        var geometry = new DimGeometry();
        var style = Style ?? DimStyle.Default;

        // 计算尺寸线方向
        Vector2d dimDirection;
        switch (LinearType)
        {
            case DimLinearType.Horizontal:
                dimDirection = Vector2d.XAxis;
                break;
            case DimLinearType.Vertical:
                dimDirection = Vector2d.YAxis;
                break;
            default:
                dimDirection = (DefPoint2 - DefPoint1).Normalize();
                break;
        }

        var perpDirection = dimDirection.Perpendicular();

        // 将定义点投影到尺寸线上
        var dimLineY = (DimLinePoint - DefPoint1).Dot(perpDirection);
        var ext1End = DefPoint1 + perpDirection * dimLineY;
        var ext2End = DefPoint2 + perpDirection * dimLineY;

        // 延伸线
        var ext1Start = DefPoint1 + perpDirection *
            Math.Sign(dimLineY) * style.ExtensionLineOffset;
        var ext2Start = DefPoint2 + perpDirection *
            Math.Sign(dimLineY) * style.ExtensionLineOffset;

        geometry.ExtensionLines.Add((ext1Start,
            ext1End + perpDirection * Math.Sign(dimLineY) *
            style.ExtensionLineExtension));
        geometry.ExtensionLines.Add((ext2Start,
            ext2End + perpDirection * Math.Sign(dimLineY) *
            style.ExtensionLineExtension));

        // 尺寸线
        geometry.DimensionLine = (ext1End, ext2End);

        // 箭头
        geometry.Arrow1Position = ext1End;
        geometry.Arrow2Position = ext2End;
        geometry.ArrowDirection = dimDirection;

        // 文本位置
        geometry.TextPosition = ext1End.MidPoint(ext2End);
        geometry.Text = DisplayText;

        return geometry;
    }
}

public enum DimLinearType
{
    Horizontal,  // 水平标注
    Vertical,    // 垂直标注
    Aligned,     // 对齐标注
    Rotated      // 旋转标注
}

18.4 角度标注

public class LcDimAngular : LcDimension
{
    public Point2d Vertex { get; set; }
    public Point2d Point1 { get; set; }
    public Point2d Point2 { get; set; }
    public Point2d ArcPoint { get; set; }

    public override double MeasuredValue
    {
        get
        {
            var dir1 = (Point1 - Vertex).Normalize();
            var dir2 = (Point2 - Vertex).Normalize();
            var angle = Math.Acos(dir1.Dot(dir2));
            return AngleUtils.RadToDeg(angle);
        }
    }

    protected override string FormatMeasuredValue()
    {
        return $"{MeasuredValue.ToString($"F{Style?.Precision ?? 1}")}°";
    }
}

18.5 半径和直径标注

public class LcDimRadial : LcDimension
{
    public Point2d Center { get; set; }
    public Point2d ChordPoint { get; set; }

    public override double MeasuredValue =>
        Center.DistanceTo(ChordPoint);

    protected override string FormatMeasuredValue()
    {
        var value = MeasuredValue;
        return $"R{value.ToString($"F{Style?.Precision ?? 2}")}";
    }
}

public class LcDimDiameter : LcDimension
{
    public Point2d Center { get; set; }
    public Point2d ChordPoint { get; set; }

    public override double MeasuredValue =>
        Center.DistanceTo(ChordPoint) * 2;

    protected override string FormatMeasuredValue()
    {
        var value = MeasuredValue;
        return $"⌀{value.ToString($"F{Style?.Precision ?? 2}")}";
    }
}

18.6 文本系统

18.6.1 文本实体

public class LcText : LcEntity, IUpdateObject
{
    /// <summary>
    /// 文本内容
    /// </summary>
    public string Content { get; set; }

    /// <summary>
    /// 插入点
    /// </summary>
    public Point2d InsertionPoint { get; set; }

    /// <summary>
    /// 文字高度
    /// </summary>
    public double Height { get; set; } = 3.5;

    /// <summary>
    /// 旋转角度
    /// </summary>
    public double Rotation { get; set; } = 0;

    /// <summary>
    /// 宽度因子
    /// </summary>
    public double WidthFactor { get; set; } = 1.0;

    /// <summary>
    /// 倾斜角度
    /// </summary>
    public double ObliqueAngle { get; set; } = 0;

    /// <summary>
    /// 字体名称
    /// </summary>
    public string FontName { get; set; } = "SimSun";

    /// <summary>
    /// 水平对齐方式
    /// </summary>
    public TextHorizontalAlignment HorizontalAlignment { get; set; }
        = TextHorizontalAlignment.Left;

    /// <summary>
    /// 垂直对齐方式
    /// </summary>
    public TextVerticalAlignment VerticalAlignment { get; set; }
        = TextVerticalAlignment.Baseline;

    /// <summary>
    /// 是否为多行文本
    /// </summary>
    public bool IsMultiline { get; set; } = false;

    /// <summary>
    /// 多行文本宽度
    /// </summary>
    public double TextWidth { get; set; }

    public override string TypeName => "Text";

    public override BoundingBox2d Bounds
    {
        get
        {
            var textWidth = CalculateTextWidth();
            var textHeight = Height;

            // 考虑旋转
            if (Math.Abs(Rotation) < 1e-10)
            {
                return new BoundingBox2d
                {
                    Min = InsertionPoint,
                    Max = new Point2d(
                        InsertionPoint.X + textWidth,
                        InsertionPoint.Y + textHeight)
                };
            }
            else
            {
                // 计算旋转后的包围盒
                var corners = GetRotatedCorners(textWidth, textHeight);
                return BoundingBox2d.FromPoints(corners);
            }
        }
    }

    /// <summary>
    /// 计算文本宽度
    /// </summary>
    private double CalculateTextWidth()
    {
        var font = FontManager.GetFont(FontName);
        if (font == null) return Content.Length * Height * 0.6;

        double width = 0;
        foreach (var ch in Content)
        {
            var charWidth = font.GetCharWidth(ch);
            width += charWidth * Height * WidthFactor;
        }
        return width;
    }

    /// <summary>
    /// 获取文本轮廓(用于渲染)
    /// </summary>
    public List<List<Point2d>> GetOutlines()
    {
        var font = FontManager.GetFont(FontName);
        var outlines = new List<List<Point2d>>();
        var currentX = 0.0;

        foreach (var ch in Content)
        {
            var charOutline = font.GetCharOutline(ch);
            if (charOutline != null)
            {
                var transformed = charOutline.Select(p =>
                {
                    var scaled = new Point2d(
                        p.X * Height * WidthFactor + currentX,
                        p.Y * Height);

                    // 应用旋转
                    if (Math.Abs(Rotation) > 1e-10)
                    {
                        var rotated = scaled.ToVector().Rotate(Rotation);
                        scaled = new Point2d(rotated.X, rotated.Y);
                    }

                    // 应用插入点偏移
                    return new Point2d(
                        scaled.X + InsertionPoint.X,
                        scaled.Y + InsertionPoint.Y);
                }).ToList();

                outlines.Add(transformed);
            }

            currentX += font.GetCharWidth(ch) * Height * WidthFactor;
        }

        return outlines;
    }

    public bool NeedsUpdate { get; private set; }
    public void OnUpdate(UpdateContext context) { NeedsUpdate = false; }
}

public enum TextHorizontalAlignment
{
    Left,
    Center,
    Right
}

public enum TextVerticalAlignment
{
    Baseline,
    Bottom,
    Middle,
    Top
}

18.7 表格系统

18.7.1 表格实体

public class LcTable : LcEntity, IUpdateObject
{
    public Point2d InsertionPoint { get; set; }
    public int RowCount { get; set; }
    public int ColumnCount { get; set; }
    public double[] RowHeights { get; set; }
    public double[] ColumnWidths { get; set; }
    public TableCell[,] Cells { get; set; }
    public TableStyle Style { get; set; }

    public override string TypeName => "Table";

    /// <summary>
    /// 获取指定单元格
    /// </summary>
    public TableCell GetCell(int row, int col)
    {
        return Cells[row, col];
    }

    /// <summary>
    /// 设置单元格内容
    /// </summary>
    public void SetCellContent(int row, int col, string content)
    {
        Cells[row, col].Content = content;
    }

    /// <summary>
    /// 计算表格总宽度
    /// </summary>
    public double TotalWidth => ColumnWidths.Sum();

    /// <summary>
    /// 计算表格总高度
    /// </summary>
    public double TotalHeight => RowHeights.Sum();

    public bool NeedsUpdate { get; private set; }
    public void OnUpdate(UpdateContext context) { NeedsUpdate = false; }
}

public class TableCell
{
    public string Content { get; set; }
    public TextHorizontalAlignment Alignment { get; set; }
    public LcColor TextColor { get; set; }
    public LcColor BackgroundColor { get; set; }
    public double TextHeight { get; set; }
    public int MergeRight { get; set; } = 0;
    public int MergeDown { get; set; } = 0;
}

18.8 SHX字体解析

18.8.1 SHX字体格式

SHX(Shape eXtension)是AutoCAD专用的矢量字体格式:

public class ShxFont
{
    private Dictionary<char, ShxGlyph> glyphs = new();
    public string Name { get; set; }
    public double Above { get; set; }
    public double Below { get; set; }

    /// <summary>
    /// 加载SHX文件
    /// </summary>
    public static ShxFont Load(string filePath)
    {
        var font = new ShxFont();
        font.Name = Path.GetFileNameWithoutExtension(filePath);

        using var stream = File.OpenRead(filePath);
        using var reader = new BinaryReader(stream);

        // 读取文件头
        var header = reader.ReadBytes(32);
        font.Above = reader.ReadDouble();
        font.Below = reader.ReadDouble();

        // 读取字形定义
        while (stream.Position < stream.Length)
        {
            var charCode = reader.ReadUInt16();
            var glyphData = ReadGlyphData(reader);
            font.glyphs[(char)charCode] = glyphData;
        }

        return font;
    }

    /// <summary>
    /// 获取字符的轮廓点
    /// </summary>
    public List<Point2d> GetCharOutline(char ch)
    {
        if (glyphs.TryGetValue(ch, out var glyph))
        {
            return glyph.Points;
        }
        return null;
    }

    /// <summary>
    /// 获取字符宽度
    /// </summary>
    public double GetCharWidth(char ch)
    {
        if (glyphs.TryGetValue(ch, out var glyph))
        {
            return glyph.Width;
        }
        return 0.6; // 默认宽度
    }
}

18.9 本章小结

本章全面介绍了LightCAD的标注和文本系统。标注系统支持线性、角度、半径、直径等多种标注类型,通过标注样式(DimStyle)统一管理标注的外观。文本系统支持单行和多行文本,通过TTF和SHX字体渲染。表格系统支持创建和编辑工程表格。SHX字体解析器确保了与AutoCAD字体的兼容性。


上一章第十七章:数据库与项目管理

下一章第十九章:调试测试与性能优化