使用iTextPDF生成的PDF页脚无法被屏幕阅读器读取的求助
问题描述
我用iTextPDF生成了带页脚的PDF,遇到了无障碍访问问题:开启setTagged()后,JAWS和Adobe Reader都无法读取页脚;但关闭setTagged()会导致图片、表格等其他无障碍问题。我尝试过设置各种标签和角色都没用,同时看到有文章说页眉页脚不该加入文档结构以避免影响阅读流程,但我的需求是页脚必须能被屏幕阅读器读取。想知道有没有解决办法,或者是否有标准明确规定页眉页脚不能被屏幕阅读器读取。
主PDF生成代码
import com.itextpdf.io.font.FontConstants; import com.itextpdf.io.image.ImageDataFactory; import com.itextpdf.kernel.events.PdfDocumentEvent; import com.itextpdf.kernel.font.PdfFontFactory; import com.itextpdf.kernel.geom.PageSize; import com.itextpdf.kernel.pdf.PdfDocument; import com.itextpdf.kernel.pdf.PdfViewerPreferences; import com.itextpdf.kernel.pdf.PdfWriter; import com.itextpdf.kernel.pdf.tagging.StandardRoles; import com.itextpdf.layout.Document; import com.itextpdf.layout.element.*; import com.itextpdf.layout.property.TextAlignment; import java.io.File; import java.io.IOException; public class PDFGenerator { public static final String DEST = "C:\\Users\\skesha01\\temp\\output.pdf"; public static void main(String[] args) throws IOException { File file = new File(DEST); file.getParentFile().mkdirs(); new PDFGenerator().createPdf(DEST); } public void createPdf(String dest) throws IOException { PdfDocument pdfDoc = new PdfDocument(new PdfWriter(dest)); pdfDoc.setTagged(); pdfDoc.getCatalog().setViewerPreferences(new PdfViewerPreferences().setDisplayDocTitle(true)); Document doc = new Document(pdfDoc, PageSize.A4); pdfDoc.addEventHandler(PdfDocumentEvent.END_PAGE, new FooterEventHandler(doc)); addContent(doc); doc.close(); } public void addContent(Document doc) throws IOException { doc.add(new Paragraph("Sample PDF with footer, page numbers, and table fields") .setFont(PdfFontFactory.createFont(FontConstants.HELVETICA)) .setFontSize(12) .setTextAlignment(TextAlignment.CENTER)); Table table = new Table(3).useAllAvailableWidth(); Cell headerCell1 = new Cell().add(new Paragraph("Header 1")); Cell headerCell2 = new Cell().add(new Paragraph("Header 2")); Cell headerCell3 = new Cell().add(new Paragraph("Header 3")); table.addHeaderCell(headerCell1).getAccessibilityProperties().setRole(StandardRoles.TH); table.addHeaderCell(headerCell2).getAccessibilityProperties().setRole(StandardRoles.TH); table.addHeaderCell(headerCell3).getAccessibilityProperties().setRole(StandardRoles.TH); for (int i = 0; i < 1; i++) { table.addCell("Row " + (i + 1) + ", Column 1").getAccessibilityProperties().setRole(StandardRoles.TD); table.addCell("Row " + (i + 1) + ", Column 2").getAccessibilityProperties().setRole(StandardRoles.TD); table.addCell("Row " + (i + 1) + ", Column 3").getAccessibilityProperties().setRole(StandardRoles.TD); } table.getAccessibilityProperties().setRole(StandardRoles.TABLE); table.getAccessibilityProperties().setAlternateDescription("I am reading this table alternate text"); doc.add(table); } }
原页脚事件处理器代码
import com.itextpdf.io.font.FontConstants; import com.itextpdf.kernel.events.Event; import com.itextpdf.kernel.events.IEventHandler; import com.itextpdf.kernel.events.PdfDocumentEvent; import com.itextpdf.kernel.font.PdfFont; import com.itextpdf.kernel.font.PdfFontFactory; import com.itextpdf.kernel.pdf.PdfDocument; import com.itextpdf.kernel.pdf.PdfName; import com.itextpdf.kernel.pdf.PdfPage; import com.itextpdf.kernel.pdf.canvas.PdfCanvas; import com.itextpdf.kernel.pdf.tagging.StandardRoles; import com.itextpdf.layout.Document; import com.itextpdf.layout.element.Text; import java.io.IOException; public class FooterEventHandler implements IEventHandler { protected PdfFont font; private Document document; public FooterEventHandler(Document document) { try { font = PdfFontFactory.createFont(FontConstants.HELVETICA); this.document = document; } catch (IOException e) { e.printStackTrace(); } } @Override public void handleEvent(Event event) { PdfDocumentEvent docEvent = (PdfDocumentEvent) event; PdfDocument pdfDoc = docEvent.getDocument(); PdfCanvas canvas = new PdfCanvas(pdfDoc.getLastPage()); canvas.setStrokeColorRgb(0, 0, 0).setLineWidth(0.5f); canvas.moveTo(36, 20).lineTo(pdfDoc.getDefaultPageSize().getRight() - 36, 20).stroke(); addFooter(canvas, pdfDoc, docEvent.getPage()); } private void addFooter(PdfCanvas canvas, PdfDocument pdfDoc, PdfPage page) { canvas.setStrokeColorRgb(0, 0, 0).setLineWidth(0.5f); canvas.moveTo(36, 20).lineTo(pdfDoc.getDefaultPageSize().getRight() - 36, 20).stroke(); Text footerText = new Text("Footer Text - Page " + pdfDoc.getPageNumber(page)); footerText.getAccessibilityProperties().setRole(StandardRoles.ARTIFACT); canvas.beginText().beginMarkedContent(PdfName.Artifact) .setFontAndSize(font, 10) .moveText((pdfDoc.getDefaultPageSize().getLeft() + pdfDoc.getDefaultPageSize().getRight()) / 2, 10) .showText(footerText.getText()) .endText(); } }
解决方案
问题根源
- 原代码将页脚标记为Artifact(人工制品),这会告诉屏幕阅读器忽略这段内容;
- 直接使用
PdfCanvas绘制文本,没有将其纳入PDF的结构化内容树,开启setTagged()后,页脚不会被识别为可访问内容; - 关于标准:PDF/UA(无障碍PDF国际标准)并未禁止页眉页脚被读取,只是建议避免重复内容干扰正常阅读流程——若页脚包含重要信息(如页码),完全可以纳入结构化内容,只需处理得当。
修改后的页脚事件处理器代码
import com.itextpdf.io.font.FontConstants; import com.itextpdf.kernel.events.Event; import com.itextpdf.kernel.events.IEventHandler; import com.itextpdf.kernel.events.PdfDocumentEvent; import com.itextpdf.kernel.font.PdfFont; import com.itextpdf.kernel.font.PdfFontFactory; import com.itextpdf.kernel.geom.Rectangle; import com.itextpdf.kernel.pdf.PdfDocument; import com.itextpdf.kernel.pdf.PdfPage; import com.itextpdf.kernel.pdf.tagging.StandardRoles; import com.itextpdf.layout.Document; import com.itextpdf.layout.element.Paragraph; import com.itextpdf.layout.property.TextAlignment; import com.itextpdf.layout.property.VerticalAlignment; import java.io.IOException; public class FooterEventHandler implements IEventHandler { protected PdfFont font; private Document document; public FooterEventHandler(Document document) { try { font = PdfFontFactory.createFont(FontConstants.HELVETICA); this.document = document; } catch (IOException e) { e.printStackTrace(); } } @Override public void handleEvent(Event event) { PdfDocumentEvent docEvent = (PdfDocumentEvent) event; PdfDocument pdfDoc = docEvent.getDocument(); PdfPage page = docEvent.getPage(); // 绘制页脚分隔线(用Paragraph边框保持结构化) Rectangle pageSize = page.getPageSize(); document.showTextAligned(new Paragraph("") .setBorderBottom(0.5f) .setWidth(pageSize.getWidth() - 72), pageSize.getLeft() + 36, 25, pdfDoc.getPageNumber(page), TextAlignment.CENTER, VerticalAlignment.BOTTOM, 0); // 添加结构化页脚文本 Paragraph footer = new Paragraph("Footer Text - Page " + pdfDoc.getPageNumber(page)) .setFont(font) .setFontSize(10); // 设置无障碍角色为脚注类,帮助屏幕阅读器识别 footer.getAccessibilityProperties().setRole(StandardRoles.FNOTE); // 对齐到页面底部居中 document.showTextAligned(footer, pageSize.getWidth() / 2, 10, pdfDoc.getPageNumber(page), TextAlignment.CENTER, VerticalAlignment.BOTTOM, 0); } }
关键修改点
- 移除
Artifact标记,不再将页脚设为可忽略内容; - 使用
document.showTextAligned()添加Paragraph元素,自动将页脚纳入PDF结构化内容树; - 为页脚设置
StandardRoles.FNOTE角色,明确内容类型; - 用
Paragraph边框替代直接绘制线条,保持内容结构化一致性。
验证效果
修改后,开启setTagged()生成的PDF,JAWS和Adobe Reader可正常读取页脚,同时图片、表格的无障碍属性也能正常保留。
内容的提问来源于stack exchange,提问作者karthik b
相关产品推荐
相关产品推荐

