【问题标题】:PDF coordinate system in iTextSharpiTextSharp中的PDF坐标系
【发布时间】:2015-09-14 08:22:34
【问题描述】:

现在我正在使用 iTextSharp 从 PDF 中提取线条和矩形。我使用的方法如下:

PdfReader reader = new PdfReader(strPDFFileName);
var pageSize = reader.GetPageSize(1);
var cropBox = reader.GetCropBox(1);
byte[] pageBytes = reader.GetPageContent(1);
PRTokeniser tokeniser = new PRTokeniser(new(RandomAccessFileOrArray(pageBytes));
PRTokeniser.TokType tokenType;
string tokenValue;
CoordinateCollection cc = new CoordinateCollection();
while (tokeniser.NextToken())
{
   tokenType = tokeniser.TokenType;
   tokenValue = tokeniser.StringValue;

   if (tokenType == PRTokeniser.TokType.OTHER)
   {
      if (tokenValue == "re")
      {
         if (buf.Count < 5)
         {
            continue;
         }

         float x = float.Parse(buf[buf.Count - 5]);
         float y = float.Parse(buf[buf.Count - 4]);
         float w = float.Parse(buf[buf.Count - 3]);
         float h = float.Parse(buf[buf.Count - 2]);
         Coordinate co = new Coordinate();
         co.type = "re";
         co.X1 = x;
         co.Y1 = y;
         co.W = w;
         co.H = h;
         cc.AddCoordinate(co);

      }


    }
 }

代码运行良好。但是我遇到了一个关于 PDF 测量单位的问题。从 reader.getPageSize 得到的值是 (619*792),这意味着页面大小是 691*792,但是当我从 tokeniser 获取矩形时,x 和 y 总是超过页面大小,它的值总是 x =150,y=4200,w=1500,h=2000。

我相信 reader.getPageSize 和 tokeniser 的度量单位是不同的。

那么请您帮忙告诉我如何转换它们?

【问题讨论】:

    标签: pdf itextsharp itext


    【解决方案1】:

    首先说明:你提取的实际上是在PDF内容流中 re 操作的坐标参数,它们的值是不是 iTextSharp 特定。

    你得到的价值

    要理解为什么矩形的坐标看起来如此离页,您首先必须意识到 PDF 中使用的坐标系是可变的!

    用户空间坐标系只是初始化到默认状态,在该状态下页面字典中的CropBox条目指定用户的矩形可见区域对应的空间。

    在页面内容操作的过程中,坐标系可能会被变换,甚至多次,使用cm操作。常见的变换是旋转、平移、倾斜缩放

    在您的情况下,很可能至少有一个缩放。

    您可能想在PDF specification 的第 8.3 节“坐标系”中学习详细信息。

    如何提取包括变换在内的位置

    要检索包括变换在内的坐标,除了 re 操作之外,您还可以找到 cm 操作。此外,您必须找到 qQ 操作(保存和恢复图形状态,包括当前的变换矩阵)。

    幸运的是,iTextSharp 的解析器命名空间类可以为您完成大部分繁重的工作,因为 5.5.6 版它们还支持矢量图形。您只需要实现IExtRenderListener 并使用实例解析内容。

    例如要在控制台上输出矢量图形信息,可以使用如下实现:

    class VectorGraphicsListener : IExtRenderListener
    {
        public void ModifyPath(PathConstructionRenderInfo renderInfo)
        {
            if (renderInfo.Operation == PathConstructionRenderInfo.RECT)
            {
                float x = renderInfo.SegmentData[0];
                float y = renderInfo.SegmentData[1];
                float w = renderInfo.SegmentData[2];
                float h = renderInfo.SegmentData[3];
                Vector a = new Vector(x, y, 1).Cross(renderInfo.Ctm);
                Vector b = new Vector(x + w, y, 1).Cross(renderInfo.Ctm);
                Vector c = new Vector(x + w, y + h, 1).Cross(renderInfo.Ctm);
                Vector d = new Vector(x, y + h, 1).Cross(renderInfo.Ctm);
    
                Console.Out.WriteLine("Rectangle at ({0}, {1}) with size ({2}, {3})", x, y, w, h);
                Console.Out.WriteLine("--> at ({0}, {1}) ({2}, {3}) ({4}, {5}) ({6}, {7})", a[Vector.I1], a[Vector.I2], b[Vector.I1], b[Vector.I2], c[Vector.I1], c[Vector.I2], d[Vector.I1], d[Vector.I2]);
            }
            else
            {
                switch (renderInfo.Operation)
                {
                    case PathConstructionRenderInfo.MOVETO:
                        Console.Out.Write("Move to");
                        break;
                    case PathConstructionRenderInfo.LINETO:
                        Console.Out.Write("Line to");
                        break;
                    case PathConstructionRenderInfo.CLOSE:
                        Console.Out.WriteLine("Close");
                        return;
                    default:
                        Console.Out.Write("Curve along");
                        break;
                }
                List<Vector> points = new List<Vector>();
                for (int i = 0; i < renderInfo.SegmentData.Count - 1; i += 2)
                {
                    float x = renderInfo.SegmentData[i];
                    float y = renderInfo.SegmentData[i + 1];
                    Console.Out.Write(" ({0}, {1})", x, y);
                    Vector a = new Vector(x, y, 1).Cross(renderInfo.Ctm);
                    points.Add(a);
                }
                Console.Out.WriteLine();
                Console.Out.Write("--> at ");
                foreach (Vector point in points)
                {
                    Console.Out.Write(" ({0}, {1})", point[Vector.I1], point[Vector.I2]);
                }
                Console.Out.WriteLine();
            }
        }
    
        public void ClipPath(int rule)
        {
            Console.Out.WriteLine("Clip");
        }
    
        public iTextSharp.text.pdf.parser.Path RenderPath(PathPaintingRenderInfo renderInfo)
        {
            switch (renderInfo.Operation)
            {
                case PathPaintingRenderInfo.FILL:
                    Console.Out.WriteLine("Fill");
                    break;
                case PathPaintingRenderInfo.STROKE:
                    Console.Out.WriteLine("Stroke");
                    break;
                case PathPaintingRenderInfo.STROKE + PathPaintingRenderInfo.FILL:
                    Console.Out.WriteLine("Stroke and fill");
                    break;
                case PathPaintingRenderInfo.NO_OP:
                    Console.Out.WriteLine("Drop");
                    break;
            }
            return null;
        }
    
        public void BeginTextBlock() { }
        public void EndTextBlock() { }
        public void RenderImage(ImageRenderInfo renderInfo) { }
        public void RenderText(TextRenderInfo renderInfo) { }
    }
    

    并将其应用到这样的 PDF 中:

    using (var pdfReader = new PdfReader(....))
    {
        // Loop through each page of the document
        for (var page = 1; page <= pdfReader.NumberOfPages; page++)
        {
            VectorGraphicsListener listener = new VectorGraphicsListener();
    
            PdfReaderContentParser parser = new PdfReaderContentParser(pdfReader);
            parser.ProcessContent(page, listener);
        }
    }
    

    矩形移动到直线到沿曲线之后,您会看到坐标不应用转换的信息,即像您一样检索。

    -->之后你会看到对应的变换坐标。

    PS这个功能还是新的。可能很快就会通过使用另一种更简单的方法来支持它,在该方法中,iTextSharp 为您捆绑路径信息,而不是简单地一次转发每个路径构建操作。

    【讨论】:

    • +1,尤其适用于阅读 PDF 规范。 OP 的代码看起来很像几年前的 I wrote,我在第一段中明确指出了这一点。
    • 感谢您的帮助。此外,您能教我如何获取 PDF 中每条线/矩形的位置吗?
    • 你能教我如何获取 PDF 中每条线/矩形的位置 - 我使用最近引入的IExtRenderListener 接口添加了一个 iTextSharp 示例。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多