如何在合并时删除空格

时间:2015-03-11 15:50:46

标签: c# pdf itextsharp

我有一些代码需要3个不同的PDF字节数组并合并它们。这段代码效果很好。问题(有些人)认为每个PDF都被认为是一个完整的页面(如果打印),即使它上面只有4英寸的内容,因此垂直留下7英寸的空白区域。然后将中间文档放入其中,并且可以在其末尾处具有或不具有垂直空白空间。然后页脚也会放在自己的页面上。

以下是代码:

byte[] Bytes = rv.LocalReport.Render("PDF", null, out MimeType, out Encoding, out Extension, out StreamIDs, out Warnings);
List<byte[]> MergeSets = // This is filled prior to this code

// Append any other pages to this primary letter
if (MergeSets.Count > 0) {
  MemoryStream ms = new MemoryStream();
  Document document = new Document();
  PdfCopy copy = new PdfCopy(document, ms);
  document.Open();
  PdfImportedPage page;
  PdfReader reader = new PdfReader(Bytes); // read the generated primary Letter
  int pages = reader.NumberOfPages;

  for (int i = 0; i < pages; ) {
    page = copy.GetImportedPage(reader, ++i);
    copy.AddPage(page);
  } // foreach of the pages in the Cover Letter

  // Now append the merge sets
  foreach (byte[] ba in MergeSets) {
    reader = new PdfReader(ba);
    pages = reader.NumberOfPages;

    for (int i = 0; i < pages; ) {
      page = copy.GetImportedPage(reader, ++i);
      copy.AddPage(page);
    } // foreach of the pages in the current merge set
  } // foreach of the sets of data

  document.Close();

  ServerSaved = SaveGeneratedLetter(ms.GetBuffer(), DateTime.Now.Year, hl.LetterName, SaveName);
} // if there is anything to merge

当我合并每个页面以剪切/删除/删除每个pdf末尾的垂直空白区域时,有没有办法让它显示为一个无缝文档?

更新: 以下是我尝试合并的一些示例.pdf文件。

headerbodyfooter

  

更新2:使用答案:

     

我已经将@ mkl的代码转换为C#,并且在这里。

     

工具类:

public class PdfVeryDenseMergeTool {

  private Rectangle PageSize;
  private float TopMargin;
  private float BottomMargin;
  private float Gap;
  private Document Document = null;
  private PdfWriter Writer = null;
  private float YPosition = 0;

  public PdfVeryDenseMergeTool(Rectangle size, float top, float bottom, float gap) {
    this.PageSize = size;
    this.TopMargin = top;
    this.BottomMargin = bottom;
    this.Gap = gap;
  } // PdfVeryDenseMergeTool

  public void Merge(MemoryStream outputStream, List<PdfReader> inputs) {
    try {
      this.OpenDocument(outputStream);

      foreach (PdfReader reader in inputs) {
        this.Merge(reader);
      } // foreach of the PDF files to merge
    } finally {
      this.CloseDocument();
    } // try-catch-finally
  } // Merge

  public void OpenDocument(MemoryStream outputStream) {
    this.Document = new Document(PageSize, 36, 36, this.TopMargin, this.BottomMargin);
    this.Writer = PdfWriter.GetInstance(Document, outputStream);

    this.Document.Open();
    this.NewPage();
  } // OpenDocument

  public void CloseDocument() {
    try {
      this.Document.Close();
    } finally {
      this.Document = null;
      this.Writer = null;
      this.YPosition = 0;
    } // try-finally
  } // CloseDocument

  public void NewPage() {
    this.Document.NewPage();
    this.YPosition = PageSize.GetTop(this.TopMargin);
  } // Merge

  public void Merge(PdfReader reader) {
    PdfReaderContentParser parser = new PdfReaderContentParser(reader);

    for (int pageIndex = 1; pageIndex <= reader.NumberOfPages; pageIndex++) {
      this.Merge(reader, parser, pageIndex);
    } // foreach of the pages of the current PDF
  } // Merge

  public void Merge(PdfReader reader, PdfReaderContentParser parser, int pageIndex) {
    PdfImportedPage importedPage = Writer.GetImportedPage(reader, pageIndex);
    PdfContentByte directContent = Writer.DirectContent;

    PageVerticalAnalyzer finder = parser.ProcessContent(pageIndex, new PageVerticalAnalyzer());

    if (finder.VerticalFlips.Count < 2)
      return;

    Rectangle pageSizeToImport = reader.GetPageSize(pageIndex);

    int startFlip = finder.VerticalFlips.Count - 1;
    bool first = true;

    while (startFlip > 0) {
      if (!first)
        this.NewPage();

      float freeSpace = this.YPosition - PageSize.GetBottom(BottomMargin);
      int endFlip = startFlip + 1;

      while ((endFlip > 1) && (finder.VerticalFlips[startFlip] - finder.VerticalFlips[endFlip - 2] < freeSpace))
        endFlip -= 2;

      if (endFlip < startFlip) {
        float height = finder.VerticalFlips[startFlip] - finder.VerticalFlips[endFlip];

        directContent.SaveState();
        directContent.Rectangle(0, this.YPosition - height, pageSizeToImport.Width, height);
        directContent.Clip();
        directContent.NewPath();

        this.Writer.DirectContent.AddTemplate(importedPage, 0, this.YPosition - (finder.VerticalFlips[startFlip] - pageSizeToImport.Bottom));

        directContent.RestoreState();
        this.YPosition -= height + this.Gap;
        startFlip = endFlip - 1;
      } else if (!first) {
        throw new ArgumentException(string.Format("Page {0} content too large", pageIndex));
      } // if

      first = false;
    } // while
  } // Merge
} // PdfVeryDenseMergeTool
  

RenderListener类:
  更新3:修复了1行代码及其工作原理:查看代码中的注释

public class PageVerticalAnalyzer : IRenderListener {

  public PageVerticalAnalyzer() { }

  public List<float> VerticalFlips = new List<float>();

  public void AddVerticalUseSection(float from, float to) {
    if (to < from) {
      float temp = to;
      to = from;
      from = temp;
    }

    int i = 0;
    int j = 0;

    for (i = 0; i < VerticalFlips.Count; i++) {
      float flip = VerticalFlips[i];
      if (flip < from)
        continue;

      for (j = i; j < VerticalFlips.Count; j++) {
        flip = VerticalFlips[j];
        if (flip < to)
          continue;
        break;
      }
      break;
    } // foreach of the vertical flips

    bool fromOutsideInterval = i % 2 == 0;
    bool toOutsideInterval = j % 2 == 0;

    while (j-- > i)
      VerticalFlips.RemoveAt(j); // This was the problem line with just .Remove(j)
    if (toOutsideInterval)
      VerticalFlips.Insert(i, to);
    if (fromOutsideInterval)
      VerticalFlips.Insert(i, from);
  } // AddVerticalUseSection

  public void BeginTextBlock() { /* Do nothing */  }

  public void EndTextBlock() { /* Do nothing */ }

  public void RenderImage(ImageRenderInfo renderInfo) {
    Matrix ctm = renderInfo.GetImageCTM();
    List<float> YCoords = new List<float>(4) { 0, 0, 0, 0 };

    for (int x = 0; x < 2; x++) {
      for (int y = 0; y < 2; y++) {
        Vector corner = new Vector(x, y, 1).Cross(ctm);
        YCoords[2 * x + y] = corner[Vector.I2];
      }
    }

    YCoords.Sort();
    AddVerticalUseSection(YCoords[0], YCoords[3]);
  } // RenderImage

  public void RenderText(TextRenderInfo renderInfo) {
    LineSegment ascentLine = renderInfo.GetAscentLine();
    LineSegment descentLine = renderInfo.GetDescentLine();
    List<float> YCoords = new List<float>(4) {
      ascentLine.GetStartPoint()[Vector.I2],
      ascentLine.GetEndPoint()[Vector.I2],
      descentLine.GetStartPoint()[Vector.I2],
      descentLine.GetEndPoint()[Vector.I2],
    };

    YCoords.Sort();
    AddVerticalUseSection(YCoords[0], YCoords[3]);
  } // RenderText
} // PageVericalAnalyzer
  

收集文件和运行工具的代码:

public void TestMergeDocuments() {
  PdfVeryDenseMergeTool tool = new PdfVeryDenseMergeTool(iTextSharp.text.PageSize.A4, 18, 18, 10);
  List<byte[]> Files = new List<byte[]>();

  // Code to load each of the 3 files I need into this byte array list

  using (MemoryStream ms = new MemoryStream()) {
    List<PdfReader> files = new List<PdfReader>();

    foreach (byte[] ba in Files) {
      files.Add(new PdfReader(ba));
    } // foreach of the sets of data

    tool.Merge(ms, files);

    // Save the file using: ms.GetBuffer()
  } // using the memory stream
} // TestMergeDocuments

1 个答案:

答案 0 :(得分:6)

以下示例工具已根据this answer的工具PdfDenseMergeTool的想法实施,OP已评论 SO接近 [he ] 需要 s。就像PdfDenseMergeTool一样,这个工具在Java / iText中实现,我在家里比C#/ iTextSharp更多。作为OP has already translated PdfDenseMergeTool到C#/ iTextSharp,在此处翻译此工具也不应该是一个太大的问题。

PdfVeryDenseMergeTool

此工具与PdfDenseMergeTool类似,从多个PdfReader实例中获取页面的页面内容,并尝试密集合并它们,即将多个源页面的内容放在单个目标页面上(如果有)足够的自由空间来做到这一点。与之前的工具相比,此工具甚至可以拆分源页面内容,以实现更密集的合并。

就像其他工具一样PdfVeryDenseMergeTool没有考虑矢量图形,因为iText(Sharp)解析API只能转发文本和位图图像

PdfVeryDenseMergeTool拆分源页面,这些页面不完全适合水平线上的目标页面,该水平线未与文本字形或位图图形的边界框相交。

工具类:

public class PdfVeryDenseMergeTool
{
    public PdfVeryDenseMergeTool(Rectangle size, float top, float bottom, float gap)
    {
        this.pageSize = size;
        this.topMargin = top;
        this.bottomMargin = bottom;
        this.gap = gap;
    }

    public void merge(OutputStream outputStream, Iterable<PdfReader> inputs) throws DocumentException, IOException
    {
        try
        {
            openDocument(outputStream);
            for (PdfReader reader: inputs)
            {
                merge(reader);
            }
        }
        finally
        {
            closeDocument();
        }
    }

    void openDocument(OutputStream outputStream) throws DocumentException
    {
        final Document document = new Document(pageSize, 36, 36, topMargin, bottomMargin);
        final PdfWriter writer = PdfWriter.getInstance(document, outputStream);
        document.open();
        this.document = document;
        this.writer = writer;
        newPage();
    }

    void closeDocument()
    {
        try
        {
            document.close();
        }
        finally
        {
            this.document = null;
            this.writer = null;
            this.yPosition = 0;
        }
    }

    void newPage()
    {
        document.newPage();
        yPosition = pageSize.getTop(topMargin);
    }

    void merge(PdfReader reader) throws IOException
    {
        PdfReaderContentParser parser = new PdfReaderContentParser(reader);
        for (int page = 1; page <= reader.getNumberOfPages(); page++)
        {
            merge(reader, parser, page);
        }
    }

    void merge(PdfReader reader, PdfReaderContentParser parser, int page) throws IOException
    {
        PdfImportedPage importedPage = writer.getImportedPage(reader, page);
        PdfContentByte directContent = writer.getDirectContent();

        PageVerticalAnalyzer finder = parser.processContent(page, new PageVerticalAnalyzer());
        if (finder.verticalFlips.size() < 2)
            return;
        Rectangle pageSizeToImport = reader.getPageSize(page);

        int startFlip = finder.verticalFlips.size() - 1;
        boolean first = true;
        while (startFlip > 0)
        {
            if (!first)
                newPage();

            float freeSpace = yPosition - pageSize.getBottom(bottomMargin);
            int endFlip = startFlip + 1;
            while ((endFlip > 1) && (finder.verticalFlips.get(startFlip) - finder.verticalFlips.get(endFlip - 2) < freeSpace))
                endFlip -=2;
            if (endFlip < startFlip)
            {
                float height = finder.verticalFlips.get(startFlip) - finder.verticalFlips.get(endFlip);

                directContent.saveState();
                directContent.rectangle(0, yPosition - height, pageSizeToImport.getWidth(), height);
                directContent.clip();
                directContent.newPath();

                writer.getDirectContent().addTemplate(importedPage, 0, yPosition - (finder.verticalFlips.get(startFlip) - pageSizeToImport.getBottom()));

                directContent.restoreState();
                yPosition -= height + gap;
                startFlip = endFlip - 1;
            }
            else if (!first) 
                throw new IllegalArgumentException(String.format("Page %s content sections too large.", page));
            first = false;
        }
    }

    Document document = null;
    PdfWriter writer = null;
    float yPosition = 0; 

    final Rectangle pageSize;
    final float topMargin;
    final float bottomMargin;
    final float gap;
}
     

PdfVeryDenseMergeTool.java

此工具使用自定义RenderListener与iText解析器API一起使用:

public class PageVerticalAnalyzer implements RenderListener
{
    @Override
    public void beginTextBlock() { }
    @Override
    public void endTextBlock() { }

    /*
     * @see RenderListener#renderText(TextRenderInfo)
     */
    @Override
    public void renderText(TextRenderInfo renderInfo)
    {
        LineSegment ascentLine = renderInfo.getAscentLine();
        LineSegment descentLine = renderInfo.getDescentLine();
        float[] yCoords = new float[]{
                ascentLine.getStartPoint().get(Vector.I2),
                ascentLine.getEndPoint().get(Vector.I2),
                descentLine.getStartPoint().get(Vector.I2),
                descentLine.getEndPoint().get(Vector.I2)
        };
        Arrays.sort(yCoords);
        addVerticalUseSection(yCoords[0], yCoords[3]);
    }

    /*
     * @see RenderListener#renderImage(ImageRenderInfo)
     */
    @Override
    public void renderImage(ImageRenderInfo renderInfo)
    {
        Matrix ctm = renderInfo.getImageCTM();
        float[] yCoords = new float[4];
        for (int x=0; x < 2; x++)
            for (int y=0; y < 2; y++)
            {
                Vector corner = new Vector(x, y, 1).cross(ctm);
                yCoords[2*x+y] = corner.get(Vector.I2);
            }
        Arrays.sort(yCoords);
        addVerticalUseSection(yCoords[0], yCoords[3]);
    }

    /**
     * This method marks the given interval as used.
     */
    void addVerticalUseSection(float from, float to)
    {
        if (to < from)
        {
            float temp = to;
            to = from;
            from = temp;
        }

        int i=0, j=0;
        for (; i<verticalFlips.size(); i++)
        {
            float flip = verticalFlips.get(i);
            if (flip < from)
                continue;

            for (j=i; j<verticalFlips.size(); j++)
            {
                flip = verticalFlips.get(j);
                if (flip < to)
                    continue;
                break;
            }
            break;
        }
        boolean fromOutsideInterval = i%2==0;
        boolean toOutsideInterval = j%2==0;

        while (j-- > i)
            verticalFlips.remove(j);
        if (toOutsideInterval)
            verticalFlips.add(i, to);
        if (fromOutsideInterval)
            verticalFlips.add(i, from);
    }

    final List<Float> verticalFlips = new ArrayList<Float>();
}
     

PageVerticalAnalyzer.java

它的用法如下:

PdfVeryDenseMergeTool tool = new PdfVeryDenseMergeTool(PageSize.A4, 18, 18, 5);
tool.merge(output, inputs);
     

VeryDenseMerging.java

应用于OP的样本文件

Header.pdf

Header.pdf pages

Body.pdf

Body.pdf pages

Footer.pdf

Footer.pdf pages

它会生成

A4 very dense merge result

如果将目标文档页面大小定义为A5 landscape:

PdfVeryDenseMergeTool tool = new PdfVeryDenseMergeTool(new RectangleReadOnly(595,421), 18, 18, 5);
tool.merge(output, inputs);
     

VeryDenseMerging.java

它生成了这个:

A5 very dense merge result

小心! 这只是一个概念验证,并没有考虑所有可能性。例如。如果没有正确处理具有非平凡旋转值的源页面或目标页面的情况。因此,它尚未准备好用于生产。


当前(5.5.6 SNAPSHOT)iText版本的改进

当前的针对5.5.6的iText开发版增强了解析器功能,也可以发送矢量图形信号。因此,我扩展了PageVerticalAnalyzer以使用它:

public class PageVerticalAnalyzer implements ExtRenderListener
{
    @Override
    public void beginTextBlock() { }
    @Override
    public void endTextBlock() { }
    @Override
    public void clipPath(int rule) { }
    ...
    static class SubPathSection
    {
        public SubPathSection(float x, float y, Matrix m)
        {
            float effectiveY = getTransformedY(x, y, m);
            pathFromY = effectiveY;
            pathToY = effectiveY;
        }

        void extendTo(float x, float y, Matrix m)
        {
            float effectiveY = getTransformedY(x, y, m);
            if (effectiveY < pathFromY)
                pathFromY = effectiveY;
            else if (effectiveY > pathToY)
                pathToY = effectiveY;
        }

        float getTransformedY(float x, float y, Matrix m)
        {
            return new Vector(x, y, 1).cross(m).get(Vector.I2);
        }

        float getFromY()
        {
            return pathFromY;
        }

        float getToY()
        {
            return pathToY;
        }

        private float pathFromY;
        private float pathToY;
    }

    /*
     * Beware: The implementation is not correct as it includes the control points of curves
     * which may be far outside the actual curve.
     * 
     * @see ExtRenderListener#modifyPath(PathConstructionRenderInfo)
     */
    @Override
    public void modifyPath(PathConstructionRenderInfo renderInfo)
    {
        Matrix ctm = renderInfo.getCtm();
        List<Float> segmentData = renderInfo.getSegmentData();

        switch (renderInfo.getOperation())
        {
        case PathConstructionRenderInfo.MOVETO:
            subPath = null;
        case PathConstructionRenderInfo.LINETO:
        case PathConstructionRenderInfo.CURVE_123:
        case PathConstructionRenderInfo.CURVE_13:
        case PathConstructionRenderInfo.CURVE_23:
            for (int i = 0; i < segmentData.size()-1; i+=2)
            {
                if (subPath == null)
                {
                    subPath = new SubPathSection(segmentData.get(i), segmentData.get(i+1), ctm);
                    path.add(subPath);
                }
                else
                    subPath.extendTo(segmentData.get(i), segmentData.get(i+1), ctm);
            }
            break;
        case PathConstructionRenderInfo.RECT:
            float x = segmentData.get(0);
            float y = segmentData.get(1);
            float w = segmentData.get(2);
            float h = segmentData.get(3);
            SubPathSection section = new SubPathSection(x, y, ctm);
            section.extendTo(x+w, y, ctm);
            section.extendTo(x, y+h, ctm);
            section.extendTo(x+w, y+h, ctm);
            path.add(section);
        case PathConstructionRenderInfo.CLOSE:
            subPath = null;
            break;
        default:
        }
    }

    /*
     * @see ExtRenderListener#renderPath(PathPaintingRenderInfo)
     */
    @Override
    public Path renderPath(PathPaintingRenderInfo renderInfo)
    {
        if (renderInfo.getOperation() != PathPaintingRenderInfo.NO_OP)
        {
            for (SubPathSection section : path)
                addVerticalUseSection(section.getFromY(), section.getToY());
        }

        path.clear();
        subPath = null;
        return null;
    }

    List<SubPathSection> path = new ArrayList<SubPathSection>();
    SubPathSection subPath = null;
    ...
}
     

PageVerticalAnalyzer.java

简单测试(VeryDenseMerging.java方法testMergeOnlyGraphics)合并这些文件

circlesOnlyA.pdf

circlesOnlyB.pdf

circlesOnlyC.pdf

circlesOnlyD.pdf

进入这个:

circlesOnlyMerge-veryDense.pdf

但又一次 要小心:这仅仅是概念的证明。特别是modifyPath()需要改进,实施不正确,因为它包括可能远远超出实际曲线的曲线控制点。