Add ConvertHtmlToPdfAction

This commit is contained in:
2022-11-28 11:52:48 -06:00
parent b2d76e8206
commit 792383d21a
10 changed files with 541 additions and 3 deletions

View File

@ -97,6 +97,23 @@
<artifactId>java-dotenv</artifactId> <artifactId>java-dotenv</artifactId>
<version>5.2.2</version> <version>5.2.2</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
<!-- the next 2 deps are for html to pdf - per https://www.baeldung.com/java-html-to-pdf -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.15.3</version>
</dependency>
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf-openpdf</artifactId>
<version>9.1.22</version>
</dependency>
<!-- the next 3 deps are being added for google drive support --> <!-- the next 3 deps are being added for google drive support -->
<dependency> <dependency>

View File

@ -0,0 +1,99 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.templates;
import java.nio.file.Path;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.templates.ConvertHtmlToPdfInput;
import com.kingsrook.qqq.backend.core.model.templates.ConvertHtmlToPdfOutput;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.xhtmlrenderer.layout.SharedContext;
import org.xhtmlrenderer.pdf.ITextRenderer;
/*******************************************************************************
** Action to convert a string of HTML to a PDF!
**
** Much credit to https://www.baeldung.com/java-html-to-pdf
*******************************************************************************/
public class ConvertHtmlToPdfAction extends AbstractQActionFunction<ConvertHtmlToPdfInput, ConvertHtmlToPdfOutput>
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public ConvertHtmlToPdfOutput execute(ConvertHtmlToPdfInput input) throws QException
{
try
{
ConvertHtmlToPdfOutput output = new ConvertHtmlToPdfOutput();
//////////////////////////////////////////////////////////////////
// convert the input HTML to XHTML, as needed for ITextRenderer //
//////////////////////////////////////////////////////////////////
Document document = Jsoup.parse(input.getHtml());
document.outputSettings().syntax(Document.OutputSettings.Syntax.xml);
//////////////////////////////
// convert the XHTML to PDF //
//////////////////////////////
ITextRenderer renderer = new ITextRenderer();
SharedContext sharedContext = renderer.getSharedContext();
sharedContext.setPrint(true);
sharedContext.setInteractive(false);
if(input.getBasePath() != null)
{
String baseUrl = input.getBasePath().toUri().toURL().toString();
renderer.setDocumentFromString(document.html(), baseUrl);
}
else
{
renderer.setDocumentFromString(document.html());
}
//////////////////////////////////////////////////
// register any custom fonts the input supplied //
//////////////////////////////////////////////////
for(Map.Entry<String, Path> entry : CollectionUtils.nonNullMap(input.getCustomFonts()).entrySet())
{
renderer.getFontResolver().addFont(entry.getValue().toAbsolutePath().toString(), entry.getKey(), "UTF-8", true, null);
}
renderer.layout();
renderer.createPDF(input.getOutputStream());
return (output);
}
catch(Exception e)
{
throw (new QException("Error converting html to pdf", e));
}
}
}

View File

@ -0,0 +1,207 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.templates;
import java.io.OutputStream;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
**
*******************************************************************************/
public class ConvertHtmlToPdfInput extends AbstractActionInput
{
private String html;
private OutputStream outputStream;
private Path basePath;
private Map<String, Path> customFonts = new HashMap<>();
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ConvertHtmlToPdfInput(QInstance instance)
{
super(instance);
}
/*******************************************************************************
** Getter for html
**
*******************************************************************************/
public String getHtml()
{
return html;
}
/*******************************************************************************
** Setter for html
**
*******************************************************************************/
public void setHtml(String html)
{
this.html = html;
}
/*******************************************************************************
** Fluent setter for html
**
*******************************************************************************/
public ConvertHtmlToPdfInput withHtml(String html)
{
this.html = html;
return (this);
}
/*******************************************************************************
** Getter for outputStream
**
*******************************************************************************/
public OutputStream getOutputStream()
{
return outputStream;
}
/*******************************************************************************
** Setter for outputStream
**
*******************************************************************************/
public void setOutputStream(OutputStream outputStream)
{
this.outputStream = outputStream;
}
/*******************************************************************************
** Fluent setter for outputStream
**
*******************************************************************************/
public ConvertHtmlToPdfInput withOutputStream(OutputStream outputStream)
{
this.outputStream = outputStream;
return (this);
}
/*******************************************************************************
** Getter for basePath
**
*******************************************************************************/
public Path getBasePath()
{
return basePath;
}
/*******************************************************************************
** Setter for basePath
**
*******************************************************************************/
public void setBasePath(Path basePath)
{
this.basePath = basePath;
}
/*******************************************************************************
** Fluent setter for basePath
**
*******************************************************************************/
public ConvertHtmlToPdfInput withBasePath(Path basePath)
{
this.basePath = basePath;
return (this);
}
/*******************************************************************************
** Getter for customFonts
**
*******************************************************************************/
public Map<String, Path> getCustomFonts()
{
return customFonts;
}
/*******************************************************************************
** Setter for customFonts
**
*******************************************************************************/
public void setCustomFonts(Map<String, Path> customFonts)
{
this.customFonts = customFonts;
}
/*******************************************************************************
** Fluent setter for customFonts
**
*******************************************************************************/
public ConvertHtmlToPdfInput withCustomFonts(Map<String, Path> customFonts)
{
this.customFonts = customFonts;
return (this);
}
/*******************************************************************************
** Fluent setter for customFonts
**
*******************************************************************************/
public ConvertHtmlToPdfInput withCustomFont(String name, Path path)
{
if(this.customFonts == null)
{
this.customFonts = new HashMap<>();
}
this.customFonts.put(name, path);
return (this);
}
}

View File

@ -0,0 +1,69 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.templates;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
/*******************************************************************************
**
*******************************************************************************/
public class ConvertHtmlToPdfOutput extends AbstractActionOutput
{
private String result;
/*******************************************************************************
** Getter for result
**
*******************************************************************************/
public String getResult()
{
return result;
}
/*******************************************************************************
** Setter for result
**
*******************************************************************************/
public void setResult(String result)
{
this.result = result;
}
/*******************************************************************************
** Fluent setter for result
**
*******************************************************************************/
public ConvertHtmlToPdfOutput withResult(String result)
{
this.result = result;
return (this);
}
}

View File

@ -436,13 +436,29 @@ public class CollectionUtils
** **
** Meant to help avoid null checks on foreach loops. ** Meant to help avoid null checks on foreach loops.
*******************************************************************************/ *******************************************************************************/
public static <T> Collection<T> nonNullCollection(Collection<T> list) public static <T> Collection<T> nonNullCollection(Collection<T> c)
{ {
if(list == null) if(c == null)
{ {
return (new ArrayList<>()); return (new ArrayList<>());
} }
return (list); return (c);
}
/*******************************************************************************
** Returns the input map, unless it was null - in which case a new HashMap is returned.
**
** Meant to help avoid null checks on foreach loops.
*******************************************************************************/
public static <K, V> Map<K, V> nonNullMap(Map<K, V> map)
{
if(map == null)
{
return (new HashMap<>());
}
return (map);
} }

View File

@ -0,0 +1,104 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.templates;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Path;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.model.templates.ConvertHtmlToPdfInput;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test;
/*******************************************************************************
** Unit test for ConvertHtmlToPdfAction
*******************************************************************************/
class ConvertHtmlToPdfActionTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void test() throws QException, IOException
{
QInstance instance = TestUtils.defineInstance();
ConvertHtmlToPdfInput input = new ConvertHtmlToPdfInput(instance);
input.setSession(new QSession());
input.setHtml("""
<html>
<head>
<style>
.center_div {
border: 1px solid gray;
margin-left: auto;
margin-right: auto;
width: 90%;
background-color: #d0f0f6;
text-align: left;
padding: 8px;
font-family: Helvetica;
}
</style>
<link href="styles/styles.css" rel="stylesheet">
</head>
<body>
<div class="center_div">
<h1>
<img src="images/qqq-logo-2.png" width=50>
Hello QQQ!
</h1>
<div class="myclass">
<p>This is a test of converting HTML to PDF!!</p>
<p style="font-family: SF-Pro; font-size: 24px;">(btw, is this in SF-Pro???)</p>
</div>
</div>
</body>
</html>
""");
OutputStream outputStream = new FileOutputStream("/tmp/file.pdf");
input.setOutputStream(outputStream);
String resourceDir = "src/test/resources/actions/templates";
input.withCustomFont("SF-Pro", Path.of(resourceDir + "/fonts/SF-Pro-Rounded-Regular.otf"));
input.withCustomFont("Helvetica", Path.of(resourceDir + "/fonts/Helvetica.ttc"));
input.withBasePath(Path.of(resourceDir));
new ConvertHtmlToPdfAction().execute(input);
System.out.println("Wrote /tmp/file.pdf");
outputStream.close();
/////////////////////////////////////////////////////////////////////////
// for local dev on a mac, turn this on to auto-open the generated PDF //
/////////////////////////////////////////////////////////////////////////
// Runtime.getRuntime().exec(new String[] { "/usr/bin/open", "/tmp/file.pdf" });
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -0,0 +1,26 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
h1
{
text-align: center;
border-bottom: 1px solid red;
}