# Justifying Text in PDF

**URL:** <https://discourse.processing.org/t/justifying-text-in-pdf/13564>\
**Category:** Coding Questions\
**Created:** [August 23, 2019, 8:29pm UTC](https://discourse.processing.org/t/justifying-text-in-pdf/13564 "2019-08-23T20:29:44Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![grumo](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/grumo/32/604_2.png) [@grumo](https://discourse.processing.org/u/grumo)\
**Post date:** [August 23, 2019, 8:29pm UTC](https://discourse.processing.org/t/justifying-text-in-pdf/13564/1 "2019-08-23T20:29:45Z")

</div>

Ok. I fixed the code and I could run it. The script justifies the text in the box on screen BUT when I try to use it in the PDF mode it won’t work. If you run it you’ll see a justify-aligned box of text (finally!) but the generated PDF will show a left-aligned text. Am I missing something?

The scripts:

justify.pde

```auto
import processing.core.PApplet;
import processing.awt.PGraphicsJava2D;
import processing.pdf.*;

PGraphics pdf;
PFont font, font2, font3;
int fontheight=26; 

void setup(){ 
  size(600,900, "PGraphicsJava2DJustify");
  background(255);
  fill(0); 
  
  String texto = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";  
    
  font =createFont("Georgia", fontheight);
  
  textAlign(PGraphicsJava2DJustify.JUSTIFY);
  textFont(font,fontheight); 
  text(texto, 40, 40, 500, 700);
  
  
  // pdf creation
  
  pdf = createGraphics(600,900, PDF, "pdf/justify.pdf"); 
  pdf.beginDraw();
  pdf.background(255);
  pdf.fill(0); 
    
  pdf.textAlign(PGraphicsJava2DJustify.JUSTIFY);
  pdf.textFont(font,fontheight); 
  pdf.text(texto,40,40, 500,700);
  
  pdf.dispose();
  pdf.endDraw();
}

```

PGraphicsJava2DJustify.java

```auto
import processing.core.PApplet;
import processing.awt.PGraphicsJava2D;

public class PGraphicsJava2DJustify extends PGraphicsJava2D {
  
  public static int JUSTIFY = 4;
  
  static protected void showTextFontException(String method) {
    throw new RuntimeException("Use textFont() before " + method + "()");
  }
  
  public void text(String str, float x1, float y1, float x2, float y2) {
    if (textFont == null) {
      showTextFontException("text");
    }

    if (textMode == SCREEN) loadPixels();

    float hradius, vradius;
    switch (rectMode) {
    case CORNER:
      x2 += x1; y2 += y1;
      break;
    case RADIUS:
      hradius = x2;
      vradius = y2;
      x2 = x1 + hradius;
      y2 = y1 + vradius;
      x1 -= hradius;
      y1 -= vradius;
      break;
    case CENTER:
      hradius = x2 / 2.0f;
      vradius = y2 / 2.0f;
      x2 = x1 + hradius;
      y2 = y1 + vradius;
      x1 -= hradius;
      y1 -= vradius;
    }
    if (x2 < x1) {
      float temp = x1; x1 = x2; x2 = temp;
    }
    if (y2 < y1) {
      float temp = y1; y1 = y2; y2 = temp;
    }

// float currentY = y1;
    float boxWidth = x2 - x1;

// // ala illustrator, the text itself must fit inside the box
// currentY += textAscent(); //ascent() * textSize;
// // if the box is already too small, tell em to f off
// if (currentY > y2) return;

    float spaceWidth = textWidth(' ');

    if (textBreakStart == null) {
      textBreakStart = new int[20];
      textBreakStop = new int[20];
    }
    textBreakCount = 0;

    int length = str.length();
    if (length + 1 > textBuffer.length) {
      textBuffer = new char[length + 1];
    }
    str.getChars(0, length, textBuffer, 0);
    // add a fake newline to simplify calculations
    textBuffer[length++] = '\n';

    int sentenceStart = 0;
    for (int i = 0; i < length; i++) {
      if (textBuffer[i] == '\n') {
// currentY = textSentence(textBuffer, sentenceStart, i,
// lineX, boxWidth, currentY, y2, spaceWidth);
        boolean legit =
          textSentence(textBuffer, sentenceStart, i, boxWidth, spaceWidth);
        if (!legit) break;
// if (Float.isNaN(currentY)) break; // word too big (or error)
// if (currentY > y2) break; // past the box
        sentenceStart = i + 1;
      }
    }

    // lineX is the position where the text starts, which is adjusted
    // to left/center/right based on the current textAlign
    float lineX = x1; //boxX1;
    if (textAlign == CENTER) {
      lineX = lineX + boxWidth/2f;
    } else if (textAlign == RIGHT) {
      lineX = x2; //boxX2;
    }

    float boxHeight = y2 - y1;
    //int lineFitCount = 1 + PApplet.floor((boxHeight - textAscent()) / textLeading);
    // incorporate textAscent() for the top (baseline will be y1 + ascent)
    // and textDescent() for the bottom, so that lower parts of letters aren't
    // outside the box. [0151]
    float topAndBottom = textAscent() + textDescent();
    int lineFitCount = 1 + PApplet.floor((boxHeight - topAndBottom) / textLeading);
    int lineCount = Math.min(textBreakCount, lineFitCount);

    if (textAlignY == CENTER) {
      float lineHigh = textAscent() + textLeading * (lineCount - 1);
      float y = y1 + textAscent() + (boxHeight - lineHigh) / 2;
      for (int i = 0; i < lineCount; i++) {
        textLineAlignImpl(textBuffer, textBreakStart[i], textBreakStop[i], lineX, y, boxWidth);
        y += textLeading;
      }

    } else if (textAlignY == BOTTOM) {
      float y = y2 - textDescent() - textLeading * (lineCount - 1);
      for (int i = 0; i < lineCount; i++) {
        textLineAlignImpl(textBuffer, textBreakStart[i], textBreakStop[i], lineX, y, boxWidth);
        y += textLeading;
      }

    } else { // TOP or BASELINE just go to the default
      float y = y1 + textAscent();
      for (int i = 0; i < lineCount; i++) {
        textLineAlignImpl(textBuffer, textBreakStart[i], textBreakStop[i], lineX, y, boxWidth);
        y += textLeading;
      }
    }

    if (textMode == SCREEN) updatePixels();
  }

  //////////////////////////////////////////////////////////////

  // TEXT IMPL

  // These are most likely to be overridden by subclasses, since the other
  // (public) functions handle generic features like setting alignment.

  /**
   * Handles placement of a text line, then calls textLineImpl
   * to actually render at the specific point.
   */
  protected void textLineAlignImpl(char buffer[], int start, int stop,
                                   float x, float y, float boxWidth) {
    if (textAlign == CENTER) {
      x -= textWidthImpl(buffer, start, stop) / 2f;

    } else if (textAlign == RIGHT) {
      x -= textWidthImpl(buffer, start, stop);
    }

    textLineImpl(buffer, start, stop, x, y, boxWidth);
  }
  protected void textLineAlignImpl(char buffer[], int start, int stop,
                                   float x, float y) {
    textLineAlignImpl(buffer, start, stop, x, y, 0);
  }

  /**
   * Implementation of actual drawing for a line of text.
    */
  protected void textLineImpl(char buffer[], int start, int stop,
                              float x, float y) {
    textLineImpl(buffer, start, stop, x, y, 0);
  }
  protected void textLineImpl(char buffer[], int start, int stop,
                              float x, float y, float boxWidth) {
    
    int nbSpaces = 0, strWidth = 0, nbPixels = 0, j = 0;
    if (textAlign == JUSTIFY) {
      // remove last character if this is a space or similar
      if (stop > 0 && WHITESPACE.indexOf(buffer[stop-1]) > -1) stop--;
      // count number of spaces and total string width
      for (int index = start; index < stop; index++) {
        if (WHITESPACE.indexOf(buffer[index]) > -1) nbSpaces++;
        strWidth += textWidth(buffer[index]);
      }
      // count how many extra-pixels to add to reach boxWidth
      nbPixels = (int) boxWidth - strWidth;
      // distribute the extra pixels after each space
      int[] pixelsToAddAfterToken = new int[nbSpaces];
      for (int i = 0; i < nbSpaces; i++) {
        pixelsToAddAfterToken[i] = (int) (nbPixels/nbSpaces);
        if (i <= nbPixels%nbSpaces) pixelsToAddAfterToken[i]++;
      }
      
      for (int index = start; index < stop; index++) {
          textCharImpl(buffer[index], x, y);
          
          x += textWidth(buffer[index]);
          // if this is a space char, add the extra pixel for justification
          if (WHITESPACE.indexOf(buffer[index]) > -1) x += pixelsToAddAfterToken[j++];
          
      }
      
    }
    else {
        
        for (int index = start; index < stop; index++) {
          textCharImpl(buffer[index], x, y);

          // this doesn't account for kerning
          x += textWidth(buffer[index]);
          
        }
        
    }

  }

}

```

This is really urgent ☹ I’ll appreciate any hint with this.

Thanks!

---

<div class="post-metadata">

**Author:** ![jeremydouglass](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/jeremydouglass/32/20_2.png) [@jeremydouglass](https://discourse.processing.org/u/jeremydouglass)\
**Post date:** [August 25, 2019, 4:27pm UTC](https://discourse.processing.org/t/justifying-text-in-pdf/13564/2 "2019-08-25T16:27:35Z")

</div>

Did you change the code while posting it? I get “textX cannot be resolved to a variable”

Might want to delete those three lines.

I don’t know how the pdf renderer works, but I would guess that it writes output to PDF _in parallel_ with output to the screen, not from the screen, and so it is ignoring your custom justify renderer.

If that is the case, here is my guess at how you could fix this:

1. use the logic from your custom renderer to identify the insertion points for space.
2. split your text lines into words, distribute the blank space into the word indexes, and then loop over the line and call text() on each word. Then the pdf renderer will pick that up because it is just normal text calls.

The other approach would be to try to override the pdf renderer, but I suspect (?) that will be much harder.

---

<div class="post-metadata">

**Author:** ![grumo](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/grumo/32/604_2.png) [@grumo](https://discourse.processing.org/u/grumo)\
**Post date:** [August 26, 2019, 9:38pm UTC](https://discourse.processing.org/t/justifying-text-in-pdf/13564/3 "2019-08-26T21:38:29Z")

</div>

Thanks for your message! It could be possible to apply the PDF method of text in the Java Justification class? Any hint about this option?

---

<div class="post-metadata">

**Author:** ![jeremydouglass](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/jeremydouglass/32/20_2.png) [@jeremydouglass](https://discourse.processing.org/u/jeremydouglass)\
**Post date:** [August 26, 2019, 9:46pm UTC](https://discourse.processing.org/t/justifying-text-in-pdf/13564/4 "2019-08-26T21:46:15Z")

</div>

> [@grumo](#):
>
> apply the PDF method of text

I don’t know, but if you want to change the PDF renderer, here it is:

> <https://github.com/processing/processing/blob/349f413a3fb63a75e0b096097a5b0ba7f5565198/java/libraries/pdf/src/processing/pdf/PGraphicsPDF.java#L50>

I honestly think splitting on words and calling text multiple times would be easier. But if you can patch the PDF renderer to support justify, great! Here is textLineImpl:

> <https://github.com/processing/processing/blob/349f413a3fb63a75e0b096097a5b0ba7f5565198/java/libraries/pdf/src/processing/pdf/PGraphicsPDF.java#L485-L489>

---

<div class="post-metadata">

**Author:** ![grumo](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/grumo/32/604_2.png) [@grumo](https://discourse.processing.org/u/grumo)\
**Post date:** [August 29, 2019, 5:09am UTC](https://discourse.processing.org/t/justifying-text-in-pdf/13564/5 "2019-08-29T05:09:03Z")

</div>

Thanks for your help, Jeremy. I’m thinking this approach in the one in the second script I posted (in textLineImpl)? what I don’t get is how to redirect the output for the PDF renderer.
