Como remover o espaço em branco na mesclagem

Eu tenho um código que leva 3 diferentes matrizes de bytes PDF e mescla-os. Este código funciona muito bem. O problema (algumas pessoas) é que cada PDF é considerado uma página inteira (se impressa), mesmo que haja apenas 4 polegadas de conteúdo, deixando 7 polegadas de espaço em branco verticalmente. Em seguida, o documento do meio é colocado e pode ou não ter espaço em branco vertical no final dele. Então o rodapé também é colocado em sua própria página.

Aqui está o código:

byte[] Bytes = rv.LocalReport.Render("PDF", null, out MimeType, out Encoding, out Extension, out StreamIDs, out Warnings); List 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 

Existe uma maneira quando estou fundindo cada página para recortar / remover / apagar o espaço em branco vertical no final de cada pdf para que ele apareça como um documento transparente?

ATUALIZAÇÃO: Aqui estão alguns exemplos de arquivos .pdf que estou tentando mesclar.

header , corpo , rodapé

ATUALIZAÇÃO 2: USANDO A RESPOSTA:

Eu converti o código do @mkl para C # e aqui está.

A class de ferramenta:

 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 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  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 

A class RenderListener:
ATUALIZAÇÃO 3: FIXO 1 LINHA DE CÓDIGO E FUNCIONA: Veja o comentário no código

 public class PageVerticalAnalyzer : IRenderListener { public PageVerticalAnalyzer() { } public List VerticalFlips = new List(); 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  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 YCoords = new List(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 YCoords = new List(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 

Código para reunir arquivos e executar a ferramenta:

 public void TestMergeDocuments() { PdfVeryDenseMergeTool tool = new PdfVeryDenseMergeTool(iTextSharp.text.PageSize.A4, 18, 18, 10); List Files = new List(); // Code to load each of the 3 files I need into this byte array list using (MemoryStream ms = new MemoryStream()) { List files = new List(); 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 

A ferramenta de amostra a seguir foi implementada ao longo das idéias da ferramenta PdfDenseMergeTool partir desta resposta que o OP comentou estar tão perto do que ele precisa . Assim como o PdfDenseMergeTool esta ferramenta aqui é implementada em Java / iText, com a qual estou mais à PdfDenseMergeTool que C # / iTextSharp. Como o OP já traduziu o PdfDenseMergeTool para C # / iTextSharp, traduzir essa ferramenta aqui também não deve ser um problema muito grande.

PdfVeryDenseMergeTool

Essa ferramenta, similarmente ao PdfDenseMergeTool pega o conteúdo das páginas de várias instâncias de PdfReader e tenta mesclá-las densamente, isto é, colocar o conteúdo de várias páginas de origem em uma única página de destino, se houver espaço livre suficiente para isso. Em contraste com essa ferramenta anterior, essa ferramenta divide até o conteúdo da página de origem para permitir uma mesclagem ainda mais densa.

Assim como a outra ferramenta, o PdfVeryDenseMergeTool não leva em conta os charts vetoriais, pois a API de análise do iText (Sharp) só encaminha imagens de texto e de bitmap

O PdfVeryDenseMergeTool divide páginas de origem que não se encheckboxm completamente em uma página de destino em uma linha horizontal que não é interceptada pelas checkboxs delimitadoras de glifos de texto ou charts de bitmap.

A class de ferramenta:

 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 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 )

Esta ferramenta faz uso de um RenderListener personalizado para uso com a API do analisador iText:

 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 i) verticalFlips.remove(j); if (toOutsideInterval) verticalFlips.add(i, to); if (fromOutsideInterval) verticalFlips.add(i, from); } final List verticalFlips = new ArrayList(); } 

( PageVerticalAnalyzer.java )

É usado assim:

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

( VeryDenseMerging.java )

Aplicado aos documentos de amostra do OP

Header.pdf

Header.pdf pages

Body.pdf

Body.pdf pages

Footer.pdf

Footer.pdf pages

gera

Resultado de mesclagem muito densa A4

Se alguém definir o tamanho da página do documento de destino como paisagem A5:

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

( VeryDenseMerging.java )

isso gera isso:

A5 resultado de mesclagem muito densa

Cuidado! Esta é apenas uma prova de conceito e não considera todas as possibilidades. Por exemplo, o caso de páginas de origem ou de destino com um valor de rotação não trivial não é tratado adequadamente. Assim, ainda não está pronto para uso em produção.


Melhoria na versão atual do iText (5.5.6 SNAPSHOT)

A atual versão de desenvolvimento do iText para o 5.5.6 aprimora a funcionalidade do analisador para também sinalizar charts vetoriais. Assim, eu estendi o PageVerticalAnalyzer para fazer uso disto:

 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 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 path = new ArrayList(); SubPathSection subPath = null; ... } 

( PageVerticalAnalyzer.java )

Um teste simples ( VeryDenseMerging.java method testMergeOnlyGraphics ) mescla esses arquivos

circlesOnlyA.pdf

circlesOnlyB.pdf

circlesOnlyC.pdf

circlesOnlyD.pdf

nisso:

circlesOnlyMerge-veryDense.pdf

Mas, mais uma vez, cuidado: isso é uma mera prova de conceito. Especialmente modifyPath() precisa ser melhorado, a implementação não está correta, pois inclui os pontos de controle de curvas que podem estar muito fora da curva real.