0

I'd like to offer a Starbucks card to whomever can help me understand completely this code. It's important to me. Thank you for your time!

I started to read the wonderful book of Mr. Lowagie (iText in Action, Second Edition). It is really well written and informative. Due to the high pressure of my employer, I am pushed to skip ahead to chapter 6. Understanding the n-Up code example is rather important for me right now, and being a new programmer, I will try to comment the code below with my questions.

If you could, when you can, spare some time and help me walk through it by looking at my questions below, it would be great. I appreciate all the helpful comments.

public void manipulatePdf(String src, String dest, int pow)

Q: Here the 'pow' variable indicates, for example, that in a pow of 3, the resulting n-up would be (2x2x2) 8. The resulting pdf would have 8 copies of the 'src' PDF. Is this correct?

A:

 throws IOException, DocumentException 
 {
   PdfReader reader = new PdfReader(src);
   Rectangle pageSize = reader.getPageSize(1);

   Rectangle newSize = (pow % 2) == 0 ?
     new Rectangle( pageSize.getWidth(), pageSize.getHeight()) : new Rectangle( pageSize.getHeight(), pageSize.getWidth());

Q: Above I am unsure what is going on. It appears, to me, that if the remainder of the modulus operation is zero then the new rectangle will be created in Portrait orientation and vice-versa. Is this correct? If so, may I ask why?

A:

  Rectangle unitSize = new Rectangle(pageSize.getWidth(), pageSize.getHeight());

  for (int i = 0; i < pow; i++) 
  {
    unitSize = new Rectangle(
    unitSize.getHeight() / 2, unitSize.getWidth());
  }

Q: Above, in the 'for' loop, we are, I believe, preparing the unitsize for each of the pdf that will be squeezed onto the final pdf surface. I do not understand why the getWidth() is not divided by 2 as well?

A:

  int n = (int)Math.pow(2, pow);
  int r = (int)Math.pow(2, pow / 2);
  int c = n / r;

Q: 'n' is the number of pdfPages that will be n'uped unto the final pdf surface. So far so good, right?

'r' may be the number of rows and 'c' the number of columns?

So, assuming a 'pow' of 3.. 
n = 8 which means 8 up of the pdf when you'll open up the final pdf?
r = 2 which means there'll be be two rows.
c = 4 which means there'll be four columns. 

A:

  Document document = new Document(newSize, 0, 0, 0, 0);
  PdfWriter writer = PdfWriter.getInstance(document,
  new FileOutputStream(String.format(dest, n)));
  document.open();
  PdfContentByte cb = writer.getDirectContent();
  PdfImportedPage page;
  Rectangle currentSize;
  float offsetX, offsetY, factor;
  int total = reader.getNumberOfPages();

  for (int i = 0; i < total; ) 
  {
    if (i % n == 0) 
    {
      document.newPage();
    }

Q: The above 'for' loop adds a new page to the final pdf each time the predetermined nup number has been reached in the current working page. So if we have n = 8, then new page will be called at i = 8, 16, 24, etc. Right?

A:

    currentSize = reader.getPageSize(++i);

Q: This line accounts for situations when the source pdf has many pages and not all pages share the same pdf page size. Correct?

A:

    factor = Math.min(unitSize.getWidth() / currentSize.getWidth(),
                      unitSize.getHeight() / currentSize.getHeight());

Q: The min function returns the smallest number. Why is unitSize divided by currentSize? This is where my comprehension breaks down.

A:

    offsetX = unitSize.getWidth() * ((i % n) % c)
              +(unitSize.getWidth()
              - (currentSize.getWidth() * factor))/2f;

Q: Okay, the offsetX and offsetY are used to position the pdf page unto the larger final pdf surface. That I understand. Could someone explain the calculation to a non programmer, in English? This is a case of knowing what each math operation does but not understanding the big picture.

A:

    offsetY = newSize.getHeight()
              - (unitSize.getHeight() * (((i % n) / c) + 1))
              + (unitSize.getHeight()
              - (currentSize.getHeight() * factor))/2f;

    page = writer.getImportedPage(reader, i);

    cb.addTemplate(page,factor, 0, 0, factor, offsetX, offsetY);
  }
  document.close();
}

I hope this question can help many others, who are new to iText, and may need some detailed code walkthrough for this code.

I hope, as well, that negative and generally unhelpful comments will not be posted.

Thank you all for you time and help!

1 Answers1

1

Suppose that you have a document with 67 pages.

If you want to 2-up this document, you'll end up with 34 pages: 33 pages with 2 pages of the original document + 1 page that is only half full. To fit two pages on one next to each other, you need to scale the original pages down to half their size and you need to rotate the original page size with 90 degrees.

If you want to 4-up this document, you'll end up with 17 pages: 16 pages with 4 pages of the original document + 1 page that is three quarters full. To fit four pages on one next to each other, you need to scale the original pages down to a quarter of their size and you don't need to rotate the original page size.

If you want to 8-up this document, you'll end up with 9 pages: 8 pages with 8 pages of the original document + 1 page that is only full for 3 / 8. To fit eight pages on one page next to each other, you need to scale the original pages down to 1/8 their size and you need to rotate the original page size with 90 degrees.

If you want to 16-up this document, you'll end up with 5 pages: 4 pages with 16 pages of the original document + 1 page that is only full for 3 / 16. To fit 16 pages on one page next to each other, you need to scale the original pages down to 1/16 their size and you don't need to rotate the original page size.

If you want to 32-up this document, you'll end up with 3 pages: 2 pages with 32 pages of the original document + 1 page that is only full for 3 / 32. To fit 32 pages on one page next to each other, you need to scale the original pages down to 1/32 their size and you need to rotate the original page size with 90 degrees.

If you want to 64-up this document, you'll end up with 2 pages: 1 page with 64 pages of the original document + 1 page that is only full for 3 / 64. To fit 64 pages on one page next to each other, you need to scale the original pages down to 1/64 their size and you don't need to rotate the original page size.

2 = 2 (pow 1) => rotate page

4 = 2 x 2 (pow 2) => do not rotate page

8 = 2 x 2 x 2 (pow 3) => rotate page

16 = 2 x 2 x 2 x 2 (pow 4) => do not rotate page

32 = 2 x 2 x 2 x 2 x 2 (pow 5) => rotate page

64 = 2 x 2 x 2 x 2 x 2 x 2 (pow 6) => do not rotate page

Do you see the pattern? When pow is an odd number, one needs to rotate the page; when it's an even number, the page doesn't need to be rotated. That's why you need to test (pow % 2) == 0. It defines the orientation of the new page.

The loop is also easy to understand, especially if you look at this image:

enter image description here

The big page A0 can be divided into two A1 pages. The height of this A1 page is the width of the A0 page; the width of the A1 page is half the height of the A0 page. The A1 can be divided into two A2 pages. The height of this A2 page is the width of the A1 page; the width of the A2 page is half the height of the A1 page. And so on.

You were right about n being the number of pages we'll try to fit on one, r being the number of rows and c being the number of columns and about what follows.

Now we need to define the factor. You already know that width and height are switched a couple of times. For instance: when 2-upping a document with A4 pages, the original page size is 210 by 297, and it needs to be changed to the unitSize 148 by 210. In other words it needs to be size down with a factor 148 / 210 (0.705) or 210 / 297 (0.707). In this case, we use the factor 0.705.

We could calculate the offsets like this:

offsetX = unitSize.getWidth() * ((i % n) % c);
offsetY = newSize.getHeight() - (unitSize.getHeight() * (((i % n) / c);

In case you want to 8-up a document, c equals 4. So you have:

(0 % 8) % 4 = 0 % 4 = 0; (0 % 8) / 4 = 0 / 4 = 0;
(1 % 8) % 4 = 1 % 4 = 1; (1 % 8) / 4 = 1 / 4 = 0;
(2 % 8) % 4 = 2 % 4 = 2; (2 % 8) / 4 = 2 / 4 = 0;
(3 % 8) % 4 = 3 % 4 = 3; (3 % 8) / 4 = 3 / 4 = 0;
(4 % 8) % 4 = 4 % 4 = 0; (4 % 8) / 4 = 4 / 4 = 1;
(5 % 8) % 4 = 5 % 4 = 1; (5 % 8) / 4 = 5 / 4 = 1;
(6 % 8) % 4 = 6 % 4 = 2; (6 % 8) / 4 = 6 / 4 = 1;
(7 % 8) % 4 = 7 % 4 = 3; (7 % 8) / 4 = 7 / 4 = 1;
(8 % 8) % 4 = 0 % 4 = 0; (8 % 8) / 4 = 0 / 4 = 0;
(9 % 8) % 4 = 1 % 4 = 1; (9 % 8) / 4 = 1 / 4 = 0;

Do you see the pattern? We add the pages in a grid that looks like this: (0, 0), (1, 0), (2, 0), (3, 0), (0, 1), (1, 1), (2, 1), (3, 1). To get the real dimensions, we multiply with unitSize.getWidth() and unitSize.getHeight(). Obviously, we need

offsetY = newSize.getHeight() - (unitSize.getHeight() * (((i % n) / c);

because of the nature of the coordinate system: the origin of the coordinate system is in the lower-left corner.

We adjust offsetX and offsetY with half of the difference of the actual width of the size down page and the available space to center the imported page.

You say you don't want any negative remarks. Allow me to give you one remark: your question is not an iText question, nor is it a programming question. It is a Math question. You really need to study Math before trying to program, just like a child need to learn how to walk before he can learn how to run.

One possible reason for confusion may be that you don't take into account one special feature of ISO 216. The algorithm won't work on non-standard pages. For instance: if your page is a square, you can't divide the height in two and keep the width. But that's obvious, isn't it?

Bruno Lowagie
  • 75,994
  • 9
  • 109
  • 165
  • Mr. Lowagie, (you can't really address a luminary by his or her first name), this makes sense. My math has never been strong. I'm 37, with two young kids, and getting in programming is sort of like a promotion for me. Doing my best, and thanks to helpful people like you, doing better. Now I'll have to figure out how to buy you that cup of coffee :) – InnerOrchestra Jul 27 '14 at 15:40
  • No need to buy me a cup of coffee. Use the money to buy some ice cream for your kids (or chocolate chip cookies, of whatever they like most) ;-) – Bruno Lowagie Jul 28 '14 at 14:28