Browse Source

Feature/per object options (#106)

* Added multiple object support each with their own params.
Allows generating more complicated documents, for example, when the table of contents might need to come after a particular page and not always first.

* Integration test for new multi object support.

* updated readme

* Added backwards compatibility

* Keep command output consistent with past implementations

* Added 'alwaysPutTocFirst' configuration option for backwards compatibility

* Added unit and integration tests to cover new command possibilities. Scenarios covered:
- using wkhtmltopdf object types toc, page and cover
- object ordering
- providing per object params
- wrapper config 'alwaysPutTocFirst'

---------

Co-authored-by: Worik McShane <wmcshane@geometryit.com>
Co-authored-by: Jhonny Mertz <jhonnymertz@gmail.com>
wozza 1 year ago
parent
commit
013a4d83f3

+ 95 - 44
src/main/java/com/github/jhonnymertz/wkhtmltopdf/wrapper/Pdf.java

@@ -3,8 +3,11 @@ package com.github.jhonnymertz.wkhtmltopdf.wrapper;
 import com.github.jhonnymertz.wkhtmltopdf.wrapper.configurations.FilenameFilterConfig;
 import com.github.jhonnymertz.wkhtmltopdf.wrapper.configurations.WrapperConfig;
 import com.github.jhonnymertz.wkhtmltopdf.wrapper.exceptions.PDFExportException;
-import com.github.jhonnymertz.wkhtmltopdf.wrapper.page.Page;
-import com.github.jhonnymertz.wkhtmltopdf.wrapper.page.PageType;
+import com.github.jhonnymertz.wkhtmltopdf.wrapper.objects.BaseObject;
+import com.github.jhonnymertz.wkhtmltopdf.wrapper.objects.Cover;
+import com.github.jhonnymertz.wkhtmltopdf.wrapper.objects.Page;
+import com.github.jhonnymertz.wkhtmltopdf.wrapper.objects.TableOfContents;
+import com.github.jhonnymertz.wkhtmltopdf.wrapper.objects.SourceType;
 import com.github.jhonnymertz.wkhtmltopdf.wrapper.params.Param;
 import com.github.jhonnymertz.wkhtmltopdf.wrapper.params.Params;
 import org.apache.commons.io.FileUtils;
@@ -22,8 +25,8 @@ import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.UUID;
 import java.util.concurrent.*;
+import java.util.stream.Collectors;
 
 /**
  * Represents a Pdf file and wraps up the wkhtmltopdf processing
@@ -38,11 +41,9 @@ public class Pdf {
 
     private final Params params;
 
-    private final Params tocParams;
+    private final List<BaseObject> objects;
 
-    private final List<Page> pages;
-
-    private boolean hasToc = false;
+    private TableOfContents lastToc;
 
     /**
      * Timeout to wait while generating a PDF, in seconds
@@ -51,7 +52,7 @@ public class Pdf {
 
     private File tempDirectory;
 
-    private static String TEMPORARY_FILE_PREFIX = "java-wkhtmltopdf-wrapper";
+    public static String TEMPORARY_FILE_PREFIX = "java-wkhtmltopdf-wrapper";
 
     private String outputFilename = null;
 
@@ -69,8 +70,7 @@ public class Pdf {
     public Pdf(WrapperConfig wrapperConfig) {
         this.wrapperConfig = wrapperConfig;
         this.params = new Params();
-        this.tocParams = new Params();
-        this.pages = new ArrayList<Page>();
+        this.objects = new ArrayList<BaseObject>();
         logger.info("Initialized with {}", wrapperConfig);
     }
 
@@ -80,41 +80,85 @@ public class Pdf {
      * @deprecated Use the specific type method to a better semantic
      */
     @Deprecated
-    public void addPage(String source, PageType type) {
-        this.pages.add(new Page(source, type));
+    public Page addPage(String source, SourceType type) {
+        Page page = new Page(source, type);
+        this.objects.add(page);
+        return page;
+    }
+
+     /**
+     * Add a cover from an URL to the pdf
+     */
+    public Cover addCoverFromUrl(String source) {
+        Cover cover = new Cover(source, SourceType.url);
+        this.objects.add(cover);
+        return cover;
+    }
+
+    /**
+     * Add a cover from a HTML-based string to the pdf
+     */
+    public Cover addCoverFromString(String source) {
+        Cover cover = new Cover(source, SourceType.htmlAsString);
+        this.objects.add(cover);
+        return cover;
+    }
+
+    /**
+     * Add a cover from a file to the pdf
+     */
+    public Cover addCoverFromFile(String source) {
+        Cover cover = new Cover(source, SourceType.file);
+        this.objects.add(cover);
+        return cover;
     }
 
     /**
      * Add a page from an URL to the pdf
      */
-    public void addPageFromUrl(String source) {
-        this.pages.add(new Page(source, PageType.url));
+    public Page addPageFromUrl(String source) {
+        Page page = new Page(source, SourceType.url);
+        this.objects.add(page);
+        return page;
     }
 
     /**
      * Add a page from a HTML-based string to the pdf
      */
-    public void addPageFromString(String source) {
-        this.pages.add(new Page(source, PageType.htmlAsString));
+    public Page addPageFromString(String source) {
+        Page page = new Page(source, SourceType.htmlAsString);
+        this.objects.add(page);
+        return page;
     }
 
     /**
      * Add a page from a file to the pdf
      */
-    public void addPageFromFile(String source) {
-        this.pages.add(new Page(source, PageType.file));
+    public Page addPageFromFile(String source) {
+        Page page = new Page(source, SourceType.file);
+        this.objects.add(page);
+        return page;
     }
 
-    public void addToc() {
-        this.hasToc = true;
+    /**
+     * Add a toc
+     */
+    public TableOfContents addToc() {
+        TableOfContents toc = new TableOfContents();
+        this.objects.add(toc);
+        this.lastToc = toc;
+        return toc;
     }
 
     public void addParam(Param param, Param... params) {
         this.params.add(param, params);
     }
 
+    /**
+     * Adds a param to the most recently added toc
+     */
     public void addTocParam(Param param, Param... params) {
-        this.tocParams.add(param, params);
+        this.lastToc.addParam(param, params);
     }
 
     /**
@@ -160,6 +204,15 @@ public class Pdf {
         this.tempDirectory = tempDirectory;
     }
 
+    /**
+     * Gets the temporary folder where files are stored during processing
+     * 
+     * @return
+     */
+    public File getTempDirectory() {
+        return this.tempDirectory;
+    }
+
     /**
      * Executes the wkhtmltopdf into standard out and captures the results.
      *
@@ -247,27 +300,22 @@ public class Pdf {
 
         commandLine.addAll(Arrays.asList(wrapperConfig.getWkhtmltopdfCommandAsArray()));
 
+        // Global options
         commandLine.addAll(params.getParamsAsStringList());
 
-        if (hasToc) {
-            commandLine.add("toc");
-            commandLine.addAll(tocParams.getParamsAsStringList());
+        // Check if TOC is always first
+        if(wrapperConfig.getAlwaysPutTocFirst()) {
+            // remove and add TOC to top
+            List<BaseObject> tocObjects = objects.stream().filter((o) -> o instanceof TableOfContents).collect(Collectors.toList()); // .getObjectIdentifier().equalsIgnoreCase("toc")
+            objects.removeAll(tocObjects);
+            objects.addAll(0, tocObjects);
         }
 
-        for (Page page : pages) {
-            if (page.getType().equals(PageType.htmlAsString)) {
-                //htmlAsString pages are first store into a temp file, then the location is passed as parameter to
-                // wkhtmltopdf, this is a workaround to avoid huge commands
-                if (page.getFilePath() != null)
-                    Files.deleteIfExists(Paths.get(page.getFilePath()));
-                File temp = File.createTempFile(TEMPORARY_FILE_PREFIX + UUID.randomUUID().toString(), ".html", tempDirectory);
-                FileUtils.writeStringToFile(temp, page.getSource(), "UTF-8");
-                page.setFilePath(temp.getAbsolutePath());
-                commandLine.add(temp.getAbsolutePath());
-            } else {
-                commandLine.add(page.getSource());
-            }
+        // Object and object options
+        for (BaseObject object : objects) {
+            commandLine.addAll(object.getCommandAsList(this));
         }
+
         commandLine.add((null != outputFilename) ? outputFilename : STDINOUT);
         logger.debug("Command generated: {}", commandLine.toString());
         return commandLine.toArray(new String[commandLine.size()]);
@@ -294,13 +342,16 @@ public class Pdf {
      */
     private void cleanTempFiles() {
         logger.debug("Cleaning up temporary files...");
-        for (Page page : pages) {
-            if (page.getType().equals(PageType.htmlAsString)) {
-                try {
-                    Path p = Paths.get(page.getFilePath());
-                    logger.debug("Delete temp file at: " + page.getFilePath() + " " + Files.deleteIfExists(p));
-                } catch (IOException ex) {
-                    logger.warn("Couldn't delete temp file " + page.getFilePath());
+        for (BaseObject object : objects) {
+            if(object instanceof Page) {
+                Page page = (Page) object;
+                if (page.getType().equals(SourceType.htmlAsString)) {
+                    try {
+                        Path p = Paths.get(page.getFilePath());
+                        logger.debug("Delete temp file at: " + page.getFilePath() + " " + Files.deleteIfExists(p));
+                    } catch (IOException ex) {
+                        logger.warn("Couldn't delete temp file " + page.getFilePath());
+                    }
                 }
             }
         }

+ 24 - 0
src/main/java/com/github/jhonnymertz/wkhtmltopdf/wrapper/configurations/WrapperConfig.java

@@ -26,6 +26,12 @@ public class WrapperConfig {
      */
     private String wkhtmltopdfCommand = "wkhtmltopdf";
 
+	/**
+	 * Default - Command to always put TOC first
+	 * Toggle this off to customise TOC location
+	 */
+	private boolean alwaysPutTocFirst = true;
+
     /**
      * Initialize the configuration based on searching for wkhtmltopdf command to be used into the SO's path
      *
@@ -120,6 +126,24 @@ public class WrapperConfig {
         this.xvfbConfig = xvfbConfig;
     }
 
+	/**
+	 * Sets the way TOC locations are determined in the command
+	 * 
+	 * @param alwaysFirst - desired TOC location
+	 */
+	public void setAlwaysPutTocFirst(boolean alwaysFirst) {
+		this.alwaysPutTocFirst = alwaysFirst;
+	}
+
+	/**
+	 * Gets if the TOC is always placed first in the command
+	 * 
+	 * @return the desired TOC location as being always first
+	 */
+	public boolean getAlwaysPutTocFirst() {
+		return this.alwaysPutTocFirst;
+	}
+
     @Override
     public String toString() {
         return "{" +

+ 44 - 0
src/main/java/com/github/jhonnymertz/wkhtmltopdf/wrapper/objects/BaseObject.java

@@ -0,0 +1,44 @@
+package com.github.jhonnymertz.wkhtmltopdf.wrapper.objects;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+
+import com.github.jhonnymertz.wkhtmltopdf.wrapper.Pdf;
+import com.github.jhonnymertz.wkhtmltopdf.wrapper.params.Param;
+import com.github.jhonnymertz.wkhtmltopdf.wrapper.params.Params;
+
+public abstract class BaseObject {
+	String objectIdentifier;
+
+	Params params;
+
+	BaseObject()
+	{
+		objectIdentifier = SetObjectIdentifier();
+		params = new Params();
+	}
+
+	public abstract String SetObjectIdentifier();
+
+	public String getObjectIdentifier() {
+		return this.objectIdentifier;
+	}
+
+	public void addParam( Param param, Param... params )
+	{
+		this.params.add( param, params );
+	}
+
+	public List<String> getCommandAsList(Pdf pdf) throws IOException
+	{
+		List<String> commands = new ArrayList<>();
+		if(StringUtils.isNotBlank(objectIdentifier)){
+			commands.add( objectIdentifier );
+		}
+		commands.addAll( this.params.getParamsAsStringList() );
+		return commands;
+	}
+}

+ 15 - 0
src/main/java/com/github/jhonnymertz/wkhtmltopdf/wrapper/objects/Cover.java

@@ -0,0 +1,15 @@
+package com.github.jhonnymertz.wkhtmltopdf.wrapper.objects;
+
+public class Cover extends Page {
+    @Override
+    public String SetObjectIdentifier()
+    {
+        return "cover";
+    }
+
+    public Cover( String source, SourceType type )
+    {
+        super( source, type );
+    }
+    
+}

+ 87 - 0
src/main/java/com/github/jhonnymertz/wkhtmltopdf/wrapper/objects/Page.java

@@ -0,0 +1,87 @@
+package com.github.jhonnymertz.wkhtmltopdf.wrapper.objects;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import com.github.jhonnymertz.wkhtmltopdf.wrapper.Pdf;
+
+public class Page extends BaseObject {
+    private String source;
+    private String filePath;
+    private SourceType type;
+
+    public Page(String source, SourceType type) {
+        this.source = source;
+        this.type = type;
+    }
+
+    @Override
+    public String SetObjectIdentifier()
+    {
+        // leave blank as 'page' identifier is optional as per the wkhtmltopdf documentation
+        return "";
+    }
+
+    @Override
+    public List<String> getCommandAsList(Pdf pdf) throws IOException
+    {
+        List<String> commands = new ArrayList<>();
+        if(StringUtils.isNotBlank(objectIdentifier)){
+            commands.add( objectIdentifier );
+        }
+
+        // specify input
+        if ( this.getType().equals( SourceType.htmlAsString ) )
+        {
+            // htmlAsString pages are first store into a temp file, then the location is passed as parameter to
+            // wkhtmltopdf, this is a workaround to avoid huge commands
+            if ( this.getFilePath() != null )
+                Files.deleteIfExists( Paths.get( this.getFilePath() ) );
+            File temp = File.createTempFile( Pdf.TEMPORARY_FILE_PREFIX + UUID.randomUUID().toString(), ".html", pdf.getTempDirectory() ); // TODO: figure out a way to seperate this, so we don't need to pass the Pdf object in
+            FileUtils.writeStringToFile( temp, this.getSource(), "UTF-8" );
+            this.setFilePath( temp.getAbsolutePath() );
+            commands.add( temp.getAbsolutePath() );
+        }
+        else
+        {
+            commands.add( this.getSource() );
+        }
+
+        // specify options
+        commands.addAll( this.params.getParamsAsStringList() );
+        return commands;
+
+    }
+
+    public String getSource() {
+        return source;
+    }
+
+    public void setSource(String source) {
+        this.source = source;
+    }
+
+    public SourceType getType() {
+        return type;
+    }
+
+    public void setType(SourceType type) {
+        this.type = type;
+    }
+
+    public String getFilePath() {
+        return filePath;
+    }
+
+    public void setFilePath(String filePath) {
+        this.filePath = filePath;
+    }
+}

+ 3 - 3
src/main/java/com/github/jhonnymertz/wkhtmltopdf/wrapper/page/PageType.java → src/main/java/com/github/jhonnymertz/wkhtmltopdf/wrapper/objects/SourceType.java

@@ -1,9 +1,9 @@
-package com.github.jhonnymertz.wkhtmltopdf.wrapper.page;
+package com.github.jhonnymertz.wkhtmltopdf.wrapper.objects;
 
 /**
- * The Page type accepted by wkhtmltopdf
+ * The source type accepted by wkhtmltopdf
  */
-public enum PageType {
+public enum SourceType {
 
     /**
      * Html as string

+ 10 - 0
src/main/java/com/github/jhonnymertz/wkhtmltopdf/wrapper/objects/TableOfContents.java

@@ -0,0 +1,10 @@
+package com.github.jhonnymertz.wkhtmltopdf.wrapper.objects;
+
+public class TableOfContents extends BaseObject
+{
+    @Override
+    public String SetObjectIdentifier()
+    {
+        return "toc";
+    }
+}

+ 0 - 37
src/main/java/com/github/jhonnymertz/wkhtmltopdf/wrapper/page/Page.java

@@ -1,37 +0,0 @@
-package com.github.jhonnymertz.wkhtmltopdf.wrapper.page;
-
-public class Page {
-
-    private String source;
-    private String filePath;
-    private PageType type;
-
-    public Page(String source, PageType type) {
-        this.source = source;
-        this.type = type;
-    }
-
-    public String getSource() {
-        return source;
-    }
-
-    public void setSource(String source) {
-        this.source = source;
-    }
-
-    public PageType getType() {
-        return type;
-    }
-
-    public void setType(PageType type) {
-        this.type = type;
-    }
-
-    public String getFilePath() {
-        return filePath;
-    }
-
-    public void setFilePath(String filePath) {
-        this.filePath = filePath;
-    }
-}

+ 77 - 0
src/test/java/com/github/jhonnymertz/wkhtmltopdf/wrapper/integration/PdfIntegrationTests.java

@@ -1,9 +1,12 @@
 package com.github.jhonnymertz.wkhtmltopdf.wrapper.integration;
 
 import com.github.jhonnymertz.wkhtmltopdf.wrapper.Pdf;
+import com.github.jhonnymertz.wkhtmltopdf.wrapper.objects.Page;
+import com.github.jhonnymertz.wkhtmltopdf.wrapper.objects.TableOfContents;
 import com.github.jhonnymertz.wkhtmltopdf.wrapper.configurations.WrapperConfig;
 import com.github.jhonnymertz.wkhtmltopdf.wrapper.configurations.XvfbConfig;
 import com.github.jhonnymertz.wkhtmltopdf.wrapper.params.Param;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.pdfbox.pdmodel.PDDocument;
 import org.apache.pdfbox.text.PDFTextStripper;
 import org.junit.Assert;
@@ -14,6 +17,9 @@ import java.io.File;
 import java.io.IOException;
 
 import static org.hamcrest.core.StringContains.containsString;
+import static org.hamcrest.core.IsNot.not;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.Is.is;
 
 public class PdfIntegrationTests {
 
@@ -63,6 +69,69 @@ public class PdfIntegrationTests {
         Assert.assertThat("document should contain the creditorName", pdfText, containsString("Müller"));
     }
 
+    @Test
+    public void testMultipleObjectsOrder() throws Exception {
+        final String executable = WrapperConfig.findExecutable();
+        WrapperConfig config = new WrapperConfig(executable);
+        config.setAlwaysPutTocFirst(false);
+        Pdf pdf = new Pdf(config);
+
+        pdf.addCoverFromString("<html><head><meta charset=\"utf-8\"></head><h1>CoverPage</h1></html>");
+
+        TableOfContents toc = pdf.addToc();
+        toc.addParam(new Param("--toc-header-text", "TableOfContents"));
+
+        Page mainPage = pdf.addPageFromString("<html><head><meta charset=\"utf-8\"></head><h2>Heading1</h2></html>");
+        mainPage.addParam(new Param("--exclude-from-outline"));
+        
+        byte[] pdfBytes = pdf.getPDF();
+        String pdfText = getPdfTextFromBytes(pdfBytes);
+
+        // verify that cover comes before toc and toc before the main page
+        int indexOfCover = pdfText.indexOf("CoverPage");
+        int indexOfToc = pdfText.indexOf("TableOfContents");
+        int indexOfMainPage = pdfText.indexOf("Heading1");
+        
+        Assert.assertThat("document should have a cover page before the table of contents", indexOfCover < indexOfToc, is(true));
+        Assert.assertThat("document should have a table of contents before the main page", indexOfToc < indexOfMainPage, is(true));
+    }
+
+    @Test
+    public void testMultipleObjectsWithOptions() throws Exception {
+        final String executable = WrapperConfig.findExecutable();
+        WrapperConfig config = new WrapperConfig(executable);
+        Pdf pdf = new Pdf(config);
+
+        pdf.addParam( new Param( "--header-center", "GlobalHeader" ) );
+
+        TableOfContents toc = pdf.addToc();
+        toc.addParam(new Param("--footer-center", "TocFooter"));
+
+        Page page1 = pdf.addPageFromString("<html><head><meta charset=\"utf-8\"></head><h1>Page1</h1></html>");
+        page1.addParam(new Param("--footer-center", "Page1Footer"));
+        page1.addParam(new Param("--exclude-from-outline")); // removes from toc
+
+        Page page2 = pdf.addPageFromString("<html><head><meta charset=\"utf-8\"></head><h1>Page2</h1></html>");
+        page2.addParam( new Param( "--header-center", "Page2HeaderOverride" ) ); // override global header
+        
+        byte[] pdfBytes = pdf.getPDF();
+        String pdfText = getPdfTextFromBytes(pdfBytes);
+
+        int globalHeaderCount = StringUtils.countMatches(pdfText, "GlobalHeader");
+        int tocFooterCount = StringUtils.countMatches(pdfText, "TocFooter");
+        int page1FooterCount = StringUtils.countMatches(pdfText, "Page1Footer");
+
+        Assert.assertThat("document doesn't contain correct number of global headers", 2, equalTo(globalHeaderCount));
+        Assert.assertThat("document doesn't contain correct number of toc footers", 1, equalTo(tocFooterCount));
+        Assert.assertThat("document doesn't contain correct number of page 1 footers", 1, equalTo(page1FooterCount));
+
+        try (PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes))) {
+            String pdfTocPageText = getPdfTextForPage(document,1);
+            Assert.assertThat("document toc shouldn't contain page1", pdfTocPageText, not(containsString("Page1")));
+            Assert.assertThat("document toc is missing page2", pdfTocPageText, containsString("Page2"));
+        }
+    }
+
     @Test
     public void testMultiplePages() throws Exception {
         Pdf pdf = new Pdf();
@@ -115,6 +184,14 @@ public class PdfIntegrationTests {
         return text;
     }
 
+    private String getPdfTextForPage(PDDocument document, int page) throws IOException {
+        PDFTextStripper reader = new PDFTextStripper();
+        reader.setStartPage(page);
+        reader.setEndPage(page);
+        String pageText = reader.getText(document);
+        return pageText;
+    }
+
     @Test
     public void CleanUpTempFilesTest() {
         Pdf pdf = new Pdf();

+ 58 - 1
src/test/java/com/github/jhonnymertz/wkhtmltopdf/wrapper/unit/PdfTests.java

@@ -4,6 +4,9 @@ import com.github.jhonnymertz.wkhtmltopdf.wrapper.Pdf;
 import com.github.jhonnymertz.wkhtmltopdf.wrapper.configurations.FilenameFilterConfig;
 import com.github.jhonnymertz.wkhtmltopdf.wrapper.configurations.WrapperConfig;
 import com.github.jhonnymertz.wkhtmltopdf.wrapper.configurations.XvfbConfig;
+import com.github.jhonnymertz.wkhtmltopdf.wrapper.objects.Cover;
+import com.github.jhonnymertz.wkhtmltopdf.wrapper.objects.Page;
+import com.github.jhonnymertz.wkhtmltopdf.wrapper.objects.TableOfContents;
 import com.github.jhonnymertz.wkhtmltopdf.wrapper.params.Param;
 import org.junit.After;
 import org.junit.Assert;
@@ -95,10 +98,64 @@ public class PdfTests {
         Assert.assertThat("command params should contain toc params", pdf.getCommand(), containsString("--test-param2 test-value"));
     }
 
+    @Test
+    public void testTocParamsUsingTocObject() throws IOException {
+        TableOfContents toc = pdf.addToc();
+        toc.addParam(new Param("--test-param"), new Param("--test-param2", "test-value"));
+        pdf.addPageFromUrl("http://www.google.com");
+        Assert.assertThat("command params should contain toc params", pdf.getCommand(), containsString("--test-param2 test-value"));
+    }
+
     @Test
     public void testXvfbCommand() throws Exception {
         wc.setXvfbConfig(new XvfbConfig());
         pdf = new Pdf(wc);
         Assert.assertThat("command should contain xvfb-run config", pdf.getCommand(), containsString("xvfb-run"));
     }
-}
+
+    @Test
+    public void testTocAlwaysFirstByDefault() throws Exception {
+        pdf.addPageFromUrl("http://www.google.com");
+        pdf.addToc();
+        pdf.addPageFromFile("test.html");
+        Assert.assertThat("command params should contain toc before pages", pdf.getCommand(), containsString("wkhtmltopdf toc http://www.google.com test.html -"));
+    }
+
+    @Test
+    public void testTocCustomLocation() throws Exception {
+        wc.setAlwaysPutTocFirst(false);
+        pdf.addPageFromUrl("http://www.google.com");
+        pdf.addToc();
+        pdf.addPageFromFile("test.html");
+        Assert.assertThat("command params should contain toc after url page and before file page", pdf.getCommand(), containsString("wkhtmltopdf http://www.google.com toc test.html -"));
+    }
+
+    @Test
+    public void testPageParams() throws IOException {
+        Page page1 = pdf.addPageFromUrl("http://www.google.com");
+        page1.addParam(new Param("--exclude-from-outline"));
+        Page page2 = pdf.addPageFromFile("test.html");
+        page2.addParam( new Param("--zoom", "2"));
+        Assert.assertThat("command url page should contain page specific params", pdf.getCommand(), containsString("http://www.google.com --exclude-from-outline"));
+        Assert.assertThat("command file page should contain page specific params", pdf.getCommand(), containsString("test.html --zoom"));
+    }
+
+    @Test
+    public void testCoverParams() throws IOException {
+        Cover cover = pdf.addCoverFromFile("cover.html");
+        cover.addParam(new Param("--test-param"), new Param("--test-param2", "test-value"));
+        pdf.addPageFromUrl("http://www.google.com");
+        Assert.assertThat("command params should contain cover params", pdf.getCommand(), containsString("cover cover.html --test-param --test-param2 test-value"));
+    }
+
+    @Test
+    public void testMulipleObjects() throws IOException {
+        wc.setAlwaysPutTocFirst(false);
+        pdf.addCoverFromFile("cover.html");
+        pdf.addPageFromFile("foreword.html");
+        pdf.addToc();
+        pdf.addPageFromUrl("http://www.google.com");
+        pdf.addPageFromFile("test.html");
+        Assert.assertThat("command should match the order objects are added", pdf.getCommand(), containsString("wkhtmltopdf cover cover.html foreword.html toc http://www.google.com test.html -"));
+    }
+}