开发环境
名称 | 版本 |
---|---|
操作系统 | Windows 10 X64 |
Visual Studio Ultimate | 12.0.21005.1 REL |
iText | itext7.7.1.2 |
参考
源码下载
需要引用的 dll
下载地址:
链接:https://share.weiyun.com/CZJPfYvG
密码:jgdh6g
Common.Logging.3.4.1
Common.Logging.Core.3.4.1
itext7.7.1.2
itext7.font-asian.7.1.2
iTextSharp.5.5.13
Portable.BouncyCastle.1.8.1.3
PDF 工具类
加密类型
/// <summary>
/// 加密类型
/// </summary>
public enum EncryptionType
{
AES_256 = EncryptionConstants.ENCRYPTION_AES_256,
AES_128 = EncryptionConstants.ENCRYPTION_AES_128,
RC4_128 = EncryptionConstants.STANDARD_ENCRYPTION_128,
RC4_40 = EncryptionConstants.STANDARD_ENCRYPTION_40
}
PDF 参数类
/// <summary>
/// PDF 参数类
/// </summary>
public class PDFParams
{
/// <summary>
/// PDF 模板路径
/// </summary>
public string TemplatePath;
/// <summary>
/// PDF 模版字节
/// </summary>
public byte[] TempLateByte;
/// <summary>
/// PDF 存放的路径
/// </summary>
public string FileDirectory;
/// <summary>
/// 默认字体大小
/// </summary>
//public int DefaultFontSize = 12;
public int DefaultFontSize = 32;
/// <summary>
/// 默认字体:宋体
/// </summary>
public string DefaultFontPath = @"C:\Windows\Fonts\simsun.ttc,0";
/// <summary>
/// 需要添加水印的 X 坐标
/// </summary>
public float X { get; set; }
/// <summary>
/// 需要添加水印的 Y 坐标
/// </summary>
public float Y { get; set; }
/// <summary>
/// 应用于文本的旋转角度,以弧度为单位
/// 为 0 则不旋转
/// </summary>
public float Angle { get; set; }
/// <summary>
/// 水印文字
/// </summary>
public string Watermark { get; set; }
/// <summary>
/// 水印文字-RBG 值-R 值
/// </summary>
public int R = 210;
/// <summary>
/// 水印文字-RBG 值-G 值
/// </summary>
public int G = 210;
/// <summary>
/// 水印文字-RBG 值-B 值
/// </summary>
public int B = 210;
/// <summary>
/// 打开文档时输入的密码
/// </summary>
public string userPassword;
/// <summary>
/// 用户编辑 PDF 时需要的密码
/// </summary>
public string ownerPassword;
}
生产字节码文件需要用的类-MemoryTributary
/// <summary>
/// MemoryTributary is a re-implementation of MemoryStream that uses a dynamic list of byte arrays as a backing store, instead of a single byte array, the allocation
/// of which will fail for relatively small streams as it requires contiguous memory.
/// </summary>
public class MemoryTributary : Stream /* http://msdn.microsoft.com/en-us/library/system.io.stream.aspx */
{
#region Constructors
public MemoryTributary()
{
Position = 0;
}
public MemoryTributary(byte[] source)
{
this.Write(source, 0, source.Length);
Position = 0;
}
/* length is ignored because capacity has no meaning unless we implement an artifical limit */
public MemoryTributary(int length)
{
SetLength(length);
Position = length;
byte[] d = block; //access block to prompt the allocation of memory
Position = 0;
}
#endregion
#region Status Properties
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return true; }
}
public override bool CanWrite
{
get { return true; }
}
#endregion
#region Public Properties
public override long Length
{
get { return length; }
}
public override long Position { get; set; }
#endregion
#region Members
protected long length = 0;
protected long blockSize = 65536;
protected List<byte[]> blocks = new List<byte[]>();
#endregion
#region Internal Properties
/* Use these properties to gain access to the appropriate block of memory for the current Position */
/// <summary>
/// The block of memory currently addressed by Position
/// </summary>
protected byte[] block
{
get
{
while (blocks.Count <= blockId)
blocks.Add(new byte[blockSize]);
return blocks[(int)blockId];
}
}
/// <summary>
/// The id of the block currently addressed by Position
/// </summary>
protected long blockId
{
get { return Position / blockSize; }
}
/// <summary>
/// The offset of the byte currently addressed by Position, into the block that contains it
/// </summary>
protected long blockOffset
{
get { return Position % blockSize; }
}
#endregion
#region Public Stream Methods
public override void Flush()
{
}
public override int Read(byte[] buffer, int offset, int count)
{
long lcount = (long)count;
if (lcount < 0)
{
throw new ArgumentOutOfRangeException("count", lcount, "Number of bytes to copy cannot be negative.");
}
long remaining = (length - Position);
if (lcount > remaining)
lcount = remaining;
if (buffer == null)
{
throw new ArgumentNullException("buffer", "Buffer cannot be null.");
}
if (offset < 0)
{
throw new ArgumentOutOfRangeException("offset", offset, "Destination offset cannot be negative.");
}
int read = 0;
long copysize = 0;
do
{
copysize = Math.Min(lcount, (blockSize - blockOffset));
Buffer.BlockCopy(block, (int)blockOffset, buffer, offset, (int)copysize);
lcount -= copysize;
offset += (int)copysize;
read += (int)copysize;
Position += copysize;
} while (lcount > 0);
return read;
}
public override long Seek(long offset, SeekOrigin origin)
{
switch (origin)
{
case SeekOrigin.Begin:
Position = offset;
break;
case SeekOrigin.Current:
Position += offset;
break;
case SeekOrigin.End:
Position = Length - offset;
break;
}
return Position;
}
public override void SetLength(long value)
{
length = value;
}
public override void Write(byte[] buffer, int offset, int count)
{
long initialPosition = Position;
int copysize;
try
{
do
{
copysize = Math.Min(count, (int)(blockSize - blockOffset));
EnsureCapacity(Position + copysize);
Buffer.BlockCopy(buffer, (int)offset, block, (int)blockOffset, copysize);
count -= copysize;
offset += copysize;
Position += copysize;
} while (count > 0);
}
catch (Exception e)
{
Position = initialPosition;
throw e;
}
}
public override int ReadByte()
{
if (Position >= length)
return -1;
byte b = block[blockOffset];
Position++;
return b;
}
public override void WriteByte(byte value)
{
EnsureCapacity(Position + 1);
block[blockOffset] = value;
Position++;
}
protected void EnsureCapacity(long intended_length)
{
if (intended_length > length)
length = (intended_length);
}
#endregion
#region IDispose
/* http://msdn.microsoft.com/en-us/library/fs2xkftw.aspx */
protected override void Dispose(bool disposing)
{
/* We do not currently use unmanaged resources */
base.Dispose(disposing);
}
#endregion
#region Public Additional Helper Methods
/// <summary>
/// Returns the entire content of the stream as a byte array. This is not safe because the call to new byte[] may
/// fail if the stream is large enough. Where possible use methods which operate on streams directly instead.
/// </summary>
/// <returns>A byte[] containing the current data in the stream</returns>
public byte[] ToArray()
{
long firstposition = Position;
Position = 0;
byte[] destination = new byte[Length];
Read(destination, 0, (int)Length);
Position = firstposition;
return destination;
}
/// <summary>
/// Reads length bytes from source into the this instance at the current position.
/// </summary>
/// <param name="source">The stream containing the data to copy</param>
/// <param name="length">The number of bytes to copy</param>
public void ReadFrom(Stream source, long length)
{
byte[] buffer = new byte[4096];
int read;
do
{
read = source.Read(buffer, 0, (int)Math.Min(4096, length));
length -= read;
this.Write(buffer, 0, read);
} while (length > 0);
}
/// <summary>
/// Writes the entire stream into destination, regardless of Position, which remains unchanged.
/// </summary>
/// <param name="destination">The stream to write the content of this stream to</param>
public void WriteTo(Stream destination)
{
long initialpos = Position;
Position = 0;
this.CopyTo(destination);
Position = initialpos;
}
#endregion
}
核心-PDF 生成类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using iText.IO.Font;
using iText.IO.Image;
using iText.Barcodes;
using iText.Kernel.Colors;
using iText.Kernel.Font;
using iText.Kernel.Pdf;
using iText.Layout;
using iText.Layout.Borders;
using iText.Layout.Element;
using iText.Layout.Properties;
using iText.Barcodes.Qrcode;
using iText.IO.Source;
using iText.Kernel.Pdf.Annot;
using iText.Kernel.Pdf.Xobject;
namespace ConsoleApplication1.Utils
{
/// <summary>
/// PDF 生成类
/// </summary>
public class PDFMaker
{
/// <summary>
/// PDF 参数
/// </summary>
PDFParams1 pdfParams;
/// <summary>
/// PDF 模版
/// </summary>
Lazy<PdfPage> Template;
/// <summary>
/// 字体
/// </summary>
Lazy<PdfFont> defaultFont;
/// <summary>
/// 构造函数
/// </summary>
public PDFMaker1(PDFParams pdfParam)
{
pdfParams = pdfParam;
if (!string.IsNullOrWhiteSpace(pdfParams.TemplatePath))
{
Template = new Lazy<PdfPage>(() =>
{
return new PdfDocument(new PdfReader(pdfParams.TemplatePath)).GetPage(1);
});
}
else if (pdfParam.TempLateByte != null && pdfParam.TempLateByte.Length > 0)
{
Template = new Lazy<PdfPage>(() =>
{
return new PdfDocument(new PdfReader(new MemoryStream(pdfParam.TempLateByte))).GetPage(1);
});
}
defaultFont = new Lazy<PdfFont>(() =>
{
PdfFont font;
font= PdfFontFactory.CreateFont(pdfParams.DefaultFontPath, PdfEncodings.IDENTITY_H,true);
font.SetSubset(true);
return font;
});
}
/// <summary>
/// 生成 PDF 到指定路径
/// </summary>
/// <returns>生成成功返回 true</returns>
public bool MakePDF()
{
string filePath = pdfParams.FileDirectory;
PdfWriter writer = new PdfWriter(filePath);
MakePDFInit(writer);
return true;
}
/// <summary>
/// 生成加密后的 PDF 到指定路径
/// 参考:https://www.cnblogs.com/nuomibaibai/p/16635287.html
/// </summary>
/// <returns>生成成功返回 true</returns>
public bool MakePasswordPDF() {
// Set document options
int document_options = 0;
WriterProperties prop = new WriterProperties(); // Set properties of output
//userPassword:打开文档时输入的密码
byte[] userPassword = null;
if (!String.IsNullOrWhiteSpace(pdfParams.userPassword)) {
userPassword = Encoding.ASCII.GetBytes(pdfParams.userPassword);
}
//ownerPassword:用户编辑 PDF 时需要的密码
byte[] ownerPassword = null;
if (!String.IsNullOrWhiteSpace(pdfParams.ownerPassword))
{
ownerPassword = Encoding.ASCII.GetBytes(pdfParams.ownerPassword);
}
if (userPassword == null && ownerPassword == null) {
throw new Exception("必须设置打开文档时输入的密码或者用户编辑 PDF 时需要的密码");
}
//设置密码
prop.SetStandardEncryption(userPassword, ownerPassword, document_options, (int)EncryptionType.AES_256); // Enable encryption
string filePath = pdfParams.FileDirectory;
PdfWriter writer = new PdfWriter(filePath, prop);
MakePDFInit(writer);
return true;
}
/// <summary>
/// 生成 PDF 字节码
/// </summary>
/// <returns>PDF 字节码</returns>
public byte[] MakePDFStream()
{
MemoryTributary file = new MemoryTributary();
PdfWriter writer = new PdfWriter(file);
MakePDFInit(writer);
file.Position = 0;
byte[] bytes = new byte[file.Length];
file.Read(bytes, 0, bytes.Length);
return bytes;
}
/// <summary>
/// 生成 PDF
/// </summary>
/// <param name="writer">PdfWriter</param>
public void MakePDFInit(PdfWriter writer)
{
PdfDocument pdfDoc = new PdfDocument(writer);
Document doc = new Document(pdfDoc);
doc.SetFont(defaultFont.Value);
doc.SetFontSize(pdfParams.DefaultFontSize);
//doc.SetFontColor(new DeviceRgb(pdfParams.R, pdfParams.G, pdfParams.B));
//doc.SetStrokeColor(new DeviceRgb(pdfParams.R, pdfParams.G, pdfParams.B));
//上面的 rgb 无法生效,只能生效一种颜色
doc.SetFontColor(new DeviceRgb(System.Drawing.Color.FromArgb((int)pdfParams.R, (int)pdfParams.G, (int)pdfParams.B)));
doc.SetBold();
pdfDoc.AddPage(Template.Value.CopyTo(pdfDoc));//在当前页的基础上添加,没有这句话就是把整个 PDF 的内容覆盖掉
if (pdfParams.Angle == 0)
{
Text txt = new Text(pdfParams.Watermark);
doc.ShowTextAligned(new Paragraph(txt), pdfParams.X, pdfParams.Y, TextAlignment.LEFT, VerticalAlignment.TOP);
}
else
{
doc.ShowTextAligned(pdfParams.Watermark, pdfParams.X, pdfParams.Y, TextAlignment.LEFT, VerticalAlignment.TOP, pdfParams.Angle);
}
doc.Close();
}
}
}
测试
生成 PDF(不加密)
//第一种方式:生成 PDF
//PDF 参数
PDFParams pdfParams = new PDFParams();
pdfParams.TemplatePath = "D:\\data\\105.pdf";
pdfParams.FileDirectory = "D:\\data\\105-1.pdf";
pdfParams.Watermark = "适用范围:xxx\n有效期:2022年5月9日11:14:36\n再次复印无效";
pdfParams.X = 77f;
pdfParams.Y = 692.5f;
pdfParams.Angle = 0f;
PDFMaker pdfMaker = new PDFMaker(pdfParams);
pdfMaker.MakePDF();
生成字节码(不加密)
//第二种方式:生成字节码保存为 pdf
//PDF 参数
PDFParams pdfParams = new PDFParams();
pdfParams.TemplatePath = "D:\\data\\105A.pdf";
pdfParams.Watermark = "适用范围:xxx\n有效期:2022年5月9日11:14:36\n再次复印无效";
pdfParams.X = 77f;
pdfParams.Y = 692.5f;
pdfParams.Angle = 4.71f;//这里适合 PDF 页面是旋转过的,比如 A4 变成了旋转过的,正常写入字体就是竖着的 A4,这个时候就需要对字体就行旋转
PDFMaker pdfMaker = new PDFMaker(pdfParams);
byte[] bytes = pdfMaker.MakePDFStream();
File.WriteAllBytes("D:/data/105-2.pdf", bytes);
pdfParams.Angle = 0f;
pdfParams.Angle = 4.71f;
根据字节码生成(不加密)
//第三种方式:根据字节码保存为 pdf
//PDF 参数
PDFParams pdfParams = new PDFParams();
pdfParams.TempLateByte = File.ReadAllBytes("D:\\data\\105.pdf");
pdfParams.FileDirectory = "D:\\data\\105-1.pdf";
pdfParams.Watermark = "适用范围:xxx\n有效期:2022年5月9日11:14:36\n再次复印无效";
//纵向
pdfParams.X = 45f;
pdfParams.Y = 330.5f;
pdfParams.Angle = 0f;
PDFMaker pdfMaker = new PDFMaker(pdfParams);
pdfMaker.MakePDF();
生成 PDF(加密)
//PDF 参数
PDFParams pdfParams = new PDFParams();
pdfParams.TemplatePath = "D:\\data\\141.pdf";
pdfParams.FileDirectory = "D:\\data\\142-加密-加密.pdf";
pdfParams.Watermark = "\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0保密文件,禁止外传,改文件仅用于\r\n\r\n[xx项目][xx平台]使用\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0出具给[xx集团]";
//纵向
pdfParams.X = 50f;
pdfParams.Y = 125.5f;
pdfParams.Angle = 19.5f;
pdfParams.userPassword = "123";
pdfParams.ownerPassword = "456";
PDFMaker pdfMaker = new PDFMaker(pdfParams);
pdfMaker.MakePasswordPDF();
- 打开
142-加密-加密.pdf
- 编辑
142-加密-加密.pdf
错误-itext not assignable to target type ‘Common.Logging.Configuration.LogSetting
解决思路:iText7 unable to setup logging
问题描述
当我把上面的代码应用到我的 Web 项目中时,报了下面的错误
{"ConfigurationReader Common.Logging.Configuration.DefaultConfigurationReader
returned unknown settings instance of type
Luoma.FMS.Framework.Logging.Configuration.LogSetting"}
{"Type 'Luoma.FMS.Framework.Logging.Configuration.LogSetting, Luoma.FMS.Framework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
of parameter 'sectionResult' is not assignable to target type
'Common.Logging.Configuration.LogSetting, Common.Logging, Version=3.4.1.0, Culture=neutral, PublicKeyToken=af08829b84f0328e'\r\n
参数名: sectionResult\r\n
实际值是 Luoma.FMS.Framework.Logging.Configuration.LogSetting。"
}
在 Common.Logging.Configuration.ArgUtils.Guard[T](Function`1 function, String messageFormat, Object[] args) 位置 C:\_oss\common-logging\src\Common.Logging.Portable\Logging\Configuration\ArgUtils.cs:行号 336
在 Common.Logging.Configuration.ArgUtils.Guard(Action action, String messageFormat, Object[] args) 位置 C:\_oss\common-logging\src\Common.Logging.Portable\Logging\Configuration\ArgUtils.cs:行号 296
在 Common.Logging.LogManager.BuildLoggerFactoryAdapter() 位置 C:\_oss\common-logging\src\Common.Logging.Portable\Logging\LogManager.cs:行号 526
在 Common.Logging.LogManager.get_Adapter() 位置 C:\_oss\common-logging\src\Common.Logging.Portable\Logging\LogManager.cs:行号 225
在 iText.Kernel.Colors.DeviceRgb..ctor(Single r, Single g, Single b)
在 Luoma.EACC.Web.Utils.PDFMaker.MakePDFInit(PdfWriter writer) 位置 d:\luoma\Proj-EACC\Code\EAC_net\Luoma.EACC.Web\Utils\PDFMaker.cs:行号 185
在 Luoma.EACC.Web.Utils.PDFMaker.MakePDFStream() 位置 d:\luoma\Proj-EACC\Code\EAC_net\Luoma.EACC.Web\Utils\PDFMaker.cs:行号 167
在 Luoma.EACC.Web.Areas.SP.Controllers.ApplyController.MakeAccountLicencePDF(AccountOpeningPermitVO accountOpeningPermitVO, String companyName, String receiverUnit, String applyReason, Nullable`1 receiptDate) 位置 d:\luoma\Proj-EACC\Code\EAC_net\Luoma.EACC.Web\Areas\SP\Controllers\ApplyController.cs:行号 1741
问题分析
根据报错信息,是下面这行代码报的错误
其实很奇怪,这行代码为什么要去写日志呢
PdfDocument pdfDoc = new PdfDocument(writer);
问题原因就是我的 Web.config
中配置了下面的日志配置信息
<configuration>
<configSections>
<sectionGroup name="common">
<section name="logging" type="Luoma.FMS.Framework.Logging.ConfigurationSectionHandler, Luoma.FMS.Framework"/>
</sectionGroup>
</configSections>
<common>
<logging>
<factoryAdapter type="LuomaFMS.Framework.Logging.Log4Net.Log4NetLoggerFactoryAdapter, Luoma.FMS.Framework">
<arg key="configType" value="FILE-WATCH" />
<arg key="configFile" value="~/log4net.config" />
</factoryAdapter>
</logging>
</common>
</configuration>
注释掉这一段,也能正常运行
<common>
<logging>
<factoryAdapter type="Luoma.FMS.Framework.Logging.Log4Net.Log4NetLoggerFactoryAdapter, Luoma.FMS.Framework">
<arg key="configType" value="FILE-WATCH" />
<arg key="configFile" value="~/log4net.config" />
</factoryAdapter>
</logging>
</common>
说明就是 PdfDocument pdfDoc = new PdfDocument(writer);
去写日志,读取了配置信息,发现不是 Common.Logging
相关的配置信息,所以报错。
问题解决
参考了 iText7 unable to setup logging
手动指定日志配置类即可
using Common.Logging;
using Common.Logging.Simple;
using Common.Logging.Configuration;
// create properties
NameValueCollection properties = new NameValueCollection();
properties["showDateTime"] = "true";
properties["level"] = "All";
// set Adapter
LogManager.Adapter = new ConsoleOutLoggerFactoryAdapter(properties);
完整代码
/// <summary>
/// 生成 PDF
/// </summary>
/// <param name="writer">PdfWriter</param>
public void MakePDFInit(PdfWriter writer)
{
//----------------------------------------------------
//https://stackoverflow.com/questions/50987189/itext7-unable-to-setup-logging
//new PdfDocument(writer); 会去写日志,会去 .config 文件读取 common.logging 配置,
//本项目的 common.logging 是单独的,和 Common.Logging 的配置不同,所以会报错 itext not assignable to target type 'Common.Logging.Configuration.LogSetting
//所以这里手动指定 LogManager,让它别去读取配置信息
// create properties
NameValueCollection properties = new NameValueCollection();
properties["showDateTime"] = "true";
properties["level"] = "All";
// set Adapter
LogManager.Adapter = new ConsoleOutLoggerFactoryAdapter(properties);
//----------------------------------------------------
PdfDocument pdfDoc = new PdfDocument(writer);
Document doc = new Document(pdfDoc);
doc.SetFont(defaultFont.Value);
doc.SetFontSize(pdfParams.DefaultFontSize);
doc.SetFontColor(new DeviceRgb(pdfParams.R, pdfParams.G, pdfParams.B));
doc.SetStrokeColor(new DeviceRgb(pdfParams.R, pdfParams.G, pdfParams.B));
doc.SetBold();
pdfDoc.AddPage(Template.Value.CopyTo(pdfDoc));//在当前页的基础上添加,没有这句话就是把整个 PDF 的内容覆盖掉
if (pdfParams.Angle == 0)
{
Text txt = new Text(pdfParams.Watermark);
doc.ShowTextAligned(new Paragraph(txt), pdfParams.X, pdfParams.Y, TextAlignment.LEFT, VerticalAlignment.TOP);
}
else
{
doc.ShowTextAligned(pdfParams.Watermark, pdfParams.X, pdfParams.Y, TextAlignment.LEFT, VerticalAlignment.TOP, pdfParams.Angle);
}
doc.Close();
}
}
我真的是服了,这个 PDF 的操作类库,去写日志信息干什么?服
其它-水印文字特殊格式
特殊文字 | 符号 |
---|---|
空格 | \u00a0 |
换行 | \r\n |