Chapter 4: Adding AbstractElement objects (part 1)

In previous chapters, we've already discussed five classes that implement the AbstractElement class. We've discussed the AreaBreak class in chapter 2, and we've discussed the four classes implementing the ILeafElementTab, Link, Text, and Image– in chapter 3. In this chapter, we'll start with a first series of AbstractElement implementations. We'll take a look at the Div class to group elements and at the LineSeparator to draw lines between elements. We've already used the Paragraph class many times in previous chapters, but we'll revisit it in this chapter. Finally, we'll introduce the List and the ListItem class. We'll save the Table and Cell class for the next chapter.

Grouping elements with the Div class

The Div class is a BlockElement implementation that can be used to group different elements. In Figure 4.1, we see an overview of movies based on the Jekyll and Hyde story. Each entry consists of at most three elements:

  • a Paragraph showing the title of the movie,

  • a Paragraph showing the director, the country, and a year,

  • an Image showing the movie poster (if any).

We combined these three elements in a Div and we defined a left border, left padding and bottom margin for that Div.

Figure 4.1: Grouping elements in a Div
Figure 4.1: Grouping elements in a Div

The DivExample1 example shows how this is done:

  1. public void createPdf(String dest) throws IOException {
  2. PdfDocument pdf = new PdfDocument(new PdfWriter(dest));
  3. Document document = new Document(pdf);
  4. List<List<String>> resultSet = CsvTo2DList.convert(SRC, "|");
  5. resultSet.remove(0);
  6. for (List<String> record : resultSet) {
  7. Div div = new Div()
  8. .setBorderLeft(new SolidBorder(2))
  9. .setPaddingLeft(3)
  10. .setMarginBottom(10);
  11. String url = String.format(
  12. "http://www.imdb.com/title/tt%s", record.get(0));
  13. Link movie = new Link(record.get(2), PdfAction.createURI(url));
  14. div.add(new Paragraph(movie.setFontSize(14)))
  15. .add(new Paragraph(String.format(
  16. "Directed by %s (%s, %s)",
  17. record.get(3), record.get(4), record.get(1))));
  18. File file = new File(String.format(
  19. "src/main/resources/img/%s.jpg", record.get(0)));
  20. if (file.exists()) {
  21. Image img = new Image(
  22. ImageDataFactory.create(file.getPath()));
  23. img.scaleToFit(10000, 120);
  24. div.add(img);
  25. }
  26. document.add(div);
  27. }
  28. document.close();
  29. }

As usual, we create a PdfDocument and a Document instance (line 2-3). We reuse the CSV file that was introduced in the previous chapter, and we loop over all the movies listed in that CSV file, excluding the header row (line 4-6). We create a new Div object (line 7) and we define the left border as a solid border with a thickness of 2 user units (line 8), we set the left padding to 3 user units (line 9), and we introduce a bottom margin of 10 user units (line 10). We add the title Paragraph to this Div (line 14), as well as a Paragraph with additional info (line 15 - 17). If we find a movie poster, we add it as an Image (line 24). We add each Div to the document (line 26) and we close the document (line 28).

If we look at the bottom of the first page and at the top of the second page in Figure 4.1, we see that the Div containing the information about the movie "Dr. Jekyll and Mr. Hyde" directed by John S. Roberson, is distributed over two pages. The movie poster didn't fit on the first page, so it was forwarded to the second page. Maybe this isn't the behavior we desire. Maybe we want to keep the elements added to the same Div together as shown in figure 4.2.

Figure 4.2: Keeping a Div on one page
Figure 4.2: Keeping a Div on one page

We use only one extra method to achieve this; see the DivExample2 example.

  1. Div div = new Div()
  2. .setKeepTogether(true)
  3. .setBorderLeft(new SolidBorder(2))
  4. .setPaddingLeft(3)
  5. .setMarginBottom(10);

By adding setKeepTogether(true), we tell iText to try to keep the content of a Div on the same page. If the content of that Div fits on the next page, all the elements in the Div will be forwarded to the next page. This is the case in figure 4.2 where the title and the info about the 1920 movie "Dr. Jekyll and Mr. Hyde" directed by John S. Roberson is no longer added on the first page. Instead it's forwarded to the next page.

This approach won't work if the content of a Div doesn't fit on the next page. In that case, the elements are distributed over the current page and subsequent pages as if the setKeepTogether() method wasn't used. There's a workaround in case you really want to keep one element on the same page as the next element. We'll look at an example demonstrating this workaround after we've discussed the LineSeparator object.

Drawing horizontal lines with the LineSeparator object

The building blocks created for iText are inspired by the tags that are available for HTML. That's not a secret. The Text object roughly corresponds with <span>, Paragraph corresponds with <p>, Div corresponds with <div>, and so on. The best way to explain what the LineSeparator is about, is to say that it corresponds with the <hr> tag. Figure 4.3 shows a horizontal rule consisting of a red line, 1 user unit thick, that takes 50% of the available width, for which a top margin of 5 user units was defined.

Figure 4.3: Using a LineSeparator
Figure 4.3: Using a LineSeparator

The LineSeparatorExample example shows how it's done.

  1. SolidLine line = new SolidLine(1f);
  2. line.setColor(Color.RED);
  3. LineSeparator ls = new LineSeparator(line);
  4. ls.setWidthPercent(50);
  5. ls.setMarginTop(5);

We create a SolidLine object, passing a parameter that defines the thickness. We remember from the previous chapter that SolidLine is one of the implementations of the ILineDrawer interface. We set its color to red and we use this ILineDrawer to create a LineSeparator instance. In this case, we define the width of the line using the setWidthPercent() method. We could also have used the setWidth() method to define an absolute width expressed in user units. Finally, we set the top margin to 5 user units.

In the LineSeparatorExample example, we add the ls object to our Div element containing information about a movie.

  1. div.add(ls);

There isn't much more to be said about LineSeparator. Just make sure that you use the right methods to set properties. For instance: you can't change the color of a line at the level of the LineSeparator, you have to set it at the level of the ILineDrawer. The same goes for the thickness of the line. Check Appendix B to find out which AbstractElement methods are implemented for the LineSeparator class, and which methods are ignored.

Keeping content together

We've been working with the Paragraph class many times in previous examples. For instance: in chapter 2, we've used the Paragraph class to convert a text file to PDF by creating a Paragraph object for each line in the text file, and by adding all of these Paragraph objects to a Document instance one way or another. The screen shots in the previous chapters showed that we can make some really nice PDF documents, but there's always room for improvement.

Figure 4.4 demonstrates one of the flaws that we still need to fix: we have the title of a chapter on page 3, but the content of that chapter starts on page 4.

Figure 4.4: a widowed title
Figure 4.4: a widowed title

We'd like to avoid this kind of behavior. We'd like the title to be on the same page as the start of the content of the chapter. We do a first attempt to fix this problem in the ParagraphAndDiv1 example.

  1. public void createPdf(String dest) throws IOException {
  2. PdfDocument pdf = new PdfDocument(new PdfWriter(dest));
  3. Document document = new Document(pdf);
  4. PdfFont font = PdfFontFactory.createFont(FontConstants.TIMES_ROMAN);
  5. PdfFont bold = PdfFontFactory.createFont(FontConstants.HELVETICA_BOLD);
  6. document.setTextAlignment(TextAlignment.JUSTIFIED)
  7. .setHyphenation(new HyphenationConfig("en", "uk", 3, 3));
  8. BufferedReader br = new BufferedReader(new FileReader(SRC));
  9. String line;
  10. Div div = new Div();
  11. while ((line = br.readLine()) != null) {
  12. Paragraph title = new Paragraph(line)
  13. .setFont(bold).setFontSize(12)
  14. .setMarginBottom(0);
  15. div = new Div()
  16. .add(title)
  17. .setFont(font).setFontSize(11)
  18. .setMarginBottom(18);
  19. while ((line = br.readLine()) != null) {
  20. div.add(
  21. new Paragraph(line)
  22. .setMarginBottom(0)
  23. .setFirstLineIndent(36)
  24. );
  25. if (line.isEmpty()) {
  26. document.add(div);
  27. break;
  28. }
  29. }
  30. }
  31. document.add(div);
  32. document.close();
  33. }

This example is very similar to the examples we made in chapter 2. The main difference is that we no longer add the Paragraph objects straight to the Document. Instead, we store the Paragraph objects in a Div object, and we add the Div object to the Document at the end of each chapter.

We could add .setKeepTogether(true) between line 15 and 16, but that wouldn't have any effect as the full content of the Div doesn't fit on a single page. As documented before, the setKeepTogether() method is ignored. We've had long discussions at iText on how to solve this problem. We decided that the most elegant way to avoid widowed objects consisted of introducing a setKeepWithNext() method.

The setKeepWithNext() method was introduced in iText 7.0.1. You won't find it in the very first iText 7 release. We're investigating if we could support the method for nested objects. We're reluctant to do this because this could have a significant negative impact on the overall performance of the library.

The ParagraphAndDiv2 example shows how it's used.

  1. BufferedReader br = new BufferedReader(new FileReader(SRC));
  2. String line;
  3. Div div = new Div();
  4. while ((line = br.readLine()) != null) {
  5. document.add(new Paragraph(line)
  6. .setFont(bold).setFontSize(12)
  7. .setMarginBottom(0)
  8. .setKeepWithNext(true));
  9. div = new Div()
  10. .setFont(font).setFontSize(11)
  11. .setMarginBottom(18);
  12. while ((line = br.readLine()) != null) {
  13. div.add(
  14. new Paragraph(line)
  15. .setMarginBottom(0)
  16. .setFirstLineIndent(36)
  17. );
  18. if (line.isEmpty()) {
  19. document.add(div);
  20. break;
  21. }
  22. }
  23. }
  24. document.add(div);

We use a Paragraph added straight to the Document for the title (line 5); we create a Div to combine the rest of the content in the chapter (line 9). We indicate that the Paragraph needs to be kept on the same page as (the first part of) the Div by adding setKeepWithNext(true). The result is shown in figure 4.5. The title "SEARCH FOR MR. HYDE" is now forwarded to the next page when compared to figure 4.4.

Figure 4.5: keeping the title together with the text
Figure 4.5: keeping the title together with the text

The setKeepWithNext() method can be used with all other AbstractElement implementations, except Cell. The method only works for elements added straight to the Document instance. It doesn't work for nested objects such as a Cell that is always added to a Table and never straight to a Document. In the case of our example, it wouldn't work if the title Paragraph was added to the Div instead of to the Document.

Changing the leading of a Paragraph

The Paragraph class has some extra methods on top of the methods defined at the AbstractElement level. We've already used the methods involving TabStops in the previous chapter. We also introduced the setFirstLineIndent() method on the sly. Now we are going to look at a method to change the leading.

The word leading is pronounced as ledding, and it's derived from the word lead (the metal). When type was set by hand for printing presses, strips of lead were placed between lines of type to add space. The word originally referred to the thickness of these strips of lead that were placed between the lines. The PDF standard redefines the leading as "the vertical distanced between the baselines of adjacent lines of text" (ISO-32000-1, section 9.3.5).

There are two ways to change the leading of a Paragraph:

  • setFixedLeading()— changes the leading to an absolute value. For instance: if you define a fixed leading of 18, the distance between the baseline of two lines of text will be 18 user units.

  • setMultipliedLeading—changes the leading to a value relative to the font size. For instance, if you define a multiplied leading of 1.5f and the font is 12 pt, then the leading will be 18 user units (which is 1.5 times 12).

These methods are mutually exclusive. If you use both methods on the same Paragraph, the last method that was invoked will prevail. Figure 4.6 shows yet another conversion of the story to PDF. The total number of pages is lower because we changed the distance between the lines by adding .setMultipliedLeading(1.2f).

Figure 4.6: changing indentation and leading
Figure 4.6: changing indentation and leading

The code of the ParagraphAndDiv3 example is identical to what we had in the previous example, except for the following snippet.

  1. div.add(
  2. new Paragraph(line)
  3. .setMarginBottom(0)
  4. .setFirstLineIndent(36)
  5. .setMultipliedLeading(1.2f)
  6. );

When we add an object to a Document either directly or indirectly (e.g. through a Div), iText uses the appropriate IRenderer to render this object to PDF. In the "Before we start" section of this book, figure 0.4 shows an overview of the different renderers. Normal use of iText hardly ever requires creating a custom renderer, but we'll take a look at one example in which we create a MyParagraphRenderer extending the default ParagraphRenderer.

Creating a custom renderer

When we look at figure 4.7, we see two Paragraphs with a different background. For the first Paragraph, we used the .setBackgroundColor() method. This method draws a rectangle based on the position of the Paragraph. For the second Paragraph, we wanted a rectangle with rounded corners. As iText 7 doesn't have a method to achieve this, we wrote a custom ParagraphRenderer class.

Figure 4.7: default and custom background for a Paragraph
Figure 4.7: default and custom background for a Paragraph

Let's take a look at the CustomParagraph example to see the difference between the two approaches. The first Paragraph was added like this:

  1. Paragraph p1 = new Paragraph(
  2. "The Strange Case of Dr. Jekyll and Mr. Hyde");
  3. p1.setBackgroundColor(Color.ORANGE);
  4. document.add(p1);

The second Paragraph was added like this:

  1. Paragraph p2 = new Paragraph(
  2. "The Strange Case of Dr. Jekyll and Mr. Hyde");
  3. p2.setBackgroundColor(Color.ORANGE);
  4. p2.setNextRenderer(new MyParagraphRenderer(p2));
  5. document.add(p2);

This second approach requires an extra class:

  1. class MyParagraphRenderer extends ParagraphRenderer {
  2. public MyParagraphRenderer(Paragraph modelElement) {
  3. super(modelElement);
  4. }
  5. @Override
  6. public void drawBackground(DrawContext drawContext) {
  7. Background background =
  8. this.<Background>getProperty(Property.BACKGROUND);
  9. if (background != null) {
  10. Rectangle bBox = getOccupiedAreaBBox();
  11. boolean isTagged =
  12. drawContext.isTaggingEnabled()
  13. && getModelElement() instanceof IAccessibleElement;
  14. if (isTagged) {
  15. drawContext.getCanvas().openTag(new CanvasArtifact());
  16. }
  17. Rectangle bgArea = applyMargins(bBox, false);
  18. if (bgArea.getWidth() <= 0 || bgArea.getHeight() <= 0) {
  19. return;
  20. }
  21. drawContext.getCanvas().saveState()
  22. .setFillColor(background.getColor())
  23. .roundRectangle(
  24. (double)bgArea.getX() - background.getExtraLeft(),
  25. (double)bgArea.getY() - background.getExtraBottom(),
  26. (double)bgArea.getWidth()
  27. + background.getExtraLeft() + background.getExtraRight(),
  28. (double)bgArea.getHeight()
  29. + background.getExtraTop() + background.getExtraBottom(),
  30. 5)
  31. .fill().restoreState();
  32. if (isTagged) {
  33. drawContext.getCanvas().closeTag();
  34. }
  35. }
  36. }
  37. }

We extend the existing ParagraphRenderer class and we override one single method. We take the original drawBackground() method from the AbstractRenderer class, and we replace the rectangle() method with the roundRectangle() method (line 23). As you can see in line 24-29. the dimension of the rectangle can be fine-tuned with extra space to the left, right, top, and bottom. These values can be passed to the internal Background object by using a different flavor of the setBackgroundColor() method that takes 4 extra float values (extraLeft, extraTop, extraRight, and extraBottom).

We'll conclude this chapter with some examples involving the List and ListItem class.

Lists and list symbols

Figure 4.8 shows the different types of lists that are available by default. We recognized numbered lists (roman and arabic numbers), lists with letters of the alphabet (lowercase, uppercase, Latin, Greek), and so on.

Figure 4.8: different types of lists
Figure 4.8: different types of lists

The ListTypes example shows how the first three lists are added.

  1. List list = new List();
  2. list.add("Dr. Jekyll");
  3. list.add("Mr. Hyde");
  4. document.add(list);
  5. list = new List(ListNumberingType.DECIMAL);
  6. list.add("Dr. Jekyll");
  7. list.add("Mr. Hyde");
  8. document.add(list);
  9. list = new List(ListNumberingType.ENGLISH_LOWER);
  10. list.add("Dr. Jekyll");
  11. list.add("Mr. Hyde");
  12. document.add(list);

In line 1, we create a list without specifying a type. By default, this will result in a list with hyphens as list symbols. We add two list items the quick and dirty way in line 2-3; then we add the list to the Document in line 4. We repeat these four lines many times, first we create a decimal list (line 5), then we define an alphabetic list with lowercase letters (line 9).

The parameters we use to create different types of lists are stored in an enum. This ListNumberingType enumeration consists of the following values:

  • DECIMAL– the list symbols are arabic numbers: 1, 2, 3, 4, 5,...

  • ROMAN_LOWER– the list symbols are lowercase roman numbers: i, ii, iii, iv, v,...

  • ROMAN_UPPER– the list symbols are uppercase roman numbers: I, II, III, IV, V,...

  • ENGLISH_LOWER– the list symbols are lowercase alphabetic letters (using the English alphabet): a, b, c, d, e,...

  • ENGLISH_UPPER– the list symbols are uppercase alphabetic letters (using the English alphabet): A, B, C, D, E,...

  • GREEK_LOWER– the list symbols are lowercase Greek letters: α, β, γ, δ, ε,...

  • GREEK_UPPER– the list symbols are uppercase Greek letters: Α, Β, Γ, Δ, Ε,...

  • ZAPF_DINGBATS_1– the list symbols are bullets from the Zapfdingbats font, more specifically characters in the range [172; 181].

  • ZAPF_DINGBATS_2– the list symbols are bullets from the Zapfdingbats font, more specifically characters in the range [182; 191].

  • ZAPF_DINGBATS_3– the list symbols are bullets from the Zapfdingbats font, more specifically characters in the range [192; 201].

  • ZAPF_DINGBATS_4– the list symbols are bullets from the Zapfdingbats font, more specifically characters in the range [202; 221].

Obviously, we can also define our own custom list symbols, or we can use a combination of the default list symbols (e.g. numbers) and combine them with a prefix or a suffix. That's demonstrated in figure 4.9.

Figure 4.9: custom list symbols
Figure 4.9: custom list symbols

The PDF in the screen shot of figure 4.9 was the result of the CustomListSymbols example. We'll examine this example snippet by snippet.

First we take a look at how we can introduce a simple bullet as list symbol, instead of the default hyphen.

  1. List list = new List();
  2. list.setListSymbol("\u2022");
  3. list.add("Dr. Jekyll");
  4. list.add("Mr. Hyde");
  5. document.add(list);

We create a List and we use the setListSymbol() method to change the list symbol. We can use any String as list symbol. In our case, we want a single bullet. The Unicode value of the bullet character is /u2022. If you examine the screen shot, you notice that the bullet is rather close to the content of the list items. We can change this by defining an indentation using the setSymbolIndent() method as is done in the next code snippet.

  1. list = new List();
  2. PdfFont font = PdfFontFactory.createFont(FontConstants.ZAPFDINGBATS);
  3. list.setListSymbol(new Text("*").setFont(font).setFontColor(Color.ORANGE));
  4. list.setSymbolIndent(10);
  5. list.add("Dr. Jekyll");
  6. list.add("Mr. Hyde");
  7. document.add(list);

Here we set the list symbol to *, but we use a Text object instead of a String. and we set the font to ZapfDingbats. We also change the font color to orange. This results in a list symbol that looks as an orange pointing finger. In the next snippet, we use an Image object as a list symbol.

  1. Image info = new Image(ImageDataFactory.create(INFO));
  2. info.scaleAbsolute(12, 12);
  3. list = new List().setSymbolIndent(3);
  4. list.setListSymbol(info);
  5. list.add("Dr. Jekyll");
  6. list.add("Mr. Hyde");
  7. document.add(list);

In line 1. we create an Image object; INFO contains the path to a blue info bullet. We scale the image so that it measures 12 by 12 user units, and we pass the Image as a parameter of the setListSymbol() method.

In the default list types, iText always added a dot after the list symbol of numbered lists: a., b., c., and so on. Maybe we don't want this dot. Maybe we want the list symbols to look like this: a-, b-, c-, and so on. The following code snippet shows how to achieve this.

  1. list = new List();
  2. list.setListSymbol(ListNumberingType.ENGLISH_LOWER);
  3. list.setPostSymbolText("- ");
  4. list.add("Dr. Jekyll");
  5. list.add("Mr. Hyde");
  6. document.add(list);

Line 1 and 2 are the equivalent of list = new List(ListNumberingType.ENGLISH_LOWER); It results in a numbered list using the English alphabet. We use the setPostSymbolText() method to replace the dot that is automatically added after each letter with "- ".

There's also a setPreSymbolText() method to add text in front of the default list symbol. The following code snippet creates a decimal list (1., 2., 3.,...), but by adding a pre- and a post-symbol, the list symbols have become list labels that look like this: Part 1:, Part 2:, Part 3:, and so on.

  1. list = new List(ListNumberingType.DECIMAL);
  2. list.setPreSymbolText("Part ");
  3. list.setPostSymbolText(": ");
  4. list.add("Dr. Jekyll");
  5. list.add("Mr. Hyde");
  6. document.add(list);

Not every numbered list needs to start with 1, i, a, and so on. You can also choose to start with a higher number (or letter) using the setItemStartIndex() method. In the following code sample, we start counting at 5.

  1. list = new List(ListNumberingType.DECIMAL);
  2. list.setItemStartIndex(5);
  3. list.add("Dr. Jekyll");
  4. list.add("Mr. Hyde");
  5. document.add(list);

Finally, we'll use the setListSymbolAlignment() to change the alignment of the labels. If you compare the lowercase Roman numbers list in figure 4.8 with the one in figure 4.9, you'll see a difference in the way the list labels are aligned.

  1. list = new List(ListNumberingType.ROMAN_LOWER);
  2. list.setListSymbolAlignment(ListSymbolAlignment.LEFT);
  3. for (int i = 0; i < 6; i++) {
  4. list.add("Dr. Jekyll");
  5. list.add("Mr. Hyde");
  6. }
  7. document.add(list);

So far, we've always added list items to a list using Strings. These String values are changed into ListItems internally.

Adding ListItem objects to a List

Looking at the class diagram in the "Before we start" section of this book, we notice that ListItem is a subclass of the Div class. We can add different objects to a ListItem just like we did with the Div object, but now we do so in the context of a list.

Let's do the test and adapt one of the first examples of this chapter to use ListItems instead of Divs. Figure 4.10 shows the result.

Figure 4.10: List items
Figure 4.10: List items

The code of the ListItemExample example is very similar to the code of the Div examples.

  1. public void createPdf(String dest) throws IOException {
  2. PdfDocument pdf = new PdfDocument(new PdfWriter(dest));
  3. Document document = new Document(pdf);
  4. List<List<String>> resultSet = CsvTo2DList.convert(SRC, "|");
  5. resultSet.remove(0);
  6. com.itextpdf.layout.element.List list =
  7. new com.itextpdf.layout.element.List(ListNumberingType.DECIMAL);
  8. for (List<String> record : resultSet) {
  9. ListItem li = new ListItem();
  10. li.setKeepTogether(true);
  11. String url = String.format(
  12. "http://www.imdb.com/title/tt%s", record.get(0));
  13. Link movie = new Link(record.get(2), PdfAction.createURI(url));
  14. li.add(new Paragraph(movie.setFontSize(14)))
  15. .add(new Paragraph(String.format(
  16. "Directed by %s (%s, %s)",
  17. record.get(3), record.get(4), record.get(1))));
  18. File file = new File(String.format(
  19. "src/main/resources/img/%s.jpg", record.get(0)));
  20. if (file.exists()) {
  21. Image img = new Image(ImageDataFactory.create(file.getPath()));
  22. img.scaleToFit(10000, 120);
  23. li.add(img);
  24. }
  25. list.add(li);
  26. }
  27. document.add(list);
  28. document.close();
  29. }

As we already use a java.util.List (line 4), we need to fully qualify com.itextpdf.layout.element.List (line 6) to avoid ambiguity for our compiler. We use iText's List class to create a numbered list (line 7). We create a ListItem for every item in the java.util.List (line 9). We add Paragraphs and an Image (if present) to each ListItem (line 11-24). We add each ListItem to the List (line 25), and eventually we add the List to the Document (line 27).

Nested lists

In the final example of this chapter, we'll create nested lists as shown in figure 4.11.

Figure 4.11: nested lists
Figure 4.11: nested lists

The NestedLists example is rather artificial, so please bear with me. We start with an ordinary list, named list. That's the list with the hyphens as list symbol.

  1. List list = new List();

We create a numbered list list1 (line 1). This list will have two ListItems, liEL (line 5) and liEU (line 11). We create a new List to be added to each of these list items respectively: listEL (line 2; lowercase English letters) and listEU (line 8, uppercase English letters). We add list items "Dr. Jekyll" and "Mr. Hyde" to each of these lists (line 3-4; line 9-10).

  1. List list1 = new List(ListNumberingType.DECIMAL);
  2. List listEL = new List(ListNumberingType.ENGLISH_LOWER);
  3. listEL.add("Dr. Jekyll");
  4. listEL.add("Mr. Hyde");
  5. ListItem liEL = new ListItem();
  6. liEL.add(listEL);
  7. list1.add(liEL);
  8. List listEU = new List(ListNumberingType.ENGLISH_UPPER);
  9. listEU.add("Dr. Jekyll");
  10. listEU.add("Mr. Hyde");
  11. ListItem liEU = new ListItem();
  12. liUL.add(listEU);
  13. list1.add(liEU);
  14. ListItem li1 = new ListItem();
  15. li1.add(list1);
  16. list.add(li1);

When we look at figure 4.11, we see the hyphen, we see a numbered list with list symbols 1. and 2.. Nested inside these lists are two lists using the English alphabet (lower- and uppercase).

In the next snippet, we create an extra ListItem for list, more specifically li (line 1). We add four lists to this ListItem: listGL (line 2), listGU (line 6), listRL (line 10), and listRU (line 14). These lists are added one after the other (Greek lowercase, Greek uppercase, Roman numbers lowercase, Roman number uppercase) to the list item with the default list symbol.

  1. ListItem li = new ListItem();
  2. List listGL = new List(ListNumberingType.GREEK_LOWER);
  3. listGL.add("Dr. Jekyll");
  4. listGL.add("Mr. Hyde");
  5. li.add(listGL);
  6. List listGU = new List(ListNumberingType.GREEK_UPPER);
  7. listGU.add("Dr. Jekyll");
  8. listGU.add("Mr. Hyde");
  9. li.add(listGU);
  10. List listRL = new List(ListNumberingType.ROMAN_LOWER);
  11. listRL.add("Dr. Jekyll");
  12. listRL.add("Mr. Hyde");
  13. li.add(listRL);
  14. List listRU = new List(ListNumberingType.ROMAN_UPPER);
  15. listRU.add("Dr. Jekyll");
  16. listRU.add("Mr. Hyde");
  17. li.add(listRU);
  18. list.add(li);

Furthermore, we create a list listZ1 with numbered ZapfDingbats bullets. We add this list to a list item named listZ1.

  1. List listZ1 = new List(ListNumberingType.ZAPF_DINGBATS_1);
  2. listZ1.add("Dr. Jekyll");
  3. listZ1.add("Mr. Hyde");
  4. ListItem liZ1 = new ListItem();
  5. liZ1.add(listZ1);

We create a second list listZ2 with a different set of ZapfDingbats bullets. We add this list to a list item named listZ2.

  1. List listZ2 = new List(ListNumberingType.ZAPF_DINGBATS_2);
  2. listZ2.add("Dr. Jekyll");
  3. listZ2.add("Mr. Hyde");
  4. ListItem liZ2 = new ListItem();
  5. liZ2.add(listZ2);

We create a second list listZ3 with another set of ZapfDingbats bullets. We add this list to a list item named listZ3.

  1. List listZ3 = new List(ListNumberingType.ZAPF_DINGBATS_3);
  2. listZ3.add("Dr. Jekyll");
  3. listZ3.add("Mr. Hyde");
  4. ListItem liZ3 = new ListItem();
  5. liZ3.add(listZ3);

We create a final list listZ4 with yet another set of ZapfDingbats bullets. We add this list to a list item named listZ4.

  1. List listZ4 = new List(ListNumberingType.ZAPF_DINGBATS_4);
  2. listZ4.add("Dr. Jekyll");
  3. listZ4.add("Mr. Hyde");
  4. ListItem liZ4 = new ListItem();
  5. liZ4.add(listZ4);

Now we nest these lists as follows:

  • we add liZ4 to listZ3, which was already added to liZ3,

  • we add liZ3 to listZ2, which was already added to liZ2,

  • we add liZ2 to listZ1, which was already added to liZ1.

  • we add liZ1 to list, which is the original list we created (the one with the hyphen as list symbol).

Finally, we add list to the Document.

  1. listZ3.add(liZ4);
  2. listZ2.add(liZ3);
  3. listZ1.add(liZ2);
  4. list.add(liZ1);
  5. document.add(list);

The nested ZapfDingbats list is shown to the right in figure 4.11. As you can see, the different list items are indented exactly the way one would expect. This concludes the first series of AbstractElement examples.

Summary

In this chapter, we discussed the building blocks Div, LineSeparator, Paragraph, List, and ListItem. We used Div to group other building blocks and LineSeparator to draw horizontal lines. We fixed a problem with the chapter 2 examples we weren't aware of: we learned how to keep specific elements together on one page. We didn't go into detail regarding the IRenderer implementations, but we looked at an example in which we changed the way a background is drawn for a Paragraph. We created a custom ParagraphRenderer to achieve this. Finally, we created a handful of List examples demonstrating different types of lists (numbered, unnumbered, straight-forward, nested, and so on).

The next chapter will be dedicated entirely to tables, more specifically to the Table and Cell class.