All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.craigburke.document.builder.render.ParagraphRenderer.groovy Maven / Gradle / Ivy

The newest version!
package com.craigburke.document.builder.render

import com.craigburke.document.builder.PdfDocument
import com.craigburke.document.core.Align
import com.craigburke.document.core.Font
import com.craigburke.document.core.ImageType
import com.craigburke.document.core.Link
import com.craigburke.document.core.Text
import com.craigburke.document.core.TextBlock
import org.apache.pdfbox.pdmodel.PDPageContentStream
import org.apache.pdfbox.pdmodel.common.PDRectangle
import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject
import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink
import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary

import javax.imageio.ImageIO
import java.awt.image.BufferedImage

/**
 * Rendering element for a Paragraph node
 * @author Craig Burke
 */
class ParagraphRenderer implements Renderable {
    TextBlock node

    List lines

    private int parseStart = 0
    private int linesParsed = 0

    float renderedHeight = 0
    private float startX
    private float totalWidth
    private boolean parsedAndRendered = false
    private boolean fullyRendered = false
    private boolean fullyParsed = false

    ParagraphRenderer(TextBlock paragraph, PdfDocument pdfDocument, float startX, float totalWidth) {
        node = paragraph
        this.pdfDocument = pdfDocument
        this.startX = startX
        this.totalWidth = totalWidth
        lines = ParagraphParser.getLines(paragraph, totalWidth)
    }

    boolean getFullyParsed() {
        this.fullyParsed
    }

    int getParseStart() {
        this.parseStart
    }

    int getParseEnd() {
        int parseEnd = Math.max(0f, (parseStart + linesParsed - 1))
        Math.min(lines.size() - 1, parseEnd)
    }

    int getLinesParsed() {
        this.linesParsed
    }

    void parse(float height) {
        if (!lines || fullyRendered) {
            fullyParsed = true
            return
        }

        if (parsedAndRendered) {
            parseStart += linesParsed
            parseStart = Math.min(lines.size() - 1, parseStart)
        }
        linesParsed = 0

        boolean reachedEnd = false
        float parsedHeight = 0

        while (!reachedEnd) {
            ParagraphLine line = lines[parseStart + linesParsed]
            parsedHeight += line.totalHeight
            linesParsed++

            if (parsedHeight > height) {
                linesParsed = Math.max(0f, linesParsed - 1)
                reachedEnd = true
                fullyParsed = false
            }
            else if (line == lines.last()) {
                reachedEnd = true
                fullyParsed = true
            }
        }
        parsedAndRendered = false
    }

    void renderElement(float startY) {
        if (fullyRendered || !linesParsed) {
            return
        }

        renderBackground()

        lines[parseStart..parseEnd].each { ParagraphLine line ->
            pdfDocument.x = startX
            renderLine(line)
        }
        renderedHeight = parsedHeight
        fullyRendered = fullyParsed
        parsedAndRendered = true
    }

    void renderBackground() {
        if (!node.background) {
            return
        }
        PDPageContentStream contentStream = pdfDocument.contentStream

        float height = parsedHeight
        float backgroundBottomY = pdfDocument.translateY(pdfDocument.y + parsedHeight)

        contentStream.setNonStrokingColor(*node.background.rgb)
        contentStream.addRect(startX, backgroundBottomY, totalWidth, height)
        contentStream.fill()

    }

    float getTotalHeight() {
        lines.sum { it.totalHeight } as float
    }

    float getParsedHeight() {
        if (!linesParsed) {
            return 0f
        }

        lines[parseStart..parseEnd]*.totalHeight.sum() as float ?: 0f
    }

    private void renderLine(ParagraphLine line) {
        float renderStartX = startX

        switch (line.paragraph.align) {
            case Align.RIGHT:
                renderStartX += line.maxWidth - line.contentWidth
                break
            case Align.CENTER:
                renderStartX += Math.round((line.maxWidth - line.contentWidth) / 2)
        }

        pdfDocument.x = renderStartX
        pdfDocument.y += line.contentHeight

        line.elements.each { element ->
            if (element instanceof TextElement) {
                renderTextElement(element as TextElement, line)
                pdfDocument.x += element.width
            }
            else if (element instanceof ImageElement) {
                renderImageElement(element as ImageElement)
                pdfDocument.x += element.node.width
            }
        }

        pdfDocument.y += line.lineSpacing
    }

    private void renderTextElement(TextElement element, ParagraphLine line) {
        Text text = element.node
        Font font = text.font

        PDPageContentStream contentStream = pdfDocument.contentStream
        float startX = pdfDocument.x
        float startY = pdfDocument.translatedY
        float bottomY = pdfDocument.translateY(pdfDocument.y + line.contentHeight - line.lineSpacing)

        if (text.background) {
            float height = line.contentHeight + line.lineSpacing
            contentStream.setNonStrokingColor(*text.background.rgb)
            contentStream.addRect(startX, bottomY, element.width, height)
            contentStream.fill()
        }

        if (text.font.underline) {
            float textBottom = pdfDocument.translateY(pdfDocument.y + line.contentHeight - (line.lineSpacing * 1.5f))
            float endX = startX + element.width

            contentStream.setStrokingColor(*font.color.rgb)
            float lineWidth = ((font.size as Float) / 16f)
            contentStream.setLineWidth(lineWidth)

            contentStream.moveTo(startX, textBottom)
            contentStream.lineTo(endX, textBottom)
            contentStream.stroke()
        }

        contentStream.beginText()
        contentStream.newLineAtOffset(startX, startY)

        contentStream.setNonStrokingColor(*font.color.rgb)
        contentStream.setFont(element.pdfFont, font.size)
        contentStream.showText(element.text)

        contentStream.endText()

        if (text instanceof Link) {
            Link link = text as Link
            PDRectangle position = new PDRectangle(startX, bottomY, element.width as float, line.totalHeight)

            PDActionURI action = new PDActionURI(URI: link.url)
            PDAnnotationLink linkAnnotation = new PDAnnotationLink()
            PDBorderStyleDictionary border = new PDBorderStyleDictionary(width: 0)
            linkAnnotation.borderStyle = border
            linkAnnotation.rectangle = position
            linkAnnotation.action = action
            pdfDocument.currentPage.annotations.add(linkAnnotation)
        }

    }

    private void renderImageElement(ImageElement element) {
        InputStream inputStream = new ByteArrayInputStream(element.node.data)
        BufferedImage bufferedImage = ImageIO.read(inputStream)

        PDImageXObject img
        if (element.node.type == ImageType.PNG) {
            img = LosslessFactory.createFromImage(pdfDocument.pdDocument, bufferedImage)
        }
        else {
            img = JPEGFactory.createFromImage(pdfDocument.pdDocument, bufferedImage)
        }

        int width = element.node.width
        int height = element.node.height

        pdfDocument.contentStream.drawImage(img, pdfDocument.x, pdfDocument.translatedY, width, height)
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy