首先,让我向您展示,如果您可以使用一个像样的 PDF 库,事情会变得多么容易。我使用 iTextSharp 作为示例,但也可以使用 PDFBox 或 PDFNet 等其他工具来完成(@Ika 在他的回答中已经提到过):
PdfReader reader = new PdfReader(sourcePdf);
using (PdfStamper stamper = new PdfStamper(reader, targetPdfStream)) {
Font FONT = new Font(Font.FontFamily.HELVETICA, 12, Font.BOLD, new GrayColor(0.75f));
PdfContentByte canvas = stamper.GetOverContent(1);
ColumnText.ShowTextAligned(
canvas,
Element.ALIGN_LEFT,
new Phrase("Hello people!", FONT),
36, 540, 0
);
}
(源自网络化 iTextSharp 示例 StampText.cs解释于第6章 of iText 实际应用 — 第二版.)
(您选择哪个 PDF 库取决于您的一般要求和可用的许可证模型。)
尽管此类 PDF 库易于使用,但如果您坚持手动执行此操作,请注意以下几点:
首先,您必须找到要添加内容的页面的页面字典。根据 PDF 的类型,这可能已经需要解压缩对象流等,但在您的示例中修改1.pdf那是没有必要的:
7 0 obj
<</Rotate 90
/Type /Page
/TrimBox [ 9.54 6.12 585.68 835.88 ]
/Resources 8 0 R
/CropBox [ 0 0 595.22 842 ]
/ArtBox [ 9.54 18.36 585.68 842 ]
/Contents [ 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R 15 0 R 16 0 R ]
/Parent 6 0 R
/MediaBox [ 0 0 595.22 842 ]
/Annots 17 0 R
/BleedBox [ 9.54 6.12 585.68 835.88 ]
>>
endobj
您会看到对内容流的引用数组。这是您必须添加新页面内容的地方。您可以操作现有流或创建新流并将其添加到该数组中。
(大多数 PDF 的内容流都是压缩的。因此,对于一般情况,您必须先解压缩流,然后才能对其进行处理。因此,在我看来,更简单的方法是启动一个新流。)
您选择操作最后引用的流 16 0,该流在 PDF 中未压缩:
16 0 obj
<</Length 37 0 R>>
stream
S 1 0 0 1 13.183 0 cm 0 0 m
[...]
0 10 -10 -0 506.238 342.629 Tm
.13333 .11765 .12157 scn
-.0002 Tc
.0006 Tw
(the Bank and branch on which cheque is drawn\).)Tj
/F1 2 Tf
-15.1279 10.9462 Td
(abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789~!@#$%^&*aaaaaaaaaaaaa)Tj
/F2 1 Tf
015.1279 01.9462 Td
(ANAabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789)Tj
ET
endstream
endobj
我猜想,您添加的内容是底部的两个 3 行,它们首先选择一种字体,然后定位插入点,最后打印一组字母。
现在你说你添加文本 abc..z 和 ABC...Z 只是为了测试。但字母 b j k q v 等没有出现在 pdf 中。第二次添加字母时,问题变得更加明显;这里只显示大写的“A”和“N”。
这是因为相关字体被嵌入到 PDF 中 --- 字体被嵌入到 PDF 中,以允许没有相关字体的系统上的 PDF 查看器显示 PDF --- 但它们是未完全嵌入,仅嵌入该字体所需的字符子集。
让我们寻找仅出现“N”和“A”的字体 F2:
根据页面对象,可以在对象8 0中找到页面资源:
8 0 obj
<</Font <</F1 45 0 R /TT2 46 0 R /F2 47 0 R>>
/ExtGState <</GS2 48 0 R>>
/ProcSet [ /PDF /Text ]
/ColorSpace <</Cs6 49 0 R>>
>>
endobj
所以F2定义在47 0中:
47 0 obj
<</Subtype /Type1
/Type /Font
/Widths [ 722 250 250 250 250 250 250 250 250 250 250 250 250 722 ]
/Encoding 52 0 R
/FirstChar 65
/FontDescriptor 53 0 R
/ToUnicode 54 0 R
/BaseFont /ILBPOB+TimesNewRomanPSMT-Bold
/LastChar 78
>>
endobj
在引用的 ToUnicode 映射 54 0 中,您会看到
54 0 obj
<</Length 55 0 R>>stream
/CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo <<
/Registry (AAAAAA+F2+0) /Ordering (T1UV) /Supplement 0 >> def
/CMapName /AAAAAA+F2+0 def
/CMapType 2 def
1 begincodespacerange <41> <4e> endcodespacerange
2 beginbfchar
<41> <0041>
<4e> <004E>
endbfchar
endcmap CMapName currentdict /CMap defineresource pop end end
endstream
endobj
在此映射中,您会看到仅映射字符代码 0x41 'A' 和 0x4e 'N'
在您的文档中,该字体仅用于在金额表单元格中打印“NA”,而不用于其他用途。因此,仅嵌入这两个字母“N”和“A”,这会导致您使用该字体进行添加时仅输出这些字母。
因此,要成功向页面添加文本,您必须检查与页面关联的字体资源以获取它们提供的字形(并限制您对这些字形的添加),或者必须添加您自己的字体资源。
由于编码中字符的存在通常不像这里那么容易看到(ToUnicode 是可选的),我建议您添加自己的字体资源。 PDF 规范ISO 32000-1解释了如何做到这一点。
此外,您还声明了文本的 x 和 y 轴位置在 pdf 中无法正确显示。虽然您没有确切说明您的意思,但您应该知道,在内容流中,您可以对页面的坐标系应用仿射变换,即拉伸、倾斜、旋转和移动轴。
如果您想使用原始坐标系并且不依赖坐标来正确添加内容,则应该向包含以下内容的页面添加初始内容流:q运算符(至将当前图形状态保存到图形状态堆栈中)并在新的最终内容流中开始添加Q运算符(至通过从堆栈中删除最近保存的状态并将其设为当前状态来恢复图形状态).
EDIT作为示例,我将顶部 C# 代码的 Java 等效项应用到您的修改1.pdf激活追加模式。结果更改或添加了以下对象:
页面对象 7 0 已更新:
7 0 obj
<</CropBox[0 0 595.22 842]
/Parent 6 0 R
/Contents[69 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R 15 0 R 16 0 R 70 0 R]
/Type/Page
/Resources<<
/ExtGState<</GS2 48 0 R>>
/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]
/ColorSpace<</Cs6 49 0 R>>
/Font<</F1 45 0 R/F2 47 0 R/TT2 46 0 R/Xi0 68 0 R>>
>>
/MediaBox[0 0 595.22 842]
/TrimBox[9.54 6.12 585.68 835.88]
/BleedBox[9.54 6.12 585.68 835.88]
/Annots 17 0 R
/ArtBox[9.54 18.36 585.68 842]
/Rotate 90
>>
endobj
如果您与以前的版本进行比较,您会发现
- 添加了两个新的内容流,开头为 69 0,结尾为 70 0;
- 资源不再是间接对象,而是直接包含在此处;
- 该资源包含位于 68 0 的新字体资源 Xi0。
现在让我们看看添加的对象。
这是 Helvetica-Bold 的字体资源,名为 Xi0,位于 68 0:
68 0 obj
<</BaseFont/Helvetica-Bold
/Type/Font
/Encoding/WinAnsiEncoding
/Subtype/Type1
>>
endobj
非嵌入的、标准的14种字体资源一点也不复杂……
现在有额外的内容流。 iText 确实压缩了它们,但我将在此处以未压缩状态显示它们:
69 0 obj
<</Length 1>>stream
q
endstream
endobj
70 0 obj
<</Length 106>>stream
Q
q
0 1 -1 0 595.22 0 cm
q
BT
1 0 0 1 36 540 Tm
/Xi0 12 Tf
0.75 g
(Hello people!)Tj
0 g
ET
Q
Q
endstream
endobj
因此,开头的新内容流存储当前图形状态,末尾的新内容流检索存储的状态,更改坐标系、文本插入位置,选择字体、字体大小和填充颜色,最后打印一个字符串。