Skip to content

Commit 06e8323

Browse files
committed
Created first version of wkhtmltoimage
1 parent ac3f7f3 commit 06e8323

3 files changed

Lines changed: 227 additions & 0 deletions

File tree

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package io.github.cloudify.scala.spdf
2+
3+
import scala.sys.process._
4+
import java.io.File
5+
6+
class Image(executablePath: String, config: ImageConfig) {
7+
validateExecutable_!(executablePath)
8+
9+
/**
10+
* Runs the conversion tool to convert sourceDocument HTML into
11+
* destinationDocument image.
12+
*/
13+
def run[A, B](sourceDocument: A, destinationDocument: B)(implicit sourceDocumentLike: SourceDocumentLike[A], destinationDocumentLike: DestinationDocumentLike[B]): Int = {
14+
val commandLine = toCommandLine(sourceDocument, destinationDocument)
15+
val process = Process(commandLine)
16+
def source = sourceDocumentLike.sourceFrom(sourceDocument) _
17+
def sink = destinationDocumentLike.sinkTo(destinationDocument) _
18+
19+
(sink compose source)(process).!
20+
}
21+
22+
/**
23+
* Generates the command line needed to execute `wkhtmltoimage`
24+
*/
25+
private def toCommandLine[A: SourceDocumentLike, B: DestinationDocumentLike](source: A, destination: B): Seq[String] =
26+
Seq(executablePath) ++
27+
ImageConfig.toParameters(config) ++
28+
Seq(
29+
implicitly[SourceDocumentLike[A]].commandParameter(source),
30+
implicitly[DestinationDocumentLike[B]].commandParameter(destination)
31+
)
32+
33+
/**
34+
* Check whether the executable is actually executable, if it isn't
35+
* a NoExecutableException is thrown.
36+
*/
37+
private def validateExecutable_!(executablePath: String): Unit = {
38+
val executableFile = new File(executablePath)
39+
if(!executableFile.canExecute) throw new NoExecutableException(executableFile.getAbsolutePath)
40+
}
41+
42+
}
43+
44+
object Image {
45+
46+
/**
47+
* Creates a new instance of Image with default configuration
48+
* @return
49+
*/
50+
def apply(config: ImageConfig): Image = {
51+
val executablePath: String = ImageConfig.findExecutable.getOrElse {
52+
throw new NoExecutableException(System.getenv("PATH"))
53+
}
54+
55+
apply(executablePath, config)
56+
}
57+
58+
/**
59+
* Creates a new instance of Image with the passed configuration
60+
*/
61+
def apply(executablePath: String, config: ImageConfig): Image =
62+
new Image(executablePath, config)
63+
64+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package io.github.cloudify.scala.spdf
2+
3+
import scala.sys.process._
4+
import ParamShow._
5+
6+
/**
7+
* Holds the configuration parameters of Pdf Kit
8+
*/
9+
trait ImageConfig extends PdfConfig
10+
11+
object ImageConfig {
12+
13+
/**
14+
* An instance of the default configuration
15+
*/
16+
object default extends ImageConfig
17+
18+
/**
19+
* Generates a sequence of command line parameters from a `PdfKitConfig`
20+
*/
21+
def toParameters(config: ImageConfig): Seq[String] = {
22+
import config._
23+
Seq(
24+
allow.toParameter,
25+
background.toParameter,
26+
defaultHeader.toParameter,
27+
disableExternalLinks.toParameter,
28+
disableInternalLinks.toParameter,
29+
disableJavascript.toParameter,
30+
noPdfCompression.toParameter,
31+
disableSmartShrinking.toParameter,
32+
javascriptDelay.toParameter,
33+
enableForms.toParameter,
34+
encoding.toParameter,
35+
footerCenter.toParameter,
36+
footerFontName.toParameter,
37+
footerFontSize.toParameter,
38+
footerHtml.toParameter,
39+
footerLeft.toParameter,
40+
footerLine.toParameter,
41+
footerRight.toParameter,
42+
footerSpacing.toParameter,
43+
grayScale.toParameter,
44+
headerCenter.toParameter,
45+
headerFontName.toParameter,
46+
headerFontSize.toParameter,
47+
headerHtml.toParameter,
48+
headerLeft.toParameter,
49+
headerLine.toParameter,
50+
headerRight.toParameter,
51+
headerSpacing.toParameter,
52+
lowQuality.toParameter,
53+
marginBottom.toParameter,
54+
marginLeft.toParameter,
55+
marginRight.toParameter,
56+
marginTop.toParameter,
57+
minimumFontSize.toParameter,
58+
orientation.toParameter,
59+
outline.toParameter,
60+
outlineDepth.toParameter,
61+
pageHeight.toParameter,
62+
pageOffset.toParameter,
63+
pageSize.toParameter,
64+
pageWidth.toParameter,
65+
password.toParameter,
66+
printMediaType.toParameter,
67+
tableOfContent.toParameter,
68+
tableOfContentDepth.toParameter,
69+
tableOfContentDisableBackLinks.toParameter,
70+
tableOfContentDisableLinks.toParameter,
71+
tableOfContentFontName.toParameter,
72+
tableOfContentHeaderFontName.toParameter,
73+
tableOfContentHeaderFontSize.toParameter,
74+
tableOfContentHeaderText.toParameter,
75+
tableOfContentLevel1FontSize.toParameter,
76+
tableOfContentLevel1Indentation.toParameter,
77+
tableOfContentLevel2FontSize.toParameter,
78+
tableOfContentLevel2Indentation.toParameter,
79+
tableOfContentLevel3FontSize.toParameter,
80+
tableOfContentLevel3Indentation.toParameter,
81+
tableOfContentLevel4FontSize.toParameter,
82+
tableOfContentLevel4Indentation.toParameter,
83+
tableOfContentLevel5FontSize.toParameter,
84+
tableOfContentLevel5Indentation.toParameter,
85+
tableOfContentLevel6FontSize.toParameter,
86+
tableOfContentLevel6Indentation.toParameter,
87+
tableOfContentLevel7FontSize.toParameter,
88+
tableOfContentLevel7Indentation.toParameter,
89+
tableOfContentNoDots.toParameter,
90+
title.toParameter,
91+
userStyleSheet.toParameter,
92+
username.toParameter,
93+
useXServer.toParameter,
94+
viewportSize.toParameter,
95+
zoom.toParameter
96+
).flatten
97+
}
98+
99+
/**
100+
* Attempts to find the `wkhtmltoimage` executable in the system path.
101+
* @return
102+
*/
103+
def findExecutable: Option[String] = try {
104+
val os = System.getProperty("os.name").toLowerCase
105+
val cmd = if(os.contains("windows")) "where wkhtmltoimage" else "which wkhtmltoimage"
106+
107+
Option(cmd.!!.trim).filter(_.nonEmpty)
108+
} catch {
109+
case _: RuntimeException => None
110+
}
111+
112+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package io.github.cloudify.scala.spdf
2+
3+
import java.io.File
4+
import scala.sys.process._
5+
import org.scalatest.Matchers
6+
import org.scalatest.WordSpec
7+
8+
class ImageSpec extends WordSpec with Matchers {
9+
10+
"An Image" should {
11+
12+
"require the executionPath config" in {
13+
val file = new File("notexecutable")
14+
val filePath = file.getAbsolutePath
15+
16+
assertThrows[NoExecutableException] {
17+
new Image(filePath, ImageConfig.default)
18+
}
19+
20+
assertThrows[NoExecutableException] {
21+
Image(filePath, ImageConfig.default)
22+
}
23+
24+
}
25+
26+
PdfConfig.findExecutable match {
27+
case Some(_) =>
28+
"generate an image file from an HTML string" in {
29+
30+
val page =
31+
"""
32+
|<html><body><h1>Hello</h1></body></html>
33+
""".stripMargin
34+
35+
val file = File.createTempFile("scala.spdf", "pdf")
36+
37+
val image = Image(ImageConfig.default)
38+
39+
image.run(page, file)
40+
41+
Seq("file", file.getAbsolutePath).!! should include("PDF document")
42+
}
43+
44+
case None =>
45+
"Skipping test, missing wkhtmltopdf binary" in { true should equal(true) }
46+
}
47+
48+
49+
}
50+
51+
}

0 commit comments

Comments
 (0)