You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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应该包含文本位置的必要信息,因此更新了程序,新增三个测试方法,但仍存在问题:

  1. Utils.Enclose方法生成的文件中,矩形位置依然错误,无法正确框住文本。
  2. Utils.DoubleWrite方法生成的文件中,用红色重写的文本与原黑色文本位置存在明显偏差。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 20:35:53