通过Spire.pdf生成新版税务局监制章

因为国家税务局发文,要求将电子发票上旧的税务局监制章改为新版监制章,按我们当初电子发票的做法,此处部分是将已有的监制章png图片绘制到指定位置,但让美工按税务局行文要求制图后,生成出来的效果怎么预览都不满意,所以就产生了直接在pdf上绘制税务局监制章的想法。

想法很美好,现实很骨感!平面几何知识早就忘到了不知哪个角落,什么a、b、c,什么切线,还有最难的计算弧线长度(高等数学),所以事实是知道应该按什么思路来绘制,但苦于相关数学知识不够,无从下手。

不过万能的互联网上早就有人实现了类似的功能,具体链接在此,第36楼有完整的代码。此处虽然是通过Graphics来进行绘制操作,但绘制逻辑部分代码很是完整!而最后平移坐标系、旋转角度这些在spire.pdf中都是有对应功能的,所以在此基础上,加上一些重构调整,虽然有些曲折,但还是产生了如下绘制代码(说实在的平面几何部分完全不懂,所以那部分代码只能完整的抄袭了),另外也新增了绘制外部椭圆以及水平方向绘制的功能。

    using Spire.Pdf.Graphics;
    /// <summary>
    /// 印章绘制类
    /// </summary>
    public class StampDrawer
    {
        private float _radiusX, _radiusY;
        private PointF _centerPoint;
        private double _stepAngle;
        private List<Tuple<double, double>> _arcTupleList;//Tuple.Item1为角度 Tuple.Item2为弦长
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="radiusX">椭圆半长边长</param>
        /// <param name="radiusY">椭圆半短边长</param>
        /// <param name="centerPoint">中心点</param>
        /// <param name="stepAngle">以多少角度将椭圆进行分割以进行逼近计算</param>
        public StampDrawer(float radiusX, float radiusY, PointF centerPoint, double stepAngle = 0.3d)
        {
            if (radiusX <= 0)
            {
                throw new ArgumentException(nameof(radiusX));
            }
            if (radiusY <= 0)
            {
                throw new ArgumentException(nameof(radiusY));
            }
            if (stepAngle <= 0)
            {
                throw new ArgumentException(nameof(stepAngle));
            }
            this._radiusX = radiusX;
            this._radiusY = radiusY;
            this._centerPoint = centerPoint;
            this._stepAngle = stepAngle;
            this.InitArcTupleList();
        }
        private void InitArcTupleList()
        {
            var totalAngle = 360d;
            var startAngle = -90 - totalAngle / 2;
            _arcTupleList = new List<Tuple<double, double>>();
            _arcTupleList.Add(Tuple.Create(startAngle, 0d));
            var angR = startAngle * Math.PI / 180;
            var lastX = _radiusX * Math.Cos(angR) + _centerPoint.X;
            var laxtY = _radiusY * Math.Sin(angR) + _centerPoint.Y;
            for (var i = 1; i <= (int)Math.Ceiling(totalAngle / _stepAngle); i++)
            {
                var angle = startAngle + i * this._stepAngle;
                angR = angle * Math.PI / 180;
                var x = _radiusX * Math.Cos(angR) + _centerPoint.X;
                var y = _radiusY * Math.Sin(angR) + _centerPoint.Y;
                var arcLen = Math.Sqrt((lastX - x) * (lastX - x) + (laxtY - y) * (laxtY - y));
                lastX = x;
                laxtY = y;
                _arcTupleList.Add(Tuple.Create(angle, _arcTupleList[i - 1].Item2 + arcLen));
            }
        }
        /// <summary>
        /// 绘制弧线部分文字内容
        /// </summary>
        /// <param name="canvas">要绘制字体的主体</param>
        /// <param name="font">绘制要使用的字体</param>
        /// <param name="brush">绘制要是用的刷子</param>
        /// <param name="content">绘制内容</param>
        /// <param name="minRat">从边线向中心的移动因子</param>
        /// <param name="fixArc">单字符修正宽度</param>
        /// <param name="isTop">是否上方向绘制</param>
        /// <param name="format">PdfStringFormat that represents formatting information, such as line spacing,for the string.</param>
        public void PaintArcContent(PdfCanvas canvas, PdfFontBase font, PdfBrush brush, string content, float minRat, float fixArc, bool isTop, PdfStringFormat format = null)
        {
            var contentSize = font.MeasureString(content);
            var arcPer = contentSize.Width / content.Length + fixArc;
            var skipLen = (_arcTupleList[_arcTupleList.Count - 1].Item2 - contentSize.Width - fixArc * (content.Length - 1)) / 2;
            for (int i = 0; i < content.Length; i++)
            {
                var arcL = i * arcPer + arcPer / 2.0 + skipLen;
                var ang = 0.0d;
                for (int p = 0; p < _arcTupleList.Count - 1; p++)
                {
                    if (_arcTupleList[p].Item2 <= arcL && arcL <= _arcTupleList[p + 1].Item2)
                    {
                        ang = (arcL >= ((_arcTupleList[p].Item2 + _arcTupleList[p + 1].Item2) / 2.0)) ? _arcTupleList[p + 1].Item1 : _arcTupleList[p].Item1;
                        if (!isTop)
                        {
                            ang += 180;
                        }
                        break;
                    }
                }
                var angR = (ang * Math.PI / 180f);
                var x = _radiusX * (float)Math.Cos(angR) + _centerPoint.X;
                var y = _radiusY * (float)Math.Sin(angR) + _centerPoint.Y;
                var qxang = Math.Atan2(_radiusY * Math.Cos(angR), -_radiusX * Math.Sin(angR));
                var fxang = qxang + Math.PI / 2.0;
                var c = content[isTop ? i : content.Length - 1 - i].ToString();
                var charSize = font.MeasureString(c, format);
                var w = charSize.Width;
                var h = charSize.Height;
                if (isTop)
                {
                    x += h * minRat * (float)Math.Cos(fxang);
                    y += h * minRat * (float)Math.Sin(fxang);
                    x += -w / 2f * (float)Math.Cos(qxang);
                    y += -w / 2f * (float)Math.Sin(qxang);
                }
                else
                {
                    x += (h * minRat + h) * (float)Math.Cos(fxang);
                    y += (h * minRat + h) * (float)Math.Sin(fxang);
                    x += w / 2f * (float)Math.Cos(qxang);
                    y += w / 2f * (float)Math.Sin(qxang);
                }
                float rotateAngle = (float)(fxang * 180.0 / Math.PI - 90);
                if (!isTop)
                {
                    rotateAngle = rotateAngle + 180;
                }
                canvas.TranslateTransform(x, y);
                canvas.RotateTransform(rotateAngle);
                canvas.DrawString(c, font, brush, 0, 0, format);
                canvas.RotateTransform(-rotateAngle);
                canvas.TranslateTransform(-x, -y);
            }
        }
        /// <summary>
        /// 绘制水平方向居中显示的文字
        /// </summary>
        /// <param name="canvas">要绘制字体的主体</param>
        /// <param name="font">绘制要使用的字体</param>
        /// <param name="brush">绘制要是用的刷子</param>
        /// <param name="content">绘制内容</param>
        /// <param name="relativeToVertical">要绘制的文字中心点相对于椭圆中心点在垂直方向上的偏移量,正值表示向下偏倚,负值表示向上偏倚</param>
        /// <param name="format">PdfStringFormat that represents formatting information, such as line spacing,for the string.</param>
        public void PaintHorizontalCenterContent(PdfCanvas canvas, PdfFontBase font, PdfBrush brush, string content, float relativeToVertical = 0, PdfStringFormat format = null)
        {
            var fontSize = font.MeasureString(content, format);
            var x = _centerPoint.X - fontSize.Width / 2;
            var y = _centerPoint.Y - fontSize.Height / 2 + relativeToVertical;
            canvas.DrawString(content, font, brush, x, y, format);
        }
        /// <summary>
        /// 按中心点以及半径填充绘制椭圆
        /// </summary>
        /// <param name="canvas">要绘制的主体</param>
        /// <param name="brush">绘制要是用的刷子</param>
        /// <param name="lineWidth">要绘制的椭圆线条宽度</param>
        /// <param name="radiusPadding">相对于椭圆径长填充宽度,正值表示向外填充,负值表示向内填充,注意此处不包含椭圆线条宽度</param>
        public void DrawEllipse(PdfCanvas canvas, PdfBrush brush, float lineWidth, float radiusPadding)
        {
            var fx = radiusPadding + lineWidth + _radiusX;
            var fy = radiusPadding + lineWidth + _radiusY;
            var rec = new RectangleF(_centerPoint.X - fx, _centerPoint.Y - fy,
                fx * 2, fy * 2);
            canvas.DrawEllipse(new PdfPen(brush, lineWidth), rec);
        }
    }

原文中区分上下两部分的角度切割及弧长初始化,在此代码中被合并成了只有一次且是按360度进行得初始化,而且调整了原方法中按总角度进行平均分布的方式,改为按单字宽度加上修正宽度来作为一个字符实际显示宽度,毕竟按新版监制章要求,下半部分xx税务局这个长度是会动态变化的。

最后是测试绘制代码

            using (var doc = new PdfDocument())
            {
                var page = doc.Pages.Add();
                var stamp1CenterPoint = new Point(50, 50);
                var stamp1RadiusX = 36f;
                var stamp1RaidusY = 22f;
                var draw1 = new StampDrawer(stamp1RadiusX, stamp1RaidusY, stamp1CenterPoint);
                var canvas = page.Canvas;
                var brush = PdfBrushes.Red;
                draw1.DrawEllipse(canvas, brush, 2.8f, 3f);
                draw1.DrawEllipse(canvas, brush, 1, 0.5f);
                var font1 = new PdfTrueTypeFont(new Font("楷体", 7f), true);

                draw1.PaintArcContent(canvas, font1, brush, "全国统一发票监制章", 0, 2, true);
                draw1.PaintHorizontalCenterContent(canvas, font1, brush, "国家税务总局");
                draw1.PaintArcContent(canvas, font1, brush, "江苏省税务局", 0, 0.5f, false);

                var stamp2CenterPoint = new Point(70, 120);
                var font2C = new PdfTrueTypeFont(new Font("仿宋", 10f), true);
                var font2T= new PdfTrueTypeFont(new Font("Arial", 10f, FontStyle.Regular, GraphicsUnit.Point, 174), false);
                var stamp2RadiusX = 48f;
                var stamp2RaidusY = 33f;
                var draw2 = new StampDrawer(stamp2RadiusX, stamp2RaidusY, stamp2CenterPoint);
                draw2.DrawEllipse(canvas, brush, 3.3f, 2f);
                draw2.PaintArcContent(canvas, font2C, brush, "江苏省某某某有限公司", -0.2f, 6, true);
                draw2.PaintHorizontalCenterContent(canvas, font2C, brush, "某某专用章", 15f);

                doc.SaveToFile("stamp.pdf");
            }

注意此处只是例子,实际生成的监制章与税务局额要求略有出入,最终效果如下
生成效果

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
免费Spire.PDF for .NET 是一款由e-iceblue公司开发的专业性的PDF文档创建组件。它能够使用户在不用Adobe Acrobat和其他外部控件的情况下,运用.NET 应用程序阅读,编写和操纵PDF 文档。Spire.PDF for .NET不仅可以运用在服端比如:ASP.NET 或者其他环境,还可以应用在Windows Forms 应用程序中。Spire.PDF for .NET 适合应用于所有常见的坏境中,比如:创建好的PDF文档可以存到磁盘中, 还可以在Windows Forms应用程序,ASP.NET 应用程序客户端浏览器中保存为数据流。 Spire.PDF for .NET 功能丰富。 除了基本的功能比如:绘制多种图形,图片,创建窗体字段,插入页眉页脚,输入数据表,自动对大型表格进行分页外,Spire.PDF for .NET还支持PDF数字签名,将HTML转换成PDF格式,提取PDF文档中的文本信息和图片,存为文本格式和各种图片格式,甚至可以将PDF中的附件提取出来。 主要功能 支持嵌入式字体,Truetype 字体和CJK字体。 支持绘图。比如:矩形,环形,弧形,椭圆形,也可以自定笔刷将其填充。 可以将图片从数据流,磁盘文件中载入到PDF 文档中。 在PDF 文档中既可以绘制梯状图形和矢量图像,还支持掩模和水印图像。 可以在PDF 文档中载入数据表。可以设置表中的行和列的格式,还可以在表内加入图形元素。 自动对PDF 中的大型表格进行分页。 创建窗体字段。比如在PDF 文档中创建按钮,文本框,列表框,复选框等等。 在PDF 中插入页眉页脚。 通过设置所有者密码和用户密码来加密PDF文档。 通过作者的签名来保护PDF文档。 读取当前PDF文档的表格并且填充表格。 HTML网页在转换到PDF文档时会拆分为多个大型页面,这些页面可以原原本本的展现在PDF文档中,而且在PDF文档的分页处没有任何文字的截断。用户还可以将这些网页在不需要临时文件的情况下,直接转换为数据流来创建PDF文档。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值