Windows 10+「打印到PDF」生成PDF的iText7水印翻转问题
iText7添加水印时Windows Print to PDF生成的PDF出现镜像翻转问题
我使用iText7库为PDF添加水印时发现,仅Windows 10及以上系统通过「Print to PDF」生成的PDF会出现水印翻转/镜像显示的问题,其他方式生成的PDF无异常。
核心代码
添加水印的主逻辑:
try { using var inputStream = new MemoryStream(pdfBytes); using var outputStream = new MemoryStream(); var writerProperties = password != null ? CreateEncryptedWriterProperties(password) : new WriterProperties(); using var reader = new PdfReader(inputStream); using var writer = new PdfWriter(outputStream, writerProperties); using var document = new PdfDocument(reader, writer); var pageCount = document.GetNumberOfPages(); for (int i = 1; i <= pageCount; i++) { var page = document.GetPage(i); var canvas = new PdfCanvas(page.NewContentStreamAfter(), page.GetResources(), document); DrawWatermark(canvas, watermarkText, document, page); } document.Close(); return outputStream.ToArray(); } catch (Exception ex) { _logger?.LogError(ex, "Error watermarking PDF bytes"); return null; }
水印绘制相关方法:
private void DrawWatermark(PdfCanvas canvas, string watermarkText, PdfDocument document, PdfPage page) { var pageSize = page.GetPageSize(); var (pageWidth, pageHeight) = (pageSize.GetWidth(), pageSize.GetHeight()); var dateText = _options.IncludeDate ? DateTime.Now.ToString(_options.DateFormat) : string.Empty; var font = string.IsNullOrEmpty(_options.FontPath) ? PdfFontFactory.CreateFont() : PdfFontFactory.CreateFont(_options.FontPath); var position = CalculateWatermarkPosition(watermarkText, dateText, pageWidth, pageHeight, _options.FontSize, font); ApplyWatermark(canvas, watermarkText, dateText, position, font, pageWidth, pageHeight); } private (float TextX, float TextY, float DateX, float DateY, float TextWidth, float DateWidth) CalculateWatermarkPosition( string watermarkText, string dateText, float pageWidth, float pageHeight, float fontSize, PdfFont font) { var textWidth = (font.GetWidth(watermarkText) * fontSize) / 1000; var dateWidth = (font.GetWidth(dateText) * (fontSize / 2)) / 1000; var centerX = pageWidth / 2f; var centerY = pageHeight / 2f; float verticalSpacing = fontSize / 4; return ( TextX: centerX - textWidth / 2f, TextY: centerY, DateX: centerX - dateWidth / 2f, DateY: centerY - fontSize - verticalSpacing, TextWidth: textWidth, DateWidth: dateWidth ); } private void ApplyWatermark( PdfCanvas canvas, string watermarkText, string dateText, (float TextX, float TextY, float DateX, float DateY, float TextWidth, float DateWidth) position, PdfFont font, float pageWidth, float pageHeight) { var centerX = pageWidth / 2f; var centerY = pageHeight / 2f; var angleInRadians = (float)(_options.RotationAngle * Math.PI / 180); canvas.SaveState(); var gs = new PdfExtGState().SetFillOpacity(_options.Opacity); canvas.SetExtGState(gs); canvas.BeginText() .SetFontAndSize(font, _options.FontSize) .SetFillColor(_options.Color) .SetTextRenderingMode(PdfCanvasConstants.TextRenderingMode.FILL_STROKE); var cosAngle = (float)Math.Cos(angleInRadians); var sinAngle = (float)Math.Sin(angleInRadians); var tx = position.TextX - centerX; var ty = position.TextY - centerY; var finalX = centerX + (tx * cosAngle - ty * sinAngle); var finalY = centerY + (tx * sinAngle + ty * cosAngle); canvas.SetTextMatrix( cosAngle, sinAngle, -sinAngle, cosAngle, finalX, finalY ) .ShowText(watermarkText); canvas.EndText() .RestoreState(); }
问题现象


问题原因
Windows「Print to PDF」生成的PDF页面通常带有特殊的旋转属性(Rotate)或坐标系变换,导致页面的实际显示坐标系与GetPageSize()返回的原始尺寸坐标系不一致。当前代码直接基于原始页面尺寸计算水印位置和变换矩阵,未抵消页面本身的变换,最终造成水印镜像翻转。
解决方案
修改代码以适配页面的实际旋转和坐标系:
- 获取带旋转的页面尺寸:将
GetPageSize()替换为GetPageSizeWithRotation(),获取页面实际显示的宽高。 - 抵消页面旋转:获取页面的旋转角度,调整水印的旋转逻辑,避免叠加旋转导致镜像。
- 修正文本矩阵计算:基于页面的实际坐标系重新计算水印的变换矩阵。
修改后的关键代码部分:
private void DrawWatermark(PdfCanvas canvas, string watermarkText, PdfDocument document, PdfPage page) { // 获取带旋转的页面实际尺寸 var pageSize = page.GetPageSizeWithRotation(); var (pageWidth, pageHeight) = (pageSize.GetWidth(), pageSize.GetHeight()); // 获取页面原始旋转角度 int pageRotation = page.GetRotation(); var dateText = _options.IncludeDate ? DateTime.Now.ToString(_options.DateFormat) : string.Empty; var font = string.IsNullOrEmpty(_options.FontPath) ? PdfFontFactory.CreateFont() : PdfFontFactory.CreateFont(_options.FontPath); var position = CalculateWatermarkPosition(watermarkText, dateText, pageWidth, pageHeight, _options.FontSize, font); ApplyWatermark(canvas, watermarkText, dateText, position, font, pageWidth, pageHeight, pageRotation); } private void ApplyWatermark( PdfCanvas canvas, string watermarkText, string dateText, (float TextX, float TextY, float DateX, float DateY, float TextWidth, float DateWidth) position, PdfFont font, float pageWidth, float pageHeight, int pageRotation) { var centerX = pageWidth / 2f; var centerY = pageHeight / 2f; // 调整水印旋转角度,抵消页面本身的旋转 var adjustedAngle = _options.RotationAngle - pageRotation; var angleInRadians = (float)(adjustedAngle * Math.PI / 180); canvas.SaveState(); var gs = new PdfExtGState().SetFillOpacity(_options.Opacity); canvas.SetExtGState(gs); // 先应用页面旋转的逆变换,确保坐标系正常 canvas.ConcatMatrix(page.GetPageSize().GetRotationMatrix(-pageRotation)); canvas.BeginText() .SetFontAndSize(font, _options.FontSize) .SetFillColor(_options.Color) .SetTextRenderingMode(PdfCanvasConstants.TextRenderingMode.FILL_STROKE); var cosAngle = (float)Math.Cos(angleInRadians); var sinAngle = (float)Math.Sin(angleInRadians); var tx = position.TextX - centerX; var ty = position.TextY - centerY; var finalX = centerX + (tx * cosAngle - ty * sinAngle); var finalY = centerY + (tx * sinAngle + ty * cosAngle); canvas.SetTextMatrix( cosAngle, sinAngle, -sinAngle, cosAngle, finalX, finalY ) .ShowText(watermarkText); canvas.EndText() .RestoreState(); }
说明
GetPageSizeWithRotation()返回页面旋转后的实际显示尺寸,确保位置计算基于正确的页面宽高。GetRotationMatrix(-pageRotation)抵消页面本身的旋转,将画布坐标系恢复为正常状态后再绘制水印。- 调整水印的旋转角度,避免页面旋转与水印旋转叠加导致的方向错误。
内容的提问来源于stack exchange,提问作者MiracleX
相关产品推荐
相关产品推荐

