ralfstuckert/pdfbox-layout

TextFlow: capping the height

Closed this issue · 9 comments

fref commented

Would it be possible to create a text flow, capping its height, and then ask it to render and return, in a way or another, the characters which couldn't be rendered within the selected height?
(context: I have a project which I need to migrate away from PdfClown, and its BlockComposer provided such an api: showText(String text) returns Last shown character index. This was pretty useful for pagination/page breaks inside tables).

Since PDFBox-Layout supports pagination, you may benefit from the existing API. The key is the interface Dividable which allows to divide a Drawable (like e.g. Paragraph or TextFlow) at a given height.

Here is an example using Paragraph, but it is exactly the same with TextFlow:

    String text = "At *vero eos et accusam* et justo duo dolores et ea rebum."
        + "Stet clita kasd gubergren, no sea takimata\n\n"
        + "sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, "
        + "_consetetur sadipscing elitr_, sed diam nonumy eirmod tempor invidunt "
        + "ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero "
        + "eos et _accusam et *justo* duo dolores_ et ea rebum. Stet clita kasd "
        + "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\n";

    Document document = new Document(Constants.A5);

    Paragraph paragraph = new Paragraph();
    paragraph.addMarkup(text, 11, BaseFont.Times);

    // constraint width
    paragraph.setMaxWidth(200);

    // divide after 100
    Divided divide = paragraph.divide(100, Constants.A4.getHeight());

    // height <= 100
    Paragraph first = (Paragraph)divide.getFirst();

    // tail is the remainder of the paragraph
    Paragraph tail = (Paragraph)divide.getTail();

    document.add(first, VerticalLayoutHint.LEFT);
    document.add(tail, VerticalLayoutHint.RIGHT);

    final OutputStream outputStream = new FileOutputStream("test.pdf");
    document.save(outputStream);

Does that help you?

In case you want to use the low-level API, you can do this:

TextFlow text = ...
float maxWidth = 200f;
float heightToDivideAt = 100f;

Divided divided = TextSequenceUtil.divide(text, maxWidth, heightToDivideAt);
...

Actually this is what the implementation of Paragraph.divide() does ;-)

fref commented

That seems perfect, I'll try next week, thank you. Apache should really
include layouting tools like yours.

Le 15 oct. 2016 10:52, "Ralf Stuckert" notifications@github.com a écrit :

In case you want to use the low-level API, you can do this:

TextFlow text = ...
float maxWidth = 200f;
float heightToDivideAt = 100f;

Divided divided = TextSequenceUtil.divide(text, maxWidth, heightToDivideAt);
...

Actually this is what the implementation of Paragraph.divided() does ;-)


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
#8 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAH_CCd1dVSXsQbgBsq7sVju83pqo_Tvks5q0JRigaJpZM4KXD2B
.

Please let me know, if this fulfills your needs, so I may close the issue.

fref commented

I had to make some adjustments, but it's working perfectly, thank you.

I'm hijacking the topic because I don't know how to contact you about this
(let me know if I should open another issue for this):

  • is there a way to force wrapping inside the boundaries of a word if there
    is not enough space on a line? (maybe inject a "wrapping strategy" or a
    parameter; from what I glanced at, wrapping seems mostly static)
  • is there a way to avoid the removal of leading white space? (in some
    cases, I need to output source code, when the white space is removed, this
    doesn't look very good)

Regards

Frédéric

fref commented

I need to make a few more adjustments today, but this is looking good.
Any reason why TextSequenceUtil.divide takes TextSequence as parameter, but
Divided is made of Drawable, which are not TextSequence?

Regards

On 21 October 2016 at 07:44, Ralf Stuckert notifications@github.com wrote:

Please let me know, if this fulfills your needs, so I may close the issue.


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
#8 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAH_COEUKmk-9NwKmMRsYRZXBkyNNWZIks5q2FE4gaJpZM4KXD2B
.

Hi,

  1. Divided is a general container defined by interface Dividable. Dividable is implemented by all kind of elements, e.g. text, images etc. Since Drawable is the common interface, that's the reason it is used in Divided.
  2. Wrapping Strategy seems like a good idea, please open a separate issue for that. Please give some hint where you would expect to specify the strategy.
  3. What would be the use-case for avoiding the leading-space removal, preformatted text? Would you expect any layouting/wrapping in that case (cp. to HTML <pre> element).

Since this issue seems solved, I will close it.

In the meantime you could replace leading space with indentation markup (--{3em}). It's a crude workaround, but does solve your problem:

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.pdfbox.io.IOUtils;

import rst.pdfbox.layout.elements.Document;
import rst.pdfbox.layout.elements.Paragraph;
import rst.pdfbox.layout.text.BaseFont;

public class CodeSample {

    public static void main(String[] args) throws Exception {

    String code = getCode();
    code = indentLeadingSpace(code);

    Document document = new Document(40, 60, 40, 60);

    Paragraph paragraph = new Paragraph();
    paragraph.addMarkup(code, 10, BaseFont.Courier);
    document.add(paragraph);

    final OutputStream outputStream = new FileOutputStream("code.pdf");
    document.save(outputStream);

    }

    private static String indentLeadingSpace(final String text) {
    Pattern pattern = Pattern.compile("^(\\h+)", Pattern.MULTILINE);
    Matcher matcher = pattern.matcher(text);
    StringBuffer buffer = new StringBuffer(text.length());
    while (matcher.find()) {
        String group = matcher.group();
        int indent = 0;
        for (char character : group.toCharArray()) {
        if (character == '\t') {
            indent += 4;
        } else {
            indent += 1;
        }
        }
        matcher.appendReplacement(buffer, String.format("--{%sem}", indent));
    }
    matcher.appendTail(buffer);
    return buffer.toString();
    }

    private static String getCode() throws IOException {
    InputStream input = new FileInputStream("code.txt");
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    IOUtils.copy(input, baos);
    return new String(baos.toByteArray());
    }
}