danfickle/openhtmltopdf

Implement text justification in PDF-BOX output device

danfickle opened this issue · 4 comments

As of PDF-BOX 2.0.0-RC3 there is no way to implement text justification. Need to open PDF-BOX JIRA issue asking them to implement the text spacing and space spacing operators.

surfx commented

I've read in stackoverflow [1] about the class: InlineBoxing (package com.openhtmltopdf.layout;)

[1] http://stackoverflow.com/questions/36982586/flying-saucer-generates-pdf-with-badly-justified-text
Line 239, alter to:

if (lbContext.isNeedsNewLine()) {
currentLine.trimTrailingSpace(c); //justify

add before:
saveLine(currentLine, c, box, minimumLineHeight,...

And it works fine for flying-saucer, but not to openhtmltopdf :(

I've managed to get PdfBox to actually start adding spacing by upgrading to pdfbox 2.0.2 and changing the PdfPageContentStream's setTextSpacing and setSpaceSpacing to:

public void setTextSpacing(float nonSpaceAdjust) {
        try {
            cs.appendRawCommands(String.format("%f Tc\n", nonSpaceAdjust).replace(',', '.'));
        } catch (IOException e) {
            logAndThrow("setSpaceSpacing", e);
        }    
    }

    public void setSpaceSpacing(float spaceAdjust) {
        try {
            cs.appendRawCommands(String.format("%f Tw\n", spaceAdjust).replace(',', '.'));
        } catch (IOException e) {
            logAndThrow("setSpaceSpacing", e);
        }
    }

appendRawCommands is deprecated, but PdfBox still doesn't support the Tw and Tc operators.

The spacing is wrong, I guess the LineBox.justify() method needs some rework as well. I managed to make word spacing work correctly by changing:

        if (info != null) {
            _cp.setTextSpacing(info.getNonSpaceAdjust());
            _cp.setSpaceSpacing(info.getSpaceAdjust());
        }

to

        if (info != null) {
            int spaces = 0;
            for (int i = 0; i < s.length(); i++) {
                char chr = s.charAt(i);
                if (chr == ' ' || chr == '\u00a0' || chr == '\u3000') {
                    spaces++;
                }
            }
            float free = 0;
            try {
                free = width - ((fontSize * (_font.getFontDescription().get(0).getFont().getStringWidth(s) / 1000)));
            } catch (IOException e) {
                e.printStackTrace();
            }
//            _cp.setTextSpacing(info.getNonSpaceAdjust());
//            _cp.setSpaceSpacing(info.getSpaceAdjust());
            _cp.setSpaceSpacing(free / spaces);
        } else {
            _cp.setTextSpacing(0.0f);
            _cp.setSpaceSpacing(0.0f);
        }

Yeah it's ugly as hell but you get the drill :)

Thanks @surfx and especially @HiddenDog for the code. I'll try to look at this in the next 24 hours but I think you've put me in the right direction.

Thanks everyone - now working well.