mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Add ConvertHtmlToPdfAction
This commit is contained in:
@ -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>
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
@ -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;
|
||||||
|
}
|
Reference in New Issue
Block a user