diff --git a/debian/changelog b/debian/changelog
index 75f7eb17..6a35c6ff 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+libsejda-java (4.2.16+git20220329.1.9baa817-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Thu, 31 Mar 2022 22:10:36 -0000
+
 libsejda-java (4.2.13-1) unstable; urgency=medium
 
   * New upstream version 4.2.13.
diff --git a/pom.xml b/pom.xml
index 0a0e602e..9ea91d05 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
 	<artifactId>sejda-parent</artifactId>
 	<packaging>pom</packaging>
 	<name>sejda</name>
-	<version>4.2.13</version>
+	<version>4.2.17-SNAPSHOT</version>
 	<description>An extendible and configurable PDF manipulation layer library. A ready to use java library to perform PDF documents manipulation without having to deal with the low level API. Sejda offers many "ready to go" manipulations implemented using the SAMBox but it can be extended to use other implementations.</description>
 
 	<organization>
diff --git a/sejda-conversion/pom.xml b/sejda-conversion/pom.xml
index 4bff1895..0f400fed 100644
--- a/sejda-conversion/pom.xml
+++ b/sejda-conversion/pom.xml
@@ -10,7 +10,7 @@
 	<parent>
 		<groupId>org.sejda</groupId>
 		<artifactId>sejda-parent</artifactId>
-		<version>4.2.13</version>
+		<version>4.2.17-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	
diff --git a/sejda-core/pom.xml b/sejda-core/pom.xml
index 5541f657..8ecb403f 100644
--- a/sejda-core/pom.xml
+++ b/sejda-core/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>org.sejda</groupId>
 		<artifactId>sejda-parent</artifactId>
-		<version>4.2.13</version>
+		<version>4.2.17-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
diff --git a/sejda-core/src/test/java/org/sejda/core/service/TestUtils.java b/sejda-core/src/test/java/org/sejda/core/service/TestUtils.java
index 1b5d1a17..ea01ef54 100755
--- a/sejda-core/src/test/java/org/sejda/core/service/TestUtils.java
+++ b/sejda-core/src/test/java/org/sejda/core/service/TestUtils.java
@@ -36,12 +36,14 @@ import java.util.List;
 import java.util.function.Consumer;
 
 import org.sejda.commons.util.StringUtils;
+import org.sejda.sambox.pdmodel.PDDocument;
 import org.sejda.sambox.pdmodel.PDPage;
 import org.sejda.sambox.pdmodel.common.PDPageLabelRange;
 import org.sejda.sambox.pdmodel.common.PDPageLabels;
 import org.sejda.sambox.pdmodel.common.PDRectangle;
 import org.sejda.sambox.pdmodel.interactive.annotation.PDAnnotationLink;
 import org.sejda.sambox.pdmodel.interactive.documentnavigation.destination.PDPageDestination;
+import org.sejda.sambox.text.PDFTextStripper;
 import org.sejda.sambox.text.PDFTextStripperByArea;
 
 public class TestUtils {
@@ -66,6 +68,14 @@ public class TestUtils {
             fail(e.getMessage());
         }
     }
+    
+    public static String getPageTextNormalized(PDPage page) throws IOException {
+        return normalizeLineEndings(getPageText(page));
+    }
+    
+    public static String getDocTextNormalized(PDDocument doc) throws IOException {
+        return normalizeLineEndings(new PDFTextStripper().getText(doc));
+    }
 
     public static void assertPageText(PDPage page, String text) {
         withPageText(page, pageText -> {
@@ -85,6 +95,10 @@ public class TestUtils {
         });
     }
 
+    public static void assertDocTextExactLines(PDDocument doc, String text) throws IOException {
+        assertEquals((text), getDocTextNormalized(doc));
+    }
+
     public static void assertPageTextContains(PDPage page, String text) {
         withPageText(page, pageText -> {
             pageText = StringUtils.normalizeWhitespace(pageText);
diff --git a/sejda-core/src/test/resources/pdf/font-with-zero-width-glyphs.pdf b/sejda-core/src/test/resources/pdf/font-with-zero-width-glyphs.pdf
new file mode 100644
index 00000000..3e9dc3db
Binary files /dev/null and b/sejda-core/src/test/resources/pdf/font-with-zero-width-glyphs.pdf differ
diff --git a/sejda-distribution/pom.xml b/sejda-distribution/pom.xml
index 148bd79e..b7d05406 100644
--- a/sejda-distribution/pom.xml
+++ b/sejda-distribution/pom.xml
@@ -10,7 +10,7 @@
 	<parent>
 		<groupId>org.sejda</groupId>
 		<artifactId>sejda-parent</artifactId>
-		<version>4.2.13</version>
+		<version>4.2.17-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
diff --git a/sejda-docs/pom.xml b/sejda-docs/pom.xml
index 16519d97..abc6d6d8 100644
--- a/sejda-docs/pom.xml
+++ b/sejda-docs/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>org.sejda</groupId>
 		<artifactId>sejda-parent</artifactId>
-		<version>4.2.13</version>
+		<version>4.2.17-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
diff --git a/sejda-fonts/pom.xml b/sejda-fonts/pom.xml
index 855c8ff2..6a647dfc 100644
--- a/sejda-fonts/pom.xml
+++ b/sejda-fonts/pom.xml
@@ -10,7 +10,7 @@
 	<parent>
 		<groupId>org.sejda</groupId>
 		<artifactId>sejda-parent</artifactId>
-		<version>4.2.13</version>
+		<version>4.2.17-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	
diff --git a/sejda-image-writers/pom.xml b/sejda-image-writers/pom.xml
index 5c12b535..57258eac 100644
--- a/sejda-image-writers/pom.xml
+++ b/sejda-image-writers/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.sejda</groupId>
 		<artifactId>sejda-parent</artifactId>
-		<version>4.2.13</version>
+		<version>4.2.17-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
diff --git a/sejda-model/pom.xml b/sejda-model/pom.xml
index 85ae99cc..edc388da 100644
--- a/sejda-model/pom.xml
+++ b/sejda-model/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.sejda</groupId>
 		<artifactId>sejda-parent</artifactId>
-		<version>4.2.13</version>
+		<version>4.2.17-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
diff --git a/sejda-sambox/pom.xml b/sejda-sambox/pom.xml
index 7da9720e..cbe94b4b 100644
--- a/sejda-sambox/pom.xml
+++ b/sejda-sambox/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.sejda</groupId>
 		<artifactId>sejda-parent</artifactId>
-		<version>4.2.13</version>
+		<version>4.2.17-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
diff --git a/sejda-sambox/src/main/java/org/sejda/impl/sambox/MergeTask.java b/sejda-sambox/src/main/java/org/sejda/impl/sambox/MergeTask.java
index 77ac79e4..8aac8146 100644
--- a/sejda-sambox/src/main/java/org/sejda/impl/sambox/MergeTask.java
+++ b/sejda-sambox/src/main/java/org/sejda/impl/sambox/MergeTask.java
@@ -25,9 +25,7 @@ import static org.sejda.impl.sambox.component.SignatureClipper.clipSignatures;
 
 import java.io.Closeable;
 import java.io.File;
-import java.util.LinkedList;
-import java.util.Queue;
-import java.util.Set;
+import java.util.*;
 
 import org.apache.commons.io.FilenameUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -116,6 +114,8 @@ public class MergeTask extends BaseTask<MergeParameters> {
 
         ImagesToPdfDocumentConverter.convertImageMergeInputToPdf(parameters, executionContext());
 
+        List<FooterWriterEntry> footerWriterEntries = new ArrayList<>();
+
         for (PdfMergeInput input : parameters.getPdfInputList()) {
             inputsCounter++;
             LOG.debug("Opening {}", input.getSource());
@@ -167,12 +167,17 @@ public class MergeTask extends BaseTask<MergeParameters> {
                         }
                     }
 
-                    long currentPageNumber = pagesCounter + tocCreator.tocNumberOfPages();
+                    boolean isPlacedAfterToc = true;
                     if (parameters.isFirstInputCoverTitle() && inputsCounter == 1) {
                         // the toc will be added after the cover/title pages
-                        currentPageNumber = pagesCounter;
+                        isPlacedAfterToc = false;
                     }
-                    this.footerWriter.addFooter(importedPage, sourceBaseName, currentPageNumber);
+
+                    // we can determine the correct final page numbers only after the ToC has been generated
+                    // otherwise we don't know how many pages the ToC consists of
+                    // queue up footer writer items for after ToC generation
+                    footerWriterEntries.add(new FooterWriterEntry(importedPage, sourceBaseName, pagesCounter, isPlacedAfterToc));
+                    
                     LOG.trace("Added imported page");
                 } catch (PageNotFoundException e) {
                     executionContext().assertTaskIsLenient(e);
@@ -180,6 +185,7 @@ public class MergeTask extends BaseTask<MergeParameters> {
                             .taskWarning(String.format("Page %d was skipped, could not be processed", currentPage), e);
                 }
             }
+            
             relativePagesCounter = 0;
 
             outlineMerger.updateOutline(sourceDocumentHandler.getUnderlyingPDDocument(), input.getSource().getName(),
@@ -218,17 +224,24 @@ public class MergeTask extends BaseTask<MergeParameters> {
             new PdfScaler(ScaleType.PAGE).scalePages(destinationDocument.getUnderlyingPDDocument());
         }
 
+        int tocNumberOfPages = 0;
         if (tocCreator.hasToc()) {
             LOG.debug("Adding generated ToC");
             try {
                 // add ToC as first page or after the cover/title pages
                 int beforePageNumber = parameters.isFirstInputCoverTitle() ? firstInputNumberOfPages : 0;
-                tocCreator.addToC(beforePageNumber);
+                tocNumberOfPages = tocCreator.addToC(beforePageNumber);
             } catch (TaskException e) {
                 notifyEvent(executionContext().notifiableTaskMetadata())
                         .taskWarning("Unable to create the Table of Contents", e);
             }
         }
+        
+        LOG.debug("Writing page footers");
+        for(FooterWriterEntry entry: footerWriterEntries) {
+            long finalPageNumber = entry.isPlacedAfterToc ? entry.pageNumber + tocNumberOfPages : entry.pageNumber;
+            this.footerWriter.addFooter(entry.page, entry.fileName, finalPageNumber);
+        }
 
         if (catalogPageLabelsMerger.hasPageLabels()) {
             LOG.debug("Adding merged /Catalog /PageLabels");
@@ -257,5 +270,18 @@ public class MergeTask extends BaseTask<MergeParameters> {
         closeResources();
         outputWriter = null;
     }
-
+    
+    private static class FooterWriterEntry {
+        final PDPage page; 
+        final String fileName; 
+        final long pageNumber;
+        final boolean isPlacedAfterToc;
+
+        public FooterWriterEntry(PDPage page, String fileName, long pageNumber, boolean isPlacedAfterToc) {
+            this.page = page;
+            this.fileName = fileName;
+            this.pageNumber = pageNumber;
+            this.isPlacedAfterToc = isPlacedAfterToc;
+        }
+    }
 }
diff --git a/sejda-sambox/src/main/java/org/sejda/impl/sambox/SetMetadataTask.java b/sejda-sambox/src/main/java/org/sejda/impl/sambox/SetMetadataTask.java
index 2e44f7e3..50ec28e3 100644
--- a/sejda-sambox/src/main/java/org/sejda/impl/sambox/SetMetadataTask.java
+++ b/sejda-sambox/src/main/java/org/sejda/impl/sambox/SetMetadataTask.java
@@ -41,8 +41,7 @@ import org.xml.sax.SAXParseException;
 import javax.xml.XMLConstants;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.*;
 import javax.xml.transform.dom.DOMSource;
 import javax.xml.transform.stream.StreamResult;
 import javax.xml.xpath.*;
@@ -221,6 +220,7 @@ public class SetMetadataTask extends BaseTask<SetMetadataParameters> {
             transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
             transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
             transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
+            transformerFactory.setURIResolver(new NoopURIResolver());
 
             Transformer transformer = transformerFactory.newTransformer();
             StringWriter writer = new StringWriter();
@@ -243,5 +243,12 @@ public class SetMetadataTask extends BaseTask<SetMetadataParameters> {
     public void after() {
         closeQuietly(documentHandler);
     }
+    
+    private static class NoopURIResolver implements URIResolver {
+        @Override
+        public Source resolve(String href, String base) throws TransformerException {
+            return null;
+        }
+    }
 
 }
diff --git a/sejda-sambox/src/main/java/org/sejda/impl/sambox/component/TableOfContentsCreator.java b/sejda-sambox/src/main/java/org/sejda/impl/sambox/component/TableOfContentsCreator.java
index dbbac3b8..4fa64642 100644
--- a/sejda-sambox/src/main/java/org/sejda/impl/sambox/component/TableOfContentsCreator.java
+++ b/sejda-sambox/src/main/java/org/sejda/impl/sambox/component/TableOfContentsCreator.java
@@ -19,7 +19,6 @@
 package org.sejda.impl.sambox.component;
 
 import static java.util.Objects.nonNull;
-import static java.util.Optional.ofNullable;
 import static org.sejda.commons.util.RequireUtils.requireArg;
 import static org.sejda.commons.util.RequireUtils.requireNotBlank;
 import static org.sejda.commons.util.RequireUtils.requireNotNullArg;
@@ -73,8 +72,6 @@ public class TableOfContentsCreator {
     private float margin = DEFAULT_MARGIN;
     private PDFont font = PDType1Font.HELVETICA;
     private float lineHeight;
-    private int maxRowsPerPage;
-    private int tocNumberOfPages;
     private MergeParameters params;
     private PageTextWriter writer;
 
@@ -84,7 +81,6 @@ public class TableOfContentsCreator {
         this.document = document;
         this.params = params;
         this.writer = new PageTextWriter(document);
-        recalculateDimensions();
     }
 
     /**
@@ -98,9 +94,9 @@ public class TableOfContentsCreator {
         requireNotBlank(text, "ToC item cannot be blank");
         requireArg(pageNumber > 0, "ToC item cannot point to a negative page");
         requireNotNullArg(page, "ToC page cannot be null");
+        requireNotAlreadyGenerated();
         if (shouldGenerateToC()) {
             items.add(new ToCItem(text, pageNumber, linkAnnotationFor(page)));
-            recalculateDimensions();
         }
     }
 
@@ -118,8 +114,8 @@ public class TableOfContentsCreator {
      * @throws TaskException
      *             if there is an error generating the ToC
      */
-    public void addToC() throws TaskException {
-        addToC(0);
+    public int addToC() throws TaskException {
+        return addToC(0);
     }
 
     /**
@@ -128,38 +124,55 @@ public class TableOfContentsCreator {
      * @throws TaskException
      *             if there is an error generating the ToC
      */
-    public void addToC(int beforePageNumber) throws TaskException {
-        try {
-            PDPageTree pagesTree = document.getPages();
-            ofNullable(generateToC()).filter(l -> !l.isEmpty()).ifPresent(t -> {
-                int toCPagesCount = t.size();
-                t.descendingIterator().forEachRemaining(p -> {
-                    if (pagesTree.getCount() > 0) {
-                        pagesTree.insertBefore(p, pagesTree.get(beforePageNumber));
-                    } else {
-                        pagesTree.add(p);
-                    }
-                });
-                if (params.isBlankPageIfOdd() && toCPagesCount % 2 == 1) {
-                    PDPage lastTocPage = pagesTree.get(beforePageNumber + toCPagesCount - 1);
-                    PDPage blankPage = new PDPage(lastTocPage.getMediaBox());
-                    pagesTree.insertAfter(blankPage, lastTocPage);
-                }
-            });
-        } catch (IOException e) {
-            throw new TaskException("An error occurred while create the ToC", e);
+    public int addToC(int beforePageNumber) throws TaskException {
+        PDPageTree pagesTree = document.getPages();
+        LinkedList<PDPage> toc = generateToC();
+        
+        toc.descendingIterator().forEachRemaining(p -> {
+            if (pagesTree.getCount() > 0) {
+                pagesTree.insertBefore(p, pagesTree.get(beforePageNumber));
+            } else {
+                pagesTree.add(p);
+            }
+        });
+        
+        return toc.size();
+    }
+
+    private LinkedList<PDPage> generatedToC;
+    private LinkedList<PDPage> generateToC() throws TaskIOException {
+        if(generatedToC == null) {
+            generatedToC = _generateToC();
         }
+        
+        return generatedToC;
     }
 
-    private LinkedList<PDPage> generateToC() throws TaskIOException, IOException {
+    private LinkedList<PDPage> _generateToC() throws TaskIOException {
+        // we need to know how many pages the ToC itself has
+        // so we can write the page numbers of the ToC items correctly
+        // but can only know how many pages the ToC has after we generate it
+        
+        // therefore, 1) generate ToC using a dummy estimate for the tocNumberOfPages
+        int tocNumberOfPages = _generateToC(0).size();
+        
+        // 2) generate ToC again with correct tocNumberOfPages
+        return _generateToC(tocNumberOfPages);
+    }
+    
+    private LinkedList<PDPage> _generateToC(int tocNumberOfPages) throws TaskIOException {
         LinkedList<PDPage> pages = new LinkedList<>();
+        recalculateDimensions();
+        
+        int maxRowsPerPage = (int) ((pageSize().getHeight() - (margin * 2) + lineHeight) / lineHeight);
+        Deque<ToCItem> items = new LinkedList<>(this.items);
+         
         if (shouldGenerateToC()) {
-
             while (!items.isEmpty()) {
                 int row = 0;
 
                 float separatorWidth = stringLength(SEPARATOR);
-                float separatingLineEndingX = getSeparatingLineEndingX(separatorWidth, tocNumberOfPages);
+                float separatingLineEndingX = getSeparatingLineEndingX(separatorWidth);
 
                 PDPage page = createPage(pages);
                 try (PDPageContentStream stream = new PDPageContentStream(document, page)) {
@@ -197,7 +210,7 @@ public class TableOfContentsCreator {
 
                             long pageNumber = i.page + tocNumberOfPages;
                             String pageString = SEPARATOR + Long.toString(pageNumber);
-                            float x2 = getPageNumberX(separatorWidth, pageNumber);
+                            float x2 = getPageNumberX(separatorWidth);
                             writeText(page, pageString, x2, y);
 
                             // make the item clickable and link to the page number
@@ -222,12 +235,27 @@ public class TableOfContentsCreator {
                         }
                         row++;
                     }
+                } catch (IOException e) {
+                    throw new TaskIOException("An error occurred while create the ToC", e);
                 }
             }
+
+            if (params.isBlankPageIfOdd() && pages.size() % 2 == 1) {
+                PDPage lastTocPage = pages.getLast();
+                PDPage blankPage = new PDPage(lastTocPage.getMediaBox());
+                pages.add(blankPage);
+            }
         }
+        
         return pages;
     }
 
+    private void requireNotAlreadyGenerated() {
+        if(generatedToC != null) {
+            throw new IllegalStateException("ToC has already been generated");
+        }
+    }
+
     private void writeText(PDPage page, String s, float x, float y) throws TaskIOException {
         writer.write(page, new Point.Float(x, y), s, font, (double) fontSize, Color.BLACK);
     }
@@ -246,16 +274,14 @@ public class TableOfContentsCreator {
         return page;
     }
 
-    private float getSeparatingLineEndingX(float separatorWidth, long indexPages) throws TaskIOException {
-        // this method gets called from recalculateFontSize()
-        // which in turn could be called from the constructor, when no items are yet available
-        ToCItem last = items.peekLast();
-        long lastItemPage = last == null ? 0 : last.page;
-        return getPageNumberX(separatorWidth, lastItemPage + indexPages);
+    private float getSeparatingLineEndingX(float separatorWidth) throws TaskIOException {
+        return getPageNumberX(separatorWidth);
     }
 
-    private float getPageNumberX(float separatorWidth, long pageNumber) throws TaskIOException {
-        return pageSize().getWidth() - margin - separatorWidth - stringLength(Long.toString(pageNumber));
+    private float getPageNumberX(float separatorWidth) throws TaskIOException {
+        return pageSize().getWidth() - margin - separatorWidth
+                /* leave enough space for a 4 digit page number, assumes 9 to be a wide enough digit */        
+                - stringLength(Long.toString(9999));
     }
 
     private float stringLength(String text) throws TaskIOException {
@@ -271,9 +297,9 @@ public class TableOfContentsCreator {
     }
 
     public void pageSizeIfNotSet(PDRectangle pageSize) {
+        requireNotAlreadyGenerated();
         if (this.pageSize == null) {
             this.pageSize = pageSize;
-            recalculateDimensions();
         }
     }
 
@@ -282,16 +308,7 @@ public class TableOfContentsCreator {
 
         this.fontSize = scalingFactor * DEFAULT_FONT_SIZE;
         this.margin = scalingFactor * DEFAULT_MARGIN;
-
         this.lineHeight = (float) (fontSize + (fontSize * 0.7));
-        this.maxRowsPerPage = (int) ((pageSize().getHeight() - (margin * 2) + lineHeight) / lineHeight);
-        if (shouldGenerateToC()) {
-            tocNumberOfPages = params.getInputList().size() / maxRowsPerPage
-                    + (params.getInputList().size() % maxRowsPerPage == 0 ? 0 : 1);
-            if (params.isBlankPageIfOdd() && tocNumberOfPages % 2 == 1) {
-                tocNumberOfPages++;
-            }
-        }
     }
 
     private PDRectangle pageSize() {
@@ -302,11 +319,8 @@ public class TableOfContentsCreator {
         return fontSize;
     }
 
-    /**
-     * @return the number of pages this toc will consist of
-     */
-    public long tocNumberOfPages() {
-        return tocNumberOfPages;
+    PDDocument getDoc() {
+        return document;
     }
 
     private static class ToCItem {
diff --git a/sejda-sambox/src/main/java/org/sejda/impl/sambox/util/FontUtils.java b/sejda-sambox/src/main/java/org/sejda/impl/sambox/util/FontUtils.java
index 9fd60348..8b041190 100644
--- a/sejda-sambox/src/main/java/org/sejda/impl/sambox/util/FontUtils.java
+++ b/sejda-sambox/src/main/java/org/sejda/impl/sambox/util/FontUtils.java
@@ -196,7 +196,26 @@ public final class FontUtils {
     public static boolean canDisplaySpace(PDFont font) {
         try {
             // try encode
-            font.encode(" ");
+            byte[] encoded = font.encode(" ");
+
+            if (font instanceof PDVectorFont) {
+                InputStream in = new ByteArrayInputStream(encoded);
+                while (in.available() > 0) {
+                    int code = font.readCode(in);
+
+                    // LOG.debug("Read codePoint {}", code);
+
+                    PDVectorFont vectorFont = (PDVectorFont) font;
+                    GeneralPath path = vectorFont.getPath(code);
+                    // if(path != null) {
+                    // LOG.debug("GeneralPath is {} for '{}' (code = {}, font = {})", path.getBounds2D(), new String(Character.toChars(code)), code, font.getName());
+                    // }
+
+                    if (path == null || path.getBounds2D().getWidth() == 0) {
+                        return false;
+                    }
+                }
+            }
 
             // see if width is non zero
             return font.getStringWidth(" ") > 0;
@@ -210,6 +229,10 @@ public final class FontUtils {
      * Returns true if the given font can display the given text. IMPORTANT: Ignores all whitespace in text.
      */
     public static boolean canDisplay(String text, PDFont font) {
+        return canDisplayString(removeWhitespace(text), font);
+    }
+
+    private static boolean canDisplayString(String text, PDFont font) {
         if (font == null)
             return false;
 
@@ -217,7 +240,7 @@ public final class FontUtils {
 
         try {
             // remove all whitespace characters and check only if those can be written using the font
-            byte[] encoded = font.encode(removeWhitespace(text));
+            byte[] encoded = font.encode(text);
 
             if (font instanceof PDVectorFont) {
                 InputStream in = new ByteArrayInputStream(encoded);
diff --git a/sejda-sambox/src/test/java/org/sejda/impl/sambox/MergeSamboxTaskTest.java b/sejda-sambox/src/test/java/org/sejda/impl/sambox/MergeSamboxTaskTest.java
index dced6171..fe3df7c6 100644
--- a/sejda-sambox/src/test/java/org/sejda/impl/sambox/MergeSamboxTaskTest.java
+++ b/sejda-sambox/src/test/java/org/sejda/impl/sambox/MergeSamboxTaskTest.java
@@ -861,6 +861,44 @@ public class MergeSamboxTaskTest extends BaseTaskTest<MergeParameters> {
         testContext.assertNoTaskWarnings();
         testContext.assertPages(13);
     }
+    
+    @Test
+    public void withLargeTocItemsThatWrapAndGenerateMultipleTocPages() throws IOException {
+        List<String> entries = new ArrayList<>();
+        for(int i = 1; i <= 26; i++) {
+            if(i % 3 == 0) {
+                entries.add("Attachment " + i + " - Sample file name - longer item that might wrap on the next line");
+            } else {
+                entries.add("Attachment " + i + " - Sample file name - shorter");
+            }
+        }
+
+        List<PdfMergeInput> inputs = new ArrayList<PdfMergeInput>(entries.size());
+        for(String entry: entries) {
+            inputs.add(new PdfMergeInput(customInput("pdf/one_page.pdf", entry + ".pdf"))); // 1 page
+        }
+
+        MergeParameters parameters = setUpParameters(inputs);
+        parameters.setTableOfContentsPolicy(ToCPolicy.FILE_NAMES);
+        parameters.setFilenameFooter(true);
+
+        testContext.pdfOutputTo(parameters);
+
+        execute(parameters);
+
+        testContext.assertTaskCompleted();
+        testContext.assertPages(entries.size() + 2).forEachPdfOutput(d -> {
+            // the TOC
+            assertPageTextExactLines(d.getPage(0), "Attachment 1 - Sample file name - shorter   3\nAttachment 2 - Sample file name - shorter   4\nAttachment 3 - Sample file name - longer item that might wrap on\nthe next line   5\nAttachment 4 - Sample file name - shorter   6\nAttachment 5 - Sample file name - shorter   7\nAttachment 6 - Sample file name - longer item that might wrap on\nthe next line   8\nAttachment 7 - Sample file name - shorter   9\nAttachment 8 - Sample file name - shorter   10\nAttachment 9 - Sample file name - longer item that might wrap on\nthe next line   11\nAttachment 10 - Sample file name - shorter   12\nAttachment 11 - Sample file name - shorter   13\nAttachment 12 - Sample file name - longer item that might wrap\non the next line   14\nAttachment 13 - Sample file name - shorter   15\nAttachment 14 - Sample file name - shorter   16\nAttachment 15 - Sample file name - longer item that might wrap\non the next line   17\nAttachment 16 - Sample file name - shorter   18\nAttachment 17 - Sample file name - shorter   19\nAttachment 18 - Sample file name - longer item that might wrap\non the next line   20\nAttachment 19 - Sample file name - shorter   21\nAttachment 20 - Sample file name - shorter   22\nAttachment 21 - Sample file name - longer item that might wrap\non the next line   23\nAttachment 22 - Sample file name - shorter   24\nAttachment 23 - Sample file name - shorter   25\n");
+            assertPageTextExactLines(d.getPage(1), "Attachment 24 - Sample file name - longer item that might wrap\non the next line   26\nAttachment 25 - Sample file name - shorter   27\nAttachment 26 - Sample file name - shorter   28\n");
+
+            // next pages are the merged inputs
+            assertFooterHasText(d.getPage(2), "Attachment 1 - Sample file name - shorter 3");
+            assertFooterHasText(d.getPage(3), "Attachment 2 - Sample file name - shorter 4");
+            assertFooterHasText(d.getPage(4), "Attachment 3 - Sample file name - longer item that might wrap on the next line 5");
+            assertFooterHasText(d.getPage(27), "Attachment 26 - Sample file name - shorter 28");
+        });
+    }
 
     private float widthOfCropBox(PDPage page) {
         return page.getCropBox().rotate(page.getRotation()).getWidth();
diff --git a/sejda-sambox/src/test/java/org/sejda/impl/sambox/component/TableOfContentsCreatorTest.java b/sejda-sambox/src/test/java/org/sejda/impl/sambox/component/TableOfContentsCreatorTest.java
index 4a885b47..306fa5ae 100755
--- a/sejda-sambox/src/test/java/org/sejda/impl/sambox/component/TableOfContentsCreatorTest.java
+++ b/sejda-sambox/src/test/java/org/sejda/impl/sambox/component/TableOfContentsCreatorTest.java
@@ -18,17 +18,18 @@
  */
 package org.sejda.impl.sambox.component;
 
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.instanceOf;
-import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
+import static org.sejda.core.service.TestUtils.*;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 import org.junit.Test;
@@ -46,7 +47,6 @@ import org.sejda.sambox.pdmodel.PDPage;
 import org.sejda.sambox.pdmodel.common.PDRectangle;
 import org.sejda.sambox.pdmodel.interactive.annotation.PDAnnotationLink;
 import org.sejda.sambox.pdmodel.interactive.documentnavigation.destination.PDPageXYZDestination;
-import org.sejda.sambox.text.PDFTextStripper;
 
 /**
  * @author Andrea Vacondio
@@ -110,30 +110,6 @@ public class TableOfContentsCreatorTest {
         assertTrue(victim.hasToc());
     }
 
-    @Test
-    public void testAddToCNone() throws TaskException {
-        PDDocument doc = new PDDocument();
-        MergeParameters params = new MergeParameters();
-        params.setTableOfContentsPolicy(ToCPolicy.NONE);
-        assertEquals(0, doc.getNumberOfPages());
-        TableOfContentsCreator victim = new TableOfContentsCreator(params, doc);
-        victim.appendItem("text", 10, new PDPage());
-        victim.addToC();
-        assertEquals(0, doc.getNumberOfPages());
-    }
-
-    @Test
-    public void testAddToC() throws TaskException {
-        MergeParameters params = new MergeParameters();
-        params.setTableOfContentsPolicy(ToCPolicy.DOC_TITLES);
-        PDDocument doc = new PDDocument();
-        assertEquals(0, doc.getNumberOfPages());
-        TableOfContentsCreator victim = new TableOfContentsCreator(params, doc);
-        victim.appendItem("text", 10, new PDPage());
-        victim.addToC();
-        assertEquals(1, doc.getNumberOfPages());
-    }
-
     @Test
     public void testAddToCTop() throws IOException, TaskException {
         PDDocument doc = PDFParser.parse(SeekableSources
@@ -177,6 +153,7 @@ public class TableOfContentsCreatorTest {
         for (int i = 1; i < 40; i++) {
             victim.appendItem("text", i, new PDPage());
         }
+        
         victim.addToC();
         assertEquals(5, doc.getNumberOfPages());
         assertEquals(firstPage.getCOSObject(), doc.getPage(2).getCOSObject());
@@ -184,122 +161,100 @@ public class TableOfContentsCreatorTest {
 
     @Test
     public void testAddToCSuperLongText() throws TaskException {
-        PDDocument doc = new PDDocument();
-        assertEquals(0, doc.getNumberOfPages());
-        MergeParameters params = new MergeParameters();
-        params.setTableOfContentsPolicy(ToCPolicy.DOC_TITLES);
-        TableOfContentsCreator victim = new TableOfContentsCreator(params, doc);
-        victim.appendItem(
-                "This is a very long file name and we expect that it's handled correctly and no Exception is thrown by the component. We are making this very very long so we can make sure that even the veeeery long ones are handled.",
-                100, new PDPage());
+        TableOfContentsCreator victim = newToCCreator("This is a very long file name and we expect that it's handled correctly and no Exception is thrown by the component. We are making this very very long so we can make sure that even the veeeery long ones are handled.");
+        
         victim.addToC();
-        assertEquals(1, doc.getNumberOfPages());
+        
+        assertEquals(1, victim.getDoc().getNumberOfPages());
     }
 
     @Test
     public void testToCPageSize() throws TaskException {
-        PDDocument doc = new PDDocument();
-        assertEquals(0, doc.getNumberOfPages());
-        MergeParameters params = new MergeParameters();
-        params.setTableOfContentsPolicy(ToCPolicy.DOC_TITLES);
-        TableOfContentsCreator victim = new TableOfContentsCreator(params, doc);
+        TableOfContentsCreator victim = newToCCreator();
         victim.pageSizeIfNotSet(PDRectangle.LETTER);
-        victim.appendItem("test.", 100, new PDPage());
         victim.addToC();
-        assertEquals(PDRectangle.LETTER, doc.getPage(0).getMediaBox());
+        assertEquals(PDRectangle.LETTER, victim.getDoc().getPage(0).getMediaBox());
     }
 
     @Test
     public void testToCForLargePageSize() throws TaskException {
-        PDDocument doc = new PDDocument();
-        assertEquals(0, doc.getNumberOfPages());
-        MergeParameters params = new MergeParameters();
-        params.setTableOfContentsPolicy(ToCPolicy.DOC_TITLES);
-        TableOfContentsCreator victim = new TableOfContentsCreator(params, doc);
+        TableOfContentsCreator victim = newToCCreator();
         victim.pageSizeIfNotSet(PDRectangle.A1);
-        victim.appendItem("test.", 100, new PDPage());
         victim.addToC();
         assertEquals(39.64, victim.getFontSize(), 0.1);
     }
 
     @Test
     public void testStringsThatMixMultipleFontRequirements() throws TaskException {
-        PDDocument doc = new PDDocument();
-        assertEquals(0, doc.getNumberOfPages());
-        MergeParameters params = new MergeParameters();
-        params.setTableOfContentsPolicy(ToCPolicy.DOC_TITLES);
-        TableOfContentsCreator victim = new TableOfContentsCreator(params, doc);
-        victim.appendItem("1-abc-עברית", 100, new PDPage());
+        TableOfContentsCreator victim = newToCCreator("1-abc-עברית");
         victim.addToC();
-        assertEquals(1, doc.getNumberOfPages());
+        assertEquals(1, victim.getDoc().getNumberOfPages());
     }
 
     @Test
     public void indexPageIsConsideredInPageNumbers() throws IOException, TaskException {
-        PDDocument doc = new PDDocument();
-        assertEquals(0, doc.getNumberOfPages());
-        MergeParameters params = new MergeParameters();
-        params.addInput(new PdfMergeInput(mock(PdfFileSource.class)));
-        params.setTableOfContentsPolicy(ToCPolicy.DOC_TITLES);
-        TableOfContentsCreator victim = new TableOfContentsCreator(params, doc);
-        victim.appendItem(
-                "This is a very long file name and we expect that it's handled correctly and no Exception is thrown by the component. We are making this very very long so we can make sure that even the veeeery long ones are handled.",
-                100, new PDPage());
+        TableOfContentsCreator victim = newToCCreator(ToCPolicy.DOC_TITLES, 
+                Arrays.asList("This is a very long file name and we expect that it's handled correctly and no Exception is thrown by the component. We are making this very very long so we can make sure that even the veeeery long ones are handled."));
+        
         victim.addToC();
-        assertThat(new PDFTextStripper().getText(doc), containsString("101"));
+        assertThat(getDocTextNormalized(victim.getDoc()), endsWith("veeeery long ones are handled.   101\n"));
     }
 
     @Test
-    public void testTocNumberOfPagesNoToc() {
-        MergeParameters params = new MergeParameters();
-        params.setTableOfContentsPolicy(ToCPolicy.NONE);
-        params.addInput(new PdfMergeInput(mock(PdfFileSource.class)));
-        TableOfContentsCreator victim = new TableOfContentsCreator(params, new PDDocument());
-        assertEquals(0, victim.tocNumberOfPages());
+    public void testTocNumberOfPagesNoToc() throws TaskException {
+        TableOfContentsCreator victim = newToCCreator(ToCPolicy.NONE, Arrays.asList("sample title"));
+        int tocNumPages = victim.addToC();
+        assertEquals(0, tocNumPages);
+        assertEquals(0, victim.getDoc().getNumberOfPages());
     }
 
     @Test
-    public void testTocNumberOfPages() {
-        MergeParameters params = new MergeParameters();
-        params.setTableOfContentsPolicy(ToCPolicy.DOC_TITLES);
-        params.addInput(new PdfMergeInput(mock(PdfFileSource.class)));
-        TableOfContentsCreator victim = new TableOfContentsCreator(params, new PDDocument());
-        assertEquals(1, victim.tocNumberOfPages());
+    public void testTocNumberOfPages() throws TaskException {
+        TableOfContentsCreator victim = newToCCreator("sample title");
+        int tocNumPages = victim.addToC();
+        assertEquals(1, tocNumPages);
+        assertEquals(1, victim.getDoc().getNumberOfPages());
     }
 
     @Test
-    public void testTocNumberOfPagesAddBlank() {
+    public void testTocNumberOfPages_AddBlankOption() throws TaskException {
         MergeParameters params = new MergeParameters();
         params.setBlankPageIfOdd(true);
         params.setTableOfContentsPolicy(ToCPolicy.DOC_TITLES);
         params.addInput(new PdfMergeInput(mock(PdfFileSource.class)));
-        TableOfContentsCreator victim = new TableOfContentsCreator(params, new PDDocument());
-        assertEquals(2, victim.tocNumberOfPages());
+        PDDocument doc = new PDDocument();
+        TableOfContentsCreator victim = new TableOfContentsCreator(params, doc);
+        victim.appendItem("sample title", 1, new PDPage());
+        int tocNumPages = victim.addToC();
+        assertEquals(2, tocNumPages);
+        assertEquals(2, doc.getNumberOfPages());
     }
 
     @Test
-    public void testTocNumberOfPagesMultiple() {
-        MergeParameters params = new MergeParameters();
-        params.setTableOfContentsPolicy(ToCPolicy.FILE_NAMES);
+    public void testTocNumberOfPages_Multiple() throws TaskException {
+        TableOfContentsCreator victim = newToCCreator();
+        victim.pageSizeIfNotSet(PDRectangle.A4);
         for (int i = 1; i < 40; i++) {
-            params.addInput(new PdfMergeInput(mock(PdfFileSource.class)));
+            victim.appendItem("entry " + i, i, new PDPage());
         }
-        TableOfContentsCreator victim = new TableOfContentsCreator(params, new PDDocument());
-        victim.pageSizeIfNotSet(PDRectangle.A4);
-        assertEquals(2, victim.tocNumberOfPages());
+
+        int tocNumPages = victim.addToC();
+        assertEquals(2, tocNumPages);
+        assertEquals(2, victim.getDoc().getNumberOfPages());
     }
 
     @Test
-    public void testTocNumberOfPagesMultipleInA2() {
-        MergeParameters params = new MergeParameters();
-        params.setTableOfContentsPolicy(ToCPolicy.FILE_NAMES);
+    public void testTocNumberOfPagesMultipleInA2() throws TaskException {
+        TableOfContentsCreator victim = newToCCreator();
+        victim.pageSizeIfNotSet(PDRectangle.A2);
         for (int i = 1; i < 40; i++) {
-            params.addInput(new PdfMergeInput(mock(PdfFileSource.class)));
+            victim.appendItem("sample " + i + ".pdf", i + 1, new PDPage());
         }
-        TableOfContentsCreator victim = new TableOfContentsCreator(params, new PDDocument());
-        victim.pageSizeIfNotSet(PDRectangle.A2);
+        
         // ToC font is scaled so we get 2 pages even if a2 is twice the a4 height
-        assertEquals(2, victim.tocNumberOfPages());
+        int tocNumPages = victim.addToC();
+        assertEquals(2, tocNumPages);
+        assertEquals(2, victim.getDoc().getNumberOfPages());
     }
 
     @Test
@@ -317,10 +272,10 @@ public class TableOfContentsCreatorTest {
         victim.pageSizeIfNotSet(PDRectangle.A4);
         victim.addToC();
 
-        TestUtils.assertPageTextExactLines(doc.getPage(0),
-                "This is item 1   1\n" + "This is item 2 that has a very long name and should not be\n"
-                        + "truncated so that the version is visible at the end v7.pdf   10\n"
-                        + "This is item 3   14\n");
+        assertPageTextExactLines(doc.getPage(0),
+                "This is item 1   2\n" + "This is item 2 that has a very long name and should not be\n"
+                        + "truncated so that the version is visible at the end v7.pdf   11\n"
+                        + "This is item 3   15\n");
 
         // verify size of the clickable annotations on top of the TOC items
         List<PDAnnotationLink> annotations = TestUtils.getAnnotationsOf(doc.getPage(0), PDAnnotationLink.class);
@@ -341,10 +296,7 @@ public class TableOfContentsCreatorTest {
 
     @Test
     public void test_Toc_Long_Item_That_Wraps_At_The_End_Of_The_Page() throws TaskException {
-        MergeParameters params = new MergeParameters();
-        params.setTableOfContentsPolicy(ToCPolicy.FILE_NAMES);
-        PDDocument doc = new PDDocument();
-        TableOfContentsCreator victim = new TableOfContentsCreator(params, doc);
+        TableOfContentsCreator victim = newToCCreator();
         for (int i = 0; i < 30; i++) {
             victim.appendItem("This is an item", 1, new PDPage());
         }
@@ -356,47 +308,33 @@ public class TableOfContentsCreatorTest {
         victim.pageSizeIfNotSet(PDRectangle.A4);
         victim.addToC();
 
-        TestUtils.assertPageTextDoesNotContain(doc.getPage(0), "This is a long item that");
-
-        TestUtils.assertPageTextContains(doc.getPage(1), "This is a long item that");
+        TestUtils.assertPageTextDoesNotContain(victim.getDoc().getPage(0), "This is a long item that");
+        TestUtils.assertPageTextContains(victim.getDoc().getPage(1), "This is a long item that");
     }
 
     @Test
     public void test_Toc_Long_Item_That_Has_No_Word_Breaks() throws TaskException {
-        MergeParameters params = new MergeParameters();
-        params.setTableOfContentsPolicy(ToCPolicy.FILE_NAMES);
-        PDDocument doc = new PDDocument();
-        TableOfContentsCreator victim = new TableOfContentsCreator(params, doc);
-        victim.appendItem(
-                "This_is_a_file_that_has_a_very_long_name_and_should_not_be_truncated_so_that_the_version_is_visible_at_the_end_v7.pdf",
-                10, new PDPage());
+        TableOfContentsCreator victim = newToCCreator("This_is_a_file_that_has_a_very_long_name_and_should_not_be_truncated_so_that_the_version_is_visible_at_the_end_v7.pdf");
         victim.pageSizeIfNotSet(PDRectangle.A4);
         victim.addToC();
 
-        TestUtils.assertPageTextExactLines(doc.getPage(0),
-                "This_is_a_file_that_has_a_very_long_name_and_should_not_be_tr-\n"
-                        + "uncated_so_that_the_version_is_visible_at_the_end_v7.pdf   10\n");
+        assertPageTextExactLines(victim.getDoc().getPage(0),
+                "This_is_a_file_that_has_a_very_long_name_and_should_not_be-\n"
+                        + "_truncated_so_that_the_version_is_visible_at_the_end_v7.pdf   101\n");
     }
 
     @Test
     public void test_Toc_Item_Requiring_Multiple_Fonts() throws TaskException {
-        MergeParameters params = new MergeParameters();
-        params.setTableOfContentsPolicy(ToCPolicy.FILE_NAMES);
-        PDDocument doc = new PDDocument();
-        TableOfContentsCreator victim = new TableOfContentsCreator(params, doc);
-        victim.appendItem("Item multiple fonts ทดสอบ", 10, new PDPage());
+        TableOfContentsCreator victim = newToCCreator("Item multiple fonts ทดสอบ");
         victim.pageSizeIfNotSet(PDRectangle.A4);
         victim.addToC();
 
-        TestUtils.assertPageTextExactLines(doc.getPage(0), "Item multiple fonts ทดสอบ   10\n");
+        assertPageTextExactLines(victim.getDoc().getPage(0), "Item multiple fonts ทดสอบ   101\n");
     }
 
     @Test(expected = UnsupportedTextException.class)
     public void tocItemsMultipleFontsButNotFound() throws TaskException {
-        MergeParameters params = new MergeParameters();
-        params.setTableOfContentsPolicy(ToCPolicy.FILE_NAMES);
-        PDDocument doc = new PDDocument();
-        TableOfContentsCreator victim = new TableOfContentsCreator(params, doc);
+        TableOfContentsCreator victim = newToCCreator();
         victim.appendItem("Item multiple fonts հայերէն", 10, new PDPage());
         victim.pageSizeIfNotSet(PDRectangle.A4);
         victim.addToC();
@@ -425,13 +363,13 @@ public class TableOfContentsCreatorTest {
         assertThat(doc.getNumberOfPages(), is(4)); // one extra for blank page if odd
 
         // blank page pageA
-        TestUtils.assertPageTextExactLines(doc.getPage(0), "PageA\n");
+        assertPageTextExactLines(doc.getPage(0), "PageA\n");
         // toc
-        TestUtils.assertPageTextExactLines(doc.getPage(1), "This is an item   2\n");
+        assertPageTextExactLines(doc.getPage(1), "This is an item   4\n");
         // extra blank page if odd
-        TestUtils.assertPageTextExactLines(doc.getPage(2), "");
+        assertPageTextExactLines(doc.getPage(2), "");
         // blank page pageB
-        TestUtils.assertPageTextExactLines(doc.getPage(3), "PageB\n");
+        assertPageTextExactLines(doc.getPage(3), "PageB\n");
     }
 
     @Test
@@ -479,4 +417,145 @@ public class TableOfContentsCreatorTest {
         assertEquals((int) PDRectangle.A4.getHeight(), ((PDPageXYZDestination) link270.getDestination()).getTop());
 
     }
+
+    @Test
+    public void testLongTocItemsThatWrapAndGenerateMultiplePagesOfToC_HardToEstimateToCNumberOfPages() throws TaskException, IOException {
+        List<String> entries = new ArrayList<>();
+        for(int i = 1; i <= 26; i++) {
+            if(i % 3 == 0) {
+                entries.add("Attachment " + i + " - Sample file name - longer item that might wrap on the next line");    
+            } else {
+                entries.add("Attachment " + i + " - Sample file name - shorter");
+            }
+        }
+        
+        TableOfContentsCreator victim = newToCCreator(entries);
+        victim.pageSizeIfNotSet(PDRectangle.A4);
+
+        victim.addToC();
+        
+        assertThat(victim.getDoc().getNumberOfPages(), is(2));
+        
+        String expected = "Attachment 1 - Sample file name - shorter   102\n" +
+                "Attachment 2 - Sample file name - shorter   103\n" +
+                "Attachment 3 - Sample file name - longer item that might wrap on\n" +
+                "the next line   104\n" +
+                "Attachment 4 - Sample file name - shorter   105\n" +
+                "Attachment 5 - Sample file name - shorter   106\n" +
+                "Attachment 6 - Sample file name - longer item that might wrap on\n" +
+                "the next line   107\n" +
+                "Attachment 7 - Sample file name - shorter   108\n" +
+                "Attachment 8 - Sample file name - shorter   109\n" +
+                "Attachment 9 - Sample file name - longer item that might wrap on\n" +
+                "the next line   110\n" +
+                "Attachment 10 - Sample file name - shorter   111\n" +
+                "Attachment 11 - Sample file name - shorter   112\n" +
+                "Attachment 12 - Sample file name - longer item that might wrap\n" +
+                "on the next line   113\n" +
+                "Attachment 13 - Sample file name - shorter   114\n" +
+                "Attachment 14 - Sample file name - shorter   115\n" +
+                "Attachment 15 - Sample file name - longer item that might wrap\n" +
+                "on the next line   116\n" +
+                "Attachment 16 - Sample file name - shorter   117\n" +
+                "Attachment 17 - Sample file name - shorter   118\n" +
+                "Attachment 18 - Sample file name - longer item that might wrap\n" +
+                "on the next line   119\n" +
+                "Attachment 19 - Sample file name - shorter   120\n" +
+                "Attachment 20 - Sample file name - shorter   121\n" +
+                "Attachment 21 - Sample file name - longer item that might wrap\n" +
+                "on the next line   122\n" +
+                "Attachment 22 - Sample file name - shorter   123\n" +
+                "Attachment 23 - Sample file name - shorter   124\n" +
+                "Attachment 24 - Sample file name - longer item that might wrap\n" +
+                "on the next line   125\n" +
+                "Attachment 25 - Sample file name - shorter   126\n" +
+                "Attachment 26 - Sample file name - shorter   127\n";
+
+        assertDocTextExactLines(victim.getDoc(), expected);
+    }
+
+    @Test
+    public void lastItemDoesNotFitPage_WrapsToNextPage() throws TaskException, IOException {
+        List<String> entries = new ArrayList<>();
+        for(int i = 1; i <= 22; i++) {
+            if(i % 3 == 0) {
+                entries.add("Attachment " + i + " - Sample file name - longer item that might wrap on the next line");
+            } else {
+                entries.add("Attachment " + i + " - Sample file name - shorter");
+            }
+        }
+
+        // this item will not fit the 1st TOC page, will be added to the 2nd
+        entries.add("Attachment final - Sample file name - longer item that might wrap on the next line");
+
+        TableOfContentsCreator victim = newToCCreator(entries);
+        victim.pageSizeIfNotSet(PDRectangle.A4);
+
+        victim.addToC();
+        
+        assertThat(victim.getDoc().getNumberOfPages(), is(2));
+        assertPageTextExactLines(victim.getDoc().getPage(1), "Attachment final - Sample file name - longer item that might wrap\non the next line   124\n");
+    }
+
+    @Test
+    public void lastItemBarelyFitsPage_NoWrapToNextPage() throws TaskException, IOException {
+        List<String> entries = new ArrayList<>();
+        for(int i = 1; i <= 22; i++) {
+            if(i % 3 == 0) {
+                entries.add("Attachment " + i + " - Sample file name - longer item that might wrap on the next line");
+            } else {
+                entries.add("Attachment " + i + " - Sample file name - shorter");
+            }
+        }
+
+        // this item will fit the 1st TOC page
+        entries.add("Attachment final - Sample file name - short and fits");
+
+        TableOfContentsCreator victim = newToCCreator(entries);
+        victim.pageSizeIfNotSet(PDRectangle.A4);
+
+        victim.addToC();
+
+        assertThat(victim.getDoc().getNumberOfPages(), is(1));
+        assertThat(getPageTextNormalized(victim.getDoc().getPage(0)), endsWith("\nAttachment final - Sample file name - short and fits   123\n"));
+    }
+    
+    @Test(expected = IllegalStateException.class)
+    public void catchesWrongUsage_ItemAddedAfterTocGenerated() throws TaskException {
+        TableOfContentsCreator victim = newToCCreator();
+        
+        victim.addToC();
+        
+        victim.appendItem("cannot add more", 2, new PDPage());
+    }
+    
+    private TableOfContentsCreator newToCCreator() {
+        return newToCCreator("test");
+    }
+
+    private TableOfContentsCreator newToCCreator(String item) {
+        return newToCCreator(Arrays.asList(item));
+    }
+
+    private TableOfContentsCreator newToCCreator(List<String> items) {
+        return newToCCreator(ToCPolicy.FILE_NAMES, items);
+    }
+
+    private TableOfContentsCreator newToCCreator(ToCPolicy toCPolicy, List<String> items) {
+        MergeParameters params = new MergeParameters();
+        params.setTableOfContentsPolicy(toCPolicy);
+
+        return newToCCreator(params, items);
+    }
+
+    private TableOfContentsCreator newToCCreator(MergeParameters params, List<String> items) {
+        PDDocument doc = new PDDocument();
+        TableOfContentsCreator victim = new TableOfContentsCreator(params, doc);
+        
+        for (int i = 0; i < items.size(); i++) {
+            victim.appendItem(items.get(i), 100 + i, new PDPage());    
+        }
+        
+        return  victim;
+    }
 }
diff --git a/sejda-sambox/src/test/java/org/sejda/impl/sambox/util/FontUtilsTest.java b/sejda-sambox/src/test/java/org/sejda/impl/sambox/util/FontUtilsTest.java
index edd24d78..8b9f19cb 100644
--- a/sejda-sambox/src/test/java/org/sejda/impl/sambox/util/FontUtilsTest.java
+++ b/sejda-sambox/src/test/java/org/sejda/impl/sambox/util/FontUtilsTest.java
@@ -346,4 +346,19 @@ public class FontUtilsTest {
         List<String> result = FontUtils.resolveTextFragments("FRIDA", font);
         assertThat(result, is(Arrays.asList("F", "RIDA")));
     }
+
+    @Test
+    public void brokenFontWithZeroWidthGlyphs() throws TaskIOException, IOException {
+        PDDocument doc = getTestDoc("pdf/font-with-zero-width-glyphs.pdf");
+        PDFont font = doc.getPage(0).getResources().getFont(COSName.getPDFName("F1"));
+        
+        assertThat(FontUtils.canDisplaySpace(font), is(false));
+        
+        List<TextWithFont> result = FontUtils.resolveFonts("31 days", font, doc);
+        assertThat(result.get(0).getText(), is("31"));
+        assertThat(result.get(0).getFont(), is(font));
+        assertThat(result.get(1).getText(), is(" "));
+        assertThat(result.get(1).getFont().getName(), is("Helvetica"));
+        assertThat(result.get(2).getText(), is("days"));
+    }
 }