You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();
}

问题现象

翻转的水印
正常的PDF水印

问题原因

Windows「Print to PDF」生成的PDF页面通常带有特殊的旋转属性(Rotate)或坐标系变换,导致页面的实际显示坐标系与GetPageSize()返回的原始尺寸坐标系不一致。当前代码直接基于原始页面尺寸计算水印位置和变换矩阵,未抵消页面本身的变换,最终造成水印镜像翻转。

解决方案

修改代码以适配页面的实际旋转和坐标系:

  1. 获取带旋转的页面尺寸:将GetPageSize()替换为GetPageSizeWithRotation(),获取页面实际显示的宽高。
  2. 抵消页面旋转:获取页面的旋转角度,调整水印的旋转逻辑,避免叠加旋转导致镜像。
  3. 修正文本矩阵计算:基于页面的实际坐标系重新计算水印的变换矩阵。

修改后的关键代码部分:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 22:30:06