Uploaded image for project: 'PDFBox'
  1. PDFBox
  2. PDFBOX-3992

Implement show text with positioning operator (TJ)

    Details

    • Type: Improvement
    • Status: Resolved
    • Priority: Major
    • Resolution: Fixed
    • Affects Version/s: None
    • Fix Version/s: 2.0.9, 3.0.0 PDFBox
    • Component/s: PDModel
    • Labels:
      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);
      		}
      	}
      }
      
      

        Attachments

        1. justify-example.pdf
          8 kB
          Tilman Hausherr
        2. showtextwithpositioning.patch
          4 kB
          Dan Fickling

          Activity

            People

            • Assignee:
              tilman Tilman Hausherr
              Reporter:
              danfickle Dan Fickling
            • Votes:
              1 Vote for this issue
              Watchers:
              3 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved: