C#-使用 iText 给 PDF 添加水印

2022年05月09日 16:25 · 阅读(2853) ·

开发环境

名称 版本
操作系统 Windows 10 X64
Visual Studio Ultimate 12.0.21005.1 REL
iText itext7.7.1.2

参考

C#基于 iTextSharp.text.pdf 实现PDF水印添加功能
C# PDF加密

Java-使用 iText 给 PDF 添加水印

源码下载

PDFMaker1.zip

需要引用的 dll

下载地址:

链接:https://share.weiyun.com/CZJPfYvG
密码:jgdh6g

  1. Common.Logging.3.4.1
  2. Common.Logging.Core.3.4.1
  3. itext7.7.1.2
  4. itext7.font-asian.7.1.2
  5. iTextSharp.5.5.13
  6. Portable.BouncyCastle.1.8.1.3

PDF 工具类

加密类型

  1. /// <summary>
  2. /// 加密类型
  3. /// </summary>
  4. public enum EncryptionType
  5. {
  6. AES_256 = EncryptionConstants.ENCRYPTION_AES_256,
  7. AES_128 = EncryptionConstants.ENCRYPTION_AES_128,
  8. RC4_128 = EncryptionConstants.STANDARD_ENCRYPTION_128,
  9. RC4_40 = EncryptionConstants.STANDARD_ENCRYPTION_40
  10. }

PDF 参数类

  1. /// <summary>
  2. /// PDF 参数类
  3. /// </summary>
  4. public class PDFParams
  5. {
  6. /// <summary>
  7. /// PDF 模板路径
  8. /// </summary>
  9. public string TemplatePath;
  10. /// <summary>
  11. /// PDF 模版字节
  12. /// </summary>
  13. public byte[] TempLateByte;
  14. /// <summary>
  15. /// PDF 存放的路径
  16. /// </summary>
  17. public string FileDirectory;
  18. /// <summary>
  19. /// 默认字体大小
  20. /// </summary>
  21. //public int DefaultFontSize = 12;
  22. public int DefaultFontSize = 32;
  23. /// <summary>
  24. /// 默认字体:宋体
  25. /// </summary>
  26. public string DefaultFontPath = @"C:\Windows\Fonts\simsun.ttc,0";
  27. /// <summary>
  28. /// 需要添加水印的 X 坐标
  29. /// </summary>
  30. public float X { get; set; }
  31. /// <summary>
  32. /// 需要添加水印的 Y 坐标
  33. /// </summary>
  34. public float Y { get; set; }
  35. /// <summary>
  36. /// 应用于文本的旋转角度,以弧度为单位
  37. /// 为 0 则不旋转
  38. /// </summary>
  39. public float Angle { get; set; }
  40. /// <summary>
  41. /// 水印文字
  42. /// </summary>
  43. public string Watermark { get; set; }
  44. /// <summary>
  45. /// 水印文字-RBG 值-R 值
  46. /// </summary>
  47. public int R = 210;
  48. /// <summary>
  49. /// 水印文字-RBG 值-G 值
  50. /// </summary>
  51. public int G = 210;
  52. /// <summary>
  53. /// 水印文字-RBG 值-B 值
  54. /// </summary>
  55. public int B = 210;
  56. /// <summary>
  57. /// 打开文档时输入的密码
  58. /// </summary>
  59. public string userPassword;
  60. /// <summary>
  61. /// 用户编辑 PDF 时需要的密码
  62. /// </summary>
  63. public string ownerPassword;
  64. }

生产字节码文件需要用的类-MemoryTributary

  1. /// <summary>
  2. /// 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
  3. /// of which will fail for relatively small streams as it requires contiguous memory.
  4. /// </summary>
  5. public class MemoryTributary : Stream /* http://msdn.microsoft.com/en-us/library/system.io.stream.aspx */
  6. {
  7. #region Constructors
  8. public MemoryTributary()
  9. {
  10. Position = 0;
  11. }
  12. public MemoryTributary(byte[] source)
  13. {
  14. this.Write(source, 0, source.Length);
  15. Position = 0;
  16. }
  17. /* length is ignored because capacity has no meaning unless we implement an artifical limit */
  18. public MemoryTributary(int length)
  19. {
  20. SetLength(length);
  21. Position = length;
  22. byte[] d = block; //access block to prompt the allocation of memory
  23. Position = 0;
  24. }
  25. #endregion
  26. #region Status Properties
  27. public override bool CanRead
  28. {
  29. get { return true; }
  30. }
  31. public override bool CanSeek
  32. {
  33. get { return true; }
  34. }
  35. public override bool CanWrite
  36. {
  37. get { return true; }
  38. }
  39. #endregion
  40. #region Public Properties
  41. public override long Length
  42. {
  43. get { return length; }
  44. }
  45. public override long Position { get; set; }
  46. #endregion
  47. #region Members
  48. protected long length = 0;
  49. protected long blockSize = 65536;
  50. protected List<byte[]> blocks = new List<byte[]>();
  51. #endregion
  52. #region Internal Properties
  53. /* Use these properties to gain access to the appropriate block of memory for the current Position */
  54. /// <summary>
  55. /// The block of memory currently addressed by Position
  56. /// </summary>
  57. protected byte[] block
  58. {
  59. get
  60. {
  61. while (blocks.Count <= blockId)
  62. blocks.Add(new byte[blockSize]);
  63. return blocks[(int)blockId];
  64. }
  65. }
  66. /// <summary>
  67. /// The id of the block currently addressed by Position
  68. /// </summary>
  69. protected long blockId
  70. {
  71. get { return Position / blockSize; }
  72. }
  73. /// <summary>
  74. /// The offset of the byte currently addressed by Position, into the block that contains it
  75. /// </summary>
  76. protected long blockOffset
  77. {
  78. get { return Position % blockSize; }
  79. }
  80. #endregion
  81. #region Public Stream Methods
  82. public override void Flush()
  83. {
  84. }
  85. public override int Read(byte[] buffer, int offset, int count)
  86. {
  87. long lcount = (long)count;
  88. if (lcount < 0)
  89. {
  90. throw new ArgumentOutOfRangeException("count", lcount, "Number of bytes to copy cannot be negative.");
  91. }
  92. long remaining = (length - Position);
  93. if (lcount > remaining)
  94. lcount = remaining;
  95. if (buffer == null)
  96. {
  97. throw new ArgumentNullException("buffer", "Buffer cannot be null.");
  98. }
  99. if (offset < 0)
  100. {
  101. throw new ArgumentOutOfRangeException("offset", offset, "Destination offset cannot be negative.");
  102. }
  103. int read = 0;
  104. long copysize = 0;
  105. do
  106. {
  107. copysize = Math.Min(lcount, (blockSize - blockOffset));
  108. Buffer.BlockCopy(block, (int)blockOffset, buffer, offset, (int)copysize);
  109. lcount -= copysize;
  110. offset += (int)copysize;
  111. read += (int)copysize;
  112. Position += copysize;
  113. } while (lcount > 0);
  114. return read;
  115. }
  116. public override long Seek(long offset, SeekOrigin origin)
  117. {
  118. switch (origin)
  119. {
  120. case SeekOrigin.Begin:
  121. Position = offset;
  122. break;
  123. case SeekOrigin.Current:
  124. Position += offset;
  125. break;
  126. case SeekOrigin.End:
  127. Position = Length - offset;
  128. break;
  129. }
  130. return Position;
  131. }
  132. public override void SetLength(long value)
  133. {
  134. length = value;
  135. }
  136. public override void Write(byte[] buffer, int offset, int count)
  137. {
  138. long initialPosition = Position;
  139. int copysize;
  140. try
  141. {
  142. do
  143. {
  144. copysize = Math.Min(count, (int)(blockSize - blockOffset));
  145. EnsureCapacity(Position + copysize);
  146. Buffer.BlockCopy(buffer, (int)offset, block, (int)blockOffset, copysize);
  147. count -= copysize;
  148. offset += copysize;
  149. Position += copysize;
  150. } while (count > 0);
  151. }
  152. catch (Exception e)
  153. {
  154. Position = initialPosition;
  155. throw e;
  156. }
  157. }
  158. public override int ReadByte()
  159. {
  160. if (Position >= length)
  161. return -1;
  162. byte b = block[blockOffset];
  163. Position++;
  164. return b;
  165. }
  166. public override void WriteByte(byte value)
  167. {
  168. EnsureCapacity(Position + 1);
  169. block[blockOffset] = value;
  170. Position++;
  171. }
  172. protected void EnsureCapacity(long intended_length)
  173. {
  174. if (intended_length > length)
  175. length = (intended_length);
  176. }
  177. #endregion
  178. #region IDispose
  179. /* http://msdn.microsoft.com/en-us/library/fs2xkftw.aspx */
  180. protected override void Dispose(bool disposing)
  181. {
  182. /* We do not currently use unmanaged resources */
  183. base.Dispose(disposing);
  184. }
  185. #endregion
  186. #region Public Additional Helper Methods
  187. /// <summary>
  188. /// Returns the entire content of the stream as a byte array. This is not safe because the call to new byte[] may
  189. /// fail if the stream is large enough. Where possible use methods which operate on streams directly instead.
  190. /// </summary>
  191. /// <returns>A byte[] containing the current data in the stream</returns>
  192. public byte[] ToArray()
  193. {
  194. long firstposition = Position;
  195. Position = 0;
  196. byte[] destination = new byte[Length];
  197. Read(destination, 0, (int)Length);
  198. Position = firstposition;
  199. return destination;
  200. }
  201. /// <summary>
  202. /// Reads length bytes from source into the this instance at the current position.
  203. /// </summary>
  204. /// <param name="source">The stream containing the data to copy</param>
  205. /// <param name="length">The number of bytes to copy</param>
  206. public void ReadFrom(Stream source, long length)
  207. {
  208. byte[] buffer = new byte[4096];
  209. int read;
  210. do
  211. {
  212. read = source.Read(buffer, 0, (int)Math.Min(4096, length));
  213. length -= read;
  214. this.Write(buffer, 0, read);
  215. } while (length > 0);
  216. }
  217. /// <summary>
  218. /// Writes the entire stream into destination, regardless of Position, which remains unchanged.
  219. /// </summary>
  220. /// <param name="destination">The stream to write the content of this stream to</param>
  221. public void WriteTo(Stream destination)
  222. {
  223. long initialpos = Position;
  224. Position = 0;
  225. this.CopyTo(destination);
  226. Position = initialpos;
  227. }
  228. #endregion
  229. }

核心-PDF 生成类

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.IO;
  6. using System.Runtime.InteropServices;
  7. using System.Threading.Tasks;
  8. using iText.IO.Font;
  9. using iText.IO.Image;
  10. using iText.Barcodes;
  11. using iText.Kernel.Colors;
  12. using iText.Kernel.Font;
  13. using iText.Kernel.Pdf;
  14. using iText.Layout;
  15. using iText.Layout.Borders;
  16. using iText.Layout.Element;
  17. using iText.Layout.Properties;
  18. using iText.Barcodes.Qrcode;
  19. using iText.IO.Source;
  20. using iText.Kernel.Pdf.Annot;
  21. using iText.Kernel.Pdf.Xobject;
  22. namespace ConsoleApplication1.Utils
  23. {
  24. /// <summary>
  25. /// PDF 生成类
  26. /// </summary>
  27. public class PDFMaker
  28. {
  29. /// <summary>
  30. /// PDF 参数
  31. /// </summary>
  32. PDFParams1 pdfParams;
  33. /// <summary>
  34. /// PDF 模版
  35. /// </summary>
  36. Lazy<PdfPage> Template;
  37. /// <summary>
  38. /// 字体
  39. /// </summary>
  40. Lazy<PdfFont> defaultFont;
  41. /// <summary>
  42. /// 构造函数
  43. /// </summary>
  44. public PDFMaker1(PDFParams pdfParam)
  45. {
  46. pdfParams = pdfParam;
  47. if (!string.IsNullOrWhiteSpace(pdfParams.TemplatePath))
  48. {
  49. Template = new Lazy<PdfPage>(() =>
  50. {
  51. return new PdfDocument(new PdfReader(pdfParams.TemplatePath)).GetPage(1);
  52. });
  53. }
  54. else if (pdfParam.TempLateByte != null && pdfParam.TempLateByte.Length > 0)
  55. {
  56. Template = new Lazy<PdfPage>(() =>
  57. {
  58. return new PdfDocument(new PdfReader(new MemoryStream(pdfParam.TempLateByte))).GetPage(1);
  59. });
  60. }
  61. defaultFont = new Lazy<PdfFont>(() =>
  62. {
  63. PdfFont font;
  64. font= PdfFontFactory.CreateFont(pdfParams.DefaultFontPath, PdfEncodings.IDENTITY_H,true);
  65. font.SetSubset(true);
  66. return font;
  67. });
  68. }
  69. /// <summary>
  70. /// 生成 PDF 到指定路径
  71. /// </summary>
  72. /// <returns>生成成功返回 true</returns>
  73. public bool MakePDF()
  74. {
  75. string filePath = pdfParams.FileDirectory;
  76. PdfWriter writer = new PdfWriter(filePath);
  77. MakePDFInit(writer);
  78. return true;
  79. }
  80. /// <summary>
  81. /// 生成加密后的 PDF 到指定路径
  82. /// 参考:https://www.cnblogs.com/nuomibaibai/p/16635287.html
  83. /// </summary>
  84. /// <returns>生成成功返回 true</returns>
  85. public bool MakePasswordPDF() {
  86. // Set document options
  87. int document_options = 0;
  88. WriterProperties prop = new WriterProperties(); // Set properties of output
  89. //userPassword:打开文档时输入的密码
  90. byte[] userPassword = null;
  91. if (!String.IsNullOrWhiteSpace(pdfParams.userPassword)) {
  92. userPassword = Encoding.ASCII.GetBytes(pdfParams.userPassword);
  93. }
  94. //ownerPassword:用户编辑 PDF 时需要的密码
  95. byte[] ownerPassword = null;
  96. if (!String.IsNullOrWhiteSpace(pdfParams.ownerPassword))
  97. {
  98. ownerPassword = Encoding.ASCII.GetBytes(pdfParams.ownerPassword);
  99. }
  100. if (userPassword == null && ownerPassword == null) {
  101. throw new Exception("必须设置打开文档时输入的密码或者用户编辑 PDF 时需要的密码");
  102. }
  103. //设置密码
  104. prop.SetStandardEncryption(userPassword, ownerPassword, document_options, (int)EncryptionType.AES_256); // Enable encryption
  105. string filePath = pdfParams.FileDirectory;
  106. PdfWriter writer = new PdfWriter(filePath, prop);
  107. MakePDFInit(writer);
  108. return true;
  109. }
  110. /// <summary>
  111. /// 生成 PDF 字节码
  112. /// </summary>
  113. /// <returns>PDF 字节码</returns>
  114. public byte[] MakePDFStream()
  115. {
  116. MemoryTributary file = new MemoryTributary();
  117. PdfWriter writer = new PdfWriter(file);
  118. MakePDFInit(writer);
  119. file.Position = 0;
  120. byte[] bytes = new byte[file.Length];
  121. file.Read(bytes, 0, bytes.Length);
  122. return bytes;
  123. }
  124. /// <summary>
  125. /// 生成 PDF
  126. /// </summary>
  127. /// <param name="writer">PdfWriter</param>
  128. public void MakePDFInit(PdfWriter writer)
  129. {
  130. PdfDocument pdfDoc = new PdfDocument(writer);
  131. Document doc = new Document(pdfDoc);
  132. doc.SetFont(defaultFont.Value);
  133. doc.SetFontSize(pdfParams.DefaultFontSize);
  134. //doc.SetFontColor(new DeviceRgb(pdfParams.R, pdfParams.G, pdfParams.B));
  135. //doc.SetStrokeColor(new DeviceRgb(pdfParams.R, pdfParams.G, pdfParams.B));
  136. //上面的 rgb 无法生效,只能生效一种颜色
  137. doc.SetFontColor(new DeviceRgb(System.Drawing.Color.FromArgb((int)pdfParams.R, (int)pdfParams.G, (int)pdfParams.B)));
  138. doc.SetBold();
  139. pdfDoc.AddPage(Template.Value.CopyTo(pdfDoc));//在当前页的基础上添加,没有这句话就是把整个 PDF 的内容覆盖掉
  140. if (pdfParams.Angle == 0)
  141. {
  142. Text txt = new Text(pdfParams.Watermark);
  143. doc.ShowTextAligned(new Paragraph(txt), pdfParams.X, pdfParams.Y, TextAlignment.LEFT, VerticalAlignment.TOP);
  144. }
  145. else
  146. {
  147. doc.ShowTextAligned(pdfParams.Watermark, pdfParams.X, pdfParams.Y, TextAlignment.LEFT, VerticalAlignment.TOP, pdfParams.Angle);
  148. }
  149. doc.Close();
  150. }
  151. }
  152. }

测试

生成 PDF(不加密)

  1. //第一种方式:生成 PDF
  2. //PDF 参数
  3. PDFParams pdfParams = new PDFParams();
  4. pdfParams.TemplatePath = "D:\\data\\105.pdf";
  5. pdfParams.FileDirectory = "D:\\data\\105-1.pdf";
  6. pdfParams.Watermark = "适用范围:xxx\n有效期:2022年5月9日11:14:36\n再次复印无效";
  7. pdfParams.X = 77f;
  8. pdfParams.Y = 692.5f;
  9. pdfParams.Angle = 0f;
  10. PDFMaker pdfMaker = new PDFMaker(pdfParams);
  11. pdfMaker.MakePDF();

生成字节码(不加密)

  1. //第二种方式:生成字节码保存为 pdf
  2. //PDF 参数
  3. PDFParams pdfParams = new PDFParams();
  4. pdfParams.TemplatePath = "D:\\data\\105A.pdf";
  5. pdfParams.Watermark = "适用范围:xxx\n有效期:2022年5月9日11:14:36\n再次复印无效";
  6. pdfParams.X = 77f;
  7. pdfParams.Y = 692.5f;
  8. pdfParams.Angle = 4.71f;//这里适合 PDF 页面是旋转过的,比如 A4 变成了旋转过的,正常写入字体就是竖着的 A4,这个时候就需要对字体就行旋转
  9. PDFMaker pdfMaker = new PDFMaker(pdfParams);
  10. byte[] bytes = pdfMaker.MakePDFStream();
  11. File.WriteAllBytes("D:/data/105-2.pdf", bytes);

  1. pdfParams.Angle = 0f;

  1. pdfParams.Angle = 4.71f;

根据字节码生成(不加密)

  1. //第三种方式:根据字节码保存为 pdf
  2. //PDF 参数
  3. PDFParams pdfParams = new PDFParams();
  4. pdfParams.TempLateByte = File.ReadAllBytes("D:\\data\\105.pdf");
  5. pdfParams.FileDirectory = "D:\\data\\105-1.pdf";
  6. pdfParams.Watermark = "适用范围:xxx\n有效期:2022年5月9日11:14:36\n再次复印无效";
  7. //纵向
  8. pdfParams.X = 45f;
  9. pdfParams.Y = 330.5f;
  10. pdfParams.Angle = 0f;
  11. PDFMaker pdfMaker = new PDFMaker(pdfParams);
  12. pdfMaker.MakePDF();

生成 PDF(加密)

  1. //PDF 参数
  2. PDFParams pdfParams = new PDFParams();
  3. pdfParams.TemplatePath = "D:\\data\\141.pdf";
  4. pdfParams.FileDirectory = "D:\\data\\142-加密-加密.pdf";
  5. 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集团]";
  6. //纵向
  7. pdfParams.X = 50f;
  8. pdfParams.Y = 125.5f;
  9. pdfParams.Angle = 19.5f;
  10. pdfParams.userPassword = "123";
  11. pdfParams.ownerPassword = "456";
  12. PDFMaker pdfMaker = new PDFMaker(pdfParams);
  13. pdfMaker.MakePasswordPDF();
  • 打开 142-加密-加密.pdf

  • 编辑 142-加密-加密.pdf

错误-itext not assignable to target type ‘Common.Logging.Configuration.LogSetting

解决思路:iText7 unable to setup logging

问题描述

当我把上面的代码应用到我的 Web 项目中时,报了下面的错误

  1. {"ConfigurationReader Common.Logging.Configuration.DefaultConfigurationReader
  2. returned unknown settings instance of type
  3. Luoma.FMS.Framework.Logging.Configuration.LogSetting"}
  4. {"Type 'Luoma.FMS.Framework.Logging.Configuration.LogSetting, Luoma.FMS.Framework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
  5. of parameter 'sectionResult' is not assignable to target type
  6. 'Common.Logging.Configuration.LogSetting, Common.Logging, Version=3.4.1.0, Culture=neutral, PublicKeyToken=af08829b84f0328e'\r\n
  7. 参数名: sectionResult\r\n
  8. 实际值是 Luoma.FMS.Framework.Logging.Configuration.LogSetting。"
  9. }
  10. 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
  11. 在 Common.Logging.Configuration.ArgUtils.Guard(Action action, String messageFormat, Object[] args) 位置 C:\_oss\common-logging\src\Common.Logging.Portable\Logging\Configuration\ArgUtils.cs:行号 296
  12. 在 Common.Logging.LogManager.BuildLoggerFactoryAdapter() 位置 C:\_oss\common-logging\src\Common.Logging.Portable\Logging\LogManager.cs:行号 526
  13. 在 Common.Logging.LogManager.get_Adapter() 位置 C:\_oss\common-logging\src\Common.Logging.Portable\Logging\LogManager.cs:行号 225
  14. 在 iText.Kernel.Colors.DeviceRgb..ctor(Single r, Single g, Single b)
  15. 在 Luoma.EACC.Web.Utils.PDFMaker.MakePDFInit(PdfWriter writer) 位置 d:\luoma\Proj-EACC\Code\EAC_net\Luoma.EACC.Web\Utils\PDFMaker.cs:行号 185
  16. 在 Luoma.EACC.Web.Utils.PDFMaker.MakePDFStream() 位置 d:\luoma\Proj-EACC\Code\EAC_net\Luoma.EACC.Web\Utils\PDFMaker.cs:行号 167
  17. 在 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

问题分析

根据报错信息,是下面这行代码报的错误
其实很奇怪,这行代码为什么要去写日志呢

  1. PdfDocument pdfDoc = new PdfDocument(writer);

问题原因就是我的 Web.config 中配置了下面的日志配置信息

  1. <configuration>
  2. <configSections>
  3. <sectionGroup name="common">
  4. <section name="logging" type="Luoma.FMS.Framework.Logging.ConfigurationSectionHandler, Luoma.FMS.Framework"/>
  5. </sectionGroup>
  6. </configSections>
  7. <common>
  8. <logging>
  9. <factoryAdapter type="LuomaFMS.Framework.Logging.Log4Net.Log4NetLoggerFactoryAdapter, Luoma.FMS.Framework">
  10. <arg key="configType" value="FILE-WATCH" />
  11. <arg key="configFile" value="~/log4net.config" />
  12. </factoryAdapter>
  13. </logging>
  14. </common>
  15. </configuration>

注释掉这一段,也能正常运行

  1. <common>
  2. <logging>
  3. <factoryAdapter type="Luoma.FMS.Framework.Logging.Log4Net.Log4NetLoggerFactoryAdapter, Luoma.FMS.Framework">
  4. <arg key="configType" value="FILE-WATCH" />
  5. <arg key="configFile" value="~/log4net.config" />
  6. </factoryAdapter>
  7. </logging>
  8. </common>

说明就是 PdfDocument pdfDoc = new PdfDocument(writer); 去写日志,读取了配置信息,发现不是 Common.Logging 相关的配置信息,所以报错。

问题解决

参考了 iText7 unable to setup logging
手动指定日志配置类即可

  1. using Common.Logging;
  2. using Common.Logging.Simple;
  3. using Common.Logging.Configuration;
  1. // create properties
  2. NameValueCollection properties = new NameValueCollection();
  3. properties["showDateTime"] = "true";
  4. properties["level"] = "All";
  5. // set Adapter
  6. LogManager.Adapter = new ConsoleOutLoggerFactoryAdapter(properties);

完整代码

  1. /// <summary>
  2. /// 生成 PDF
  3. /// </summary>
  4. /// <param name="writer">PdfWriter</param>
  5. public void MakePDFInit(PdfWriter writer)
  6. {
  7. //----------------------------------------------------
  8. //https://stackoverflow.com/questions/50987189/itext7-unable-to-setup-logging
  9. //new PdfDocument(writer); 会去写日志,会去 .config 文件读取 common.logging 配置,
  10. //本项目的 common.logging 是单独的,和 Common.Logging 的配置不同,所以会报错 itext not assignable to target type 'Common.Logging.Configuration.LogSetting
  11. //所以这里手动指定 LogManager,让它别去读取配置信息
  12. // create properties
  13. NameValueCollection properties = new NameValueCollection();
  14. properties["showDateTime"] = "true";
  15. properties["level"] = "All";
  16. // set Adapter
  17. LogManager.Adapter = new ConsoleOutLoggerFactoryAdapter(properties);
  18. //----------------------------------------------------
  19. PdfDocument pdfDoc = new PdfDocument(writer);
  20. Document doc = new Document(pdfDoc);
  21. doc.SetFont(defaultFont.Value);
  22. doc.SetFontSize(pdfParams.DefaultFontSize);
  23. doc.SetFontColor(new DeviceRgb(pdfParams.R, pdfParams.G, pdfParams.B));
  24. doc.SetStrokeColor(new DeviceRgb(pdfParams.R, pdfParams.G, pdfParams.B));
  25. doc.SetBold();
  26. pdfDoc.AddPage(Template.Value.CopyTo(pdfDoc));//在当前页的基础上添加,没有这句话就是把整个 PDF 的内容覆盖掉
  27. if (pdfParams.Angle == 0)
  28. {
  29. Text txt = new Text(pdfParams.Watermark);
  30. doc.ShowTextAligned(new Paragraph(txt), pdfParams.X, pdfParams.Y, TextAlignment.LEFT, VerticalAlignment.TOP);
  31. }
  32. else
  33. {
  34. doc.ShowTextAligned(pdfParams.Watermark, pdfParams.X, pdfParams.Y, TextAlignment.LEFT, VerticalAlignment.TOP, pdfParams.Angle);
  35. }
  36. doc.Close();
  37. }
  38. }

我真的是服了,这个 PDF 的操作类库,去写日志信息干什么?服

其它-水印文字特殊格式

特殊文字 符号
空格 \u00a0
换行 \r\n