如何用iText7获取竖排古籍格式PDF的文本位置?
竖排古籍PDF文本位置获取问题及iText代码调试
问题场景
我有一份竖排格式的古籍PDF《LR-10709-24-25.pdf》,格式特点为:段落由多行组成,首行居页面最右侧,后续行依次向左排列;每行字符从上到下依次排布。因分析需求需要提取其中文本的位置信息,但遇到以下问题:
- 用下方iText程序处理普通英文排版的《history-2-3.pdf》时,能正确为每个文本块绘制彩色矩形框,结果符合预期。
- 同一程序处理《LR-10709-24-25.pdf》时,绘制的矩形完全偏离文本位置,结果完全错误。
- 推测问题与PDF坐标系统、Current Transform Matrix(CTM)、TextMatrix、TextRenderInfo等概念相关,但需要结合实际问题理解这些机制。
初始测试代码
using iText.Kernel.Colors; using iText.Kernel.Geom; using iText.Kernel.Pdf; using iText.Kernel.Pdf.Canvas; using iText.Kernel.Pdf.Canvas.Parser; using iText.Kernel.Pdf.Canvas.Parser.Data; using iText.Kernel.Pdf.Canvas.Parser.Listener; using System.Text; string srcFileName = "LR-10709-24-25.pdf"; string destFileName = "LR-10709-24-25-enclose.pdf"; //string srcFileName = "history-2-3.pdf"; //string destFileName = "history-2-3-enclose.pdf"; PdfDocument pdfDoc = new PdfDocument(new PdfReader(srcFileName), new PdfWriter(destFileName)); StringBuilder sb = new StringBuilder(); for (int i = 0; i < pdfDoc.GetNumberOfPages(); i++) { SimplePositionalTextEventListener listener = new SimplePositionalTextEventListener(); new PdfCanvasProcessor(listener).ProcessPageContent(pdfDoc.GetPage(i + 1)); List<SimpleTextWithRectangle> result = listener.GetResultantTextWithPosition(); int R = 0, G = 0, B = 0; foreach (SimpleTextWithRectangle textWithRectangle in result) { R += 40; R = R % 256; G += 20; G = G % 256; B += 80; B = B % 256; PdfCanvas canvas = new PdfCanvas(pdfDoc.GetPage(i + 1)); canvas.SetStrokeColor(new DeviceRgb(R, G, B)); var rect = textWithRectangle.GetRectangle(); canvas.Rectangle(rect); canvas.Stroke(); } } pdfDoc.Close(); Console.WriteLine("Press any key to continue!"); Console.ReadKey(); class SimpleTextWithRectangle { private Rectangle rectangle; private string text; public SimpleTextWithRectangle(Rectangle rectangle, String text) { this.rectangle = rectangle; this.text = text; } public Rectangle GetRectangle() { return rectangle; } public string GetText() { return text; } } class SimplePositionalTextEventListener : IEventListener { private List<SimpleTextWithRectangle> textWithRectangleList = new List<SimpleTextWithRectangle>(); private void renderText(TextRenderInfo renderInfo) { if (renderInfo.GetText().Trim().Length == 0) return; LineSegment ascent = renderInfo.GetAscentLine(); LineSegment descent = renderInfo.GetDescentLine(); float initX = descent.GetStartPoint().Get(0); float initY = descent.GetStartPoint().Get(1); float endX = ascent.GetEndPoint().Get(0); float endY = ascent.GetEndPoint().Get(1); Rectangle rectangle = new Rectangle(initX, initY, endX - initX, endY - initY); SimpleTextWithRectangle textWithRectangle = new SimpleTextWithRectangle(rectangle, renderInfo.GetText()); textWithRectangleList.Add(textWithRectangle); } public List<SimpleTextWithRectangle> GetResultantTextWithPosition() { return textWithRectangleList; } public void EventOccurred(IEventData data, EventType type) { renderText((TextRenderInfo)data); } public ICollection<EventType> GetSupportedEvents() { return new List<EventType> { EventType.RENDER_TEXT }; } }
代码更新及新问题
我认为TextRenderInfo应该包含文本位置的必要信息,因此更新了程序,新增三个测试方法,但仍存在问题:
Utils.Enclose方法生成的文件中,矩形位置依然错误,无法正确框住文本。Utils.DoubleWrite方法生成的文件中,用红色重写的文本与原黑色文本位置存在明显偏差。Utils.AppendPage方法生成的新页面中,文本呈现出异常的旋转和拼接效果,完全不符合原页面排版。
可以确定TextRenderInfo包含文本位置的关键信息,但作为iText初学者,我对PDF坐标系统、TextMatrix、CTM、PdfFont、CropBox等相关知识理解不足,需要技术指导解决这些问题。
更新后的代码
using iText.Kernel.Colors; using iText.Kernel.Font; using iText.Kernel.Geom; using iText.Kernel.Pdf; using iText.Kernel.Pdf.Canvas; using iText.Kernel.Pdf.Canvas.Parser; using iText.Kernel.Pdf.Canvas.Parser.Data; using iText.Kernel.Pdf.Canvas.Parser.Listener; Utils.Enclose("LR-10709-24-25.pdf", "LR-10709-24-25-enclose.pdf"); Utils.DoubleWrite("LR-10709-24-25.pdf", "LR-10709-24-25-dup.pdf"); Utils.AppendPage("LR-10709-24-25.pdf", "LR-10709-24-25-append.pdf"); Console.WriteLine("Press any key to continue 123!"); Console.ReadKey(); class Utils { public static void Enclose(string srcFileName, string destFileName) { //string srcFileName = "LR-10709-24-25.pdf"; //string destFileName = "LR-10709-24-25-enclose.pdf"; //string srcFileName = "history-2-3.pdf"; //string destFileName = "history-2-3-enclose.pdf"; PdfDocument pdfDoc = new PdfDocument(new PdfReader(srcFileName), new PdfWriter(destFileName)); for (int i = 0; i < pdfDoc.GetNumberOfPages(); i++) { SimpleTextEventListener listener = new SimpleTextEventListener(); new PdfCanvasProcessor(listener).ProcessPageContent(pdfDoc.GetPage(i + 1)); List<SimpleTextInfo> textInfos = listener.GetTextInfos(); PdfCanvas canvas = new PdfCanvas(pdfDoc.GetPage(i + 1)); int R = 0, G = 0, B = 0; foreach (SimpleTextInfo textInfo in textInfos) { R += 40; R = R % 256; G += 20; G = G % 256; B += 80; B = B % 256; canvas.SetStrokeColor(new DeviceRgb(R, G, B)); var rect = textInfo.GetRectangle(); canvas.Rectangle(rect); canvas.Stroke(); } } pdfDoc.Close(); } public static void DoubleWrite(string srcFileName, string destFileName) { PdfDocument pdfDoc = new PdfDocument(new PdfReader(srcFileName), new PdfWriter(destFileName)); for (int i = 0; i < pdfDoc.GetNumberOfPages(); i++) { SimpleTextEventListener listener = new SimpleTextEventListener(); new PdfCanvasProcessor(listener).ProcessPageContent(pdfDoc.GetPage(i + 1)); List<SimpleTextInfo> textInfos = listener.GetTextInfos(); PdfCanvas canvas = new PdfCanvas(pdfDoc.GetPage(i + 1)); foreach (SimpleTextInfo textInfo in textInfos) { canvas.SaveState(); canvas.SetColor(ColorConstants.RED, true); canvas.SetFontAndSize(textInfo.GetFont(), textInfo.GetFontSize()); canvas.BeginText(); Matrix textMatrix = textInfo.GetTextMatrix(); canvas.SetTextMatrix(textMatrix.Get(0), textMatrix.Get(1), textMatrix.Get(3), textMatrix.Get(4), textMatrix.Get(6), textMatrix.Get(7)) .ShowText(textInfo.GetText()); canvas.EndText(); canvas.RestoreState(); } } pdfDoc.Close(); } public static void AppendPage(string srcFileName, string destFileName) { PdfDocument pdfDoc = new PdfDocument(new PdfReader(srcFileName), new PdfWriter(destFileName)); int n = pdfDoc.GetNumberOfPages(); for (int i = 0; i < n; i++) { SimpleTextEventListener listener = new SimpleTextEventListener(); var page = pdfDoc.GetPage(i + 1); new PdfCanvasProcessor(listener).ProcessPageContent(page); List<SimpleTextInfo> textInfos = listener.GetTextInfos(); var newPage = pdfDoc.AddNewPage(); PdfCanvas canvas = new PdfCanvas(newPage); foreach (SimpleTextInfo textInfo in textInfos) { canvas.SaveState(); canvas.SetColor(ColorConstants.RED, true); canvas.SetFontAndSize(textInfo.GetFont(), textInfo.GetFontSize()); canvas.BeginText(); Matrix textMatrix = textInfo.GetTextMatrix(); canvas.SetTextMatrix(textMatrix.Get(0), textMatrix.Get(1), textMatrix.Get(3), textMatrix.Get(4), textMatrix.Get(6), textMatrix.Get(7)) .ShowText(textInfo.GetText()); canvas.EndText(); canvas.RestoreState(); } } pdfDoc.Close(); } } class SimpleTextInfo { private Rectangle rectangle; private string text; private PdfFont font; private float fontSize; private Matrix textMatrix; private Matrix canvasMatrix; public SimpleTextInfo(Rectangle rectangle, String text, PdfFont font, float fontSize, Matrix textMatrix, Matrix canvasMatrix) { this.rectangle = rectangle; this.text = text; this.font = font; this.fontSize = fontSize; this.textMatrix = textMatrix; this.canvasMatrix = canvasMatrix; } public Rectangle GetRectangle() { return rectangle; } public string GetText() { return text; } public PdfFont GetFont() { return font; } public float GetFontSize() { return fontSize; } public Matrix GetTextMatrix() { return textMatrix; } public Matrix GetCanvasMatrix() { return canvasMatrix; } } class SimpleTextEventListener : IEventListener { private List<SimpleTextInfo> simpleTextInfos = new List<SimpleTextInfo>(); private void renderText(TextRenderInfo renderInfo) { if (renderInfo.GetText().Trim().Length == 0) return; LineSegment ascent = renderInfo.GetAscentLine(); LineSegment descent = renderInfo.GetDescentLine(); float initX = descent.GetStartPoint().Get(0); float initY = descent.GetStartPoint().Get(1); float endX = ascent.GetEndPoint().Get(0); float endY = ascent.GetEndPoint().Get(1); Rectangle rectangle = new Rectangle(initX, initY, endX - initX, endY - initY); SimpleTextInfo textInfo = new SimpleTextInfo(rectangle, renderInfo.GetText(), renderInfo.GetFont(), renderInfo.GetFontSize(), renderInfo.GetTextMatrix(), renderInfo.GetGraphicsState().GetCtm()); simpleTextInfos.Add(textInfo); } public List<SimpleTextInfo> GetTextInfos() { return simpleTextInfos; } public void EventOccurred(IEventData data, EventType type) { renderText((TextRenderInfo)data); } public ICollection<EventType> GetSupportedEvents() { return new List<EventType> { EventType.RENDER_TEXT }; } }
内容的提问来源于stack exchange,提问作者hjyanghj
相关产品推荐
相关产品推荐

