Search code examples
javaitext7

iText 7 hyphen with different size Text


I need to add certain properties to each letter (different size of letters, different font, etc)

PdfDocument pdfDoc = new PdfDocument(new PdfWriter(dest));
Document doc = new Document(pdfDoc);

PdfFont helveticaFont = PdfFontFactory.createFont(StandardFonts.HELVETICA);
PdfFont helveticaBoldFont = PdfFontFactory.createFont(StandardFonts.HELVETICA_BOLD);

Paragraph p = new Paragraph();
String s = "all text is written in red, except the letters b and g; they are written in blue and green.";
for (int i = 0; i < s.length(); i++) {
    p.add(returnCorrectColor(s.charAt(i), helveticaFont, helveticaBoldFont));
}
p.setHyphenation(new HyphenationConfig("en", "US", 2, 2)); // doesnt work

doc.add(p);

doc.close();

Helper method:

private static Text returnCorrectColor(char letter, PdfFont helveticaFont, PdfFont helveticaBoldFont) {
    if (letter == 'b') {
        return new Text("b")
                .setFontColor(ColorConstants.BLUE)
                .setFont(helveticaBoldFont)
                .setFontSize(15);
    
    } else {
        return new Text(String.valueOf(letter))
                .setFontColor(ColorConstants.RED)
                .setFont(helveticaFont)
                .setFontSize(12);
    }
}

How can I add correct hyphen with such conditions?


Solution

  • iText 7 does hyphenation at Text level at the moment. Word break is also done at the Text level by default. This is somewhat of a limitation for cases like yours when you want to add specific styling or properties to parts of words.

    To illustrate the problem better, let's add some background to the paragraph and some relatively small width to force some words being split:

    Paragraph p = new Paragraph().setBackgroundColor(ColorConstants.GRAY).setWidth(200);
    

    If we run the example, we see the following result:

    result

    So the word letters was just forcefully split into parts which is not what we typically want.

    Note that I mentioned that word break is done at the Text level by default. It's possible to override that behavior with p.setProperty(Property.RENDERING_MODE, RenderingMode.HTML_MODE); which will result in treating words closer to what HTML does. This is the result we would get:

    result

    So it's better in a sense that no unexpected word splits happened, but still there is no hyphenation.

    While 100% correct hyphenation will be hard to achieve, because hyphenation happens on the Text level, if you combine as much of the text as possible into a single Text element, the hyphenation will happen if possible. So let's adjust our code that splits the text into Text elements a bit. On top of helping to solve the hyphenation problem, it's also going to make the whole layout process more CPU and memory-efficient as we will create fewer Text instances:

    private static java.util.List<Text> splitTextIntoElements(String text, PdfFont helveticaFont, PdfFont helveticaBoldFont) {
        java.util.List<Text> result = new java.util.ArrayList<>();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < text.length(); i++) {
            if (sb.length() != 0 && getCharCategory(text.charAt(i)) != getCharCategory(sb.charAt(sb.length() - 1))) {
                result.add(createTextChunk(sb, helveticaFont, helveticaBoldFont));
                sb.setLength(0);
            }
            sb.append(text.charAt(i));
        }
        if (sb.length() != 0) {
            result.add(createTextChunk(sb, helveticaFont, helveticaBoldFont));
        }
        return result;
    }
    
    private static final int BLUE_CATEGORY = 1;
    private static final int RED_CATEGORY = 2;
    
    private static int getCharCategory(char ch) {
        if (ch == 'b') {
            return BLUE_CATEGORY;
        }
        return RED_CATEGORY;
    }
    
    private static Text createTextChunk(StringBuilder sb, PdfFont helveticaFont, PdfFont helveticaBoldFont) {
        int chunkCategory = getCharCategory(sb.charAt(sb.length() - 1));
        Text chunk = new Text(sb.toString());
        applyTextProperties(chunk, chunkCategory, helveticaFont, helveticaBoldFont);
        return chunk;
    }
    
    private static void applyTextProperties(Text text, int category, PdfFont helveticaFont, PdfFont helveticaBoldFont) {
        if (category == BLUE_CATEGORY) {
            text.setFontColor(ColorConstants.BLUE)
                    .setFont(helveticaBoldFont)
                    .setFontSize(15);
        } else {
            text.setFontColor(ColorConstants.RED)
                    .setFont(helveticaFont)
                    .setFontSize(12);
        }
    }
    

    This is what we get if we run the code:

    result

    The hyphenation was not added, but fortunately it's just because there was not enough width to hyphenate word letters. If we use 205 points instead of 200 as our width, we get the desired result:

    desired result

    Final code (helper methods are attached above):

    Document doc = new Document(pdfDocument);
    
    PdfFont helveticaFont = PdfFontFactory.createFont(StandardFonts.HELVETICA);
    PdfFont helveticaBoldFont = PdfFontFactory.createFont(StandardFonts.HELVETICA_BOLD);
    
    Paragraph p = new Paragraph().setBackgroundColor(ColorConstants.GRAY).setWidth(205);
    p.setProperty(Property.RENDERING_MODE, RenderingMode.HTML_MODE);
    String s = "all text is written in red, except the letters b and g; they are written in blue and green.";
    for (Text chunk : splitTextIntoElements(s, helveticaFont, helveticaBoldFont)) {
        p.add(chunk);
    }
    p.setHyphenation(new HyphenationConfig("en", "US", 2, 2));
    
    doc.add(p);