empira / PDFsharp

PDFsharp and MigraDoc Foundation for .NET 6 and .NET Framework
https://docs.pdfsharp.net/
Other
467 stars 109 forks source link

【Suggestion】Support grid layout for easier PDF document creation. #156

Open soul-soft opened 3 weeks ago

soul-soft commented 3 weeks ago

extensions

public static class PdfDocumentExtensions
{
    public static void DrawPage(this PdfDocument document,Action<PdfPage,XGraphics> configure)
    {
        var page = document.AddPage();
        using (var graphics = XGraphics.FromPdfPage(page))
        {
            configure(page, graphics);
        }
    }
}

public static class PdfSharpExtensions
{
    public static void DrawGrid(this XGraphics graphics, XFont font, XBrush brush, double y, Action<XGrid> conguire)
    {
        var yIndex = y;
        var grid = new XGrid();
        conguire(grid);
        var pageWidth = graphics.PageSize.Width;
        foreach (var row in grid.Rows)
        {
            var xIndex = row.Margin;
            var rowHeight = row.Height;
            foreach (var cell in row.Cells)
            {
                if (cell is XGridTextCell textCell)
                {
                    var cellSize = graphics.MeasureString(textCell.Text, font);
                    var cellWidth = cell.Width;
                    var cellHelight = cellSize.Height > rowHeight ? cellSize.Height : rowHeight;
                    //计算单元格的宽度
                    if (cellSize.Width > cellWidth)
                    {
                        cellWidth = cellSize.Width;
                    }
                    if (row.Cells.Last() == cell)
                    {
                        cellWidth = pageWidth - (xIndex + row.Margin);
                    }
                    if (!textCell.Warp)
                    {
                        graphics.DrawString(textCell.Text, font, brush, new XRect(xIndex, yIndex, cellWidth, 0), textCell.Format);
                    }
                    else
                    {
                        var lines = graphics.GetLines(font, textCell.Text, cellWidth);
                        var tempY = yIndex;
                        foreach (var item in lines)
                        {
                            graphics.DrawString(item, font, brush, new XRect(xIndex, tempY, cellWidth, 0), textCell.Format);
                            tempY += cellHelight;
                        }
                        cellHelight = lines.Count * cellHelight;
                        rowHeight = cellHelight > rowHeight ? cellHelight : rowHeight;
                    }
                    xIndex += cellWidth;
                }
            }
            yIndex += rowHeight;
        }
    }
    public static List<string> GetLines(this XGraphics graphics, XFont font, string text, double lineWidth)
    {
        var lines = new List<string>();
        var sb = new StringBuilder();
        foreach (var item in text)
        {
            sb.Append(item);
            var size = graphics.MeasureString(sb.ToString(), font);
            if (size.Width >= lineWidth)
            {
                lines.Add(sb.ToString());
                sb.Clear();
            }
        }
        lines.Add(sb.ToString());
        return lines;
    }
}

public class XGrid
{
    public List<XGridRow> Rows { get; set; } = new List<XGridRow>();

    public void DrawRow(Action<XGridRow> configure)
    {
        var row = new XGridRow();
        configure(row);
        Rows.Add(row);
    }
}

public class XGridRow
{
    /// <summary>
    /// 垂直间距
    /// </summary>
    public double Height { get; set; }
    /// <summary>
    /// 水平间距
    /// </summary>
    public double Margin { get; set; }

    public List<XGridCell> Cells { get; set; } = new List<XGridCell>();

    public void DrawTextCell(Action<XGridTextCell> configure)
    {
        var cell = new XGridTextCell();
        configure(cell);
        Cells.Add(cell);
    }
}

public class XGridCell
{
    public double Width { get; set; }
}

public class XGridTextCell : XGridCell
{
    public string Text { get; set; }
    public bool Warp { get; set; } = false;
    public XStringFormat Format { get; set; } = XStringFormats.Default;
}

Usage

class Program
{
    static void Main(string[] args)
    {
        GlobalFontSettings.FontResolver = new WindowsFontResolver();
        GlobalFontSettings.DefaultFontEncoding = PdfFontEncoding.Unicode;
        // 创建新的 PDF 文档
        PdfDocument document = new PdfDocument();
        document.Info.Title = "工程造价咨询报告书";

        // 创建一个页面
        document.DrawPage((page, gfx) =>
        {
            page.Size = PdfSharp.PageSize.A4;
            // 设置字体
            var titleFont = new XFont("STSONG.TTF", 30, XFontStyleEx.Bold);
            var footerFont = new XFont("STSONG.TTF", 18, XFontStyleEx.Bold);
            var bodyFont = new XFont("STSONG.TTF", 12, XFontStyleEx.Bold);
            gfx.DrawGrid(titleFont, XBrushes.Black, 100, grid =>
            {
                grid.DrawRow(row =>
                {
                    row.Margin = 100;
                    row.Height = 20;
                    row.DrawTextCell(cell =>
                    {
                        cell.Text = "工程造价咨询报告书";
                        cell.Format = XStringFormats.Center;
                    });
                });
            });
            gfx.DrawGrid(bodyFont, XBrushes.Black, 350, grid =>
            {
                grid.DrawRow(row =>
                {
                    row.Margin = 100;
                    row.Height = 20;
                    row.DrawTextCell(cell =>
                    {
                        cell.Text = "咨询项目全称: ";
                    });
                    row.DrawTextCell(cell =>
                    {
                        cell.Text = "伊犁新天煤化工有限责任公司年产20亿Nm3煤制天然气项目2020年煤气水膨胀入气柜施工合同";
                        cell.Warp = true;
                    });
                });
                grid.DrawRow(row =>
                {
                    row.Margin = 100;
                    row.Height = 20;
                    row.DrawTextCell(cell =>
                    {
                        cell.Text = "咨询业务类别: ";
                    });
                    row.DrawTextCell(cell =>
                    {
                        cell.Text = "工程结算审核";
                        cell.Format = XStringFormats.Center;
                    });
                });
                grid.DrawRow(row =>
                {
                    row.Margin = 100;
                    row.Height = 20;
                    row.DrawTextCell(cell =>
                    {
                        cell.Text = "咨询报告日期: ";
                    });
                    row.DrawTextCell(cell =>
                    {
                        cell.Text = "二○二三年四月二十四日";
                        cell.Format = XStringFormats.Center;
                    });
                });
            });
            gfx.DrawGrid(footerFont, XBrushes.Black, page.Height.Value - 100, grid =>
            {
                grid.DrawRow(row =>
                {
                    row.Margin = 100;
                    row.Height = 20;
                    row.DrawTextCell(cell =>
                    {
                        cell.Text = "万邦工程管理咨询有限公司";
                        cell.Format = XStringFormats.Center;
                    });
                });
            });
        });
        document.DrawPage((page, gfx) =>
        {
            gfx.DrawRectangle(XBrushes.Black, new XRect(50, 50, page.Width.Value - 100, page.Height.Value - 100));
        });
        // 保存文档
        string filename = "工程造价咨询报告书.pdf";
        document.Save(filename);
        // 打开生成的 PDF 文件
        Process.Start(new ProcessStartInfo(filename) { UseShellExecute = true });
    }

    internal class WindowsFontResolver : IFontResolver
    {
        public byte[] GetFont(string faceName)
        {
            return File.ReadAllBytes($@".\{faceName}");
        }

        public FontResolverInfo ResolveTypeface(string familyName, bool isBold, bool isItalic)
        {
            return new FontResolverInfo(familyName, isBold, isItalic);
        }
    }
}
soul-soft commented 3 weeks ago

Topic

I have implemented most of the logic, including the Grid layout. The rows support: margins, Y-axis positioning, and row height. The cells support: border lines, alignment, automatic wrapping, margins, and fluid layout.

It can easily achieve the requirements for rendering paragraphs, tables, and more. However, I do not have enough resources to support images and other features, and I hope the author will continue the development.

Source

https://github.com/soul-soft/Soul.PDFsharp.Extensions

Nuget

dotnet add package Soul.PDFsharp.Extensions --version 1.0.0

soul-soft commented 3 weeks ago

image

soul-soft commented 2 weeks ago

I have already implemented most of the requirements, including internal and external margins, alignment in all directions, the box model, and text and image rendering, among other things.

image