Details
-
Improvement
-
Status: Closed
-
Major
-
Resolution: Fixed
-
None
-
None
Description
Why: The TJ operator is required to properly implement text justification in unicode fonts. The word spacing operator (Tw) is not sufficient because of note[1] from the PDF specification.
Github user backslash47 has provided a basic implementation if that is of any help:
https://github.com/backslash47/pdfbox/commit/3c528295b16445e58dc9fe895f78384221452be2
Thanks,
Daniel.
[1] Note: Word spacing is applied to every occurrence of the single-byte character code 32 in a string when using a simple font or a composite font that defines code 32 as a single-byte code. It does not apply to occurrences of the byte value 32 in multiple-byte codes.
Example code:
import java.io.InputStream; import java.util.ArrayList; import java.util.List; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.pdmodel.font.PDType0Font; import org.apache.pdfbox.util.Matrix; public class TextWithPositioningExample { public static void main(String[] args) throws Exception { doIt("Hello World, this is a test!", "justify-example.pdf"); } /** * This example shows how to justify a string using the showTextWithPositioning method. * First only spaces are adjusted, and then every letter. */ public static void doIt(String message, String outfile) throws Exception { // the document try (PDDocument doc = new PDDocument(); InputStream is = PDDocument.class.getResourceAsStream("/org/apache/pdfbox/resources/ttf/LiberationSans-Regular.ttf")) { final float FONT_SIZE = 20.0f; // Page 1 PDFont font = PDType0Font.load(doc, is, true); //PDFont font = PDType1Font.COURIER; PDPage page = new PDPage(PDRectangle.A4); doc.addPage(page); // Get the non-justified string width in text space units. float stringWidth = font.getStringWidth(message) * FONT_SIZE; // Get the string height in text space units. float stringHeight = font.getFontDescriptor().getFontBoundingBox().getHeight() * FONT_SIZE; // Get the width we have to justify in. PDRectangle pageSize = page.getMediaBox(); PDPageContentStream contentStream = new PDPageContentStream(doc, page, AppendMode.OVERWRITE, false); contentStream.beginText(); contentStream.setFont(font, FONT_SIZE); // Start at top of page. contentStream.setTextMatrix(Matrix.getTranslateInstance(0, pageSize.getHeight() - stringHeight / 1000f)); // First show non-justified. contentStream.showText(message); // Move to next line. contentStream.setTextMatrix(Matrix.getTranslateInstance(0, pageSize.getHeight() - ((stringHeight / 1000f) * 2))); // Now show word justified. // The space we have to make up, in text space units. float justifyWidth = ((pageSize.getWidth() * 1000f) - (stringWidth)); List<Object> text = new ArrayList<>(); String[] parts = message.split("\\s"); float spaceWidth = (justifyWidth / (parts.length - 1)) / FONT_SIZE; for (int i = 0; i < parts.length; i++) { if (i != 0) { text.add(" "); // Positive values move to the left, negative to the right. text.add(Float.valueOf(-spaceWidth)); } text.add(parts[i]); } contentStream.showTextWithPositioning(text.toArray()); contentStream.setTextMatrix(Matrix.getTranslateInstance(0, pageSize.getHeight() - ((stringHeight / 1000f) * 3))); // Now show letter justified. text = new ArrayList<>(); justifyWidth = ((pageSize.getWidth() * 1000f) - stringWidth); float extraLetterWidth = (justifyWidth / (message.codePointCount(0, message.length()) - 1)) / FONT_SIZE; for (int i = 0; i < message.length();) { if (i != 0) { text.add(Float.valueOf(-extraLetterWidth)); } text.add(String.valueOf(Character.toChars(message.codePointAt(i)))); i += Character.charCount(message.codePointAt(i)); } contentStream.showTextWithPositioning(text.toArray());; // Finish up. contentStream.endText(); contentStream.close(); doc.save(outfile); } } }