如何读取外观流的文本?
How to read text of appearance stream?
我有一个 PDF,其中注释中显示的文本(在 Adobe Reader 中呈现)与其 /Contents 和 /RC 个条目。这与我在这个问题中处理的问题有关:
在这种情况下,我不想更改外观以匹配注释的内容,而是想做相反的事情:获取外观文本并更改 /Contents 和 /RC 值匹配。例如,如果注释显示 "appearance" 并且 /Contents 设置为 "content",我想做类似的事情:
void setContent(PdfDictionary dict)
{
PdfString str = dict.GetAsString(new PdfName("KeyForAppearanceText"));
dict.Put(PdfName.CONTENTS,str);
}
但我找不到存储外观文本的位置。我得到了 /AP 引用的字典,代码如下:
private PdfDictionary getAPAnnot(PdfArray annotArray,PdfDictionary annot)
{
PdfDictionary apDict = annot.GetAsDict(PdfName.AP);
if (apDict!=null)
{
PdfIndirectReference ap = (PdfIndirectReference)apDict.Get(PdfName.N);
PdfDictionary apRefDict = (PdfDictionary)pdfController.pdfReader.GetPdfObject(ap.Number);
return apRefDict;
}
else
{
return null;
}
}
此字典具有以下 hashMap:
{[/BBox, [-38.7578, -144.058, 62.0222, 1]]}
{[/Filter, /FlateDecode]}
{[/Length, 172]}
{[/Matrix, [1, 0, 0, 1, 0, 0]]}
{[/Resources, Dictionary]}
/Resources 间接引用了字体,但没有内容。所以外观流似乎不包含内容数据。
除了 /Contents 和 /RC,注释的数据结构中似乎没有任何地方存储内容数据.外观内容应该去哪里找?
很遗憾,OP 没有提供示例 PDF。不过,考虑到他之前的问题,他很可能对自由文本注释感兴趣。因此,我在这里使用 this example PDF 作为示例。它有一页带有打字机自由文本注释,如下所示:
OP 询问
Other than /Contents and /RC, there doesn't seem to be anywhere in the annotation's data structure that stores content data. Where should I be looking for the appearance contents?
OP 代码的主要缺点是他只考虑正常外观 PdfDictionary
:
PdfIndirectReference ap = (PdfIndirectReference)apDict.Get(PdfName.N);
PdfDictionary apRefDict = (PdfDictionary)pdfController.pdfReader.GetPdfObject(ap.Number);
其实是一个PdfStream
,也就是一个字典,有一个数据流,这个数据流就是外观绘制指令所在的地方。
但即使有了这个数据流,也没有OP想象的那么简单:
PdfString str = dict.GetAsString(new PdfName("KeyForAppearanceText"));
其实外观流中的文字可以分段绘制,例如在我的示例文件中,流数据如下所示:
0 w
131.2646 564.8243 180.008 30.984 re
n
q
1 0 0 1 0 0 cm
131.2646 564.8243 180.008 30.984 re
W
n
0 g
1 w
BT
/Cour 12 Tf
0 g
131.265 587.96 Td
(This ) Tj
35.999 0 Td
(is ) Tj
21.6 0 Td
(written ) Tj
57.599 0 Td
(using ) Tj
43.2 0 Td
(the ) Tj
-158.398 -16.3 Td
(typewriter ) Tj
79.199 0 Td
(tool.) Tj
ET
Q
此外,编码不需要像此处那样是某种标准编码,而是可以为动态嵌入字体定义。
因此,必须应用完整的文本提取。
这一切都可以这样实现:
for (int page = 1; page <= pdfReader.NumberOfPages; page++)
{
Console.Write("\nPage {0}\n", page);
PdfDictionary pageDictionary = pdfReader.GetPageNRelease(page);
PdfArray annotsArray = pageDictionary.GetAsArray(PdfName.ANNOTS);
if (annotsArray == null || annotsArray.IsEmpty())
{
Console.Write(" No annotations.\n");
continue;
}
foreach (PdfObject pdfObject in annotsArray)
{
PdfObject direct = PdfReader.GetPdfObject(pdfObject);
if (direct.IsDictionary())
{
PdfDictionary annotDictionary = (PdfDictionary)direct;
Console.Write(" SubType: {0}\n", annotDictionary.GetAsName(PdfName.SUBTYPE));
PdfDictionary appearancesDictionary = annotDictionary.GetAsDict(PdfName.AP);
if (appearancesDictionary == null)
{
Console.Write(" No appearances.\n");
continue;
}
foreach (PdfName key in appearancesDictionary.Keys)
{
Console.Write(" Appearance: {0}\n", key);
PdfStream value = appearancesDictionary.GetAsStream(key);
if (value != null)
{
String text = ExtractAnnotationText(value);
Console.Write(" Text:\n---\n{0}\n---\n", text);
}
}
}
}
}
使用这个辅助方法
public String ExtractAnnotationText(PdfStream xObject)
{
PdfDictionary resources = xObject.GetAsDict(PdfName.RESOURCES);
ITextExtractionStrategy strategy = new LocationTextExtractionStrategy();
PdfContentStreamProcessor processor = new PdfContentStreamProcessor(strategy);
processor.ProcessContent(ContentByteUtils.GetContentBytesFromContentObject(xObject), resources);
return strategy.GetResultantText();
}
对于上面的示例文件,代码的输出是
Page 1
SubType: /FreeText
Appearance: /N
Text:
---
This is written using the
typewriter tool.
---
注意,有一些注释,特别是复选框和单选按钮的小部件注释,其结构比此处代码预期的要深一些。
我有一个 PDF,其中注释中显示的文本(在 Adobe Reader 中呈现)与其 /Contents 和 /RC 个条目。这与我在这个问题中处理的问题有关:
在这种情况下,我不想更改外观以匹配注释的内容,而是想做相反的事情:获取外观文本并更改 /Contents 和 /RC 值匹配。例如,如果注释显示 "appearance" 并且 /Contents 设置为 "content",我想做类似的事情:
void setContent(PdfDictionary dict)
{
PdfString str = dict.GetAsString(new PdfName("KeyForAppearanceText"));
dict.Put(PdfName.CONTENTS,str);
}
但我找不到存储外观文本的位置。我得到了 /AP 引用的字典,代码如下:
private PdfDictionary getAPAnnot(PdfArray annotArray,PdfDictionary annot)
{
PdfDictionary apDict = annot.GetAsDict(PdfName.AP);
if (apDict!=null)
{
PdfIndirectReference ap = (PdfIndirectReference)apDict.Get(PdfName.N);
PdfDictionary apRefDict = (PdfDictionary)pdfController.pdfReader.GetPdfObject(ap.Number);
return apRefDict;
}
else
{
return null;
}
}
此字典具有以下 hashMap:
{[/BBox, [-38.7578, -144.058, 62.0222, 1]]}
{[/Filter, /FlateDecode]}
{[/Length, 172]}
{[/Matrix, [1, 0, 0, 1, 0, 0]]}
{[/Resources, Dictionary]}
/Resources 间接引用了字体,但没有内容。所以外观流似乎不包含内容数据。
除了 /Contents 和 /RC,注释的数据结构中似乎没有任何地方存储内容数据.外观内容应该去哪里找?
很遗憾,OP 没有提供示例 PDF。不过,考虑到他之前的问题,他很可能对自由文本注释感兴趣。因此,我在这里使用 this example PDF 作为示例。它有一页带有打字机自由文本注释,如下所示:
OP 询问
Other than /Contents and /RC, there doesn't seem to be anywhere in the annotation's data structure that stores content data. Where should I be looking for the appearance contents?
OP 代码的主要缺点是他只考虑正常外观 PdfDictionary
:
PdfIndirectReference ap = (PdfIndirectReference)apDict.Get(PdfName.N);
PdfDictionary apRefDict = (PdfDictionary)pdfController.pdfReader.GetPdfObject(ap.Number);
其实是一个PdfStream
,也就是一个字典,有一个数据流,这个数据流就是外观绘制指令所在的地方。
但即使有了这个数据流,也没有OP想象的那么简单:
PdfString str = dict.GetAsString(new PdfName("KeyForAppearanceText"));
其实外观流中的文字可以分段绘制,例如在我的示例文件中,流数据如下所示:
0 w
131.2646 564.8243 180.008 30.984 re
n
q
1 0 0 1 0 0 cm
131.2646 564.8243 180.008 30.984 re
W
n
0 g
1 w
BT
/Cour 12 Tf
0 g
131.265 587.96 Td
(This ) Tj
35.999 0 Td
(is ) Tj
21.6 0 Td
(written ) Tj
57.599 0 Td
(using ) Tj
43.2 0 Td
(the ) Tj
-158.398 -16.3 Td
(typewriter ) Tj
79.199 0 Td
(tool.) Tj
ET
Q
此外,编码不需要像此处那样是某种标准编码,而是可以为动态嵌入字体定义。
因此,必须应用完整的文本提取。
这一切都可以这样实现:
for (int page = 1; page <= pdfReader.NumberOfPages; page++)
{
Console.Write("\nPage {0}\n", page);
PdfDictionary pageDictionary = pdfReader.GetPageNRelease(page);
PdfArray annotsArray = pageDictionary.GetAsArray(PdfName.ANNOTS);
if (annotsArray == null || annotsArray.IsEmpty())
{
Console.Write(" No annotations.\n");
continue;
}
foreach (PdfObject pdfObject in annotsArray)
{
PdfObject direct = PdfReader.GetPdfObject(pdfObject);
if (direct.IsDictionary())
{
PdfDictionary annotDictionary = (PdfDictionary)direct;
Console.Write(" SubType: {0}\n", annotDictionary.GetAsName(PdfName.SUBTYPE));
PdfDictionary appearancesDictionary = annotDictionary.GetAsDict(PdfName.AP);
if (appearancesDictionary == null)
{
Console.Write(" No appearances.\n");
continue;
}
foreach (PdfName key in appearancesDictionary.Keys)
{
Console.Write(" Appearance: {0}\n", key);
PdfStream value = appearancesDictionary.GetAsStream(key);
if (value != null)
{
String text = ExtractAnnotationText(value);
Console.Write(" Text:\n---\n{0}\n---\n", text);
}
}
}
}
}
使用这个辅助方法
public String ExtractAnnotationText(PdfStream xObject)
{
PdfDictionary resources = xObject.GetAsDict(PdfName.RESOURCES);
ITextExtractionStrategy strategy = new LocationTextExtractionStrategy();
PdfContentStreamProcessor processor = new PdfContentStreamProcessor(strategy);
processor.ProcessContent(ContentByteUtils.GetContentBytesFromContentObject(xObject), resources);
return strategy.GetResultantText();
}
对于上面的示例文件,代码的输出是
Page 1
SubType: /FreeText
Appearance: /N
Text:
---
This is written using the
typewriter tool.
---
注意,有一些注释,特别是复选框和单选按钮的小部件注释,其结构比此处代码预期的要深一些。