iTextSharp中的PDF坐标系

1 pdf itext itextsharp

现在我正在使用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);

      }


    }
 }
Run Code Online (Sandbox Code Playgroud)

代码工作正常.但是我遇到了关于PDF测量单元的问题.来自reader.getPageSize的值是(619*792),这意味着页面大小是691*792,但是当我从tokeniser获得矩形时,x和y总是超过页面大小,它的值总是x = 150,Y = 4200,W = 1500,H = 2000.

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

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

mkl*_*mkl 6

作为开始注释:实际提取的是 PDF内容流中重新 操作的坐标参数,它们的值不是特定于iTextSharp的.

你得到的价值

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

用户空间坐标系统仅被初始化为默认状态,其中页面字典中的CropBox条目指定对应于可见区域的用户空间的矩形.

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

在你的情况下,最有可能至少有一个扩展.

您可能希望在PDF规范的第8.3节"坐标系"中学习详细信息.

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

要检索包含转换的坐标,除了重新操作之外,您还可以找到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) { }
}
Run Code Online (Sandbox Code Playgroud)

并将其应用于这样的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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在" 矩形"," 移动到"," 行到 "和" 曲线"之后,您将看到坐标信息而不应用变换,即像您一样检索.

之后- >你会看到相应的变换坐标.

PS此功能仍然是新功能.可能很快就会使用另一种更简单的方法来支持iTextSharp为您捆绑路径信息,而不是简单地一次转发一个路径构建操作.