5.5.4+ 版本中的 ItextSharp 字体颜色问题

ItextSharp font color issue in ver 5.5.4+

我有一些使用红色字体颜色创建红色 "stamp" 的代码:

    string StampDate = DateTime.Now.ToString("MM/dd/yyyy");
    string FontPath = Server.MapPath("/assets/Fonts");
    string OrigFile = Server.MapPath("/test.pdf");
    const int OpacityPercent = 80;
    const float PDFPaidFontSize = 28;
    const float PDFCopyX = 170;
    const float PDFPaidX = 385;
    const float PDFY = 20;
    const float PDFDateXOffset = 7;
    const float PDFDateYOffset = 12;
    const float PDFDateFontSize = 10;
    const string PaidStampTxt = "PAID";
    const string CopyStampTxt = "COPY";
    const string ArialFilename = "arialbd.ttf";
    PdfStamper stamper = null;
    PdfReader reader = null;
    PdfReader.unethicalreading = true;
    MemoryStream streamPDF;
    try
    {
        reader = new PdfReader(OrigFile);
        streamPDF = new MemoryStream();
        stamper = new PdfStamper(reader, streamPDF);
        for (int i = 1; i <= reader.NumberOfPages; i++)
        {
            PdfGState gstate = new PdfGState();
            gstate.FillOpacity = gstate.StrokeOpacity = OpacityPercent / 100F;
            PdfContentByte overContent = stamper.GetOverContent(i);
            overContent.SaveState();
            overContent.SetGState(gstate);
            overContent.SetColorFill(BaseColor.RED);
            overContent.BeginText();
            BaseFont font = BaseFont.CreateFont(BaseFont.TIMES_BOLD, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
            overContent.SetFontAndSize(font, PDFPaidFontSize);
            overContent.ShowTextAligned(PdfContentByte.ALIGN_LEFT, CopyStampTxt, PDFCopyX, PDFY, 0);
            overContent.ShowTextAligned(PdfContentByte.ALIGN_LEFT, PaidStampTxt, PDFPaidX, PDFY, 0);
            overContent.SetColorFill(BaseColor.BLACK);
            font = BaseFont.CreateFont(Path.Combine(FontPath, ArialFilename), BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
            overContent.SetFontAndSize(font, PDFDateFontSize);
            overContent.ShowTextAligned(PdfContentByte.ALIGN_LEFT, StampDate, PDFPaidX + PDFDateXOffset, PDFY - PDFDateYOffset, 0);
            overContent.EndText();
            overContent.RestoreState();
        }
    }
    finally
    {
        if (stamper != null)
        {
            stamper.Close();
        }
        if (reader != null)
        {
            reader.Close();
        }
    }
    byte[] pdf = streamPDF.ToArray();
    Response.Cache.SetCacheability(HttpCacheability.NoCache);
    Response.Buffer = false;
    Response.Clear();
    Response.ClearContent();
    Response.ClearHeaders();
    Response.Charset = string.Empty;
    Response.ContentType = "application/pdf";
    Response.AddHeader("content-length", pdf.Length.ToString());
    Response.AddHeader("Content-Disposition", "inline;filename=test.pdf;");
    Response.BinaryWrite(pdf);
    Response.Close();

生成的 pdf 文本具有灰色而不是红色。 当我恢复到版本 5.5.3 时,它再次显示为红色。我试过 5.5.4 和 5.5.5,它们似乎都有同样的问题。

我的问题是:这是一个错误还是我需要将我的代码更改为更新的代码?

编辑:这似乎只是某些 pdf 文件的问题。更改字体和pdf文件版本似乎没有效果。

比较一个有效的 pdf 和一个无效的 pdf(我都不能公开分享)我注意到无效的 pdf 是一个带标签的 pdf,启用了快速网络视图,由 adobe 制作pdf 库。有效的 pdf 不是带标签的 pdf,没有启用快速 Web 视图,并且是由 itextsharp 创建的。

由于我无法控制源 pdf 文件是什么,我恢复到似乎一直有效的早期版本的 itextsharp。

第一次尝试重现问题

我刚刚针对这些值执行了您的代码:

        int OpacityPercent = 70;
        int PDFPaidFontSize = 20;
        string CopyStampTxt = "COPY";
        string PaidStampTxt = "PAID";
        int PDFCopyX = 100;
        int PDFPaidX = 250;
        int PDFY = 500;
        string FontPath = @"C:\Windows\Fonts";
        string ArialFilename = "ariali.ttf";
        int PDFDateFontSize = 30;
        string StampDate = "TODAY";
        int PDFDateXOffset = 0;
        int PDFDateYOffset = 35;

使用简单的源 PDF,结果 PDF 如下所示:

与您的观察相反

The resulting pdf text has a gray color instead of red.

生成的文本颜色偏红(白色部分透明的红色)。

我使用 iTextSharp 5.5.5 进行了测试。

要获得灰色而不是红色,因此,您的变量值或源 PDF 必须有一些特殊之处,这不是一般的 iTextSharp 问题。

使用 OP 提供的文件重现问题

在第一次尝试重现问题后,OP 提供了一个示例文件 Test.pdf,事实上,对于这个文件,相同代码的结果是:

所以,确实有问题。

分析

两种情况下添加的内容流操作比较显示:

  • 首次尝试使用我的示例 PDF:

    /Xi0 gs
    1 0 0 rg
    BT
    /Xi1 20 Tf
    1 0 0 1 100 500 Tm
    (COPY) Tj
    
  • 第二次尝试使用 OP 的示例文件:

    /Xi0 gs
    1 0 0 rg
    BT
    0 g
    /Xi1 20 Tf
    1 0 0 1 100 500 Tm
    (COPY) Tj
    

因此,尽管使用了相同的代码,但在后一种情况下还有一个额外的 0 g 操作,并且该操作选择黑色作为填充颜色。

这真是一个惊喜。因此,我查看了 iText 代码和代码历史以寻求解释(我选择了 iText/Java 代码,因为这是原始开发发生的地方,可以更彻底地检查更改)。

确实,PdfContentByte.beginText 结尾为:

if (isTagged()) {
    try {
        restoreColor();
    } catch (IOException ioe) {

    }
}

背景

因此,对于带标签的 PDF,此方法 "resets the color"。为什么?

查看代码历史可以得到一些提示

  • 修订版 5499 标记 PDF 支持:将图形和文本合二为一 canvas。还没准备好...

  • 修订版 5515 现在 iText 可以将文本和图形写入 1 canvas。为此,您应该将 PdfDocument.putTextAndGraphicsTogether 设置为 true。

    这里先出现上面的方块,略有不同

    if (autoControlTextBlocks) {
        try {
            restoreColor();
        } catch (IOException ioe) {
    
        }
    }
    

    即此处仅当 autoControlTextBlockstrue.

  • 时才恢复颜色
  • ...

  • 修订版 5533 writer.isTagged 属性 现在决定是将所有内容写入一个 canvas 还是单独写入

    此处标志 autoControlTextBlocks 被相关编写器的 isTagged 调用所取代。

我的解读:

  • 为了正确支持带标签的 PDF,将图形和文本保存在一个 canvas 中是必要的或至少是有利的(以前它们是创建的在不同的 canvasses 中最终被连接起来,因此相关的图形和文本在内容中彼此远离)。

  • 为了在高级代码中保持图形和文本的开销最小,新的 autoControlTextBlocks 模式已添加到 PdfContentByte 中,它启动并在需要时自动停止文本对象并保存和恢复单独的文本颜色集。

  • 此模式似乎已被选为支持 iText 中标记内容的方式,但似乎未被认为对其他上下文有用。因此,此模式现在自动用于标记文件。

在我看来这个选择不是最优的。 PdfContentByte 是公开可用的 iText API 的一部分,并且公开宣传(如 "over content")用于对生成的或预先存在的 PDF 进行低级调整。引入这样的副作用违反了 API 契约,至少它是一种阻碍人们升级的麻烦。

解决方法

只需切换颜色设置和文本对象启动操作的顺序,使用

...
overContent.BeginText();
overContent.SetColorFill(BaseColor.RED);
...

结果

分辨率

如果我对最终签到的解释正确,则此问题应在 iText 5.5.6 版中得到修复。

提交 301a45b57dcef37ae0ec3625fbdd6caaf4004a3a

在 PdfContentByte class (DEV-1371) 中删除了已弃用的保存和恢复标记 pdf 文档颜色的逻辑。